kimaki 0.31.0 → 1.0.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/dist/agent-ui.d.ts +33 -0
- package/dist/agent-ui.e2e.test.d.ts +1 -0
- package/dist/agent-ui.e2e.test.js +140 -0
- package/dist/agent-ui.e2e.test.js.map +1 -0
- package/dist/agent-ui.js +199 -0
- package/dist/agent-ui.js.map +1 -0
- package/dist/analytics.d.ts +42 -0
- package/dist/analytics.e2e.test.d.ts +1 -0
- package/dist/analytics.e2e.test.js +112 -0
- package/dist/analytics.e2e.test.js.map +1 -0
- package/dist/analytics.js +162 -223
- package/dist/analytics.js.map +1 -0
- package/dist/analytics.test.d.ts +1 -0
- package/dist/analytics.test.js +121 -157
- package/dist/analytics.test.js.map +1 -0
- package/dist/autostart.d.ts +7 -0
- package/dist/autostart.js +75 -0
- package/dist/autostart.js.map +1 -0
- package/dist/bot.d.ts +90 -0
- package/dist/bot.js +120 -0
- package/dist/bot.js.map +1 -0
- package/dist/btw.e2e.test.d.ts +1 -0
- package/dist/btw.e2e.test.js +102 -0
- package/dist/btw.e2e.test.js.map +1 -0
- package/dist/catalog-refresh.e2e.test.d.ts +1 -0
- package/dist/catalog-refresh.e2e.test.js +90 -0
- package/dist/catalog-refresh.e2e.test.js.map +1 -0
- package/dist/cli/bot.d.ts +5 -0
- package/dist/cli/bot.js +427 -0
- package/dist/cli/bot.js.map +1 -0
- package/dist/cli/discord.d.ts +3 -0
- package/dist/cli/discord.js +118 -0
- package/dist/cli/discord.js.map +1 -0
- package/dist/cli/project.d.ts +5 -0
- package/dist/cli/project.js +143 -0
- package/dist/cli/project.js.map +1 -0
- package/dist/cli/schedule.d.ts +3 -0
- package/dist/cli/schedule.js +156 -0
- package/dist/cli/schedule.js.map +1 -0
- package/dist/cli/session.d.ts +4 -0
- package/dist/cli/session.js +281 -0
- package/dist/cli/session.js.map +1 -0
- package/dist/cli/shared.d.ts +55 -0
- package/dist/cli/shared.js +138 -0
- package/dist/cli/shared.js.map +1 -0
- package/dist/cli-discord.e2e.test.d.ts +1 -0
- package/dist/cli-discord.e2e.test.js +64 -0
- package/dist/cli-discord.e2e.test.js.map +1 -0
- package/dist/cli-lock.e2e.test.d.ts +1 -0
- package/dist/cli-lock.e2e.test.js +520 -0
- package/dist/cli-lock.e2e.test.js.map +1 -0
- package/dist/cli-read.e2e.test.d.ts +1 -0
- package/dist/cli-read.e2e.test.js +81 -0
- package/dist/cli-read.e2e.test.js.map +1 -0
- package/dist/cli-sessions.e2e.test.d.ts +1 -0
- package/dist/cli-sessions.e2e.test.js +116 -0
- package/dist/cli-sessions.e2e.test.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +34 -263
- package/dist/cli.js.map +1 -0
- package/dist/commands/login-commands.d.ts +59 -0
- package/dist/commands/login-commands.js +341 -0
- package/dist/commands/login-commands.js.map +1 -0
- package/dist/commands/preference-commands.d.ts +44 -0
- package/dist/commands/preference-commands.js +551 -0
- package/dist/commands/preference-commands.js.map +1 -0
- package/dist/commands/session-commands.d.ts +8 -0
- package/dist/commands/session-commands.js +267 -0
- package/dist/commands/session-commands.js.map +1 -0
- package/dist/commands/thread-commands.d.ts +16 -0
- package/dist/commands/thread-commands.js +285 -0
- package/dist/commands/thread-commands.js.map +1 -0
- package/dist/commands/worktree-commands.d.ts +41 -0
- package/dist/commands/worktree-commands.js +279 -0
- package/dist/commands/worktree-commands.js.map +1 -0
- package/dist/commands.e2e.test.d.ts +1 -0
- package/dist/commands.e2e.test.js +255 -0
- package/dist/commands.e2e.test.js.map +1 -0
- package/dist/credentials.d.ts +84 -0
- package/dist/credentials.js +320 -0
- package/dist/credentials.js.map +1 -0
- package/dist/db.d.ts +1532 -0
- package/dist/db.js +57 -245
- package/dist/db.js.map +1 -0
- package/dist/db.test.d.ts +1 -0
- package/dist/db.test.js +351 -682
- package/dist/db.test.js.map +1 -0
- package/dist/duration.d.ts +2 -0
- package/dist/duration.js +18 -0
- package/dist/duration.js.map +1 -0
- package/dist/effects.d.ts +15 -0
- package/dist/effects.js +291 -0
- package/dist/effects.js.map +1 -0
- package/dist/errors.d.ts +38 -0
- package/dist/errors.js +40 -175
- package/dist/errors.js.map +1 -0
- package/dist/event-loop.d.ts +40 -0
- package/dist/event-loop.js +414 -0
- package/dist/event-loop.js.map +1 -0
- package/dist/file-edit-log.d.ts +41 -0
- package/dist/file-edit-log.js +122 -164
- package/dist/file-edit-log.js.map +1 -0
- package/dist/file-edit-log.test.d.ts +1 -0
- package/dist/file-edit-log.test.js +68 -244
- package/dist/file-edit-log.test.js.map +1 -0
- package/dist/format-parts.d.ts +137 -0
- package/dist/format-parts.js +300 -0
- package/dist/format-parts.js.map +1 -0
- package/dist/format-parts.test.d.ts +1 -0
- package/dist/format-parts.test.js +144 -0
- package/dist/format-parts.test.js.map +1 -0
- package/dist/gateway-replay.e2e.test.d.ts +1 -0
- package/dist/gateway-replay.e2e.test.js +95 -0
- package/dist/gateway-replay.e2e.test.js.map +1 -0
- package/dist/harness.e2e.test.d.ts +1 -0
- package/dist/harness.e2e.test.js +107 -0
- package/dist/harness.e2e.test.js.map +1 -0
- package/dist/hello-thread.e2e.test.d.ts +1 -0
- package/dist/hello-thread.e2e.test.js +194 -0
- package/dist/hello-thread.e2e.test.js.map +1 -0
- package/dist/ingress.d.ts +7 -0
- package/dist/ingress.js +337 -0
- package/dist/ingress.js.map +1 -0
- package/dist/interaction-context.d.ts +45 -0
- package/dist/interaction-context.js +77 -0
- package/dist/interaction-context.js.map +1 -0
- package/dist/interrupt.e2e.test.d.ts +1 -0
- package/dist/interrupt.e2e.test.js +124 -0
- package/dist/interrupt.e2e.test.js.map +1 -0
- package/dist/legacy.e2e.test.d.ts +1 -0
- package/dist/legacy.e2e.test.js +85 -0
- package/dist/legacy.e2e.test.js.map +1 -0
- package/dist/live-call.d.ts +51 -0
- package/dist/live-call.js +207 -0
- package/dist/live-call.js.map +1 -0
- package/dist/lock-routes.d.ts +812 -0
- package/dist/lock-routes.js +635 -0
- package/dist/lock-routes.js.map +1 -0
- package/dist/lock-server.d.ts +36 -0
- package/dist/lock-server.js +223 -0
- package/dist/lock-server.js.map +1 -0
- package/dist/logger.d.ts +13 -0
- package/dist/logger.js +112 -162
- package/dist/logger.js.map +1 -0
- package/dist/login.e2e.test.d.ts +1 -0
- package/dist/login.e2e.test.js +83 -0
- package/dist/login.e2e.test.js.map +1 -0
- package/dist/main.d.ts +32 -0
- package/dist/main.js +190 -0
- package/dist/main.js.map +1 -0
- package/dist/markdown/components.d.ts +10 -0
- package/dist/markdown/components.js +77 -0
- package/dist/markdown/components.js.map +1 -0
- package/dist/markdown/markdown.test.d.ts +1 -0
- package/dist/markdown/markdown.test.js +393 -0
- package/dist/markdown/markdown.test.js.map +1 -0
- package/dist/markdown/render-markdown.d.ts +30 -0
- package/dist/markdown/render-markdown.js +265 -0
- package/dist/markdown/render-markdown.js.map +1 -0
- package/dist/message-tool.e2e.test.d.ts +1 -0
- package/dist/message-tool.e2e.test.js +39 -0
- package/dist/message-tool.e2e.test.js.map +1 -0
- package/dist/migrations.d.ts +8 -0
- package/dist/migrations.js +187 -0
- package/dist/migrations.js.map +1 -0
- package/dist/onboarding-cli.e2e.test.d.ts +1 -0
- package/dist/onboarding-cli.e2e.test.js +178 -0
- package/dist/onboarding-cli.e2e.test.js.map +1 -0
- package/dist/onboarding.d.ts +36 -0
- package/dist/onboarding.e2e.test.d.ts +1 -0
- package/dist/onboarding.e2e.test.js +148 -0
- package/dist/onboarding.e2e.test.js.map +1 -0
- package/dist/onboarding.js +261 -0
- package/dist/onboarding.js.map +1 -0
- package/dist/opencode-server.d.ts +54 -0
- package/dist/opencode-server.js +413 -0
- package/dist/opencode-server.js.map +1 -0
- package/dist/opencode-server.test.d.ts +1 -0
- package/dist/opencode-server.test.js +21 -0
- package/dist/opencode-server.test.js.map +1 -0
- package/dist/permissions.d.ts +31 -0
- package/dist/permissions.e2e.test.d.ts +1 -0
- package/dist/permissions.e2e.test.js +166 -0
- package/dist/permissions.e2e.test.js.map +1 -0
- package/dist/permissions.js +95 -0
- package/dist/permissions.js.map +1 -0
- package/dist/plugin/anthropic/index.d.ts +13 -0
- package/dist/plugin/anthropic/index.js +201 -0
- package/dist/plugin/anthropic/index.js.map +1 -0
- package/dist/plugin/anthropic/index.test.d.ts +1 -0
- package/dist/plugin/anthropic/index.test.js +39 -0
- package/dist/plugin/anthropic/index.test.js.map +1 -0
- package/dist/plugin/index.d.ts +3 -0
- package/dist/plugin/index.js +106 -0
- package/dist/plugin/index.js.map +1 -0
- package/dist/plugin/session-id/index.d.ts +3 -0
- package/dist/plugin/session-id/index.js +18 -0
- package/dist/plugin/session-id/index.js.map +1 -0
- package/dist/plugin.e2e.test.d.ts +1 -0
- package/dist/plugin.e2e.test.js +87 -0
- package/dist/plugin.e2e.test.js.map +1 -0
- package/dist/preference-commands.e2e.test.d.ts +1 -0
- package/dist/preference-commands.e2e.test.js +555 -0
- package/dist/preference-commands.e2e.test.js.map +1 -0
- package/dist/probe-live.tmp.d.ts +1 -0
- package/dist/probe-live.tmp.js +82 -0
- package/dist/probe-live.tmp.js.map +1 -0
- package/dist/project.d.ts +58 -0
- package/dist/project.js +169 -0
- package/dist/project.js.map +1 -0
- package/dist/prompt.d.ts +112 -0
- package/dist/prompt.js +578 -0
- package/dist/prompt.js.map +1 -0
- package/dist/questions.d.ts +59 -0
- package/dist/questions.e2e.test.d.ts +1 -0
- package/dist/questions.e2e.test.js +290 -0
- package/dist/questions.e2e.test.js.map +1 -0
- package/dist/questions.js +231 -0
- package/dist/questions.js.map +1 -0
- package/dist/queue.d.ts +45 -0
- package/dist/queue.e2e.test.d.ts +1 -0
- package/dist/queue.e2e.test.js +220 -0
- package/dist/queue.e2e.test.js.map +1 -0
- package/dist/queue.js +132 -0
- package/dist/queue.js.map +1 -0
- package/dist/remote-send.d.ts +32 -0
- package/dist/remote-send.e2e.test.d.ts +1 -0
- package/dist/remote-send.e2e.test.js +89 -0
- package/dist/remote-send.e2e.test.js.map +1 -0
- package/dist/remote-send.js +105 -0
- package/dist/remote-send.js.map +1 -0
- package/dist/renderer.e2e.test.d.ts +1 -0
- package/dist/renderer.e2e.test.js +357 -0
- package/dist/renderer.e2e.test.js.map +1 -0
- package/dist/restart.e2e.test.d.ts +1 -0
- package/dist/restart.e2e.test.js +68 -0
- package/dist/restart.e2e.test.js.map +1 -0
- package/dist/routes.d.ts +24 -0
- package/dist/routes.js +42 -0
- package/dist/routes.js.map +1 -0
- package/dist/routes.test.d.ts +1 -0
- package/dist/routes.test.js +79 -0
- package/dist/routes.test.js.map +1 -0
- package/dist/routines.e2e.test.d.ts +1 -0
- package/dist/routines.e2e.test.js +188 -0
- package/dist/routines.e2e.test.js.map +1 -0
- package/dist/scheduler.d.ts +116 -0
- package/dist/scheduler.e2e.test.d.ts +1 -0
- package/dist/scheduler.e2e.test.js +163 -0
- package/dist/scheduler.e2e.test.js.map +1 -0
- package/dist/scheduler.js +602 -0
- package/dist/scheduler.js.map +1 -0
- package/dist/scheduler.test.d.ts +1 -0
- package/dist/scheduler.test.js +180 -0
- package/dist/scheduler.test.js.map +1 -0
- package/dist/schema-sql.d.ts +1 -0
- package/dist/schema-sql.js +3 -0
- package/dist/schema-sql.js.map +1 -0
- package/dist/schema.d.ts +2968 -0
- package/dist/schema.js +95 -252
- package/dist/schema.js.map +1 -0
- package/dist/session-commands.e2e.test.d.ts +1 -0
- package/dist/session-commands.e2e.test.js +285 -0
- package/dist/session-commands.e2e.test.js.map +1 -0
- package/dist/session-events.d.ts +55 -0
- package/dist/session-events.js +190 -0
- package/dist/session-events.js.map +1 -0
- package/dist/session-ops.e2e.test.d.ts +1 -0
- package/dist/session-ops.e2e.test.js +114 -0
- package/dist/session-ops.e2e.test.js.map +1 -0
- package/dist/sessions.d.ts +121 -0
- package/dist/sessions.js +662 -0
- package/dist/sessions.js.map +1 -0
- package/dist/shell.e2e.test.d.ts +1 -0
- package/dist/shell.e2e.test.js +147 -0
- package/dist/shell.e2e.test.js.map +1 -0
- package/dist/slash-commands.d.ts +62 -0
- package/dist/slash-commands.js +475 -0
- package/dist/slash-commands.js.map +1 -0
- package/dist/slash-commands.test.d.ts +1 -0
- package/dist/slash-commands.test.js +87 -0
- package/dist/slash-commands.test.js.map +1 -0
- package/dist/sleep.e2e.test.d.ts +1 -0
- package/dist/sleep.e2e.test.js +115 -487
- package/dist/sleep.e2e.test.js.map +1 -0
- package/dist/sleeps.d.ts +37 -0
- package/dist/sleeps.js +220 -0
- package/dist/sleeps.js.map +1 -0
- package/dist/store.d.ts +10 -0
- package/dist/store.js +8 -28
- package/dist/store.js.map +1 -0
- package/dist/system-prompt.d.ts +65 -0
- package/dist/system-prompt.js +875 -0
- package/dist/system-prompt.js.map +1 -0
- package/dist/tasks.e2e.test.d.ts +1 -0
- package/dist/tasks.e2e.test.js +173 -0
- package/dist/tasks.e2e.test.js.map +1 -0
- package/dist/test/auth-plugin/index.d.ts +3 -0
- package/dist/test/auth-plugin/index.js +18 -0
- package/dist/test/auth-plugin/index.js.map +1 -0
- package/dist/test/fake-mcp-server.d.ts +1 -0
- package/dist/test/fake-mcp-server.js +45 -0
- package/dist/test/fake-mcp-server.js.map +1 -0
- package/dist/test/harness.d.ts +246 -0
- package/dist/test/harness.js +704 -0
- package/dist/test/harness.js.map +1 -0
- package/dist/test/replay.d.ts +14 -0
- package/dist/test/replay.js +75 -0
- package/dist/test/replay.js.map +1 -0
- package/dist/thread-reducer.d.ts +172 -0
- package/dist/thread-reducer.fixtures.test.d.ts +1 -0
- package/dist/thread-reducer.fixtures.test.js +360 -0
- package/dist/thread-reducer.fixtures.test.js.map +1 -0
- package/dist/thread-reducer.js +497 -0
- package/dist/thread-reducer.js.map +1 -0
- package/dist/thread-reducer.test.d.ts +1 -0
- package/dist/thread-reducer.test.js +369 -0
- package/dist/thread-reducer.test.js.map +1 -0
- package/dist/tts.test.d.ts +1 -0
- package/dist/tts.test.js +147 -0
- package/dist/tts.test.js.map +1 -0
- package/dist/voice-agent.d.ts +33 -0
- package/dist/voice-agent.js +159 -0
- package/dist/voice-agent.js.map +1 -0
- package/dist/voice-attachments.e2e.test.d.ts +1 -0
- package/dist/voice-attachments.e2e.test.js +253 -0
- package/dist/voice-attachments.e2e.test.js.map +1 -0
- package/dist/voice-call.d.ts +59 -0
- package/dist/voice-call.e2e.test.d.ts +1 -0
- package/dist/voice-call.e2e.test.js +259 -0
- package/dist/voice-call.e2e.test.js.map +1 -0
- package/dist/voice-call.js +760 -0
- package/dist/voice-call.js.map +1 -0
- package/dist/voice-openai.e2e.test.d.ts +1 -0
- package/dist/voice-openai.e2e.test.js +161 -0
- package/dist/voice-openai.e2e.test.js.map +1 -0
- package/dist/voice.d.ts +116 -0
- package/dist/voice.js +619 -717
- package/dist/voice.js.map +1 -0
- package/dist/working-directory.e2e.test.d.ts +1 -0
- package/dist/working-directory.e2e.test.js +231 -0
- package/dist/working-directory.e2e.test.js.map +1 -0
- package/dist/worktrees.d.ts +54 -0
- package/dist/worktrees.js +247 -709
- package/dist/worktrees.js.map +1 -0
- package/dist/worktrees.test.d.ts +1 -0
- package/dist/worktrees.test.js +97 -952
- package/dist/worktrees.test.js.map +1 -0
- package/package.json +38 -74
- package/src/agent-ui.e2e.test.ts +134 -0
- package/src/agent-ui.ts +216 -0
- package/src/analytics.e2e.test.ts +138 -0
- package/src/analytics.test.ts +122 -182
- package/src/analytics.ts +206 -258
- package/src/autostart.ts +78 -0
- package/src/bot.ts +194 -0
- package/src/btw.e2e.test.ts +130 -0
- package/src/catalog-refresh.e2e.test.ts +112 -0
- package/src/cli/bot.ts +419 -0
- package/src/cli/discord.ts +116 -0
- package/src/cli/project.ts +147 -0
- package/src/cli/schedule.ts +151 -0
- package/src/cli/session.ts +285 -0
- package/src/cli/shared.ts +139 -0
- package/src/cli-discord.e2e.test.ts +62 -0
- package/src/cli-lock.e2e.test.ts +530 -0
- package/src/cli-read.e2e.test.ts +78 -0
- package/src/cli-sessions.e2e.test.ts +122 -0
- package/src/cli.ts +35 -418
- package/src/commands/login-commands.ts +360 -0
- package/src/commands/preference-commands.ts +586 -0
- package/src/commands/session-commands.ts +289 -0
- package/src/commands/thread-commands.ts +324 -0
- package/src/commands/worktree-commands.ts +288 -0
- package/src/commands.e2e.test.ts +290 -0
- package/src/credentials.ts +425 -0
- package/src/db.test.ts +344 -759
- package/src/db.ts +66 -284
- package/src/duration.ts +18 -0
- package/src/effects.ts +303 -0
- package/src/errors.ts +39 -228
- package/src/event-loop.ts +462 -0
- package/src/file-edit-log.test.ts +68 -272
- package/src/file-edit-log.ts +108 -231
- package/src/fixtures/v1-schema.sql +241 -0
- package/src/fixtures/voice.ogg +0 -0
- package/src/format-parts.test.ts +150 -0
- package/src/format-parts.ts +405 -0
- package/src/gateway-replay.e2e.test.ts +116 -0
- package/src/harness.e2e.test.ts +124 -0
- package/src/hello-thread.e2e.test.ts +223 -0
- package/src/ingress.ts +334 -0
- package/src/interaction-context.ts +131 -0
- package/src/interrupt.e2e.test.ts +151 -0
- package/src/legacy.e2e.test.ts +105 -0
- package/src/live-call.ts +249 -0
- package/src/lock-routes.ts +625 -0
- package/src/lock-server.ts +233 -0
- package/src/logger.ts +120 -207
- package/src/login.e2e.test.ts +86 -0
- package/src/main.ts +230 -0
- package/src/markdown/components.ts +102 -0
- package/src/markdown/markdown.test.ts +414 -0
- package/src/markdown/render-markdown.ts +288 -0
- package/src/migrations.ts +198 -0
- package/src/onboarding-cli.e2e.test.ts +215 -0
- package/src/onboarding.e2e.test.ts +171 -0
- package/src/onboarding.ts +297 -0
- package/src/opencode-server.test.ts +24 -0
- package/src/opencode-server.ts +461 -0
- package/src/permissions.e2e.test.ts +191 -0
- package/src/permissions.ts +121 -0
- package/src/plugin/anthropic/index.test.ts +39 -0
- package/src/plugin/anthropic/index.ts +213 -0
- package/src/plugin/index.ts +93 -0
- package/src/plugin/session-id/index.ts +19 -0
- package/src/plugin.e2e.test.ts +112 -0
- package/src/preference-commands.e2e.test.ts +597 -0
- package/src/project.ts +216 -0
- package/src/prompt.ts +664 -0
- package/src/questions.e2e.test.ts +328 -0
- package/src/questions.ts +322 -0
- package/src/queue.e2e.test.ts +253 -0
- package/src/queue.ts +158 -0
- package/src/remote-send.e2e.test.ts +91 -0
- package/src/remote-send.ts +110 -0
- package/src/renderer.e2e.test.ts +390 -0
- package/src/restart.e2e.test.ts +90 -0
- package/src/routes.test.ts +81 -0
- package/src/routes.ts +48 -0
- package/src/routines.e2e.test.ts +210 -0
- package/src/scheduler.e2e.test.ts +182 -0
- package/src/scheduler.test.ts +183 -0
- package/src/scheduler.ts +648 -0
- package/src/schema-sql.ts +3 -0
- package/src/schema.ts +95 -271
- package/src/session-commands.e2e.test.ts +326 -0
- package/src/session-events.ts +218 -0
- package/src/session-ops.e2e.test.ts +141 -0
- package/src/sessions.ts +803 -0
- package/src/shell.e2e.test.ts +173 -0
- package/src/slash-commands.test.ts +89 -0
- package/src/slash-commands.ts +539 -0
- package/src/sleep.e2e.test.ts +135 -552
- package/src/sleeps.ts +230 -0
- package/src/store.ts +19 -217
- package/src/system-prompt.ts +944 -0
- package/src/test/auth-plugin/index.ts +17 -0
- package/src/test/fake-mcp-server.ts +49 -0
- package/src/test/harness.ts +957 -0
- package/src/test/replay.ts +90 -0
- package/src/thread-reducer.fixtures.test.ts +374 -0
- package/src/thread-reducer.test.ts +380 -0
- package/src/thread-reducer.ts +671 -0
- package/src/tts.test.ts +150 -0
- package/src/voice-agent.ts +196 -0
- package/src/voice-attachments.e2e.test.ts +298 -0
- package/src/voice-call.e2e.test.ts +293 -0
- package/src/voice-call.ts +764 -0
- package/src/voice-openai.e2e.test.ts +191 -0
- package/src/voice.ts +668 -988
- package/src/working-directory.e2e.test.ts +226 -0
- package/src/worktrees.test.ts +89 -1115
- package/src/worktrees.ts +195 -957
- package/bin.js +0 -2
- package/dist/agent-model.e2e.test.js +0 -1223
- package/dist/ai-tool-to-genai.js +0 -233
- package/dist/ai-tool-to-genai.test.js +0 -267
- package/dist/ai-tool.js +0 -6
- package/dist/anthropic-account-identity.js +0 -70
- package/dist/anthropic-account-identity.test.js +0 -38
- package/dist/anthropic-auth-plugin.js +0 -958
- package/dist/anthropic-auth-plugin.test.js +0 -115
- package/dist/anthropic-auth-state.js +0 -186
- package/dist/anthropic-auth-state.test.js +0 -203
- package/dist/bash-tool-schema-plugin.js +0 -50
- package/dist/bash-tool-schema-plugin.test.js +0 -68
- package/dist/bin.js +0 -151
- package/dist/bot-lifecycle.test.js +0 -206
- package/dist/btw-prefix-detection.js +0 -24
- package/dist/btw-prefix-detection.test.js +0 -130
- package/dist/cache-drift-plugin.js +0 -183
- package/dist/cache-rewrite.js +0 -39
- package/dist/cache-rewrite.test.js +0 -41
- package/dist/channel-management.js +0 -347
- package/dist/cli-commands/bot.js +0 -254
- package/dist/cli-commands/maintenance.js +0 -171
- package/dist/cli-commands/misc.js +0 -153
- package/dist/cli-commands/project.js +0 -510
- package/dist/cli-commands/send.js +0 -659
- package/dist/cli-commands/session.js +0 -889
- package/dist/cli-commands/task.js +0 -277
- package/dist/cli-commands/thread.js +0 -63
- package/dist/cli-commands/user.js +0 -145
- package/dist/cli-parsing.test.js +0 -226
- package/dist/cli-runner.js +0 -1591
- package/dist/cli-runner.test.js +0 -119
- package/dist/cli-send-thread.e2e.test.js +0 -470
- package/dist/cloudflare-transcription.js +0 -56
- package/dist/commands/abort.js +0 -84
- package/dist/commands/action-buttons.js +0 -248
- package/dist/commands/add-project.js +0 -111
- package/dist/commands/agent.js +0 -658
- package/dist/commands/ask-question.js +0 -371
- package/dist/commands/ask-question.test.js +0 -150
- package/dist/commands/btw.js +0 -275
- package/dist/commands/compact.js +0 -117
- package/dist/commands/context-usage.js +0 -194
- package/dist/commands/context-usage.test.js +0 -49
- package/dist/commands/create-new-project.js +0 -138
- package/dist/commands/diff.js +0 -63
- package/dist/commands/file-upload.js +0 -280
- package/dist/commands/fork-subagent.js +0 -175
- package/dist/commands/fork.js +0 -291
- package/dist/commands/gemini-apikey.js +0 -123
- package/dist/commands/last-sessions.js +0 -129
- package/dist/commands/login.js +0 -893
- package/dist/commands/mcp.js +0 -239
- package/dist/commands/memory-snapshot.js +0 -24
- package/dist/commands/mention-mode.js +0 -42
- package/dist/commands/merge-worktree.js +0 -205
- package/dist/commands/model-variant.js +0 -390
- package/dist/commands/model.js +0 -1012
- package/dist/commands/multioauth.js +0 -425
- package/dist/commands/new-worktree.js +0 -625
- package/dist/commands/paginated-select.js +0 -57
- package/dist/commands/permissions.js +0 -327
- package/dist/commands/queue.js +0 -347
- package/dist/commands/queue.test.js +0 -15
- package/dist/commands/remove-project.js +0 -111
- package/dist/commands/restart-opencode-server.js +0 -133
- package/dist/commands/resume.js +0 -148
- package/dist/commands/run-command.js +0 -79
- package/dist/commands/screenshare.js +0 -307
- package/dist/commands/screenshare.test.js +0 -20
- package/dist/commands/session-id.js +0 -78
- package/dist/commands/session.js +0 -248
- package/dist/commands/share.js +0 -80
- package/dist/commands/tasks.js +0 -296
- package/dist/commands/types.js +0 -2
- package/dist/commands/undo-redo.js +0 -313
- package/dist/commands/unset-model.js +0 -116
- package/dist/commands/upgrade.js +0 -41
- package/dist/commands/user-command.js +0 -196
- package/dist/commands/verbosity.js +0 -124
- package/dist/commands/vscode.js +0 -295
- package/dist/commands/worktrees.js +0 -548
- package/dist/condense-memory.js +0 -33
- package/dist/config.js +0 -126
- package/dist/context-awareness-plugin.js +0 -357
- package/dist/context-awareness-plugin.test.js +0 -126
- package/dist/cpu-profiler.js +0 -211
- package/dist/cpu-profiler.test.js +0 -160
- package/dist/critique-utils.js +0 -95
- package/dist/database.js +0 -1074
- package/dist/debounce-timeout.js +0 -28
- package/dist/debounced-process-flush.js +0 -77
- package/dist/default-channel-provisioning.test.js +0 -149
- package/dist/discord-bot.js +0 -1432
- package/dist/discord-command-registration.js +0 -519
- package/dist/discord-command-registration.test.js +0 -17
- package/dist/discord-urls.js +0 -81
- package/dist/discord-utils.js +0 -789
- package/dist/discord-utils.test.js +0 -400
- package/dist/escape-backticks.test.js +0 -429
- package/dist/event-stream-real-capture.e2e.test.js +0 -533
- package/dist/eventsource-parser.test.js +0 -327
- package/dist/exec-async.js +0 -38
- package/dist/external-directory-permissions.e2e.test.js +0 -86
- package/dist/external-directory-project-deny.e2e.test.js +0 -70
- package/dist/external-opencode-sync.js +0 -620
- package/dist/external-opencode-sync.test.js +0 -206
- package/dist/extract-sdk-error-message.test.js +0 -13
- package/dist/format-tables.js +0 -728
- package/dist/format-tables.test.js +0 -915
- package/dist/forum-sync/config.js +0 -79
- package/dist/forum-sync/discord-operations.js +0 -157
- package/dist/forum-sync/index.js +0 -5
- package/dist/forum-sync/markdown.js +0 -110
- package/dist/forum-sync/sync-to-discord.js +0 -417
- package/dist/forum-sync/sync-to-files.js +0 -190
- package/dist/forum-sync/types.js +0 -53
- package/dist/forum-sync/watchers.js +0 -322
- package/dist/gateway-proxy-reconnect.e2e.test.js +0 -414
- package/dist/gateway-proxy.e2e.test.js +0 -488
- package/dist/genai-worker-wrapper.js +0 -111
- package/dist/genai-worker.js +0 -309
- package/dist/genai.js +0 -232
- package/dist/git-worktree-core.js +0 -461
- package/dist/guild-categories.test.js +0 -225
- package/dist/heap-monitor.js +0 -122
- package/dist/hrana-server.js +0 -322
- package/dist/hrana-server.test.js +0 -99
- package/dist/html-actions.js +0 -123
- package/dist/html-actions.test.js +0 -70
- package/dist/html-components.js +0 -115
- package/dist/html-components.test.js +0 -34
- package/dist/image-optimizer-plugin.js +0 -153
- package/dist/image-utils.js +0 -112
- package/dist/interaction-handler.js +0 -578
- package/dist/ipc-polling.js +0 -245
- package/dist/ipc-tools-plugin.js +0 -251
- package/dist/kimaki-digital-twin.e2e.test.js +0 -161
- package/dist/kimaki-opencode-plugin-loading.e2e.test.js +0 -158
- package/dist/kimaki-opencode-plugin.js +0 -33
- package/dist/kimaki-opencode-plugin.test.js +0 -126
- package/dist/kimaki-workspace-adaptor.js +0 -118
- package/dist/limit-heading-depth.js +0 -25
- package/dist/limit-heading-depth.test.js +0 -105
- package/dist/list-channel-threads.js +0 -163
- package/dist/list-channel-threads.test.js +0 -80
- package/dist/markdown.js +0 -455
- package/dist/markdown.test.js +0 -411
- package/dist/memory-overview-plugin.js +0 -123
- package/dist/message-formatting.js +0 -788
- package/dist/message-formatting.test.js +0 -1132
- package/dist/message-preprocessing.js +0 -419
- package/dist/oauth-rotation-shared.js +0 -253
- package/dist/onboarding-tutorial.js +0 -163
- package/dist/onboarding-welcome.js +0 -37
- package/dist/openai-auth-plugin.js +0 -125
- package/dist/openai-auth-state.js +0 -237
- package/dist/openai-realtime.js +0 -224
- package/dist/opencode-command-detection.js +0 -65
- package/dist/opencode-command-detection.test.js +0 -240
- package/dist/opencode-command.js +0 -181
- package/dist/opencode-command.test.js +0 -218
- package/dist/opencode-interrupt-plugin.js +0 -253
- package/dist/opencode-interrupt-plugin.test.js +0 -293
- package/dist/opencode.js +0 -1217
- package/dist/parse-permission-rules.test.js +0 -117
- package/dist/patch-text-parser.js +0 -97
- package/dist/plugin-logger.js +0 -105
- package/dist/plugin-opencode-client.js +0 -54
- package/dist/privacy-sanitizer.js +0 -105
- package/dist/project-add.test.js +0 -62
- package/dist/question-abort-clears-dropdown.e2e.test.js +0 -89
- package/dist/question-abort-resume.e2e.test.js +0 -100
- package/dist/question-abort-retry-text-before-dropdown.e2e.test.js +0 -150
- package/dist/question-text-before-dropdown.e2e.test.js +0 -52
- package/dist/queue-advanced-abort.e2e.test.js +0 -348
- package/dist/queue-advanced-action-buttons.e2e.test.js +0 -209
- package/dist/queue-advanced-e2e-setup.js +0 -1043
- package/dist/queue-advanced-footer.e2e.test.js +0 -286
- package/dist/queue-advanced-model-switch.e2e.test.js +0 -297
- package/dist/queue-advanced-permissions-typing.e2e.test.js +0 -189
- package/dist/queue-advanced-question.e2e.test.js +0 -266
- package/dist/queue-advanced-typing-interrupt.e2e.test.js +0 -113
- package/dist/queue-advanced-typing.e2e.test.js +0 -151
- package/dist/queue-delete-message.e2e.test.js +0 -216
- package/dist/queue-drain-after-interactive-ui.e2e.test.js +0 -120
- package/dist/queue-interrupt-drain.e2e.test.js +0 -256
- package/dist/queue-question-select-drain.e2e.test.js +0 -162
- package/dist/runtime-idle-sweeper.js +0 -52
- package/dist/runtime-lifecycle.e2e.test.js +0 -570
- package/dist/sentry.js +0 -23
- package/dist/session-handler/agent-utils.js +0 -65
- package/dist/session-handler/event-stream-state.js +0 -1225
- package/dist/session-handler/event-stream-state.test.js +0 -3160
- package/dist/session-handler/global-event-listener.js +0 -212
- package/dist/session-handler/model-utils.js +0 -311
- package/dist/session-handler/model-utils.test.js +0 -445
- package/dist/session-handler/opencode-session-event-log.js +0 -121
- package/dist/session-handler/thread-ingress.test.js +0 -41
- package/dist/session-handler/thread-runtime-state.js +0 -209
- package/dist/session-handler/thread-session-runtime-summary.test.js +0 -121
- package/dist/session-handler/thread-session-runtime.js +0 -4320
- package/dist/session-handler.js +0 -9
- package/dist/session-search.js +0 -219
- package/dist/session-search.test.js +0 -287
- package/dist/session-sleep.test.js +0 -224
- package/dist/session-title-rename.test.js +0 -128
- package/dist/skill-filter.js +0 -43
- package/dist/skill-filter.test.js +0 -86
- package/dist/startup-service.js +0 -153
- package/dist/startup-time.e2e.test.js +0 -297
- package/dist/system-message.js +0 -806
- package/dist/system-message.test.js +0 -917
- package/dist/task-id-plugin.js +0 -16
- package/dist/task-runner.js +0 -554
- package/dist/task-schedule.js +0 -298
- package/dist/task-schedule.test.js +0 -366
- package/dist/test-utils.js +0 -385
- package/dist/thinking-utils.js +0 -45
- package/dist/thinking-utils.test.js +0 -35
- package/dist/thread-message-queue.e2e.test.js +0 -1227
- package/dist/tools.js +0 -357
- package/dist/undo-redo.e2e.test.js +0 -171
- package/dist/unnest-code-blocks.js +0 -146
- package/dist/unnest-code-blocks.test.js +0 -673
- package/dist/upgrade.js +0 -114
- package/dist/utils.js +0 -148
- package/dist/voice-attachment.js +0 -56
- package/dist/voice-handler.js +0 -727
- package/dist/voice-message.e2e.test.js +0 -1254
- package/dist/voice-routing-input.e2e.test.js +0 -195
- package/dist/voice-routing-question.e2e.test.js +0 -182
- package/dist/voice.test.js +0 -500
- package/dist/wait-session.js +0 -193
- package/dist/websockify.js +0 -69
- package/dist/worker-types.js +0 -4
- package/dist/worktree-clone-isolation.e2e.test.js +0 -188
- package/dist/worktree-lifecycle.e2e.test.js +0 -735
- package/dist/worktree-utils.js +0 -3
- package/dist/xai-auth-plugin.js +0 -238
- package/dist/xai-auth-state.js +0 -234
- package/dist/xai-auth-state.test.js +0 -183
- package/dist/xml.js +0 -92
- package/dist/xml.test.js +0 -32
- package/skills/bump-holocron-consumers/SKILL.md +0 -137
- package/skills/critique/SKILL.md +0 -116
- package/skills/egaki/SKILL.md +0 -86
- package/skills/errore/SKILL.md +0 -501
- package/skills/goke/SKILL.md +0 -695
- package/skills/holocron/SKILL.md +0 -942
- package/skills/lintcn/SKILL.md +0 -873
- package/skills/new-skill/SKILL.md +0 -339
- package/skills/npm-package/SKILL.md +0 -692
- package/skills/opensrc/SKILL.md +0 -102
- package/skills/playwriter/SKILL.md +0 -35
- package/skills/profano/SKILL.md +0 -33
- package/skills/readme.md +0 -20
- package/skills/sigillo/SKILL.md +0 -370
- package/skills/spiceflow/SKILL.md +0 -80
- package/skills/strada/SKILL.md +0 -104
- package/skills/termcast/SKILL.md +0 -133
- package/skills/tuistory/SKILL.md +0 -237
- package/skills/usecomputer/SKILL.md +0 -264
- package/skills/zele/SKILL.md +0 -81
- package/src/agent-model.e2e.test.ts +0 -1557
- package/src/ai-tool-to-genai.test.ts +0 -296
- package/src/ai-tool-to-genai.ts +0 -283
- package/src/ai-tool.ts +0 -39
- package/src/anthropic-account-identity.test.ts +0 -52
- package/src/anthropic-account-identity.ts +0 -96
- package/src/anthropic-auth-plugin.test.ts +0 -135
- package/src/anthropic-auth-plugin.ts +0 -1180
- package/src/anthropic-auth-state.test.ts +0 -259
- package/src/anthropic-auth-state.ts +0 -272
- package/src/bash-tool-schema-plugin.test.ts +0 -84
- package/src/bash-tool-schema-plugin.ts +0 -77
- package/src/bin.ts +0 -173
- package/src/bot-lifecycle.test.ts +0 -252
- package/src/btw-prefix-detection.test.ts +0 -142
- package/src/btw-prefix-detection.ts +0 -30
- package/src/cache-drift-plugin.ts +0 -249
- package/src/cache-rewrite.test.ts +0 -49
- package/src/cache-rewrite.ts +0 -96
- package/src/channel-management.ts +0 -494
- package/src/cli-commands/bot.ts +0 -374
- package/src/cli-commands/maintenance.ts +0 -237
- package/src/cli-commands/misc.ts +0 -208
- package/src/cli-commands/project.ts +0 -680
- package/src/cli-commands/send.ts +0 -885
- package/src/cli-commands/session.ts +0 -1181
- package/src/cli-commands/task.ts +0 -358
- package/src/cli-commands/thread.ts +0 -86
- package/src/cli-commands/user.ts +0 -195
- package/src/cli-parsing.test.ts +0 -305
- package/src/cli-runner.test.ts +0 -166
- package/src/cli-runner.ts +0 -2157
- package/src/cli-send-thread.e2e.test.ts +0 -609
- package/src/cloudflare-transcription.ts +0 -76
- package/src/commands/abort.ts +0 -113
- package/src/commands/action-buttons.ts +0 -371
- package/src/commands/add-project.ts +0 -146
- package/src/commands/agent.ts +0 -911
- package/src/commands/ask-question.test.ts +0 -177
- package/src/commands/ask-question.ts +0 -532
- package/src/commands/btw.ts +0 -356
- package/src/commands/compact.ts +0 -154
- package/src/commands/context-usage.test.ts +0 -57
- package/src/commands/context-usage.ts +0 -267
- package/src/commands/create-new-project.ts +0 -203
- package/src/commands/diff.ts +0 -91
- package/src/commands/file-upload.ts +0 -398
- package/src/commands/fork-subagent.ts +0 -262
- package/src/commands/fork.ts +0 -421
- package/src/commands/gemini-apikey.ts +0 -205
- package/src/commands/last-sessions.ts +0 -176
- package/src/commands/login.ts +0 -1182
- package/src/commands/mcp.ts +0 -307
- package/src/commands/memory-snapshot.ts +0 -30
- package/src/commands/mention-mode.ts +0 -66
- package/src/commands/merge-worktree.ts +0 -282
- package/src/commands/model-variant.ts +0 -522
- package/src/commands/model.ts +0 -1377
- package/src/commands/multioauth.ts +0 -508
- package/src/commands/new-worktree.ts +0 -835
- package/src/commands/paginated-select.ts +0 -81
- package/src/commands/permissions.ts +0 -458
- package/src/commands/queue.test.ts +0 -19
- package/src/commands/queue.ts +0 -471
- package/src/commands/remove-project.ts +0 -152
- package/src/commands/restart-opencode-server.ts +0 -173
- package/src/commands/resume.ts +0 -229
- package/src/commands/run-command.ts +0 -123
- package/src/commands/screenshare.test.ts +0 -30
- package/src/commands/screenshare.ts +0 -388
- package/src/commands/session-id.ts +0 -109
- package/src/commands/session.ts +0 -319
- package/src/commands/share.ts +0 -106
- package/src/commands/tasks.ts +0 -399
- package/src/commands/types.ts +0 -25
- package/src/commands/undo-redo.ts +0 -394
- package/src/commands/unset-model.ts +0 -152
- package/src/commands/upgrade.ts +0 -51
- package/src/commands/user-command.ts +0 -260
- package/src/commands/verbosity.ts +0 -172
- package/src/commands/vscode.ts +0 -378
- package/src/commands/worktrees.ts +0 -762
- package/src/condense-memory.ts +0 -36
- package/src/config.ts +0 -147
- package/src/context-awareness-plugin.test.ts +0 -144
- package/src/context-awareness-plugin.ts +0 -508
- package/src/cpu-profiler.test.ts +0 -183
- package/src/cpu-profiler.ts +0 -293
- package/src/critique-utils.ts +0 -139
- package/src/database.ts +0 -1484
- package/src/debounce-timeout.ts +0 -43
- package/src/debounced-process-flush.ts +0 -104
- package/src/default-channel-provisioning.test.ts +0 -156
- package/src/discord-bot.ts +0 -1852
- package/src/discord-command-registration.test.ts +0 -20
- package/src/discord-command-registration.ts +0 -704
- package/src/discord-urls.ts +0 -88
- package/src/discord-utils.test.ts +0 -470
- package/src/discord-utils.ts +0 -1079
- package/src/escape-backticks.test.ts +0 -469
- package/src/event-stream-real-capture.e2e.test.ts +0 -692
- package/src/eventsource-parser.test.ts +0 -351
- package/src/exec-async.ts +0 -44
- package/src/external-directory-permissions.e2e.test.ts +0 -103
- package/src/external-directory-project-deny.e2e.test.ts +0 -84
- package/src/external-opencode-sync.test.ts +0 -248
- package/src/external-opencode-sync.ts +0 -867
- package/src/extract-sdk-error-message.test.ts +0 -18
- package/src/format-tables.test.ts +0 -988
- package/src/format-tables.ts +0 -1031
- package/src/forum-sync/config.ts +0 -92
- package/src/forum-sync/discord-operations.ts +0 -245
- package/src/forum-sync/index.ts +0 -9
- package/src/forum-sync/markdown.ts +0 -172
- package/src/forum-sync/sync-to-discord.ts +0 -595
- package/src/forum-sync/sync-to-files.ts +0 -294
- package/src/forum-sync/types.ts +0 -175
- package/src/forum-sync/watchers.ts +0 -474
- package/src/gateway-proxy-reconnect.e2e.test.ts +0 -553
- package/src/gateway-proxy.e2e.test.ts +0 -648
- package/src/genai-worker-wrapper.ts +0 -164
- package/src/genai-worker.ts +0 -385
- package/src/genai.ts +0 -318
- package/src/git-worktree-core.ts +0 -616
- package/src/guild-categories.test.ts +0 -260
- package/src/heap-monitor.ts +0 -152
- package/src/hrana-server.test.ts +0 -130
- package/src/hrana-server.ts +0 -399
- package/src/html-actions.test.ts +0 -87
- package/src/html-actions.ts +0 -174
- package/src/html-components.test.ts +0 -38
- package/src/html-components.ts +0 -177
- package/src/image-optimizer-plugin.ts +0 -194
- package/src/image-utils.ts +0 -150
- package/src/interaction-handler.ts +0 -775
- package/src/ipc-polling.ts +0 -325
- package/src/ipc-tools-plugin.ts +0 -306
- package/src/kimaki-digital-twin.e2e.test.ts +0 -199
- package/src/kimaki-opencode-plugin-loading.e2e.test.ts +0 -208
- package/src/kimaki-opencode-plugin.test.ts +0 -142
- package/src/kimaki-opencode-plugin.ts +0 -34
- package/src/kimaki-workspace-adaptor.ts +0 -144
- package/src/limit-heading-depth.test.ts +0 -116
- package/src/limit-heading-depth.ts +0 -26
- package/src/list-channel-threads.test.ts +0 -90
- package/src/list-channel-threads.ts +0 -252
- package/src/markdown.test.ts +0 -498
- package/src/markdown.ts +0 -577
- package/src/memory-overview-plugin.ts +0 -159
- package/src/message-formatting.test.ts +0 -1401
- package/src/message-formatting.ts +0 -999
- package/src/message-preprocessing.ts +0 -557
- package/src/oauth-rotation-shared.ts +0 -332
- package/src/onboarding-tutorial.ts +0 -167
- package/src/onboarding-welcome.ts +0 -49
- package/src/openai-auth-plugin.ts +0 -158
- package/src/openai-auth-state.ts +0 -297
- package/src/openai-realtime.ts +0 -358
- package/src/opencode-command-detection.test.ts +0 -307
- package/src/opencode-command-detection.ts +0 -76
- package/src/opencode-command.test.ts +0 -290
- package/src/opencode-command.ts +0 -251
- package/src/opencode-interrupt-plugin.test.ts +0 -419
- package/src/opencode-interrupt-plugin.ts +0 -306
- package/src/opencode.ts +0 -1590
- package/src/parse-permission-rules.test.ts +0 -127
- package/src/patch-text-parser.ts +0 -107
- package/src/plugin-logger.ts +0 -139
- package/src/plugin-opencode-client.ts +0 -71
- package/src/privacy-sanitizer.ts +0 -142
- package/src/project-add.test.ts +0 -80
- package/src/question-abort-clears-dropdown.e2e.test.ts +0 -109
- package/src/question-abort-resume.e2e.test.ts +0 -121
- package/src/question-abort-retry-text-before-dropdown.e2e.test.ts +0 -172
- package/src/question-text-before-dropdown.e2e.test.ts +0 -67
- package/src/queue-advanced-abort.e2e.test.ts +0 -449
- package/src/queue-advanced-action-buttons.e2e.test.ts +0 -271
- package/src/queue-advanced-e2e-setup.ts +0 -1164
- package/src/queue-advanced-footer.e2e.test.ts +0 -357
- package/src/queue-advanced-model-switch.e2e.test.ts +0 -382
- package/src/queue-advanced-permissions-typing.e2e.test.ts +0 -256
- package/src/queue-advanced-question.e2e.test.ts +0 -322
- package/src/queue-advanced-typing-interrupt.e2e.test.ts +0 -147
- package/src/queue-advanced-typing.e2e.test.ts +0 -199
- package/src/queue-delete-message.e2e.test.ts +0 -262
- package/src/queue-drain-after-interactive-ui.e2e.test.ts +0 -152
- package/src/queue-interrupt-drain.e2e.test.ts +0 -299
- package/src/queue-question-select-drain.e2e.test.ts +0 -209
- package/src/runtime-idle-sweeper.ts +0 -76
- package/src/runtime-lifecycle.e2e.test.ts +0 -714
- package/src/schema.sql +0 -251
- package/src/sentry.ts +0 -26
- package/src/session-handler/agent-utils.ts +0 -104
- package/src/session-handler/event-stream-fixtures/real-session-action-buttons.jsonl +0 -45
- package/src/session-handler/event-stream-fixtures/real-session-footer-suppressed-on-pre-idle-interrupt.jsonl +0 -40
- package/src/session-handler/event-stream-fixtures/real-session-permission-external-file.jsonl +0 -23
- package/src/session-handler/event-stream-fixtures/real-session-task-normal.jsonl +0 -22
- package/src/session-handler/event-stream-fixtures/real-session-task-three-parallel-sleeps.jsonl +0 -277
- package/src/session-handler/event-stream-fixtures/real-session-task-user-interruption.jsonl +0 -46
- package/src/session-handler/event-stream-fixtures/session-abort-after-idle-race.jsonl +0 -21
- package/src/session-handler/event-stream-fixtures/session-concurrent-messages-serialized.jsonl +0 -56
- package/src/session-handler/event-stream-fixtures/session-explicit-abort.jsonl +0 -44
- package/src/session-handler/event-stream-fixtures/session-normal-completion.jsonl +0 -29
- package/src/session-handler/event-stream-fixtures/session-tool-call-noisy-stream.jsonl +0 -29
- package/src/session-handler/event-stream-fixtures/session-two-completions-same-session.jsonl +0 -50
- package/src/session-handler/event-stream-fixtures/session-user-interruption.jsonl +0 -59
- package/src/session-handler/event-stream-fixtures/session-voice-queued-followup.jsonl +0 -52
- package/src/session-handler/event-stream-state.test.ts +0 -3431
- package/src/session-handler/event-stream-state.ts +0 -1792
- package/src/session-handler/global-event-listener.ts +0 -258
- package/src/session-handler/model-utils.test.ts +0 -537
- package/src/session-handler/model-utils.ts +0 -489
- package/src/session-handler/opencode-session-event-log.ts +0 -159
- package/src/session-handler/thread-ingress.test.ts +0 -54
- package/src/session-handler/thread-runtime-state.ts +0 -358
- package/src/session-handler/thread-session-runtime-summary.test.ts +0 -169
- package/src/session-handler/thread-session-runtime.ts +0 -5696
- package/src/session-handler.ts +0 -15
- package/src/session-search.test.ts +0 -331
- package/src/session-search.ts +0 -382
- package/src/session-sleep.test.ts +0 -294
- package/src/session-title-rename.test.ts +0 -186
- package/src/skill-filter.test.ts +0 -110
- package/src/skill-filter.ts +0 -63
- package/src/startup-service.ts +0 -200
- package/src/startup-time.e2e.test.ts +0 -377
- package/src/system-message.test.ts +0 -1013
- package/src/system-message.ts +0 -1043
- package/src/task-id-plugin.ts +0 -17
- package/src/task-runner.ts +0 -768
- package/src/task-schedule.test.ts +0 -424
- package/src/task-schedule.ts +0 -470
- package/src/test-utils.ts +0 -549
- package/src/thinking-utils.test.ts +0 -46
- package/src/thinking-utils.ts +0 -82
- package/src/thread-message-queue.e2e.test.ts +0 -1468
- package/src/tools.ts +0 -430
- package/src/undici.d.ts +0 -12
- package/src/undo-redo.e2e.test.ts +0 -219
- package/src/unnest-code-blocks.test.ts +0 -713
- package/src/unnest-code-blocks.ts +0 -185
- package/src/upgrade.ts +0 -127
- package/src/utils.ts +0 -216
- package/src/voice-attachment.ts +0 -79
- package/src/voice-handler.ts +0 -1000
- package/src/voice-message.e2e.test.ts +0 -1513
- package/src/voice-routing-input.e2e.test.ts +0 -203
- package/src/voice-routing-question.e2e.test.ts +0 -188
- package/src/voice.test.ts +0 -565
- package/src/wait-session.ts +0 -291
- package/src/websockify.ts +0 -101
- package/src/worker-types.ts +0 -64
- package/src/worktree-clone-isolation.e2e.test.ts +0 -213
- package/src/worktree-lifecycle.e2e.test.ts +0 -900
- package/src/worktree-utils.ts +0 -4
- package/src/xai-auth-plugin.ts +0 -303
- package/src/xai-auth-state.test.ts +0 -224
- package/src/xai-auth-state.ts +0 -294
- package/src/xml.test.ts +0 -38
- package/src/xml.ts +0 -121
package/src/opencode.ts
DELETED
|
@@ -1,1590 +0,0 @@
|
|
|
1
|
-
// OpenCode single-server process manager.
|
|
2
|
-
//
|
|
3
|
-
// Architecture: ONE opencode serve process shared by all project directories.
|
|
4
|
-
// Each SDK client uses the x-opencode-directory header to scope requests to a
|
|
5
|
-
// specific project. The server lazily creates and caches an Instance per unique
|
|
6
|
-
// directory path internally.
|
|
7
|
-
//
|
|
8
|
-
// Permission layering — READ THIS BEFORE ADDING A PERMISSION RULE.
|
|
9
|
-
//
|
|
10
|
-
// opencode evaluates permissions with findLast() over a flattened list, so the
|
|
11
|
-
// last matching rule wins. The order is:
|
|
12
|
-
//
|
|
13
|
-
// opencode built-in defaults
|
|
14
|
-
// ▼
|
|
15
|
-
// merged config files ── kimaki's generated config, THEN the user's
|
|
16
|
-
// ▼ project opencode.json (deep-merged on top)
|
|
17
|
-
// config.agent.<name>.permission
|
|
18
|
-
// ▼
|
|
19
|
-
// session.permission ── buildSessionPermissions(), always wins
|
|
20
|
-
//
|
|
21
|
-
// Directory ALLOW rules therefore belong in the generated server config, never
|
|
22
|
-
// in session rules or an agent block: a project opencode.json must still be
|
|
23
|
-
// able to `deny` or `ask` for specific folders. Anything placed in
|
|
24
|
-
// session.permission silently overrides the user.
|
|
25
|
-
//
|
|
26
|
-
// external_directory is `{ '*': 'allow' }` by default. opencode's own default
|
|
27
|
-
// is `ask`, which meant the agent had to interrupt the user for ordinary reads
|
|
28
|
-
// outside the project, and an unanswered prompt was auto-rejected on TTL. Users
|
|
29
|
-
// who want stricter behaviour add `deny`/`ask` rules to their own
|
|
30
|
-
// opencode.json, or start kimaki with --restrict-directories.
|
|
31
|
-
//
|
|
32
|
-
// session.permission carries exactly one thing: the worktree original-checkout
|
|
33
|
-
// deny, which must beat user config on purpose.
|
|
34
|
-
//
|
|
35
|
-
// Uses errore for type-safe error handling.
|
|
36
|
-
|
|
37
|
-
import { spawn, execFileSync, type ChildProcess } from 'node:child_process'
|
|
38
|
-
import fs from 'node:fs'
|
|
39
|
-
import http from 'node:http'
|
|
40
|
-
import net from 'node:net'
|
|
41
|
-
import os from 'node:os'
|
|
42
|
-
import path from 'node:path'
|
|
43
|
-
import readline from 'node:readline'
|
|
44
|
-
import { createRequire } from 'node:module'
|
|
45
|
-
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
46
|
-
|
|
47
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
48
|
-
import {
|
|
49
|
-
createOpencodeClient,
|
|
50
|
-
type OpencodeClient,
|
|
51
|
-
type Config as SdkConfig,
|
|
52
|
-
type PermissionRuleset,
|
|
53
|
-
} from '@opencode-ai/sdk/v2'
|
|
54
|
-
|
|
55
|
-
import {
|
|
56
|
-
restartGlobalEventListener,
|
|
57
|
-
waitForGlobalEventListener,
|
|
58
|
-
} from './session-handler/global-event-listener.js'
|
|
59
|
-
import {
|
|
60
|
-
getDataDir,
|
|
61
|
-
getLockPort,
|
|
62
|
-
getRestrictExternalDirectories,
|
|
63
|
-
getOpencodeHostname,
|
|
64
|
-
getOpencodePort,
|
|
65
|
-
} from './config.js'
|
|
66
|
-
import { store } from './store.js'
|
|
67
|
-
import { getHranaUrl } from './hrana-server.js'
|
|
68
|
-
|
|
69
|
-
export function resolveSubrouterPluginSpec({ isDev }: { isDev: boolean }) {
|
|
70
|
-
const require = createRequire(import.meta.url)
|
|
71
|
-
const entry = require.resolve('@subrouter/opencode')
|
|
72
|
-
if (isDev) return pathToFileURL(entry).href
|
|
73
|
-
|
|
74
|
-
const packageJsonPath = require.resolve('@subrouter/opencode/package.json')
|
|
75
|
-
const version = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')).version
|
|
76
|
-
if (typeof version !== 'string' || !version) {
|
|
77
|
-
throw new Error(`Missing @subrouter/opencode version in ${packageJsonPath}`)
|
|
78
|
-
}
|
|
79
|
-
return `@subrouter/opencode@${version}`
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// SDK Config type is simplified; opencode accepts nested permission objects with path patterns
|
|
83
|
-
type PermissionAction = 'ask' | 'allow' | 'deny'
|
|
84
|
-
type PermissionRule = PermissionAction | Record<string, PermissionAction>
|
|
85
|
-
type Config = Omit<SdkConfig, 'permission'> & {
|
|
86
|
-
permission?: {
|
|
87
|
-
edit?: PermissionRule
|
|
88
|
-
bash?: PermissionRule
|
|
89
|
-
external_directory?: PermissionRule
|
|
90
|
-
webfetch?: PermissionRule
|
|
91
|
-
[key: string]: PermissionRule | undefined
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
import * as errore from 'errore'
|
|
95
|
-
import { createLogger, LogPrefix } from './logger.js'
|
|
96
|
-
import { notifyError } from './sentry.js'
|
|
97
|
-
import {
|
|
98
|
-
DirectoryNotAccessibleError,
|
|
99
|
-
ServerStartError,
|
|
100
|
-
ServerNotReadyError,
|
|
101
|
-
FetchError,
|
|
102
|
-
OpencodeIncompatibleVersionError,
|
|
103
|
-
type OpenCodeErrors,
|
|
104
|
-
} from './errors.js'
|
|
105
|
-
import {
|
|
106
|
-
ensureKimakiCommandShim,
|
|
107
|
-
getIncompatibleOpencodeVersionError,
|
|
108
|
-
getPathEnvKey,
|
|
109
|
-
getSpawnCommandAndArgs,
|
|
110
|
-
prependPathEntry,
|
|
111
|
-
selectResolvedCommand,
|
|
112
|
-
} from './opencode-command.js'
|
|
113
|
-
import { execAsync } from './exec-async.js'
|
|
114
|
-
import { computeSkillPermission } from './skill-filter.js'
|
|
115
|
-
|
|
116
|
-
const opencodeLogger = createLogger(LogPrefix.OPENCODE)
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Build Basic auth headers from OPENCODE_SERVER_PASSWORD env var.
|
|
120
|
-
* Returns empty object when no password is set.
|
|
121
|
-
*/
|
|
122
|
-
export function getOpencodeServerAuthHeaders(): Record<string, string> {
|
|
123
|
-
const serverPassword = process.env.OPENCODE_SERVER_PASSWORD
|
|
124
|
-
if (!serverPassword) return {}
|
|
125
|
-
const username = process.env.OPENCODE_SERVER_USERNAME || 'opencode'
|
|
126
|
-
const encoded = Buffer.from(`${username}:${serverPassword}`).toString('base64')
|
|
127
|
-
return { Authorization: `Basic ${encoded}` }
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const LOOPBACK_HOSTNAMES = new Set(['127.0.0.1', 'localhost', '::1'])
|
|
131
|
-
|
|
132
|
-
export function publicOpencodeBindRequiresPassword({
|
|
133
|
-
hostname,
|
|
134
|
-
}: {
|
|
135
|
-
hostname: string | null | undefined
|
|
136
|
-
}): boolean {
|
|
137
|
-
if (!hostname) return false
|
|
138
|
-
return !LOOPBACK_HOSTNAMES.has(hostname)
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Always pass --hostname so opencode.json server.hostname / mdns cannot bind 0.0.0.0.
|
|
142
|
-
const DEFAULT_OPENCODE_HOSTNAME = '127.0.0.1'
|
|
143
|
-
|
|
144
|
-
export function buildOpencodeServeArgs({
|
|
145
|
-
port,
|
|
146
|
-
hostname,
|
|
147
|
-
}: {
|
|
148
|
-
port: number
|
|
149
|
-
hostname?: string | null
|
|
150
|
-
}): string[] {
|
|
151
|
-
return [
|
|
152
|
-
'serve',
|
|
153
|
-
'--port',
|
|
154
|
-
port.toString(),
|
|
155
|
-
'--hostname',
|
|
156
|
-
hostname || DEFAULT_OPENCODE_HOSTNAME,
|
|
157
|
-
'--print-logs',
|
|
158
|
-
'--log-level',
|
|
159
|
-
'WARN',
|
|
160
|
-
]
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Tracks directories that have been initialized, to avoid repeated log spam
|
|
164
|
-
// from the external sync polling loop.
|
|
165
|
-
const initializedDirectories = new Set<string>()
|
|
166
|
-
|
|
167
|
-
const STARTUP_STDERR_TAIL_LIMIT = 30
|
|
168
|
-
const STARTUP_STDERR_LINE_MAX_LENGTH = 120
|
|
169
|
-
const STARTUP_ERROR_REASON_MAX_LENGTH = 1500
|
|
170
|
-
const ANSI_ESCAPE_REGEX =
|
|
171
|
-
/[\u001B\u009B][[\]()#;?]*(?:(?:(?:[a-zA-Z\d]*(?:;[a-zA-Z\d]*)*)?\u0007)|(?:(?:\d{1,4}(?:;\d{0,4})*)?[\dA-PR-TZcf-nq-uy=><~]))/g
|
|
172
|
-
|
|
173
|
-
export async function requestHealthcheck({
|
|
174
|
-
url,
|
|
175
|
-
timeoutMs = 2000,
|
|
176
|
-
}: {
|
|
177
|
-
url: string
|
|
178
|
-
timeoutMs?: number
|
|
179
|
-
}): Promise<{ status: number; body: string }> {
|
|
180
|
-
return new Promise((resolve, reject) => {
|
|
181
|
-
let settled = false
|
|
182
|
-
let timeout: NodeJS.Timeout | null = null
|
|
183
|
-
const settle = (
|
|
184
|
-
handler: () => void,
|
|
185
|
-
) => {
|
|
186
|
-
if (settled) return
|
|
187
|
-
settled = true
|
|
188
|
-
if (timeout) clearTimeout(timeout)
|
|
189
|
-
handler()
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
const req = http.request(
|
|
193
|
-
url,
|
|
194
|
-
{
|
|
195
|
-
method: 'GET',
|
|
196
|
-
headers: {
|
|
197
|
-
connection: 'close',
|
|
198
|
-
...getOpencodeServerAuthHeaders(),
|
|
199
|
-
},
|
|
200
|
-
},
|
|
201
|
-
(res) => {
|
|
202
|
-
const chunks: Buffer[] = []
|
|
203
|
-
res.on('data', (chunk) => {
|
|
204
|
-
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk))
|
|
205
|
-
})
|
|
206
|
-
res.on('end', () => {
|
|
207
|
-
settle(() => {
|
|
208
|
-
resolve({
|
|
209
|
-
status: res.statusCode || 0,
|
|
210
|
-
body: Buffer.concat(chunks).toString('utf-8'),
|
|
211
|
-
})
|
|
212
|
-
})
|
|
213
|
-
})
|
|
214
|
-
res.on('error', (error) => {
|
|
215
|
-
settle(() => reject(error))
|
|
216
|
-
})
|
|
217
|
-
},
|
|
218
|
-
)
|
|
219
|
-
req.on('error', (error) => {
|
|
220
|
-
settle(() => reject(error))
|
|
221
|
-
})
|
|
222
|
-
timeout = setTimeout(() => {
|
|
223
|
-
settle(() => {
|
|
224
|
-
req.destroy()
|
|
225
|
-
reject(new Error(`Health check request timed out after ${timeoutMs}ms`))
|
|
226
|
-
})
|
|
227
|
-
}, timeoutMs)
|
|
228
|
-
req.end()
|
|
229
|
-
})
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
function truncateWithEllipsis({
|
|
233
|
-
value,
|
|
234
|
-
maxLength,
|
|
235
|
-
}: {
|
|
236
|
-
value: string
|
|
237
|
-
maxLength: number
|
|
238
|
-
}): string {
|
|
239
|
-
if (maxLength <= 3) {
|
|
240
|
-
return value.slice(0, maxLength)
|
|
241
|
-
}
|
|
242
|
-
if (value.length <= maxLength) {
|
|
243
|
-
return value
|
|
244
|
-
}
|
|
245
|
-
return `${value.slice(0, maxLength - 3)}...`
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
function stripAnsiCodes(value: string): string {
|
|
249
|
-
return value.replaceAll(ANSI_ESCAPE_REGEX, '')
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
function sanitizeOutputLine(line: string): string {
|
|
253
|
-
return stripAnsiCodes(line).trim()
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
function sanitizeForCodeFence(line: string): string {
|
|
257
|
-
return line.replaceAll('```', '`\u200b``')
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
function pushStartupStderrTail({
|
|
261
|
-
stderrTail,
|
|
262
|
-
line,
|
|
263
|
-
}: {
|
|
264
|
-
stderrTail: string[]
|
|
265
|
-
line: string
|
|
266
|
-
}): void {
|
|
267
|
-
const sanitizedLine = sanitizeOutputLine(line)
|
|
268
|
-
if (sanitizedLine.length === 0) {
|
|
269
|
-
return
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
const truncatedLine = truncateWithEllipsis({
|
|
273
|
-
value: sanitizeForCodeFence(sanitizedLine),
|
|
274
|
-
maxLength: STARTUP_STDERR_LINE_MAX_LENGTH,
|
|
275
|
-
})
|
|
276
|
-
|
|
277
|
-
stderrTail.push(truncatedLine)
|
|
278
|
-
if (stderrTail.length > STARTUP_STDERR_TAIL_LIMIT) {
|
|
279
|
-
stderrTail.splice(0, stderrTail.length - STARTUP_STDERR_TAIL_LIMIT)
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
function subscribeToProcessLogStream({
|
|
284
|
-
stream,
|
|
285
|
-
onLine,
|
|
286
|
-
}: {
|
|
287
|
-
stream: NodeJS.ReadableStream | null | undefined
|
|
288
|
-
onLine: (line: string) => void
|
|
289
|
-
}): readline.Interface | null {
|
|
290
|
-
if (!stream) {
|
|
291
|
-
return null
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
const logReader = readline.createInterface({
|
|
295
|
-
input: stream,
|
|
296
|
-
crlfDelay: Infinity,
|
|
297
|
-
})
|
|
298
|
-
|
|
299
|
-
logReader.on('line', (line) => {
|
|
300
|
-
const sanitizedLine = sanitizeOutputLine(line)
|
|
301
|
-
if (sanitizedLine.length === 0) {
|
|
302
|
-
return
|
|
303
|
-
}
|
|
304
|
-
onLine(sanitizedLine)
|
|
305
|
-
})
|
|
306
|
-
|
|
307
|
-
return logReader
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
function buildStartupTimeoutReason({
|
|
311
|
-
maxAttempts,
|
|
312
|
-
stderrTail,
|
|
313
|
-
}: {
|
|
314
|
-
maxAttempts: number
|
|
315
|
-
stderrTail: string[]
|
|
316
|
-
}): string {
|
|
317
|
-
const timeoutSeconds = Math.round((maxAttempts * 100) / 1000)
|
|
318
|
-
const baseReason = `Server did not start after ${timeoutSeconds} seconds`
|
|
319
|
-
if (stderrTail.length === 0) {
|
|
320
|
-
return baseReason
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
const formatReason = ({
|
|
324
|
-
lines,
|
|
325
|
-
omitted,
|
|
326
|
-
}: {
|
|
327
|
-
lines: string[]
|
|
328
|
-
omitted: number
|
|
329
|
-
}): string => {
|
|
330
|
-
const omittedLine =
|
|
331
|
-
omitted > 0
|
|
332
|
-
? `[... ${omitted} older stderr lines omitted to fit Discord ...]\n`
|
|
333
|
-
: ''
|
|
334
|
-
const stderrCodeBlock = `${omittedLine}${lines.join('\n')}`
|
|
335
|
-
return `${baseReason}\nLast opencode stderr lines:\n\`\`\`text\n${stderrCodeBlock}\n\`\`\``
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
let lines = [...stderrTail]
|
|
339
|
-
let omitted = 0
|
|
340
|
-
let formattedReason = formatReason({ lines, omitted })
|
|
341
|
-
|
|
342
|
-
while (
|
|
343
|
-
formattedReason.length > STARTUP_ERROR_REASON_MAX_LENGTH &&
|
|
344
|
-
lines.length > 0
|
|
345
|
-
) {
|
|
346
|
-
lines = lines.slice(1)
|
|
347
|
-
omitted += 1
|
|
348
|
-
formattedReason = formatReason({ lines, omitted })
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
return truncateWithEllipsis({
|
|
352
|
-
value: formattedReason,
|
|
353
|
-
maxLength: STARTUP_ERROR_REASON_MAX_LENGTH,
|
|
354
|
-
})
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
// ── Single server state ──────────────────────────────────────────
|
|
358
|
-
// One opencode serve process, shared by all project directories.
|
|
359
|
-
// Clients are created per-directory with the x-opencode-directory header.
|
|
360
|
-
|
|
361
|
-
type SingleServer = {
|
|
362
|
-
process: ChildProcess | null
|
|
363
|
-
port: number
|
|
364
|
-
baseUrl: string
|
|
365
|
-
/** True when this server was discovered from the bot's hrana endpoint,
|
|
366
|
-
* not spawned by this process. We must not kill it on cleanup. */
|
|
367
|
-
discovered?: boolean
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
type ServerLifecycleEvent =
|
|
371
|
-
| { type: 'started'; port: number }
|
|
372
|
-
| { type: 'stopped' }
|
|
373
|
-
|
|
374
|
-
let singleServer: SingleServer | null = null
|
|
375
|
-
let serverRetryCount = 0
|
|
376
|
-
const serverLifecycleListeners = new Set<(event: ServerLifecycleEvent) => void>()
|
|
377
|
-
let processCleanupHandlersRegistered = false
|
|
378
|
-
let startingServerProcess: ChildProcess | null = null
|
|
379
|
-
const clientCache = new Map<string, OpencodeClient>()
|
|
380
|
-
|
|
381
|
-
function notifyServerLifecycle(event: ServerLifecycleEvent): void {
|
|
382
|
-
for (const listener of serverLifecycleListeners) {
|
|
383
|
-
listener(event)
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
export function subscribeOpencodeServerLifecycle(
|
|
388
|
-
listener: (event: ServerLifecycleEvent) => void,
|
|
389
|
-
): () => void {
|
|
390
|
-
serverLifecycleListeners.add(listener)
|
|
391
|
-
return () => {
|
|
392
|
-
serverLifecycleListeners.delete(listener)
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
function killSingleServerProcessNow({
|
|
397
|
-
reason,
|
|
398
|
-
}: {
|
|
399
|
-
reason: string
|
|
400
|
-
}): void {
|
|
401
|
-
if (!singleServer) {
|
|
402
|
-
return
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
// Never kill a server we didn't spawn (discovered from another process)
|
|
406
|
-
if (singleServer.discovered || !singleServer.process) {
|
|
407
|
-
return
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
const serverProcess = singleServer.process
|
|
411
|
-
const pid = serverProcess.pid
|
|
412
|
-
if (!pid || serverProcess.killed) {
|
|
413
|
-
return
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
const killResult = errore.try(
|
|
417
|
-
() => {
|
|
418
|
-
serverProcess.kill('SIGTERM')
|
|
419
|
-
},
|
|
420
|
-
(error) => {
|
|
421
|
-
return new Error('Failed to send SIGTERM to opencode server', {
|
|
422
|
-
cause: error,
|
|
423
|
-
})
|
|
424
|
-
},
|
|
425
|
-
)
|
|
426
|
-
|
|
427
|
-
if (killResult instanceof Error) {
|
|
428
|
-
opencodeLogger.warn(
|
|
429
|
-
`[cleanup:${reason}] ${killResult.message} (pid: ${pid}, port: ${singleServer.port})`,
|
|
430
|
-
)
|
|
431
|
-
return
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
opencodeLogger.log(
|
|
435
|
-
`[cleanup:${reason}] Sent SIGTERM to opencode server (pid: ${pid}, port: ${singleServer.port})`,
|
|
436
|
-
)
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
function killStartingServerProcessNow({
|
|
440
|
-
reason,
|
|
441
|
-
}: {
|
|
442
|
-
reason: string
|
|
443
|
-
}): void {
|
|
444
|
-
const serverProcess = startingServerProcess
|
|
445
|
-
if (!serverProcess) {
|
|
446
|
-
return
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
const pid = serverProcess.pid
|
|
450
|
-
if (!pid || serverProcess.killed) {
|
|
451
|
-
return
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
const killResult = errore.try(
|
|
455
|
-
() => {
|
|
456
|
-
serverProcess.kill('SIGTERM')
|
|
457
|
-
},
|
|
458
|
-
(error) => {
|
|
459
|
-
return new Error('Failed to send SIGTERM to starting opencode server', {
|
|
460
|
-
cause: error,
|
|
461
|
-
})
|
|
462
|
-
},
|
|
463
|
-
)
|
|
464
|
-
|
|
465
|
-
if (killResult instanceof Error) {
|
|
466
|
-
opencodeLogger.warn(
|
|
467
|
-
`[cleanup:${reason}] ${killResult.message} (pid: ${pid})`,
|
|
468
|
-
)
|
|
469
|
-
return
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
opencodeLogger.log(
|
|
473
|
-
`[cleanup:${reason}] Sent SIGTERM to starting opencode server (pid: ${pid})`,
|
|
474
|
-
)
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
function ensureProcessCleanupHandlersRegistered(): void {
|
|
478
|
-
if (processCleanupHandlersRegistered) {
|
|
479
|
-
return
|
|
480
|
-
}
|
|
481
|
-
processCleanupHandlersRegistered = true
|
|
482
|
-
|
|
483
|
-
opencodeLogger.log('Registering process cleanup handlers for opencode server')
|
|
484
|
-
|
|
485
|
-
process.on('exit', () => {
|
|
486
|
-
killSingleServerProcessNow({ reason: 'process-exit' })
|
|
487
|
-
killStartingServerProcessNow({ reason: 'process-exit' })
|
|
488
|
-
})
|
|
489
|
-
|
|
490
|
-
// Fallback for CLI subcommands without their own signal handling. Any signal
|
|
491
|
-
// listener disables Node's default exit, so if we are the only listener we
|
|
492
|
-
// must exit ourselves. Otherwise the process ignores Ctrl+C and, in the bot,
|
|
493
|
-
// keeps holding the hrana lock port. If another owner exists (the bot
|
|
494
|
-
// lifecycle handlers, a subcommand), it decides when to exit.
|
|
495
|
-
for (const signal of ['SIGINT', 'SIGTERM'] as const) {
|
|
496
|
-
process.on(signal, () => {
|
|
497
|
-
killSingleServerProcessNow({ reason: signal.toLowerCase() })
|
|
498
|
-
killStartingServerProcessNow({ reason: signal.toLowerCase() })
|
|
499
|
-
if (process.listenerCount(signal) > 1) {
|
|
500
|
-
return
|
|
501
|
-
}
|
|
502
|
-
process.exit(128 + os.constants.signals[signal])
|
|
503
|
-
})
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
// ── Resolve opencode binary ──────────────────────────────────────
|
|
508
|
-
// Resolve the full path to the opencode binary so we can spawn without
|
|
509
|
-
// shell: true. Using shell: true creates an intermediate sh process — when
|
|
510
|
-
// cleanup sends SIGTERM it only kills the shell, leaving the actual opencode
|
|
511
|
-
// process orphaned (reparented to PID 1). Resolving the path upfront lets
|
|
512
|
-
// us spawn the binary directly and SIGTERM reaches the right process.
|
|
513
|
-
//
|
|
514
|
-
// Resolution order:
|
|
515
|
-
// 1. OPENCODE_PATH env var (explicit user override)
|
|
516
|
-
// 2. `which opencode` / `where opencode` (system PATH)
|
|
517
|
-
// 3. Fall back to bare "opencode" (spawn will fail with a clear error)
|
|
518
|
-
//
|
|
519
|
-
// OpenCode must be installed globally before running kimaki. The bot startup
|
|
520
|
-
// checks for it via ensureCommandAvailable and prompts to install if missing.
|
|
521
|
-
|
|
522
|
-
let resolvedOpencodeCommand: string | null = null
|
|
523
|
-
|
|
524
|
-
export function resolveOpencodeCommand(): string {
|
|
525
|
-
if (resolvedOpencodeCommand) {
|
|
526
|
-
return resolvedOpencodeCommand
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
const envPath = process.env.OPENCODE_PATH
|
|
530
|
-
if (envPath) {
|
|
531
|
-
const resolvedFromEnv = selectResolvedCommand({
|
|
532
|
-
output: envPath,
|
|
533
|
-
isWindows: process.platform === 'win32',
|
|
534
|
-
})
|
|
535
|
-
if (resolvedFromEnv) {
|
|
536
|
-
resolvedOpencodeCommand = resolvedFromEnv
|
|
537
|
-
return resolvedFromEnv
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
const isWindows = process.platform === 'win32'
|
|
542
|
-
const whichCmd = isWindows ? 'where' : 'which'
|
|
543
|
-
const result = errore.try(
|
|
544
|
-
() => {
|
|
545
|
-
const commandOutput = execFileSync(whichCmd, ['opencode'], {
|
|
546
|
-
encoding: 'utf8',
|
|
547
|
-
timeout: 5000,
|
|
548
|
-
})
|
|
549
|
-
const resolved = selectResolvedCommand({
|
|
550
|
-
output: commandOutput,
|
|
551
|
-
isWindows,
|
|
552
|
-
})
|
|
553
|
-
if (resolved) {
|
|
554
|
-
return resolved
|
|
555
|
-
}
|
|
556
|
-
throw new Error('opencode not found in PATH')
|
|
557
|
-
},
|
|
558
|
-
() => new Error('opencode not found in PATH'),
|
|
559
|
-
)
|
|
560
|
-
|
|
561
|
-
if (result instanceof Error) {
|
|
562
|
-
// Fall back to bare command name — spawn will fail with a clear error
|
|
563
|
-
// if it can't find the binary.
|
|
564
|
-
opencodeLogger.warn('Could not resolve opencode path via which, falling back to "opencode"')
|
|
565
|
-
return 'opencode'
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
resolvedOpencodeCommand = result
|
|
569
|
-
opencodeLogger.log(`Resolved opencode binary: ${result}`)
|
|
570
|
-
return result
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
export async function assertCompatibleOpencodeVersion({
|
|
574
|
-
resolvedCommand = resolveOpencodeCommand(),
|
|
575
|
-
}: {
|
|
576
|
-
resolvedCommand?: string
|
|
577
|
-
} = {}): Promise<OpencodeIncompatibleVersionError | true> {
|
|
578
|
-
const { command, args } = getSpawnCommandAndArgs({
|
|
579
|
-
resolvedCommand,
|
|
580
|
-
baseArgs: ['--version'],
|
|
581
|
-
})
|
|
582
|
-
const result = await execAsync(
|
|
583
|
-
{ command, args },
|
|
584
|
-
{ timeout: 5000, encoding: 'utf8' },
|
|
585
|
-
).catch((cause) => {
|
|
586
|
-
return new Error('Failed to read OpenCode version', { cause })
|
|
587
|
-
})
|
|
588
|
-
if (result instanceof Error) {
|
|
589
|
-
opencodeLogger.warn(result.message)
|
|
590
|
-
return true
|
|
591
|
-
}
|
|
592
|
-
const incompatible = getIncompatibleOpencodeVersionError(
|
|
593
|
-
`${result.stdout}\n${result.stderr}`,
|
|
594
|
-
)
|
|
595
|
-
if (incompatible) return incompatible
|
|
596
|
-
return true
|
|
597
|
-
}
|
|
598
|
-
async function getOpenPort(): Promise<number> {
|
|
599
|
-
return new Promise((resolve, reject) => {
|
|
600
|
-
const server = net.createServer()
|
|
601
|
-
server.listen(0, () => {
|
|
602
|
-
const address = server.address()
|
|
603
|
-
if (address && typeof address === 'object') {
|
|
604
|
-
const port = address.port
|
|
605
|
-
server.close(() => {
|
|
606
|
-
resolve(port)
|
|
607
|
-
})
|
|
608
|
-
} else {
|
|
609
|
-
reject(new Error('Failed to get port'))
|
|
610
|
-
}
|
|
611
|
-
})
|
|
612
|
-
server.on('error', reject)
|
|
613
|
-
})
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
async function waitForServer({
|
|
617
|
-
port,
|
|
618
|
-
directory,
|
|
619
|
-
maxAttempts = 300,
|
|
620
|
-
startupStderrTail,
|
|
621
|
-
}: {
|
|
622
|
-
port: number
|
|
623
|
-
directory?: string
|
|
624
|
-
maxAttempts?: number
|
|
625
|
-
startupStderrTail: string[]
|
|
626
|
-
}): Promise<ServerStartError | true> {
|
|
627
|
-
const endpoint = new URL(`http://127.0.0.1:${port}/api/health`)
|
|
628
|
-
if (directory) {
|
|
629
|
-
endpoint.searchParams.set('directory', directory)
|
|
630
|
-
}
|
|
631
|
-
for (let i = 0; i < maxAttempts; i++) {
|
|
632
|
-
const response = await requestHealthcheck({ url: endpoint.toString() })
|
|
633
|
-
.catch((e) => new FetchError({ url: endpoint.toString(), cause: e }))
|
|
634
|
-
if (response instanceof Error) {
|
|
635
|
-
// Connection refused or other transient errors - continue polling.
|
|
636
|
-
// Use 100ms interval instead of 1s so we detect readiness faster.
|
|
637
|
-
// Critical for scale-to-zero cold starts where every ms matters.
|
|
638
|
-
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
639
|
-
continue
|
|
640
|
-
}
|
|
641
|
-
if (response.status < 500) {
|
|
642
|
-
return true
|
|
643
|
-
}
|
|
644
|
-
const body = response.body
|
|
645
|
-
// Fatal errors that won't resolve with retrying
|
|
646
|
-
if (body.includes('BunInstallFailedError')) {
|
|
647
|
-
return new ServerStartError({ port, reason: body.slice(0, 200) })
|
|
648
|
-
}
|
|
649
|
-
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
650
|
-
}
|
|
651
|
-
return new ServerStartError({
|
|
652
|
-
port,
|
|
653
|
-
reason: buildStartupTimeoutReason({
|
|
654
|
-
maxAttempts,
|
|
655
|
-
stderrTail: startupStderrTail,
|
|
656
|
-
}),
|
|
657
|
-
})
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
// ── Single server lifecycle ──────────────────────────────────────
|
|
661
|
-
// The server is started lazily on first initializeOpencodeForDirectory() call.
|
|
662
|
-
// It uses permissive defaults (edit: allow, bash: allow, webfetch: allow, and
|
|
663
|
-
// external_directory: '*' allow unless --restrict-directories is set).
|
|
664
|
-
|
|
665
|
-
// In-flight promise to prevent concurrent startups from racing
|
|
666
|
-
let startingServer: Promise<
|
|
667
|
-
ServerStartError | OpencodeIncompatibleVersionError | SingleServer
|
|
668
|
-
> | null = null
|
|
669
|
-
let preferredStartupDirectory: string | null = null
|
|
670
|
-
|
|
671
|
-
function ensureOpencodeHomeDirectories({
|
|
672
|
-
directories,
|
|
673
|
-
}: {
|
|
674
|
-
directories: Record<string, string>
|
|
675
|
-
}) {
|
|
676
|
-
Object.values(directories).map((directory) => {
|
|
677
|
-
fs.mkdirSync(directory, { recursive: true })
|
|
678
|
-
})
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
/**
|
|
682
|
-
* Try to discover an OpenCode server already running in the bot process.
|
|
683
|
-
* Queries the hrana server on the lock port for the OpenCode server port,
|
|
684
|
-
* then verifies the server is healthy. Returns null if no server found.
|
|
685
|
-
*/
|
|
686
|
-
async function discoverExistingServer(): Promise<SingleServer | null> {
|
|
687
|
-
const lockPort = getLockPort()
|
|
688
|
-
try {
|
|
689
|
-
const portResponse = await requestHealthcheck({
|
|
690
|
-
url: `http://127.0.0.1:${lockPort}/kimaki/opencode-port`,
|
|
691
|
-
timeoutMs: 2000,
|
|
692
|
-
})
|
|
693
|
-
if (portResponse.status !== 200) {
|
|
694
|
-
return null
|
|
695
|
-
}
|
|
696
|
-
const parsed = JSON.parse(portResponse.body)
|
|
697
|
-
const port = parsed?.port
|
|
698
|
-
if (typeof port !== 'number') {
|
|
699
|
-
return null
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
// Verify the OpenCode server is actually healthy
|
|
703
|
-
const healthResponse = await requestHealthcheck({
|
|
704
|
-
url: `http://127.0.0.1:${port}/api/health`,
|
|
705
|
-
timeoutMs: 2000,
|
|
706
|
-
})
|
|
707
|
-
if (healthResponse.status >= 500) {
|
|
708
|
-
return null
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
opencodeLogger.log(
|
|
712
|
-
`Discovered existing OpenCode server on port ${port} via hrana lock port ${lockPort}`,
|
|
713
|
-
)
|
|
714
|
-
return {
|
|
715
|
-
process: null,
|
|
716
|
-
port,
|
|
717
|
-
baseUrl: `http://127.0.0.1:${port}`,
|
|
718
|
-
discovered: true,
|
|
719
|
-
}
|
|
720
|
-
} catch {
|
|
721
|
-
// Connection refused or other network error — no bot running
|
|
722
|
-
return null
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
async function ensureSingleServer({
|
|
727
|
-
directory,
|
|
728
|
-
}: {
|
|
729
|
-
directory?: string
|
|
730
|
-
} = {}): Promise<ServerStartError | OpencodeIncompatibleVersionError | SingleServer> {
|
|
731
|
-
const startupDirectory = directory || preferredStartupDirectory || undefined
|
|
732
|
-
if (singleServer && !singleServer.process?.killed) {
|
|
733
|
-
return singleServer
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
// Deduplicate concurrent startup attempts (covers both discovery and spawn)
|
|
737
|
-
if (startingServer) {
|
|
738
|
-
return startingServer
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
// Wrap discovery + spawn in a single shared promise so concurrent callers
|
|
742
|
-
// don't each run discoverExistingServer() and then each spawn a server.
|
|
743
|
-
startingServer = (async () => {
|
|
744
|
-
// Try to discover an already-running server from the bot process via
|
|
745
|
-
// the hrana server's /kimaki/opencode-port endpoint. This lets CLI
|
|
746
|
-
// subcommands (kimaki session list, archive, wait, etc.) reuse the
|
|
747
|
-
// bot's OpenCode server instead of spawning a redundant one.
|
|
748
|
-
const discovered = await discoverExistingServer()
|
|
749
|
-
if (discovered) {
|
|
750
|
-
singleServer = discovered
|
|
751
|
-
return discovered
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
const compatibility = await assertCompatibleOpencodeVersion()
|
|
755
|
-
if (compatibility instanceof Error) return compatibility
|
|
756
|
-
|
|
757
|
-
return startSingleServer({ directory: startupDirectory })
|
|
758
|
-
})()
|
|
759
|
-
|
|
760
|
-
try {
|
|
761
|
-
return await startingServer
|
|
762
|
-
} finally {
|
|
763
|
-
startingServer = null
|
|
764
|
-
}
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
async function startSingleServer({
|
|
768
|
-
directory,
|
|
769
|
-
}: {
|
|
770
|
-
directory?: string
|
|
771
|
-
} = {}): Promise<ServerStartError | SingleServer> {
|
|
772
|
-
ensureProcessCleanupHandlersRegistered()
|
|
773
|
-
|
|
774
|
-
const configuredPort = getOpencodePort()
|
|
775
|
-
const port = configuredPort ?? (await getOpenPort())
|
|
776
|
-
const hostname = getOpencodeHostname() ?? DEFAULT_OPENCODE_HOSTNAME
|
|
777
|
-
|
|
778
|
-
if (
|
|
779
|
-
publicOpencodeBindRequiresPassword({ hostname }) &&
|
|
780
|
-
!process.env.OPENCODE_SERVER_PASSWORD
|
|
781
|
-
) {
|
|
782
|
-
return new ServerStartError({
|
|
783
|
-
port,
|
|
784
|
-
reason: `OPENCODE_SERVER_PASSWORD is required when --opencode-hostname is ${hostname}`,
|
|
785
|
-
})
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
const serveArgs = buildOpencodeServeArgs({ port, hostname })
|
|
789
|
-
|
|
790
|
-
const {
|
|
791
|
-
command: spawnCommand,
|
|
792
|
-
args: spawnArgs,
|
|
793
|
-
windowsVerbatimArguments,
|
|
794
|
-
} = getSpawnCommandAndArgs({
|
|
795
|
-
resolvedCommand: resolveOpencodeCommand(),
|
|
796
|
-
baseArgs: serveArgs,
|
|
797
|
-
})
|
|
798
|
-
|
|
799
|
-
// Server config uses permissive defaults. By default every external directory
|
|
800
|
-
// is allowed: opencode's own 'ask' default produced constant permission
|
|
801
|
-
// prompts for ordinary reads, and users who want protection can add their own
|
|
802
|
-
// `deny`/`ask` rules in opencode.json (project config is loaded after this
|
|
803
|
-
// file, so it wins).
|
|
804
|
-
// With --restrict-directories the old behaviour comes back: only a small set
|
|
805
|
-
// of known-safe paths is pre-allowed and everything else falls through to the
|
|
806
|
-
// user's opencode.json default (which is 'ask' unless they changed it).
|
|
807
|
-
const externalDirectoryPermissions = buildServerExternalDirectoryPermissions()
|
|
808
|
-
const kimakiShimDirectory = ensureKimakiCommandShim({
|
|
809
|
-
dataDir: getDataDir(),
|
|
810
|
-
execPath: process.execPath,
|
|
811
|
-
execArgv: process.execArgv,
|
|
812
|
-
entryScript: process.argv[1] || fileURLToPath(new URL('../bin.js', import.meta.url)),
|
|
813
|
-
})
|
|
814
|
-
const pathEnvKey = getPathEnvKey(process.env)
|
|
815
|
-
const pathEnv = kimakiShimDirectory instanceof Error
|
|
816
|
-
? process.env[pathEnvKey]
|
|
817
|
-
: prependPathEntry({
|
|
818
|
-
entry: kimakiShimDirectory,
|
|
819
|
-
existingPath: process.env[pathEnvKey],
|
|
820
|
-
})
|
|
821
|
-
if (kimakiShimDirectory instanceof Error) {
|
|
822
|
-
opencodeLogger.warn(kimakiShimDirectory.message)
|
|
823
|
-
}
|
|
824
|
-
const gatewayToken = store.getState().gatewayToken
|
|
825
|
-
const vitestOpencodeEnv = (() => {
|
|
826
|
-
if (process.env.KIMAKI_VITEST !== '1') {
|
|
827
|
-
return {}
|
|
828
|
-
}
|
|
829
|
-
const root = path.join(getDataDir(), 'opencode-vitest-home')
|
|
830
|
-
const directories = {
|
|
831
|
-
OPENCODE_TEST_HOME: root,
|
|
832
|
-
OPENCODE_CONFIG_DIR: path.join(root, '.opencode-kimaki'),
|
|
833
|
-
XDG_CONFIG_HOME: path.join(root, '.config'),
|
|
834
|
-
XDG_DATA_HOME: path.join(root, '.local', 'share'),
|
|
835
|
-
XDG_CACHE_HOME: path.join(root, '.cache'),
|
|
836
|
-
XDG_STATE_HOME: path.join(root, '.local', 'state'),
|
|
837
|
-
}
|
|
838
|
-
// OpenCode writes state/config files into these XDG locations during boot.
|
|
839
|
-
// In CI, a fresh temp data dir means the parent folders may not exist yet,
|
|
840
|
-
// and some writes fail closed with NotFound before OpenCode has a chance to
|
|
841
|
-
// create them lazily. Pre-create the directories so startup-time tests do
|
|
842
|
-
// not flap based on process scheduling.
|
|
843
|
-
ensureOpencodeHomeDirectories({ directories })
|
|
844
|
-
return directories
|
|
845
|
-
})()
|
|
846
|
-
|
|
847
|
-
// Write config to a file instead of passing via OPENCODE_CONFIG_CONTENT env var.
|
|
848
|
-
// OPENCODE_CONFIG (file path) is loaded before project config in opencode's
|
|
849
|
-
// priority chain, so project-level opencode.json can override kimaki defaults.
|
|
850
|
-
// OPENCODE_CONFIG_CONTENT was loaded last and overrode user project configs,
|
|
851
|
-
// causing issue #90 (project permissions not being respected).
|
|
852
|
-
const isDev = import.meta.url.endsWith('.ts') || import.meta.url.endsWith('.tsx')
|
|
853
|
-
// Skill whitelist/blacklist from --enable-skill / --disable-skill CLI flags.
|
|
854
|
-
// Applied as opencode permission.skill rules so every agent inherits the
|
|
855
|
-
// filter via Permission.merge(defaults, agentRules, user).
|
|
856
|
-
const skillPermission = computeSkillPermission({
|
|
857
|
-
enabledSkills: store.getState().enabledSkills,
|
|
858
|
-
disabledSkills: store.getState().disabledSkills,
|
|
859
|
-
})
|
|
860
|
-
const opencodeConfig = {
|
|
861
|
-
$schema: 'https://opencode.ai/config.json',
|
|
862
|
-
lsp: false,
|
|
863
|
-
formatter: false,
|
|
864
|
-
plugin: [
|
|
865
|
-
new URL(
|
|
866
|
-
isDev ? './kimaki-opencode-plugin.ts' : './kimaki-opencode-plugin.js',
|
|
867
|
-
import.meta.url,
|
|
868
|
-
).href,
|
|
869
|
-
// npm identity lets opencode dedupe a user-installed copy by package
|
|
870
|
-
// name. Development still loads this workspace's built package directly.
|
|
871
|
-
resolveSubrouterPluginSpec({ isDev }),
|
|
872
|
-
],
|
|
873
|
-
permission: {
|
|
874
|
-
edit: 'allow',
|
|
875
|
-
bash: 'allow',
|
|
876
|
-
external_directory: externalDirectoryPermissions,
|
|
877
|
-
webfetch: 'allow',
|
|
878
|
-
...(skillPermission && { skill: skillPermission }),
|
|
879
|
-
},
|
|
880
|
-
agent: {
|
|
881
|
-
explore: {
|
|
882
|
-
permission: {
|
|
883
|
-
'*': 'deny',
|
|
884
|
-
grep: 'allow',
|
|
885
|
-
glob: 'allow',
|
|
886
|
-
list: 'allow',
|
|
887
|
-
read: {
|
|
888
|
-
'*': 'allow',
|
|
889
|
-
'*.env': 'deny',
|
|
890
|
-
'*.env.*': 'deny',
|
|
891
|
-
'*.env.example': 'allow',
|
|
892
|
-
},
|
|
893
|
-
webfetch: 'allow',
|
|
894
|
-
websearch: 'allow',
|
|
895
|
-
codesearch: 'allow',
|
|
896
|
-
// No external_directory here on purpose. opencode composes agents as
|
|
897
|
-
// merge(defaults, agentSpecific, userConfig) and then appends
|
|
898
|
-
// config.agent.<name>.permission LAST, so anything set here would beat
|
|
899
|
-
// the user's own top-level opencode.json rules. The top-level
|
|
900
|
-
// permission block above already covers this agent.
|
|
901
|
-
},
|
|
902
|
-
},
|
|
903
|
-
},
|
|
904
|
-
// When a permission prompt times out and is auto-rejected, the model sees
|
|
905
|
-
// the rejection as a tool error and continues working (tries alternatives
|
|
906
|
-
// or explains it couldn't proceed) instead of the session going dead.
|
|
907
|
-
experimental: {
|
|
908
|
-
continue_loop_on_deny: true,
|
|
909
|
-
},
|
|
910
|
-
provider: {
|
|
911
|
-
xai: {
|
|
912
|
-
models: {
|
|
913
|
-
'grok-composer-2.5-fast': {
|
|
914
|
-
name: 'Grok Composer 2.5 Fast',
|
|
915
|
-
attachment: true,
|
|
916
|
-
tool_call: true,
|
|
917
|
-
limit: {
|
|
918
|
-
context: 256000,
|
|
919
|
-
output: 256000,
|
|
920
|
-
},
|
|
921
|
-
cost: {
|
|
922
|
-
input: 0.50,
|
|
923
|
-
output: 2.50,
|
|
924
|
-
cache_read: 0.20,
|
|
925
|
-
},
|
|
926
|
-
},
|
|
927
|
-
},
|
|
928
|
-
},
|
|
929
|
-
},
|
|
930
|
-
skills: {
|
|
931
|
-
paths: [path.resolve(__dirname, '..', 'skills')],
|
|
932
|
-
},
|
|
933
|
-
} satisfies Config
|
|
934
|
-
const opencodeConfigPath = path.join(getDataDir(), 'opencode-config.json')
|
|
935
|
-
const opencodeConfigJson = JSON.stringify(opencodeConfig, null, 2)
|
|
936
|
-
const existingContent = (() => {
|
|
937
|
-
try {
|
|
938
|
-
return fs.readFileSync(opencodeConfigPath, 'utf-8')
|
|
939
|
-
} catch {
|
|
940
|
-
return ''
|
|
941
|
-
}
|
|
942
|
-
})()
|
|
943
|
-
if (existingContent !== opencodeConfigJson) {
|
|
944
|
-
fs.writeFileSync(opencodeConfigPath, opencodeConfigJson)
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
const serverProcess = spawn(
|
|
948
|
-
spawnCommand,
|
|
949
|
-
spawnArgs,
|
|
950
|
-
{
|
|
951
|
-
stdio: 'pipe',
|
|
952
|
-
detached: false,
|
|
953
|
-
windowsVerbatimArguments,
|
|
954
|
-
// No project-specific cwd — the server handles all directories via
|
|
955
|
-
// x-opencode-directory header. Use home dir as a neutral working dir.
|
|
956
|
-
cwd: os.homedir(),
|
|
957
|
-
env: {
|
|
958
|
-
...process.env,
|
|
959
|
-
OPENCODE_CONFIG: opencodeConfigPath,
|
|
960
|
-
OPENCODE_PORT: port.toString(),
|
|
961
|
-
KIMAKI: '1',
|
|
962
|
-
// The browser is not on this machine, so no localhost callback fires.
|
|
963
|
-
SUBROUTER_MANUAL_OAUTH: '1',
|
|
964
|
-
OPENCODE_EXPERIMENTAL_WORKSPACES: 'true',
|
|
965
|
-
OPENCODE_ENABLE_EXA: '1',
|
|
966
|
-
KIMAKI_DATA_DIR: getDataDir(),
|
|
967
|
-
KIMAKI_LOCK_PORT: getLockPort().toString(),
|
|
968
|
-
KIMAKI_PARENT_LOCK_PORT: getLockPort().toString(),
|
|
969
|
-
...(gatewayToken && { KIMAKI_DB_AUTH_TOKEN: gatewayToken }),
|
|
970
|
-
// Guard: prevents agents from running `kimaki` root command inside
|
|
971
|
-
// an OpenCode session, which would steal the lock port and break the bot.
|
|
972
|
-
KIMAKI_OPENCODE_PROCESS: '1',
|
|
973
|
-
...(getHranaUrl() && { KIMAKI_DB_URL: getHranaUrl()! }),
|
|
974
|
-
...(process.env.KIMAKI_SENTRY_DSN && {
|
|
975
|
-
KIMAKI_SENTRY_DSN: process.env.KIMAKI_SENTRY_DSN,
|
|
976
|
-
}),
|
|
977
|
-
...vitestOpencodeEnv,
|
|
978
|
-
...(pathEnv && { [pathEnvKey]: pathEnv }),
|
|
979
|
-
},
|
|
980
|
-
},
|
|
981
|
-
)
|
|
982
|
-
|
|
983
|
-
startingServerProcess = serverProcess
|
|
984
|
-
|
|
985
|
-
// Buffer logs until we know if server started successfully.
|
|
986
|
-
const logBuffer: string[] = []
|
|
987
|
-
const startupStderrTail: string[] = []
|
|
988
|
-
let serverReady = false
|
|
989
|
-
|
|
990
|
-
logBuffer.push(
|
|
991
|
-
`Spawned opencode ${serveArgs.join(' ')} (pid: ${serverProcess.pid})`,
|
|
992
|
-
)
|
|
993
|
-
|
|
994
|
-
const stdoutReader = subscribeToProcessLogStream({
|
|
995
|
-
stream: serverProcess.stdout,
|
|
996
|
-
onLine: (line) => {
|
|
997
|
-
if (!serverReady) {
|
|
998
|
-
logBuffer.push(`[stdout] ${line}`)
|
|
999
|
-
return
|
|
1000
|
-
}
|
|
1001
|
-
opencodeLogger.log(line)
|
|
1002
|
-
},
|
|
1003
|
-
})
|
|
1004
|
-
|
|
1005
|
-
const stderrReader = subscribeToProcessLogStream({
|
|
1006
|
-
stream: serverProcess.stderr,
|
|
1007
|
-
onLine: (line) => {
|
|
1008
|
-
if (!serverReady) {
|
|
1009
|
-
logBuffer.push(`[stderr] ${line}`)
|
|
1010
|
-
pushStartupStderrTail({ stderrTail: startupStderrTail, line })
|
|
1011
|
-
return
|
|
1012
|
-
}
|
|
1013
|
-
opencodeLogger.error(line)
|
|
1014
|
-
},
|
|
1015
|
-
})
|
|
1016
|
-
|
|
1017
|
-
serverProcess.on('error', (error) => {
|
|
1018
|
-
logBuffer.push(`Failed to start server on port ${port}: ${error}`)
|
|
1019
|
-
})
|
|
1020
|
-
|
|
1021
|
-
serverProcess.on('exit', (code, signal) => {
|
|
1022
|
-
stdoutReader?.close()
|
|
1023
|
-
stderrReader?.close()
|
|
1024
|
-
|
|
1025
|
-
if (startingServerProcess === serverProcess) {
|
|
1026
|
-
startingServerProcess = null
|
|
1027
|
-
}
|
|
1028
|
-
|
|
1029
|
-
opencodeLogger.log(
|
|
1030
|
-
`Opencode server exited with code: ${code}, signal: ${signal}`,
|
|
1031
|
-
)
|
|
1032
|
-
singleServer = null
|
|
1033
|
-
clientCache.clear()
|
|
1034
|
-
notifyServerLifecycle({ type: 'stopped' })
|
|
1035
|
-
|
|
1036
|
-
// Intentional kills should not trigger auto-restart:
|
|
1037
|
-
// - SIGTERM from our cleanup/restart code
|
|
1038
|
-
// - SIGINT propagated from Ctrl+C (parent process group signal)
|
|
1039
|
-
// - any exit during bot shutdown (shuttingDown flag)
|
|
1040
|
-
// Only unexpected crashes (non-zero exit without signal) get retried.
|
|
1041
|
-
if (signal === 'SIGTERM' || signal === 'SIGINT' || global.shuttingDown) {
|
|
1042
|
-
serverRetryCount = 0
|
|
1043
|
-
return
|
|
1044
|
-
}
|
|
1045
|
-
if (code !== 0) {
|
|
1046
|
-
if (serverRetryCount < 5) {
|
|
1047
|
-
serverRetryCount += 1
|
|
1048
|
-
opencodeLogger.log(
|
|
1049
|
-
`Restarting server (attempt ${serverRetryCount}/5)`,
|
|
1050
|
-
)
|
|
1051
|
-
void ensureSingleServer().then(
|
|
1052
|
-
(result) => {
|
|
1053
|
-
if (result instanceof Error) {
|
|
1054
|
-
opencodeLogger.error(`Failed to restart opencode server:`, result)
|
|
1055
|
-
void notifyError(result, `OpenCode server restart failed`)
|
|
1056
|
-
}
|
|
1057
|
-
},
|
|
1058
|
-
)
|
|
1059
|
-
} else {
|
|
1060
|
-
const crashError = new Error(
|
|
1061
|
-
`Server crashed too many times (5), not restarting`,
|
|
1062
|
-
)
|
|
1063
|
-
opencodeLogger.error(crashError.message)
|
|
1064
|
-
void notifyError(crashError, `OpenCode server crash loop exhausted`)
|
|
1065
|
-
}
|
|
1066
|
-
} else {
|
|
1067
|
-
serverRetryCount = 0
|
|
1068
|
-
}
|
|
1069
|
-
})
|
|
1070
|
-
|
|
1071
|
-
const waitResult = await waitForServer({
|
|
1072
|
-
port,
|
|
1073
|
-
directory,
|
|
1074
|
-
startupStderrTail,
|
|
1075
|
-
})
|
|
1076
|
-
if (waitResult instanceof Error) {
|
|
1077
|
-
killStartingServerProcessNow({ reason: 'startup-failed' })
|
|
1078
|
-
if (startingServerProcess === serverProcess) {
|
|
1079
|
-
startingServerProcess = null
|
|
1080
|
-
}
|
|
1081
|
-
|
|
1082
|
-
// Dump buffered logs on failure
|
|
1083
|
-
opencodeLogger.error(`Server failed to start:`)
|
|
1084
|
-
for (const line of logBuffer) {
|
|
1085
|
-
opencodeLogger.error(` ${line}`)
|
|
1086
|
-
}
|
|
1087
|
-
return waitResult
|
|
1088
|
-
}
|
|
1089
|
-
serverReady = true
|
|
1090
|
-
// stopOpencodeServer() may have run while we waited (bot shutdown). Never
|
|
1091
|
-
// publish a server that nobody will stop.
|
|
1092
|
-
if (global.shuttingDown || serverProcess.killed) {
|
|
1093
|
-
killStartingServerProcessNow({ reason: 'stopped-during-startup' })
|
|
1094
|
-
if (startingServerProcess === serverProcess) {
|
|
1095
|
-
startingServerProcess = null
|
|
1096
|
-
}
|
|
1097
|
-
return new ServerStartError({ port, reason: 'stopped during startup' })
|
|
1098
|
-
}
|
|
1099
|
-
opencodeLogger.log(`Server ready on port ${port}`)
|
|
1100
|
-
|
|
1101
|
-
// Always dump startup logs so plugin loading errors and other startup output
|
|
1102
|
-
// are visible in kimaki.log.
|
|
1103
|
-
for (const line of logBuffer) {
|
|
1104
|
-
opencodeLogger.log(line)
|
|
1105
|
-
}
|
|
1106
|
-
|
|
1107
|
-
const server: SingleServer = {
|
|
1108
|
-
process: serverProcess,
|
|
1109
|
-
port,
|
|
1110
|
-
baseUrl: `http://127.0.0.1:${port}`,
|
|
1111
|
-
}
|
|
1112
|
-
if (startingServerProcess === serverProcess) {
|
|
1113
|
-
startingServerProcess = null
|
|
1114
|
-
}
|
|
1115
|
-
singleServer = server
|
|
1116
|
-
notifyServerLifecycle({ type: 'started', port })
|
|
1117
|
-
return server
|
|
1118
|
-
}
|
|
1119
|
-
|
|
1120
|
-
function getOrCreateClient({
|
|
1121
|
-
baseUrl,
|
|
1122
|
-
directory,
|
|
1123
|
-
}: {
|
|
1124
|
-
baseUrl: string
|
|
1125
|
-
directory: string
|
|
1126
|
-
}): OpencodeClient {
|
|
1127
|
-
const cached = clientCache.get(directory)
|
|
1128
|
-
if (cached) {
|
|
1129
|
-
return cached
|
|
1130
|
-
}
|
|
1131
|
-
|
|
1132
|
-
const fetchWithTimeout = (request: Request) =>
|
|
1133
|
-
fetch(request, {
|
|
1134
|
-
// @ts-ignore
|
|
1135
|
-
timeout: false,
|
|
1136
|
-
})
|
|
1137
|
-
|
|
1138
|
-
const client = createOpencodeClient({
|
|
1139
|
-
baseUrl,
|
|
1140
|
-
directory,
|
|
1141
|
-
fetch: fetchWithTimeout as typeof fetch,
|
|
1142
|
-
headers: getOpencodeServerAuthHeaders(),
|
|
1143
|
-
})
|
|
1144
|
-
clientCache.set(directory, client)
|
|
1145
|
-
return client
|
|
1146
|
-
}
|
|
1147
|
-
|
|
1148
|
-
// ── Public API ───────────────────────────────────────────────────
|
|
1149
|
-
// Same signatures as before so callers don't need to change.
|
|
1150
|
-
|
|
1151
|
-
/**
|
|
1152
|
-
* Initialize OpenCode server for a directory.
|
|
1153
|
-
* Starts the single shared server if not running, then returns a client
|
|
1154
|
-
* factory scoped to the given directory via x-opencode-directory header.
|
|
1155
|
-
*
|
|
1156
|
-
* @param directory - The project directory to scope requests to
|
|
1157
|
-
* @param options.originalRepoDirectory - For worktrees: the original repo directory
|
|
1158
|
-
* (no longer used for server-level permissions — use buildSessionPermissions
|
|
1159
|
-
* at session.create() time instead)
|
|
1160
|
-
*/
|
|
1161
|
-
export async function initializeOpencodeForDirectory(
|
|
1162
|
-
directory: string,
|
|
1163
|
-
_options?: { originalRepoDirectory?: string; channelId?: string },
|
|
1164
|
-
): Promise<OpenCodeErrors | (() => OpencodeClient)> {
|
|
1165
|
-
// Verify directory exists and is accessible
|
|
1166
|
-
const accessCheck = errore.tryFn({
|
|
1167
|
-
try: () => {
|
|
1168
|
-
fs.accessSync(directory, fs.constants.R_OK | fs.constants.X_OK)
|
|
1169
|
-
},
|
|
1170
|
-
catch: () => new DirectoryNotAccessibleError({ directory }),
|
|
1171
|
-
})
|
|
1172
|
-
if (accessCheck instanceof Error) return accessCheck
|
|
1173
|
-
|
|
1174
|
-
preferredStartupDirectory = directory
|
|
1175
|
-
|
|
1176
|
-
const server = await ensureSingleServer({ directory })
|
|
1177
|
-
if (server instanceof Error) return server
|
|
1178
|
-
|
|
1179
|
-
if (!initializedDirectories.has(directory)) {
|
|
1180
|
-
initializedDirectories.add(directory)
|
|
1181
|
-
}
|
|
1182
|
-
|
|
1183
|
-
return () => {
|
|
1184
|
-
if (!singleServer) {
|
|
1185
|
-
throw new ServerNotReadyError({ directory })
|
|
1186
|
-
}
|
|
1187
|
-
return getOrCreateClient({
|
|
1188
|
-
baseUrl: singleServer.baseUrl,
|
|
1189
|
-
directory,
|
|
1190
|
-
})
|
|
1191
|
-
}
|
|
1192
|
-
}
|
|
1193
|
-
|
|
1194
|
-
/**
|
|
1195
|
-
* Known-safe paths that never need an external_directory prompt, used only when
|
|
1196
|
-
* --restrict-directories is active. Without the flag every path is allowed and
|
|
1197
|
-
* this list is irrelevant.
|
|
1198
|
-
*/
|
|
1199
|
-
function knownSafeExternalDirectories(): string[] {
|
|
1200
|
-
const tmpdir = os.tmpdir().replaceAll('\\', '/')
|
|
1201
|
-
const homeDirectory = ({ relativePath }: { relativePath: string }) => {
|
|
1202
|
-
return path.resolve(os.homedir(), relativePath.replaceAll('\\', '/'))
|
|
1203
|
-
}
|
|
1204
|
-
return [
|
|
1205
|
-
'/tmp',
|
|
1206
|
-
'/private/tmp',
|
|
1207
|
-
tmpdir,
|
|
1208
|
-
// The agent can read the global AGENTS.md and opencode config; the path is
|
|
1209
|
-
// visible in the system prompt so models routinely try to open it.
|
|
1210
|
-
homeDirectory({ relativePath: '.config/opencode' }),
|
|
1211
|
-
// The Anthropic plugin rewrites the name in the system prompt, so some
|
|
1212
|
-
// models try this misspelled path instead.
|
|
1213
|
-
homeDirectory({ relativePath: '.config/openc0de' }),
|
|
1214
|
-
// Cached opensrc checkouts.
|
|
1215
|
-
homeDirectory({ relativePath: '.opensrc' }),
|
|
1216
|
-
// Kimaki data dir (logs, db, etc).
|
|
1217
|
-
homeDirectory({ relativePath: '.kimaki' }),
|
|
1218
|
-
// Prior opencode tool outputs.
|
|
1219
|
-
homeDirectory({ relativePath: '.local/share/opencode/tool-output' }),
|
|
1220
|
-
// Language toolchain caches, so builds can inspect downloaded modules.
|
|
1221
|
-
homeDirectory({ relativePath: '.cache/zig' }),
|
|
1222
|
-
homeDirectory({ relativePath: '.cargo' }),
|
|
1223
|
-
homeDirectory({ relativePath: '.cache/go-build' }),
|
|
1224
|
-
homeDirectory({ relativePath: 'go/pkg' }),
|
|
1225
|
-
]
|
|
1226
|
-
}
|
|
1227
|
-
|
|
1228
|
-
/**
|
|
1229
|
-
* Build the server-level `permission.external_directory` value.
|
|
1230
|
-
*
|
|
1231
|
-
* Default: `{ '*': 'allow' }` — no prompt for any directory.
|
|
1232
|
-
* With --restrict-directories: an allow-list of known-safe paths only. There is
|
|
1233
|
-
* deliberately no catch-all '*': 'ask' entry so opencode's own 'ask' default
|
|
1234
|
-
* still applies to everything else.
|
|
1235
|
-
*
|
|
1236
|
-
* Always an object, never the plain string 'allow'. opencode deep-merges config
|
|
1237
|
-
* files (remeda mergeDeep) and this file is loaded before the project's
|
|
1238
|
-
* opencode.json, so object keys from the project merge on top of these and win
|
|
1239
|
-
* via findLast(). A plain string would instead be replaced wholesale by the
|
|
1240
|
-
* project object, dropping allow-all for every unmatched path.
|
|
1241
|
-
*/
|
|
1242
|
-
function buildServerExternalDirectoryPermissions(): Record<
|
|
1243
|
-
string,
|
|
1244
|
-
'ask' | 'allow' | 'deny'
|
|
1245
|
-
> {
|
|
1246
|
-
if (!getRestrictExternalDirectories()) {
|
|
1247
|
-
return { [ALL_EXTERNAL_DIRECTORIES_PATTERN]: 'allow' }
|
|
1248
|
-
}
|
|
1249
|
-
|
|
1250
|
-
const permissions: Record<string, 'ask' | 'allow' | 'deny'> = {}
|
|
1251
|
-
for (const directory of knownSafeExternalDirectories()) {
|
|
1252
|
-
permissions[directory] = 'allow'
|
|
1253
|
-
permissions[`${directory}/*`] = 'allow'
|
|
1254
|
-
}
|
|
1255
|
-
return permissions
|
|
1256
|
-
}
|
|
1257
|
-
|
|
1258
|
-
/**
|
|
1259
|
-
* Build the per-session permission ruleset passed to session.create/update.
|
|
1260
|
-
*
|
|
1261
|
-
* Keep this list minimal. Session rules are the LAST ruleset opencode
|
|
1262
|
-
* evaluates — `Permission.merge(agent.permission, session.permission)` in
|
|
1263
|
-
* session/tools.ts, then `findLast()` in permission/index.ts — so every rule
|
|
1264
|
-
* here silently overrides the user's own opencode.json. Only rules that must
|
|
1265
|
-
* beat user config belong here.
|
|
1266
|
-
*
|
|
1267
|
-
* In particular, directory *allow* rules must NOT go here. They live in the
|
|
1268
|
-
* server config so a project opencode.json can still deny or ask for specific
|
|
1269
|
-
* folders. Putting an `external_directory: '*' allow` rule here would make
|
|
1270
|
-
* every user `deny` rule a no-op.
|
|
1271
|
-
*
|
|
1272
|
-
* The session's own working directory never needs a rule either: opencode skips
|
|
1273
|
-
* the external_directory gate entirely for paths inside the active instance
|
|
1274
|
-
* (`containsPath` in tool/external-directory.ts).
|
|
1275
|
-
*
|
|
1276
|
-
* That leaves one rule: worktree isolation. Once a thread moves to a managed
|
|
1277
|
-
* worktree, deny the original checkout so the agent stops editing the main repo.
|
|
1278
|
-
*/
|
|
1279
|
-
export function buildSessionPermissions({
|
|
1280
|
-
directory,
|
|
1281
|
-
originalRepoDirectory,
|
|
1282
|
-
}: {
|
|
1283
|
-
directory: string
|
|
1284
|
-
originalRepoDirectory?: string
|
|
1285
|
-
}): PermissionRuleset {
|
|
1286
|
-
// Normalize path separators for cross-platform compatibility (Windows uses backslashes)
|
|
1287
|
-
const normalizedDirectory = directory.replaceAll('\\', '/')
|
|
1288
|
-
const originalRepo = originalRepoDirectory?.replaceAll('\\', '/')
|
|
1289
|
-
|
|
1290
|
-
if (!originalRepo || originalRepo === normalizedDirectory) {
|
|
1291
|
-
return []
|
|
1292
|
-
}
|
|
1293
|
-
|
|
1294
|
-
return buildExternalDirectoryPermissionRules({
|
|
1295
|
-
resolvedPattern: originalRepo,
|
|
1296
|
-
action: 'deny',
|
|
1297
|
-
})
|
|
1298
|
-
}
|
|
1299
|
-
|
|
1300
|
-
const ALL_EXTERNAL_DIRECTORIES_PATTERN = '*'
|
|
1301
|
-
|
|
1302
|
-
function buildExternalDirectoryPermissionRules({
|
|
1303
|
-
resolvedPattern,
|
|
1304
|
-
action,
|
|
1305
|
-
}: {
|
|
1306
|
-
resolvedPattern: string
|
|
1307
|
-
action: 'allow' | 'deny' | 'ask'
|
|
1308
|
-
}): PermissionRuleset {
|
|
1309
|
-
if (resolvedPattern === ALL_EXTERNAL_DIRECTORIES_PATTERN) {
|
|
1310
|
-
return [
|
|
1311
|
-
{
|
|
1312
|
-
permission: 'external_directory',
|
|
1313
|
-
pattern: ALL_EXTERNAL_DIRECTORIES_PATTERN,
|
|
1314
|
-
action,
|
|
1315
|
-
},
|
|
1316
|
-
]
|
|
1317
|
-
}
|
|
1318
|
-
|
|
1319
|
-
return [
|
|
1320
|
-
{
|
|
1321
|
-
permission: 'external_directory',
|
|
1322
|
-
pattern: resolvedPattern,
|
|
1323
|
-
action,
|
|
1324
|
-
},
|
|
1325
|
-
{
|
|
1326
|
-
permission: 'external_directory',
|
|
1327
|
-
pattern: `${resolvedPattern}/*`,
|
|
1328
|
-
action,
|
|
1329
|
-
},
|
|
1330
|
-
]
|
|
1331
|
-
}
|
|
1332
|
-
|
|
1333
|
-
/**
|
|
1334
|
-
* Parse raw permission strings into PermissionRuleset entries.
|
|
1335
|
-
*
|
|
1336
|
-
* Accepted formats:
|
|
1337
|
-
* "tool:action" → { permission: tool, pattern: "*", action }
|
|
1338
|
-
* "tool:pattern:action" → { permission: tool, pattern, action }
|
|
1339
|
-
*
|
|
1340
|
-
* The action must be one of "allow", "deny", "ask" (case-insensitive).
|
|
1341
|
-
* Parts are trimmed to tolerate whitespace from YAML deserialization.
|
|
1342
|
-
* Invalid entries are silently skipped (bad user input shouldn't crash the bot).
|
|
1343
|
-
* If `raw` is not an array, returns empty (defensive against malformed YAML markers).
|
|
1344
|
-
*/
|
|
1345
|
-
export function parsePermissionRules(raw: unknown): PermissionRuleset {
|
|
1346
|
-
if (!Array.isArray(raw)) {
|
|
1347
|
-
return []
|
|
1348
|
-
}
|
|
1349
|
-
const validActions = new Set(['allow', 'deny', 'ask'])
|
|
1350
|
-
return raw.flatMap((entry) => {
|
|
1351
|
-
if (typeof entry !== 'string') {
|
|
1352
|
-
return []
|
|
1353
|
-
}
|
|
1354
|
-
const parts = entry.split(':').map((s) => {
|
|
1355
|
-
return s.trim()
|
|
1356
|
-
})
|
|
1357
|
-
if (parts.length === 2) {
|
|
1358
|
-
const [permission, rawAction] = parts
|
|
1359
|
-
const action = rawAction!.toLowerCase()
|
|
1360
|
-
if (!permission || !validActions.has(action)) {
|
|
1361
|
-
return []
|
|
1362
|
-
}
|
|
1363
|
-
return [{ permission, pattern: '*', action: action as 'allow' | 'deny' | 'ask' }]
|
|
1364
|
-
}
|
|
1365
|
-
if (parts.length >= 3) {
|
|
1366
|
-
// Last segment is the action, first segment is the permission,
|
|
1367
|
-
// everything in between is the pattern (may contain colons in theory,
|
|
1368
|
-
// but unlikely for tool patterns).
|
|
1369
|
-
const permission = parts[0]!
|
|
1370
|
-
const rawAction = parts[parts.length - 1]!
|
|
1371
|
-
const action = rawAction.toLowerCase()
|
|
1372
|
-
const pattern = parts.slice(1, -1).join(':')
|
|
1373
|
-
if (!permission || !pattern || !validActions.has(action)) {
|
|
1374
|
-
return []
|
|
1375
|
-
}
|
|
1376
|
-
return [{ permission, pattern, action: action as 'allow' | 'deny' | 'ask' }]
|
|
1377
|
-
}
|
|
1378
|
-
return []
|
|
1379
|
-
})
|
|
1380
|
-
}
|
|
1381
|
-
|
|
1382
|
-
// ── Injection guard per-session config ───────────────────────────
|
|
1383
|
-
// Per-session injection guard patterns are written as JSON files to
|
|
1384
|
-
// <dataDir>/injection-guard/<sessionId>.json. The injection guard plugin
|
|
1385
|
-
// (running inside the opencode server process) reads KIMAKI_DATA_DIR env
|
|
1386
|
-
// var to find these files in tool.execute.after.
|
|
1387
|
-
// This avoids needing env vars (which are per-process, not per-session).
|
|
1388
|
-
|
|
1389
|
-
function getInjectionGuardDir(): string {
|
|
1390
|
-
return path.join(getDataDir(), 'injection-guard')
|
|
1391
|
-
}
|
|
1392
|
-
|
|
1393
|
-
/**
|
|
1394
|
-
* Write per-session injection guard config so the plugin picks it up.
|
|
1395
|
-
* Only call this if injectionGuardPatterns is non-empty.
|
|
1396
|
-
*/
|
|
1397
|
-
export function writeInjectionGuardConfig({
|
|
1398
|
-
sessionId,
|
|
1399
|
-
scanPatterns,
|
|
1400
|
-
}: {
|
|
1401
|
-
sessionId: string
|
|
1402
|
-
scanPatterns: string[]
|
|
1403
|
-
}): void {
|
|
1404
|
-
if (scanPatterns.length === 0) {
|
|
1405
|
-
return
|
|
1406
|
-
}
|
|
1407
|
-
try {
|
|
1408
|
-
const dir = getInjectionGuardDir()
|
|
1409
|
-
fs.mkdirSync(dir, { recursive: true })
|
|
1410
|
-
fs.writeFileSync(
|
|
1411
|
-
path.join(dir, `${sessionId}.json`),
|
|
1412
|
-
JSON.stringify({ scanPatterns }),
|
|
1413
|
-
)
|
|
1414
|
-
} catch {
|
|
1415
|
-
// Best effort -- don't crash the bot if data dir write fails
|
|
1416
|
-
}
|
|
1417
|
-
}
|
|
1418
|
-
|
|
1419
|
-
/**
|
|
1420
|
-
* Remove per-session injection guard config file.
|
|
1421
|
-
*/
|
|
1422
|
-
export function removeInjectionGuardConfig({ sessionId }: { sessionId: string }): void {
|
|
1423
|
-
try {
|
|
1424
|
-
fs.unlinkSync(path.join(getInjectionGuardDir(), `${sessionId}.json`))
|
|
1425
|
-
} catch {
|
|
1426
|
-
// File may already be gone
|
|
1427
|
-
}
|
|
1428
|
-
}
|
|
1429
|
-
|
|
1430
|
-
/**
|
|
1431
|
-
* Read per-session injection guard config. Used by the kimaki plugin
|
|
1432
|
-
* inside the opencode server process.
|
|
1433
|
-
*/
|
|
1434
|
-
export function readInjectionGuardConfig({ sessionId }: { sessionId: string }): { scanPatterns: string[] } | null {
|
|
1435
|
-
try {
|
|
1436
|
-
const raw = fs.readFileSync(
|
|
1437
|
-
path.join(getInjectionGuardDir(), `${sessionId}.json`),
|
|
1438
|
-
'utf-8',
|
|
1439
|
-
)
|
|
1440
|
-
return JSON.parse(raw) as { scanPatterns: string[] }
|
|
1441
|
-
} catch {
|
|
1442
|
-
return null
|
|
1443
|
-
}
|
|
1444
|
-
}
|
|
1445
|
-
|
|
1446
|
-
// ── Public helpers ───────────────────────────────────────────────
|
|
1447
|
-
// These helpers expose the single shared server and directory-scoped clients.
|
|
1448
|
-
|
|
1449
|
-
export function getOpencodeServerPort(_directory?: string): number | null {
|
|
1450
|
-
return singleServer?.port ?? null
|
|
1451
|
-
}
|
|
1452
|
-
|
|
1453
|
-
export function getOpencodeServerBaseUrl(): string | null {
|
|
1454
|
-
return singleServer?.baseUrl ?? null
|
|
1455
|
-
}
|
|
1456
|
-
|
|
1457
|
-
export function getOpencodeClient(directory: string): OpencodeClient | null {
|
|
1458
|
-
if (!singleServer) {
|
|
1459
|
-
return null
|
|
1460
|
-
}
|
|
1461
|
-
return getOrCreateClient({
|
|
1462
|
-
baseUrl: singleServer.baseUrl,
|
|
1463
|
-
directory,
|
|
1464
|
-
})
|
|
1465
|
-
}
|
|
1466
|
-
|
|
1467
|
-
// Structural union of the OpenCode v2 SDK error response shapes. The concrete
|
|
1468
|
-
// type of `result.error` varies per route, so we describe the fields each shape
|
|
1469
|
-
// may carry instead of importing every per-route error union:
|
|
1470
|
-
// - NotFoundError / BadRequestError: { name, data: { message } }
|
|
1471
|
-
// - InvalidRequestError: { _tag, message }
|
|
1472
|
-
// - EffectHttpApiErrorBadRequest: { _tag: "BadRequest" } (no message)
|
|
1473
|
-
// - some routes also surface { errors: [...] }
|
|
1474
|
-
export type SdkErrorResponse = {
|
|
1475
|
-
data?: { message?: string; ref?: string } | null
|
|
1476
|
-
message?: string
|
|
1477
|
-
errors?: unknown[]
|
|
1478
|
-
_tag?: string
|
|
1479
|
-
name?: string
|
|
1480
|
-
}
|
|
1481
|
-
|
|
1482
|
-
/**
|
|
1483
|
-
* Extract a human-readable message from an OpenCode SDK error response.
|
|
1484
|
-
* Probes each known shape and falls back to a generic message.
|
|
1485
|
-
*/
|
|
1486
|
-
export function extractSdkErrorMessage(error: SdkErrorResponse | null | undefined): string {
|
|
1487
|
-
if (!error) {
|
|
1488
|
-
return 'Unknown OpenCode API error'
|
|
1489
|
-
}
|
|
1490
|
-
|
|
1491
|
-
if (error.data?.message) {
|
|
1492
|
-
const name = error.name ? `${error.name}: ` : ''
|
|
1493
|
-
const ref = error.data.ref ? ` (${error.data.ref})` : ''
|
|
1494
|
-
return `${name}${error.data.message}${ref}`
|
|
1495
|
-
}
|
|
1496
|
-
|
|
1497
|
-
if (error.message) {
|
|
1498
|
-
return error.message
|
|
1499
|
-
}
|
|
1500
|
-
|
|
1501
|
-
if (error.errors && error.errors.length > 0) {
|
|
1502
|
-
return JSON.stringify(error.errors)
|
|
1503
|
-
}
|
|
1504
|
-
|
|
1505
|
-
if (error._tag) {
|
|
1506
|
-
return error._tag
|
|
1507
|
-
}
|
|
1508
|
-
|
|
1509
|
-
if (error.name) {
|
|
1510
|
-
return error.name
|
|
1511
|
-
}
|
|
1512
|
-
|
|
1513
|
-
return 'Unknown OpenCode API error'
|
|
1514
|
-
}
|
|
1515
|
-
|
|
1516
|
-
/**
|
|
1517
|
-
* Stop the single opencode server.
|
|
1518
|
-
* Used for process teardown, tests, and explicit restarts.
|
|
1519
|
-
*/
|
|
1520
|
-
export async function stopOpencodeServer(): Promise<boolean> {
|
|
1521
|
-
// A server still booting lives only in startingServerProcess.
|
|
1522
|
-
killStartingServerProcessNow({ reason: 'stop-opencode-server' })
|
|
1523
|
-
startingServerProcess = null
|
|
1524
|
-
|
|
1525
|
-
if (!singleServer) {
|
|
1526
|
-
return false
|
|
1527
|
-
}
|
|
1528
|
-
|
|
1529
|
-
const server = singleServer
|
|
1530
|
-
|
|
1531
|
-
// For discovered servers (from another process), just clear local state
|
|
1532
|
-
// without killing the process we don't own.
|
|
1533
|
-
if (server.discovered || !server.process) {
|
|
1534
|
-
singleServer = null
|
|
1535
|
-
clientCache.clear()
|
|
1536
|
-
serverRetryCount = 0
|
|
1537
|
-
return true
|
|
1538
|
-
}
|
|
1539
|
-
|
|
1540
|
-
opencodeLogger.log(
|
|
1541
|
-
`Stopping opencode server (pid: ${server.process.pid}, port: ${server.port})`,
|
|
1542
|
-
)
|
|
1543
|
-
if (!server.process.killed) {
|
|
1544
|
-
const killResult = errore.try(
|
|
1545
|
-
() => {
|
|
1546
|
-
server.process!.kill('SIGTERM')
|
|
1547
|
-
},
|
|
1548
|
-
(error) => {
|
|
1549
|
-
return new Error('Failed to send SIGTERM to opencode server', {
|
|
1550
|
-
cause: error,
|
|
1551
|
-
})
|
|
1552
|
-
},
|
|
1553
|
-
)
|
|
1554
|
-
if (killResult instanceof Error) {
|
|
1555
|
-
opencodeLogger.warn(killResult.message)
|
|
1556
|
-
}
|
|
1557
|
-
}
|
|
1558
|
-
|
|
1559
|
-
singleServer = null
|
|
1560
|
-
clientCache.clear()
|
|
1561
|
-
serverRetryCount = 0
|
|
1562
|
-
// Don't dispose the global listener here — it will reconnect when
|
|
1563
|
-
// the server restarts. Only abort the current SSE connection so it
|
|
1564
|
-
// doesn't hang on a dead server.
|
|
1565
|
-
restartGlobalEventListener()
|
|
1566
|
-
await new Promise((resolve) => {
|
|
1567
|
-
setTimeout(resolve, 1000)
|
|
1568
|
-
})
|
|
1569
|
-
return true
|
|
1570
|
-
}
|
|
1571
|
-
|
|
1572
|
-
/**
|
|
1573
|
-
* Restart the single opencode server.
|
|
1574
|
-
* Kills the existing process and starts a new one.
|
|
1575
|
-
* Used for resolving opencode state issues, refreshing auth, plugins, etc.
|
|
1576
|
-
*/
|
|
1577
|
-
export async function restartOpencodeServer(): Promise<OpenCodeErrors | true> {
|
|
1578
|
-
if (singleServer) {
|
|
1579
|
-
await stopOpencodeServer()
|
|
1580
|
-
}
|
|
1581
|
-
|
|
1582
|
-
// Reset retry count for the fresh start
|
|
1583
|
-
serverRetryCount = 0
|
|
1584
|
-
|
|
1585
|
-
const result = await ensureSingleServer()
|
|
1586
|
-
if (result instanceof Error) return result
|
|
1587
|
-
restartGlobalEventListener()
|
|
1588
|
-
await waitForGlobalEventListener()
|
|
1589
|
-
return true
|
|
1590
|
-
}
|