agim-cli 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1234 -0
- package/LICENSE +21 -0
- package/README.md +422 -0
- package/README.zh-CN.md +414 -0
- package/dist/cli-ui/cmd-handlers.d.ts +11 -0
- package/dist/cli-ui/cmd-handlers.d.ts.map +1 -0
- package/dist/cli-ui/cmd-handlers.js +240 -0
- package/dist/cli-ui/cmd-handlers.js.map +1 -0
- package/dist/cli-ui/config-wizard.d.ts +3 -0
- package/dist/cli-ui/config-wizard.d.ts.map +1 -0
- package/dist/cli-ui/config-wizard.js +851 -0
- package/dist/cli-ui/config-wizard.js.map +1 -0
- package/dist/cli-ui/entry-menu.d.ts +28 -0
- package/dist/cli-ui/entry-menu.d.ts.map +1 -0
- package/dist/cli-ui/entry-menu.js +50 -0
- package/dist/cli-ui/entry-menu.js.map +1 -0
- package/dist/cli-ui/env-file.d.ts +35 -0
- package/dist/cli-ui/env-file.d.ts.map +1 -0
- package/dist/cli-ui/env-file.js +163 -0
- package/dist/cli-ui/env-file.js.map +1 -0
- package/dist/cli-ui/i18n.d.ts +204 -0
- package/dist/cli-ui/i18n.d.ts.map +1 -0
- package/dist/cli-ui/i18n.js +455 -0
- package/dist/cli-ui/i18n.js.map +1 -0
- package/dist/cli-ui/lang-picker.d.ts +10 -0
- package/dist/cli-ui/lang-picker.d.ts.map +1 -0
- package/dist/cli-ui/lang-picker.js +33 -0
- package/dist/cli-ui/lang-picker.js.map +1 -0
- package/dist/cli-ui/paths.d.ts +4 -0
- package/dist/cli-ui/paths.d.ts.map +1 -0
- package/dist/cli-ui/paths.js +11 -0
- package/dist/cli-ui/paths.js.map +1 -0
- package/dist/cli-ui/prompts.d.ts +65 -0
- package/dist/cli-ui/prompts.d.ts.map +1 -0
- package/dist/cli-ui/prompts.js +125 -0
- package/dist/cli-ui/prompts.js.map +1 -0
- package/dist/cli-ui/service.d.ts +41 -0
- package/dist/cli-ui/service.d.ts.map +1 -0
- package/dist/cli-ui/service.js +241 -0
- package/dist/cli-ui/service.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +1143 -0
- package/dist/cli.js.map +1 -0
- package/dist/core/acp-server.d.ts +8 -0
- package/dist/core/acp-server.d.ts.map +1 -0
- package/dist/core/acp-server.js +266 -0
- package/dist/core/acp-server.js.map +1 -0
- package/dist/core/agent-base.d.ts +94 -0
- package/dist/core/agent-base.d.ts.map +1 -0
- package/dist/core/agent-base.js +373 -0
- package/dist/core/agent-base.js.map +1 -0
- package/dist/core/agent-cwd.d.ts +48 -0
- package/dist/core/agent-cwd.d.ts.map +1 -0
- package/dist/core/agent-cwd.js +181 -0
- package/dist/core/agent-cwd.js.map +1 -0
- package/dist/core/agent-helper.d.ts +65 -0
- package/dist/core/agent-helper.d.ts.map +1 -0
- package/dist/core/agent-helper.js +150 -0
- package/dist/core/agent-helper.js.map +1 -0
- package/dist/core/agim-paths.d.ts +10 -0
- package/dist/core/agim-paths.d.ts.map +1 -0
- package/dist/core/agim-paths.js +64 -0
- package/dist/core/agim-paths.js.map +1 -0
- package/dist/core/approval-bus.d.ts +300 -0
- package/dist/core/approval-bus.d.ts.map +1 -0
- package/dist/core/approval-bus.js +990 -0
- package/dist/core/approval-bus.js.map +1 -0
- package/dist/core/approval-router.d.ts +101 -0
- package/dist/core/approval-router.d.ts.map +1 -0
- package/dist/core/approval-router.js +540 -0
- package/dist/core/approval-router.js.map +1 -0
- package/dist/core/audit-log.d.ts +55 -0
- package/dist/core/audit-log.d.ts.map +1 -0
- package/dist/core/audit-log.js +203 -0
- package/dist/core/audit-log.js.map +1 -0
- package/dist/core/bgjob-reader.d.ts +65 -0
- package/dist/core/bgjob-reader.d.ts.map +1 -0
- package/dist/core/bgjob-reader.js +212 -0
- package/dist/core/bgjob-reader.js.map +1 -0
- package/dist/core/circuit-breaker.d.ts +37 -0
- package/dist/core/circuit-breaker.d.ts.map +1 -0
- package/dist/core/circuit-breaker.js +115 -0
- package/dist/core/circuit-breaker.js.map +1 -0
- package/dist/core/commands/agent.d.ts +4 -0
- package/dist/core/commands/agent.d.ts.map +1 -0
- package/dist/core/commands/agent.js +40 -0
- package/dist/core/commands/agent.js.map +1 -0
- package/dist/core/commands/approval.d.ts +3 -0
- package/dist/core/commands/approval.d.ts.map +1 -0
- package/dist/core/commands/approval.js +85 -0
- package/dist/core/commands/approval.js.map +1 -0
- package/dist/core/commands/audit.d.ts +3 -0
- package/dist/core/commands/audit.d.ts.map +1 -0
- package/dist/core/commands/audit.js +84 -0
- package/dist/core/commands/audit.js.map +1 -0
- package/dist/core/commands/builtin.d.ts +3 -0
- package/dist/core/commands/builtin.d.ts.map +1 -0
- package/dist/core/commands/builtin.js +304 -0
- package/dist/core/commands/builtin.js.map +1 -0
- package/dist/core/commands/cron.d.ts +3 -0
- package/dist/core/commands/cron.d.ts.map +1 -0
- package/dist/core/commands/cron.js +128 -0
- package/dist/core/commands/cron.js.map +1 -0
- package/dist/core/commands/job.d.ts +3 -0
- package/dist/core/commands/job.d.ts.map +1 -0
- package/dist/core/commands/job.js +195 -0
- package/dist/core/commands/job.js.map +1 -0
- package/dist/core/commands/memo.d.ts +3 -0
- package/dist/core/commands/memo.d.ts.map +1 -0
- package/dist/core/commands/memo.js +151 -0
- package/dist/core/commands/memo.js.map +1 -0
- package/dist/core/commands/model.d.ts +9 -0
- package/dist/core/commands/model.d.ts.map +1 -0
- package/dist/core/commands/model.js +183 -0
- package/dist/core/commands/model.js.map +1 -0
- package/dist/core/commands/plan.d.ts +3 -0
- package/dist/core/commands/plan.d.ts.map +1 -0
- package/dist/core/commands/plan.js +75 -0
- package/dist/core/commands/plan.js.map +1 -0
- package/dist/core/commands/remind.d.ts +3 -0
- package/dist/core/commands/remind.d.ts.map +1 -0
- package/dist/core/commands/remind.js +271 -0
- package/dist/core/commands/remind.js.map +1 -0
- package/dist/core/commands/router.d.ts +3 -0
- package/dist/core/commands/router.d.ts.map +1 -0
- package/dist/core/commands/router.js +71 -0
- package/dist/core/commands/router.js.map +1 -0
- package/dist/core/commands/sessions.d.ts +3 -0
- package/dist/core/commands/sessions.d.ts.map +1 -0
- package/dist/core/commands/sessions.js +88 -0
- package/dist/core/commands/sessions.js.map +1 -0
- package/dist/core/commands/stats.d.ts +3 -0
- package/dist/core/commands/stats.d.ts.map +1 -0
- package/dist/core/commands/stats.js +73 -0
- package/dist/core/commands/stats.js.map +1 -0
- package/dist/core/commands/think.d.ts +3 -0
- package/dist/core/commands/think.d.ts.map +1 -0
- package/dist/core/commands/think.js +28 -0
- package/dist/core/commands/think.js.map +1 -0
- package/dist/core/commands/workspaces.d.ts +3 -0
- package/dist/core/commands/workspaces.d.ts.map +1 -0
- package/dist/core/commands/workspaces.js +47 -0
- package/dist/core/commands/workspaces.js.map +1 -0
- package/dist/core/config-schema.d.ts +60 -0
- package/dist/core/config-schema.d.ts.map +1 -0
- package/dist/core/config-schema.js +75 -0
- package/dist/core/config-schema.js.map +1 -0
- package/dist/core/coord-systems.d.ts +65 -0
- package/dist/core/coord-systems.d.ts.map +1 -0
- package/dist/core/coord-systems.js +229 -0
- package/dist/core/coord-systems.js.map +1 -0
- package/dist/core/cron.d.ts +29 -0
- package/dist/core/cron.d.ts.map +1 -0
- package/dist/core/cron.js +184 -0
- package/dist/core/cron.js.map +1 -0
- package/dist/core/event-bus.d.ts +80 -0
- package/dist/core/event-bus.d.ts.map +1 -0
- package/dist/core/event-bus.js +62 -0
- package/dist/core/event-bus.js.map +1 -0
- package/dist/core/intent-llm.d.ts +27 -0
- package/dist/core/intent-llm.d.ts.map +1 -0
- package/dist/core/intent-llm.js +170 -0
- package/dist/core/intent-llm.js.map +1 -0
- package/dist/core/intent.d.ts +12 -0
- package/dist/core/intent.d.ts.map +1 -0
- package/dist/core/intent.js +187 -0
- package/dist/core/intent.js.map +1 -0
- package/dist/core/job-board.d.ts +82 -0
- package/dist/core/job-board.d.ts.map +1 -0
- package/dist/core/job-board.js +379 -0
- package/dist/core/job-board.js.map +1 -0
- package/dist/core/location-context.d.ts +32 -0
- package/dist/core/location-context.d.ts.map +1 -0
- package/dist/core/location-context.js +69 -0
- package/dist/core/location-context.js.map +1 -0
- package/dist/core/location-token.d.ts +57 -0
- package/dist/core/location-token.d.ts.map +1 -0
- package/dist/core/location-token.js +128 -0
- package/dist/core/location-token.js.map +1 -0
- package/dist/core/logger.d.ts +6 -0
- package/dist/core/logger.d.ts.map +1 -0
- package/dist/core/logger.js +54 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/memo-rpc.d.ts +13 -0
- package/dist/core/memo-rpc.d.ts.map +1 -0
- package/dist/core/memo-rpc.js +288 -0
- package/dist/core/memo-rpc.js.map +1 -0
- package/dist/core/memos.d.ts +163 -0
- package/dist/core/memos.d.ts.map +1 -0
- package/dist/core/memos.js +502 -0
- package/dist/core/memos.js.map +1 -0
- package/dist/core/metrics.d.ts +55 -0
- package/dist/core/metrics.d.ts.map +1 -0
- package/dist/core/metrics.js +291 -0
- package/dist/core/metrics.js.map +1 -0
- package/dist/core/onboarding.d.ts +99 -0
- package/dist/core/onboarding.d.ts.map +1 -0
- package/dist/core/onboarding.js +426 -0
- package/dist/core/onboarding.js.map +1 -0
- package/dist/core/pending-reminder.d.ts +25 -0
- package/dist/core/pending-reminder.d.ts.map +1 -0
- package/dist/core/pending-reminder.js +53 -0
- package/dist/core/pending-reminder.js.map +1 -0
- package/dist/core/rate-limiter.d.ts +44 -0
- package/dist/core/rate-limiter.d.ts.map +1 -0
- package/dist/core/rate-limiter.js +115 -0
- package/dist/core/rate-limiter.js.map +1 -0
- package/dist/core/registry.d.ts +32 -0
- package/dist/core/registry.d.ts.map +1 -0
- package/dist/core/registry.js +126 -0
- package/dist/core/registry.js.map +1 -0
- package/dist/core/remind-intent.d.ts +25 -0
- package/dist/core/remind-intent.d.ts.map +1 -0
- package/dist/core/remind-intent.js +196 -0
- package/dist/core/remind-intent.js.map +1 -0
- package/dist/core/reminder-rpc.d.ts +17 -0
- package/dist/core/reminder-rpc.d.ts.map +1 -0
- package/dist/core/reminder-rpc.js +169 -0
- package/dist/core/reminder-rpc.js.map +1 -0
- package/dist/core/reminders.d.ts +159 -0
- package/dist/core/reminders.d.ts.map +1 -0
- package/dist/core/reminders.js +977 -0
- package/dist/core/reminders.js.map +1 -0
- package/dist/core/router.d.ts +55 -0
- package/dist/core/router.d.ts.map +1 -0
- package/dist/core/router.js +497 -0
- package/dist/core/router.js.map +1 -0
- package/dist/core/schedule.d.ts +65 -0
- package/dist/core/schedule.d.ts.map +1 -0
- package/dist/core/schedule.js +323 -0
- package/dist/core/schedule.js.map +1 -0
- package/dist/core/session.d.ts +182 -0
- package/dist/core/session.d.ts.map +1 -0
- package/dist/core/session.js +807 -0
- package/dist/core/session.js.map +1 -0
- package/dist/core/sqlite-helper.d.ts +37 -0
- package/dist/core/sqlite-helper.d.ts.map +1 -0
- package/dist/core/sqlite-helper.js +79 -0
- package/dist/core/sqlite-helper.js.map +1 -0
- package/dist/core/transcribe.d.ts +25 -0
- package/dist/core/transcribe.d.ts.map +1 -0
- package/dist/core/transcribe.js +217 -0
- package/dist/core/transcribe.js.map +1 -0
- package/dist/core/types.d.ts +360 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +3 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/workspace.d.ts +67 -0
- package/dist/core/workspace.d.ts.map +1 -0
- package/dist/core/workspace.js +113 -0
- package/dist/core/workspace.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/plugins/agents/acp/acp-adapter.d.ts +16 -0
- package/dist/plugins/agents/acp/acp-adapter.d.ts.map +1 -0
- package/dist/plugins/agents/acp/acp-adapter.js +49 -0
- package/dist/plugins/agents/acp/acp-adapter.js.map +1 -0
- package/dist/plugins/agents/acp/acp-client.d.ts +32 -0
- package/dist/plugins/agents/acp/acp-client.d.ts.map +1 -0
- package/dist/plugins/agents/acp/acp-client.js +177 -0
- package/dist/plugins/agents/acp/acp-client.js.map +1 -0
- package/dist/plugins/agents/acp/discovery.d.ts +19 -0
- package/dist/plugins/agents/acp/discovery.d.ts.map +1 -0
- package/dist/plugins/agents/acp/discovery.js +111 -0
- package/dist/plugins/agents/acp/discovery.js.map +1 -0
- package/dist/plugins/agents/acp/index.d.ts +4 -0
- package/dist/plugins/agents/acp/index.d.ts.map +1 -0
- package/dist/plugins/agents/acp/index.js +4 -0
- package/dist/plugins/agents/acp/index.js.map +1 -0
- package/dist/plugins/agents/acp/types.d.ts +62 -0
- package/dist/plugins/agents/acp/types.d.ts.map +1 -0
- package/dist/plugins/agents/acp/types.js +5 -0
- package/dist/plugins/agents/acp/types.js.map +1 -0
- package/dist/plugins/agents/claude-code/index.d.ts +25 -0
- package/dist/plugins/agents/claude-code/index.d.ts.map +1 -0
- package/dist/plugins/agents/claude-code/index.js +184 -0
- package/dist/plugins/agents/claude-code/index.js.map +1 -0
- package/dist/plugins/agents/claude-code/mcp-approval-server.d.ts +59 -0
- package/dist/plugins/agents/claude-code/mcp-approval-server.d.ts.map +1 -0
- package/dist/plugins/agents/claude-code/mcp-approval-server.js +645 -0
- package/dist/plugins/agents/claude-code/mcp-approval-server.js.map +1 -0
- package/dist/plugins/agents/codex/build-mcp-cli-args.d.ts +28 -0
- package/dist/plugins/agents/codex/build-mcp-cli-args.d.ts.map +1 -0
- package/dist/plugins/agents/codex/build-mcp-cli-args.js +74 -0
- package/dist/plugins/agents/codex/build-mcp-cli-args.js.map +1 -0
- package/dist/plugins/agents/codex/index.d.ts +53 -0
- package/dist/plugins/agents/codex/index.d.ts.map +1 -0
- package/dist/plugins/agents/codex/index.js +341 -0
- package/dist/plugins/agents/codex/index.js.map +1 -0
- package/dist/plugins/agents/copilot/index.d.ts +35 -0
- package/dist/plugins/agents/copilot/index.d.ts.map +1 -0
- package/dist/plugins/agents/copilot/index.js +182 -0
- package/dist/plugins/agents/copilot/index.js.map +1 -0
- package/dist/plugins/agents/opencode/ensure-mcp-config.d.ts +11 -0
- package/dist/plugins/agents/opencode/ensure-mcp-config.d.ts.map +1 -0
- package/dist/plugins/agents/opencode/ensure-mcp-config.js +100 -0
- package/dist/plugins/agents/opencode/ensure-mcp-config.js.map +1 -0
- package/dist/plugins/agents/opencode/index.d.ts +5 -0
- package/dist/plugins/agents/opencode/index.d.ts.map +1 -0
- package/dist/plugins/agents/opencode/index.js +30 -0
- package/dist/plugins/agents/opencode/index.js.map +1 -0
- package/dist/plugins/agents/opencode/opencode-http-adapter.d.ts +166 -0
- package/dist/plugins/agents/opencode/opencode-http-adapter.d.ts.map +1 -0
- package/dist/plugins/agents/opencode/opencode-http-adapter.js +682 -0
- package/dist/plugins/agents/opencode/opencode-http-adapter.js.map +1 -0
- package/dist/plugins/agents/opencode/opencode-stdio-adapter.d.ts +32 -0
- package/dist/plugins/agents/opencode/opencode-stdio-adapter.d.ts.map +1 -0
- package/dist/plugins/agents/opencode/opencode-stdio-adapter.js +137 -0
- package/dist/plugins/agents/opencode/opencode-stdio-adapter.js.map +1 -0
- package/dist/plugins/agents/opencode/serve-manager.d.ts +27 -0
- package/dist/plugins/agents/opencode/serve-manager.d.ts.map +1 -0
- package/dist/plugins/agents/opencode/serve-manager.js +194 -0
- package/dist/plugins/agents/opencode/serve-manager.js.map +1 -0
- package/dist/plugins/messengers/dingtalk/dingtalk-adapter.d.ts +57 -0
- package/dist/plugins/messengers/dingtalk/dingtalk-adapter.d.ts.map +1 -0
- package/dist/plugins/messengers/dingtalk/dingtalk-adapter.js +409 -0
- package/dist/plugins/messengers/dingtalk/dingtalk-adapter.js.map +1 -0
- package/dist/plugins/messengers/dingtalk/dingtalk-client.d.ts +48 -0
- package/dist/plugins/messengers/dingtalk/dingtalk-client.d.ts.map +1 -0
- package/dist/plugins/messengers/dingtalk/dingtalk-client.js +236 -0
- package/dist/plugins/messengers/dingtalk/dingtalk-client.js.map +1 -0
- package/dist/plugins/messengers/dingtalk/index.d.ts +3 -0
- package/dist/plugins/messengers/dingtalk/index.d.ts.map +1 -0
- package/dist/plugins/messengers/dingtalk/index.js +3 -0
- package/dist/plugins/messengers/dingtalk/index.js.map +1 -0
- package/dist/plugins/messengers/dingtalk/link-coords.d.ts +23 -0
- package/dist/plugins/messengers/dingtalk/link-coords.d.ts.map +1 -0
- package/dist/plugins/messengers/dingtalk/link-coords.js +89 -0
- package/dist/plugins/messengers/dingtalk/link-coords.js.map +1 -0
- package/dist/plugins/messengers/dingtalk/media-store.d.ts +16 -0
- package/dist/plugins/messengers/dingtalk/media-store.d.ts.map +1 -0
- package/dist/plugins/messengers/dingtalk/media-store.js +77 -0
- package/dist/plugins/messengers/dingtalk/media-store.js.map +1 -0
- package/dist/plugins/messengers/dingtalk/types.d.ts +82 -0
- package/dist/plugins/messengers/dingtalk/types.d.ts.map +1 -0
- package/dist/plugins/messengers/dingtalk/types.js +14 -0
- package/dist/plugins/messengers/dingtalk/types.js.map +1 -0
- package/dist/plugins/messengers/discord/discord-adapter.d.ts +21 -0
- package/dist/plugins/messengers/discord/discord-adapter.d.ts.map +1 -0
- package/dist/plugins/messengers/discord/discord-adapter.js +238 -0
- package/dist/plugins/messengers/discord/discord-adapter.js.map +1 -0
- package/dist/plugins/messengers/discord/index.d.ts +4 -0
- package/dist/plugins/messengers/discord/index.d.ts.map +1 -0
- package/dist/plugins/messengers/discord/index.js +4 -0
- package/dist/plugins/messengers/discord/index.js.map +1 -0
- package/dist/plugins/messengers/discord/markdown-to-discord.d.ts +11 -0
- package/dist/plugins/messengers/discord/markdown-to-discord.d.ts.map +1 -0
- package/dist/plugins/messengers/discord/markdown-to-discord.js +59 -0
- package/dist/plugins/messengers/discord/markdown-to-discord.js.map +1 -0
- package/dist/plugins/messengers/discord/types.d.ts +9 -0
- package/dist/plugins/messengers/discord/types.d.ts.map +1 -0
- package/dist/plugins/messengers/discord/types.js +3 -0
- package/dist/plugins/messengers/discord/types.js.map +1 -0
- package/dist/plugins/messengers/email/email-adapter.d.ts +33 -0
- package/dist/plugins/messengers/email/email-adapter.d.ts.map +1 -0
- package/dist/plugins/messengers/email/email-adapter.js +137 -0
- package/dist/plugins/messengers/email/email-adapter.js.map +1 -0
- package/dist/plugins/messengers/feishu/card-builder.d.ts +23 -0
- package/dist/plugins/messengers/feishu/card-builder.d.ts.map +1 -0
- package/dist/plugins/messengers/feishu/card-builder.js +89 -0
- package/dist/plugins/messengers/feishu/card-builder.js.map +1 -0
- package/dist/plugins/messengers/feishu/feishu-adapter.d.ts +23 -0
- package/dist/plugins/messengers/feishu/feishu-adapter.d.ts.map +1 -0
- package/dist/plugins/messengers/feishu/feishu-adapter.js +250 -0
- package/dist/plugins/messengers/feishu/feishu-adapter.js.map +1 -0
- package/dist/plugins/messengers/feishu/feishu-client.d.ts +43 -0
- package/dist/plugins/messengers/feishu/feishu-client.d.ts.map +1 -0
- package/dist/plugins/messengers/feishu/feishu-client.js +118 -0
- package/dist/plugins/messengers/feishu/feishu-client.js.map +1 -0
- package/dist/plugins/messengers/feishu/index.d.ts +4 -0
- package/dist/plugins/messengers/feishu/index.d.ts.map +1 -0
- package/dist/plugins/messengers/feishu/index.js +4 -0
- package/dist/plugins/messengers/feishu/index.js.map +1 -0
- package/dist/plugins/messengers/feishu/types.d.ts +113 -0
- package/dist/plugins/messengers/feishu/types.d.ts.map +1 -0
- package/dist/plugins/messengers/feishu/types.js +4 -0
- package/dist/plugins/messengers/feishu/types.js.map +1 -0
- package/dist/plugins/messengers/telegram/index.d.ts +4 -0
- package/dist/plugins/messengers/telegram/index.d.ts.map +1 -0
- package/dist/plugins/messengers/telegram/index.js +4 -0
- package/dist/plugins/messengers/telegram/index.js.map +1 -0
- package/dist/plugins/messengers/telegram/markdown-to-html.d.ts +5 -0
- package/dist/plugins/messengers/telegram/markdown-to-html.d.ts.map +1 -0
- package/dist/plugins/messengers/telegram/markdown-to-html.js +186 -0
- package/dist/plugins/messengers/telegram/markdown-to-html.js.map +1 -0
- package/dist/plugins/messengers/telegram/media-download.d.ts +59 -0
- package/dist/plugins/messengers/telegram/media-download.d.ts.map +1 -0
- package/dist/plugins/messengers/telegram/media-download.js +228 -0
- package/dist/plugins/messengers/telegram/media-download.js.map +1 -0
- package/dist/plugins/messengers/telegram/telegram-adapter.d.ts +77 -0
- package/dist/plugins/messengers/telegram/telegram-adapter.d.ts.map +1 -0
- package/dist/plugins/messengers/telegram/telegram-adapter.js +880 -0
- package/dist/plugins/messengers/telegram/telegram-adapter.js.map +1 -0
- package/dist/plugins/messengers/telegram/types.d.ts +47 -0
- package/dist/plugins/messengers/telegram/types.d.ts.map +1 -0
- package/dist/plugins/messengers/telegram/types.js +3 -0
- package/dist/plugins/messengers/telegram/types.js.map +1 -0
- package/dist/plugins/messengers/wechat/context-store.d.ts +18 -0
- package/dist/plugins/messengers/wechat/context-store.d.ts.map +1 -0
- package/dist/plugins/messengers/wechat/context-store.js +105 -0
- package/dist/plugins/messengers/wechat/context-store.js.map +1 -0
- package/dist/plugins/messengers/wechat/ilink-adapter.d.ts +71 -0
- package/dist/plugins/messengers/wechat/ilink-adapter.d.ts.map +1 -0
- package/dist/plugins/messengers/wechat/ilink-adapter.js +664 -0
- package/dist/plugins/messengers/wechat/ilink-adapter.js.map +1 -0
- package/dist/plugins/messengers/wechat/ilink-client.d.ts +75 -0
- package/dist/plugins/messengers/wechat/ilink-client.d.ts.map +1 -0
- package/dist/plugins/messengers/wechat/ilink-client.js +331 -0
- package/dist/plugins/messengers/wechat/ilink-client.js.map +1 -0
- package/dist/plugins/messengers/wechat/ilink-types.d.ts +181 -0
- package/dist/plugins/messengers/wechat/ilink-types.d.ts.map +1 -0
- package/dist/plugins/messengers/wechat/ilink-types.js +22 -0
- package/dist/plugins/messengers/wechat/ilink-types.js.map +1 -0
- package/dist/plugins/messengers/wechat/media-download.d.ts +32 -0
- package/dist/plugins/messengers/wechat/media-download.d.ts.map +1 -0
- package/dist/plugins/messengers/wechat/media-download.js +78 -0
- package/dist/plugins/messengers/wechat/media-download.js.map +1 -0
- package/dist/scripts/migrate-gcj02-to-wgs84.d.ts +3 -0
- package/dist/scripts/migrate-gcj02-to-wgs84.d.ts.map +1 -0
- package/dist/scripts/migrate-gcj02-to-wgs84.js +52 -0
- package/dist/scripts/migrate-gcj02-to-wgs84.js.map +1 -0
- package/dist/utils/backoff.d.ts +35 -0
- package/dist/utils/backoff.d.ts.map +1 -0
- package/dist/utils/backoff.js +59 -0
- package/dist/utils/backoff.js.map +1 -0
- package/dist/utils/cross-platform.d.ts +26 -0
- package/dist/utils/cross-platform.d.ts.map +1 -0
- package/dist/utils/cross-platform.js +58 -0
- package/dist/utils/cross-platform.js.map +1 -0
- package/dist/utils/message-split.d.ts +14 -0
- package/dist/utils/message-split.d.ts.map +1 -0
- package/dist/utils/message-split.js +65 -0
- package/dist/utils/message-split.js.map +1 -0
- package/dist/utils/safe-equal.d.ts +2 -0
- package/dist/utils/safe-equal.d.ts.map +1 -0
- package/dist/utils/safe-equal.js +11 -0
- package/dist/utils/safe-equal.js.map +1 -0
- package/dist/web/public/_app.js +196 -0
- package/dist/web/public/index.html +936 -0
- package/dist/web/public/loc.html +305 -0
- package/dist/web/public/login.html +106 -0
- package/dist/web/public/memos.html +271 -0
- package/dist/web/public/reminders.html +234 -0
- package/dist/web/public/settings.html +1355 -0
- package/dist/web/public/tasks.html +1835 -0
- package/dist/web/server.d.ts +12 -0
- package/dist/web/server.d.ts.map +1 -0
- package/dist/web/server.js +2399 -0
- package/dist/web/server.js.map +1 -0
- package/package.json +92 -0
|
@@ -0,0 +1,682 @@
|
|
|
1
|
+
// OpenCode HTTP driver.
|
|
2
|
+
//
|
|
3
|
+
// Why this exists:
|
|
4
|
+
// `opencode run --format json` (the stdio driver in opencode-stdio-adapter.ts)
|
|
5
|
+
// does NOT emit `permission.asked` events on its stdout stream, and its
|
|
6
|
+
// inline auto-reject logic occasionally fails to release the prompt
|
|
7
|
+
// deferred — the process then hangs until im-hub's 30-min hard SIGTERM.
|
|
8
|
+
// Audit confirmed this on 2026-05-04 (rows id=283/284, exact 1,800,000 ms).
|
|
9
|
+
//
|
|
10
|
+
// What this driver does instead:
|
|
11
|
+
// 1. Lazily starts a long-lived `opencode serve` daemon on 127.0.0.1
|
|
12
|
+
// (via OpencodeServeManager). Probes-and-reuses any healthy daemon
|
|
13
|
+
// already on the configured port so back-to-back im-hub restarts
|
|
14
|
+
// don't bind-fail on a still-running child.
|
|
15
|
+
// 2. Subscribes to /event SSE so the adapter — not the run-CLI — owns the
|
|
16
|
+
// authoritative event stream.
|
|
17
|
+
// 3. Creates sessions via POST /session and submits prompts via
|
|
18
|
+
// POST /session/:id/message. Resumes existing sessions when
|
|
19
|
+
// opts.agentSessionId is set; on first turn of a resumed session this
|
|
20
|
+
// process lifetime, PATCHes the session ruleset to apply the current
|
|
21
|
+
// gate policy.
|
|
22
|
+
// 4. Routes `permission.asked` SSE events through the IM approval bus
|
|
23
|
+
// when an IM context (threadId + platform) and notifier are both
|
|
24
|
+
// available. Decisions translate: allow → opencode `once`, deny →
|
|
25
|
+
// opencode `reject` (with optional message). Without an IM channel
|
|
26
|
+
// the adapter falls back to auto-`once` so non-IM call paths (web,
|
|
27
|
+
// scheduler) keep working.
|
|
28
|
+
// 5. Exits cleanly on `session.status: idle` for the right session,
|
|
29
|
+
// surfaces `session.error`, and is bounded by OPENCODE_TIMEOUT_MS
|
|
30
|
+
// (default 30 min).
|
|
31
|
+
//
|
|
32
|
+
// Selection: env IMHUB_OPENCODE_DRIVER=http picks this; anything else (incl
|
|
33
|
+
// unset) picks the stdio adapter. See ./index.ts factory.
|
|
34
|
+
//
|
|
35
|
+
// Gate policy: env IMHUB_OPENCODE_GATE controls which permissions surface
|
|
36
|
+
// as IM cards. See buildSessionRuleset() for the strict / medium / loose /
|
|
37
|
+
// none levels — medium is the default and gates edit / write / patch.
|
|
38
|
+
//
|
|
39
|
+
// Compatibility: extends OpenCodeAdapter so registry / tests that probe
|
|
40
|
+
// `instanceof OpenCodeAdapter` stay green. The base class's spawnStream is
|
|
41
|
+
// not used — sendPrompt is overridden end-to-end.
|
|
42
|
+
import { OpenCodeAdapter } from './opencode-stdio-adapter.js';
|
|
43
|
+
import { resolveAgentCwd } from '../../../core/agent-cwd.js';
|
|
44
|
+
import { logger as rootLogger } from '../../../core/logger.js';
|
|
45
|
+
import { opencodeServe as defaultServe } from './serve-manager.js';
|
|
46
|
+
import { approvalBus as defaultApprovalBus } from '../../../core/approval-bus.js';
|
|
47
|
+
const log = rootLogger.child({ component: 'agent.opencode.http' });
|
|
48
|
+
const DEFAULT_TIMEOUT_MS = 30 * 60 * 1000;
|
|
49
|
+
export class OpenCodeHttpAdapter extends OpenCodeAdapter {
|
|
50
|
+
serve;
|
|
51
|
+
fetchImpl;
|
|
52
|
+
/** null when the bridge is intentionally disabled. */
|
|
53
|
+
approvalBus;
|
|
54
|
+
/** Set of opencode session ids whose ruleset we've already PATCHed in this
|
|
55
|
+
* process lifetime. Lets us re-apply the gate policy when an im-hub
|
|
56
|
+
* restart resurfaces an old session, without duplicating rules every
|
|
57
|
+
* turn. Cleared only by im-hub restart — that's fine since the rules
|
|
58
|
+
* the previous run wrote are still in opencode's session DB. */
|
|
59
|
+
rulesetApplied = new Set();
|
|
60
|
+
constructor(opts = {}) {
|
|
61
|
+
super();
|
|
62
|
+
this.serve = opts.serve ?? defaultServe;
|
|
63
|
+
this.fetchImpl = opts.fetchImpl ?? fetch;
|
|
64
|
+
this.approvalBus = opts.approvalBus === null
|
|
65
|
+
? null
|
|
66
|
+
: (opts.approvalBus ?? defaultApprovalBus);
|
|
67
|
+
log.info({ event: 'opencode.http.driver_selected', bridge: this.approvalBus !== null }, '[opencode] HTTP driver active (real serve + SSE; IM approval bridge when bus has notifier)');
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Translate a single SSE event into adapter-side effects:
|
|
71
|
+
* - sessionID capture → opts.onAgentSessionId
|
|
72
|
+
* - step-finish cost/tokens → opts.onUsage
|
|
73
|
+
* - text-part with finished `time.end` → returned as a chunk (with
|
|
74
|
+
* part id when known, so the caller can dedupe against the POST-body
|
|
75
|
+
* fallback for SSE-broken builds — see drainResponseBody).
|
|
76
|
+
* Pure: tests drive this directly without spinning up a daemon.
|
|
77
|
+
*/
|
|
78
|
+
inspectHttpEvent(event, sessionID, opts) {
|
|
79
|
+
const props = event.properties ?? {};
|
|
80
|
+
const part = props.part;
|
|
81
|
+
// sessionID can appear on the event root, on properties, on properties.info
|
|
82
|
+
// or inside part. Bubble up the first one we see — setOpencodeSessionId
|
|
83
|
+
// (the receiver) is idempotent.
|
|
84
|
+
const sid = props.sessionID || part?.sessionID || props.info?.sessionID;
|
|
85
|
+
if (sid && opts.onAgentSessionId) {
|
|
86
|
+
try {
|
|
87
|
+
opts.onAgentSessionId(sid);
|
|
88
|
+
}
|
|
89
|
+
catch { /* don't let userland callbacks kill the stream */ }
|
|
90
|
+
}
|
|
91
|
+
if (event.type === 'message.part.updated' && part) {
|
|
92
|
+
if (part.type === 'step-finish') {
|
|
93
|
+
const delta = {};
|
|
94
|
+
if (typeof part.cost === 'number' && Number.isFinite(part.cost))
|
|
95
|
+
delta.costUsd = part.cost;
|
|
96
|
+
if (typeof part.tokens?.input === 'number')
|
|
97
|
+
delta.tokensInput = part.tokens.input;
|
|
98
|
+
if (typeof part.tokens?.output === 'number')
|
|
99
|
+
delta.tokensOutput = part.tokens.output;
|
|
100
|
+
if (delta.costUsd !== undefined || delta.tokensInput !== undefined || delta.tokensOutput !== undefined) {
|
|
101
|
+
if (opts.onUsage) {
|
|
102
|
+
try {
|
|
103
|
+
opts.onUsage(delta);
|
|
104
|
+
}
|
|
105
|
+
catch { /* same — user callback safety */ }
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// Match the stdio adapter: only yield text once the part is fully
|
|
110
|
+
// emitted (time.end set). Avoids streaming partial fragments and keeps
|
|
111
|
+
// /stats response_len aligned across drivers.
|
|
112
|
+
const eventSid = props.sessionID || props.info?.sessionID || part.sessionID;
|
|
113
|
+
if (part.type === 'text' &&
|
|
114
|
+
part.text &&
|
|
115
|
+
part.time?.end &&
|
|
116
|
+
eventSid === sessionID) {
|
|
117
|
+
return { text: part.text, partId: part.id };
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return {};
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* `session.status` event with status.type === 'idle' for our session is
|
|
124
|
+
* the canonical "stop" signal in opencode (cf. run.ts:532).
|
|
125
|
+
*/
|
|
126
|
+
isIdleEvent(event, sessionID) {
|
|
127
|
+
if (event.type !== 'session.status')
|
|
128
|
+
return false;
|
|
129
|
+
const props = event.properties ?? {};
|
|
130
|
+
const sid = props.sessionID || props.info?.sessionID;
|
|
131
|
+
if (sid !== sessionID)
|
|
132
|
+
return false;
|
|
133
|
+
return props.status?.type === 'idle' || props.info?.status?.type === 'idle';
|
|
134
|
+
}
|
|
135
|
+
isErrorEvent(event, sessionID) {
|
|
136
|
+
if (event.type !== 'session.error')
|
|
137
|
+
return null;
|
|
138
|
+
const props = event.properties ?? {};
|
|
139
|
+
if (props.sessionID !== sessionID)
|
|
140
|
+
return null;
|
|
141
|
+
const e = props.error;
|
|
142
|
+
let msg = 'session error';
|
|
143
|
+
if (e && typeof e === 'object') {
|
|
144
|
+
if (typeof e.name === 'string')
|
|
145
|
+
msg = e.name;
|
|
146
|
+
const data = e.data;
|
|
147
|
+
if (data && typeof data.message === 'string')
|
|
148
|
+
msg = data.message;
|
|
149
|
+
}
|
|
150
|
+
return { error: msg };
|
|
151
|
+
}
|
|
152
|
+
buildHttpContextualPrompt(prompt, history) {
|
|
153
|
+
if (!history || history.length === 0)
|
|
154
|
+
return prompt;
|
|
155
|
+
const historyText = history
|
|
156
|
+
.map(msg => `[${msg.role === 'user' ? 'User' : 'Assistant'}]: ${msg.content}`)
|
|
157
|
+
.join('\n\n');
|
|
158
|
+
return `Previous conversation context:\n${historyText}\n\nCurrent request: ${prompt}`;
|
|
159
|
+
}
|
|
160
|
+
async *sendPrompt(_sessionId, prompt, history, opts) {
|
|
161
|
+
const callOpts = opts ?? {};
|
|
162
|
+
const baseUrl = await this.serve.ensureRunning();
|
|
163
|
+
let sessionID = callOpts.agentSessionId;
|
|
164
|
+
if (!sessionID) {
|
|
165
|
+
sessionID = await this.createSession(baseUrl, callOpts);
|
|
166
|
+
if (callOpts.onAgentSessionId) {
|
|
167
|
+
try {
|
|
168
|
+
callOpts.onAgentSessionId(sessionID);
|
|
169
|
+
}
|
|
170
|
+
catch { /* ignore */ }
|
|
171
|
+
}
|
|
172
|
+
// Newly-created sessions already received the ruleset in createSession's
|
|
173
|
+
// POST body — no need to PATCH again. Mark applied so we skip the dup.
|
|
174
|
+
this.rulesetApplied.add(sessionID);
|
|
175
|
+
}
|
|
176
|
+
else if (callOpts.planMode) {
|
|
177
|
+
// Plan mode: opencode's `plan` primary agent already ships with its own
|
|
178
|
+
// stricter ruleset (edit denied except .opencode/plans/*.md). Stacking
|
|
179
|
+
// the medium-gate edit/write/patch=ask rules on top would just create
|
|
180
|
+
// redundant ask prompts during read-only planning. Skip the PATCH and
|
|
181
|
+
// let plan agent's defaults govern this turn. We intentionally do NOT
|
|
182
|
+
// mark rulesetApplied — once the user /plan off's and we resume normal
|
|
183
|
+
// turns, the next non-plan turn should re-apply the medium gate.
|
|
184
|
+
}
|
|
185
|
+
else if (!this.rulesetApplied.has(sessionID)) {
|
|
186
|
+
// Resumed session: opencode loaded its previously-stored ruleset,
|
|
187
|
+
// which may predate the current gate policy (older sessions were
|
|
188
|
+
// created with no override at all). PATCH the gate ruleset onto it
|
|
189
|
+
// so edit/write/patch correctly route through the IM bridge for the
|
|
190
|
+
// rest of this process's lifetime.
|
|
191
|
+
//
|
|
192
|
+
// Claim the slot synchronously so concurrent prompts on the same
|
|
193
|
+
// session don't fire duplicate PATCHes; remove on failure so the
|
|
194
|
+
// next turn retries instead of leaving the session permanently
|
|
195
|
+
// unguarded if this attempt fails transiently.
|
|
196
|
+
const targetSession = sessionID;
|
|
197
|
+
this.rulesetApplied.add(targetSession);
|
|
198
|
+
this.applyRuleset(baseUrl, targetSession).catch((err) => {
|
|
199
|
+
this.rulesetApplied.delete(targetSession);
|
|
200
|
+
log.warn({ event: 'opencode.http.apply_ruleset_failed', err: String(err), sessionID: targetSession });
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
log.info({ event: 'opencode.http.send', sessionID, historyLen: history?.length || 0, hasResume: !!callOpts.agentSessionResume }, 'sendPrompt');
|
|
204
|
+
// When resuming, opencode already has the conversation in its DB — feeding
|
|
205
|
+
// history again would duplicate every prior turn. (Same invariant as the
|
|
206
|
+
// stdio driver — see router.callAgentWithHistory zeroing effectiveHistory.)
|
|
207
|
+
const contextualPrompt = callOpts.agentSessionResume
|
|
208
|
+
? prompt
|
|
209
|
+
: this.buildHttpContextualPrompt(prompt, history);
|
|
210
|
+
// HTTP mode quirk: the imhub MCP server is shared across all opencode
|
|
211
|
+
// sessions because `opencode serve` is one daemon. The MCP server's
|
|
212
|
+
// env was set ONCE at daemon startup, so the runId there is stale for
|
|
213
|
+
// any given turn. Workaround: ask the agent to pass `_im_context` in
|
|
214
|
+
// every imhub-tool call; the bus accepts it as a runId fallback. This
|
|
215
|
+
// is a single-user-only path (agent could lie in multi-tenant) — see
|
|
216
|
+
// approval-bus.ts:handleReminder for the trust note.
|
|
217
|
+
const imCtxInstruction = buildImContextInstruction(callOpts);
|
|
218
|
+
const finalPrompt = imCtxInstruction
|
|
219
|
+
? `${imCtxInstruction}\n\n${contextualPrompt}`
|
|
220
|
+
: contextualPrompt;
|
|
221
|
+
// Subscribe BEFORE posting so we don't miss early events.
|
|
222
|
+
//
|
|
223
|
+
// SSE-broken fallback (opencode 1.14.45+ with node-http-server runtime):
|
|
224
|
+
// the /event stream only emits the initial server.connected and never
|
|
225
|
+
// broadcasts bus events. The POST /session/:id/message endpoint blocks
|
|
226
|
+
// until the message is complete and returns the full assistant body in
|
|
227
|
+
// JSON, so we use that as the authoritative text source. We still keep
|
|
228
|
+
// the SSE loop for older opencode versions that DO stream — yielded
|
|
229
|
+
// text-part ids are tracked so we don't emit them twice when draining
|
|
230
|
+
// the POST body. Once POST settles we schedule a brief grace then close
|
|
231
|
+
// SSE, which exits the for-await loop on the broken-stream case.
|
|
232
|
+
const sse = await this.openEventStream(baseUrl);
|
|
233
|
+
const promptPromise = this.postPrompt(baseUrl, sessionID, finalPrompt, callOpts)
|
|
234
|
+
.catch((err) => ({ error: err instanceof Error ? err : new Error(String(err)) }));
|
|
235
|
+
// 500ms after POST resolves, force-close SSE so the for-await exits even
|
|
236
|
+
// when the daemon never emits session.idle. Short enough that latency
|
|
237
|
+
// stays low; long enough to flush any final events that did stream.
|
|
238
|
+
promptPromise.finally(() => {
|
|
239
|
+
const t = setTimeout(() => { sse.close(); }, 500);
|
|
240
|
+
t.unref?.();
|
|
241
|
+
});
|
|
242
|
+
const timeoutMs = resolveTimeout();
|
|
243
|
+
const timeoutAt = Date.now() + timeoutMs;
|
|
244
|
+
let surfacedError = null;
|
|
245
|
+
const yieldedPartIds = new Set();
|
|
246
|
+
let sseYieldedAnyText = false;
|
|
247
|
+
try {
|
|
248
|
+
for await (const event of sse) {
|
|
249
|
+
if (Date.now() > timeoutAt) {
|
|
250
|
+
log.warn({ event: 'opencode.http.timeout', sessionID, timeoutMs }, 'http adapter timed out');
|
|
251
|
+
yield `\n\n⚠️ 处理超时(已超过 ${Math.round(timeoutMs / 60000)} 分钟)`;
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
if (event.type === 'permission.asked') {
|
|
255
|
+
this.routeAsk(event, baseUrl, sessionID, callOpts);
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
const errEvent = this.isErrorEvent(event, sessionID);
|
|
259
|
+
if (errEvent) {
|
|
260
|
+
surfacedError = errEvent.error;
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
const { text, partId } = this.inspectHttpEvent(event, sessionID, callOpts);
|
|
264
|
+
if (text) {
|
|
265
|
+
if (partId)
|
|
266
|
+
yieldedPartIds.add(partId);
|
|
267
|
+
sseYieldedAnyText = true;
|
|
268
|
+
yield text;
|
|
269
|
+
}
|
|
270
|
+
if (this.isIdleEvent(event, sessionID)) {
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
finally {
|
|
276
|
+
sse.close();
|
|
277
|
+
}
|
|
278
|
+
const result = await promptPromise;
|
|
279
|
+
if (result && typeof result === 'object' && 'error' in result) {
|
|
280
|
+
log.warn({ event: 'opencode.http.prompt_failed', err: String(result.error) }, 'prompt POST failed');
|
|
281
|
+
yield `opencode failed: ${result.error.message}`;
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
// SSE-broken fallback: drain text + usage from the POST response body.
|
|
285
|
+
// No-op when SSE already streamed everything (yieldedPartIds covers it).
|
|
286
|
+
if (result && typeof result === 'object' && !('error' in result)) {
|
|
287
|
+
const drained = this.drainResponseBody(result, yieldedPartIds, callOpts);
|
|
288
|
+
if (drained.length > 0) {
|
|
289
|
+
if (!sseYieldedAnyText) {
|
|
290
|
+
log.info({ event: 'opencode.http.drained_post_body', sessionID, parts: drained.length }, 'SSE yielded no text; recovered assistant message from POST body');
|
|
291
|
+
}
|
|
292
|
+
for (const text of drained)
|
|
293
|
+
yield text;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
if (surfacedError) {
|
|
297
|
+
yield `opencode session error: ${surfacedError}`;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Extract assistant text + usage from the POST /session/:id/message response
|
|
302
|
+
* body, skipping any text part already emitted via SSE (matched by part id).
|
|
303
|
+
* Used as a fallback for opencode builds whose /event stream is broken.
|
|
304
|
+
*/
|
|
305
|
+
drainResponseBody(body, alreadyYielded, opts) {
|
|
306
|
+
const parts = Array.isArray(body.parts) ? body.parts : [];
|
|
307
|
+
if (opts.onUsage && body.info) {
|
|
308
|
+
const delta = {};
|
|
309
|
+
if (typeof body.info.cost === 'number' && Number.isFinite(body.info.cost)) {
|
|
310
|
+
delta.costUsd = body.info.cost;
|
|
311
|
+
}
|
|
312
|
+
if (typeof body.info.tokens?.input === 'number')
|
|
313
|
+
delta.tokensInput = body.info.tokens.input;
|
|
314
|
+
if (typeof body.info.tokens?.output === 'number')
|
|
315
|
+
delta.tokensOutput = body.info.tokens.output;
|
|
316
|
+
if (delta.costUsd !== undefined || delta.tokensInput !== undefined || delta.tokensOutput !== undefined) {
|
|
317
|
+
try {
|
|
318
|
+
opts.onUsage(delta);
|
|
319
|
+
}
|
|
320
|
+
catch { /* ignore */ }
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
const texts = [];
|
|
324
|
+
for (const part of parts) {
|
|
325
|
+
if (part.type !== 'text')
|
|
326
|
+
continue;
|
|
327
|
+
if (typeof part.text !== 'string' || part.text.length === 0)
|
|
328
|
+
continue;
|
|
329
|
+
if (!part.time?.end)
|
|
330
|
+
continue;
|
|
331
|
+
if (part.id && alreadyYielded.has(part.id))
|
|
332
|
+
continue;
|
|
333
|
+
texts.push(part.text);
|
|
334
|
+
}
|
|
335
|
+
return texts;
|
|
336
|
+
}
|
|
337
|
+
// ─── permission bridge ────────────────────────────────────────────────
|
|
338
|
+
/**
|
|
339
|
+
* Decide how to handle a `permission.asked` SSE event:
|
|
340
|
+
* - If `this.approvalBus` is set AND has a notifier installed AND the
|
|
341
|
+
* call carries enough IM context (threadId + platform) → register a
|
|
342
|
+
* synthetic pending so the user gets an IM card; the bus's resolve
|
|
343
|
+
* callback POSTs the decision back to opencode via /permission/.../reply.
|
|
344
|
+
* - Otherwise → fall back to `once`. Without an IM channel there's no
|
|
345
|
+
* human to ask, and a plain `reject` would surface as a tool-call
|
|
346
|
+
* failure mid-conversation which is worse UX than allowing.
|
|
347
|
+
*
|
|
348
|
+
* Fire-and-forget: the SSE loop must keep draining other events while the
|
|
349
|
+
* bridge waits for the user. The bus enforces its own timeout (5 min by
|
|
350
|
+
* default) so we never deadlock the SSE loop.
|
|
351
|
+
*/
|
|
352
|
+
routeAsk(event, baseUrl, ocSessionID, opts) {
|
|
353
|
+
const props = event.properties ?? {};
|
|
354
|
+
const reqId = props.id;
|
|
355
|
+
if (typeof reqId !== 'string')
|
|
356
|
+
return;
|
|
357
|
+
const fallback = () => {
|
|
358
|
+
this.replyPermission(baseUrl, reqId, 'once').catch((err) => {
|
|
359
|
+
log.warn({ event: 'opencode.http.ask_fallback_failed', err: String(err), reqId });
|
|
360
|
+
});
|
|
361
|
+
};
|
|
362
|
+
const bus = this.approvalBus;
|
|
363
|
+
const canBridge = bus?.hasNotifier()
|
|
364
|
+
&& typeof opts.threadId === 'string' && opts.threadId.length > 0
|
|
365
|
+
&& typeof opts.platform === 'string' && opts.platform.length > 0;
|
|
366
|
+
if (!canBridge) {
|
|
367
|
+
fallback();
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
const ctx = {
|
|
371
|
+
threadId: opts.threadId,
|
|
372
|
+
platform: opts.platform,
|
|
373
|
+
userId: opts.userId ?? '',
|
|
374
|
+
channelId: opts.channelId ?? '',
|
|
375
|
+
};
|
|
376
|
+
const dispatch = (decision) => {
|
|
377
|
+
const wire = decisionToOpencodeReply(decision);
|
|
378
|
+
this.replyPermission(baseUrl, reqId, wire.reply, wire.message).catch((err) => {
|
|
379
|
+
log.warn({ event: 'opencode.http.ask_reply_failed', err: String(err), reqId });
|
|
380
|
+
});
|
|
381
|
+
};
|
|
382
|
+
bus?.registerSyntheticPending({
|
|
383
|
+
runId: ocSessionID, // opencode session id IS the run id from the bus's POV
|
|
384
|
+
reqId,
|
|
385
|
+
toolName: typeof props.permission === 'string' ? props.permission : 'permission',
|
|
386
|
+
input: {
|
|
387
|
+
...(Array.isArray(props.patterns) ? { patterns: props.patterns } : {}),
|
|
388
|
+
...(props.metadata && typeof props.metadata === 'object' ? props.metadata : {}),
|
|
389
|
+
},
|
|
390
|
+
ctx,
|
|
391
|
+
dispatch,
|
|
392
|
+
}).catch((err) => {
|
|
393
|
+
// Bus rejected (e.g. notifier removed mid-flight). Safety: fall back.
|
|
394
|
+
log.warn({ event: 'opencode.http.bridge_register_failed', err: String(err), reqId });
|
|
395
|
+
fallback();
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Build the session-level permission ruleset to append to opencode's
|
|
400
|
+
* agent defaults. See createSession's docstring for policy rationale.
|
|
401
|
+
*
|
|
402
|
+
* The returned rules are flat `{permission, pattern, action}` objects
|
|
403
|
+
* matching opencode's `Permission.Rule` schema.
|
|
404
|
+
*/
|
|
405
|
+
buildSessionRuleset() {
|
|
406
|
+
const gate = (process.env.IMHUB_OPENCODE_GATE || 'medium').toLowerCase();
|
|
407
|
+
const ask = (permission) => ({ permission, pattern: '*', action: 'ask' });
|
|
408
|
+
if (gate === 'none')
|
|
409
|
+
return [];
|
|
410
|
+
if (gate === 'strict')
|
|
411
|
+
return [ask('edit'), ask('write'), ask('patch'), ask('bash')];
|
|
412
|
+
if (gate === 'loose')
|
|
413
|
+
return [ask('edit'), ask('write')];
|
|
414
|
+
return [ask('edit'), ask('write'), ask('patch')]; // medium (default)
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* PATCH a resumed session with our gate ruleset so subsequent tool calls
|
|
418
|
+
* surface as `permission.asked` events for the bridge. opencode's update
|
|
419
|
+
* endpoint merges (current + payload) — payload comes last and wins under
|
|
420
|
+
* findLast semantics. Idempotent across im-hub restarts but NOT within a
|
|
421
|
+
* single process: the caller (sendPrompt) gates on rulesetApplied.
|
|
422
|
+
*/
|
|
423
|
+
async applyRuleset(baseUrl, sessionID) {
|
|
424
|
+
const ruleset = this.buildSessionRuleset();
|
|
425
|
+
if (ruleset.length === 0)
|
|
426
|
+
return; // gate=none
|
|
427
|
+
const res = await this.fetchImpl(`${baseUrl}/session/${sessionID}`, {
|
|
428
|
+
method: 'PATCH',
|
|
429
|
+
headers: { 'Content-Type': 'application/json' },
|
|
430
|
+
body: JSON.stringify({ permission: ruleset }),
|
|
431
|
+
});
|
|
432
|
+
if (!res.ok) {
|
|
433
|
+
throw new Error(`opencode session update failed: HTTP ${res.status}`);
|
|
434
|
+
}
|
|
435
|
+
await res.text().catch(() => '');
|
|
436
|
+
log.info({ event: 'opencode.http.ruleset_applied', sessionID, gate: process.env.IMHUB_OPENCODE_GATE || 'medium' }, 'applied gate ruleset to resumed session');
|
|
437
|
+
}
|
|
438
|
+
// ─── HTTP plumbing ──────────────────────────────────────────────────────
|
|
439
|
+
async createSession(baseUrl, opts) {
|
|
440
|
+
// Session-level permission overrides for IM-launched runs.
|
|
441
|
+
//
|
|
442
|
+
// Why we override at all:
|
|
443
|
+
// opencode's built-in `build` agent ships with a `*: allow` catch-all
|
|
444
|
+
// (agent.ts:86-103 in the upstream repo). With findLast semantics,
|
|
445
|
+
// that swallows mutating tools like edit/write/patch unless we
|
|
446
|
+
// *append* stricter rules whose specificity wins.
|
|
447
|
+
//
|
|
448
|
+
// The "medium" policy (default):
|
|
449
|
+
// - edit / write / patch → ask: any time the assistant wants to
|
|
450
|
+
// mutate files, surface an IM card. These are the operations a
|
|
451
|
+
// user wants to "拍板".
|
|
452
|
+
// - bash is intentionally NOT gated: IM-driven exploration involves
|
|
453
|
+
// a flood of `ls` / `cat` / `git status` and asking on every one
|
|
454
|
+
// would drown the user. Mutations the LLM does via bash (rm,
|
|
455
|
+
// npm install, git commit) aren't caught here — that's a known
|
|
456
|
+
// gap; revisit if it bites in practice.
|
|
457
|
+
// - external_directory is already `ask` by default for paths
|
|
458
|
+
// outside the cwd / skill whitelist; we don't need to repeat it
|
|
459
|
+
// and it would be surprising to override.
|
|
460
|
+
//
|
|
461
|
+
// Override path: env IMHUB_OPENCODE_GATE=strict|loose|none flips this
|
|
462
|
+
// policy without redeploy:
|
|
463
|
+
// strict → bash also asks
|
|
464
|
+
// loose → only write+edit (drop patch)
|
|
465
|
+
// none → no override (mostly for debugging)
|
|
466
|
+
const body = {
|
|
467
|
+
title: 'im-hub session',
|
|
468
|
+
};
|
|
469
|
+
// Plan mode: route through opencode's built-in `plan` agent and skip our
|
|
470
|
+
// medium-gate ruleset entirely (plan agent's edit-deny-except-plans
|
|
471
|
+
// policy is already stricter than the IM gate would impose).
|
|
472
|
+
if (opts.planMode) {
|
|
473
|
+
body.agent = 'plan';
|
|
474
|
+
}
|
|
475
|
+
else {
|
|
476
|
+
body.permission = this.buildSessionRuleset();
|
|
477
|
+
}
|
|
478
|
+
const res = await this.fetchImpl(`${baseUrl}/session`, {
|
|
479
|
+
method: 'POST',
|
|
480
|
+
headers: { 'Content-Type': 'application/json' },
|
|
481
|
+
body: JSON.stringify(body),
|
|
482
|
+
});
|
|
483
|
+
if (!res.ok) {
|
|
484
|
+
throw new Error(`opencode session create failed: HTTP ${res.status} ${await safeText(res)}`);
|
|
485
|
+
}
|
|
486
|
+
const json = await res.json();
|
|
487
|
+
if (!json.id)
|
|
488
|
+
throw new Error('opencode session create returned no id');
|
|
489
|
+
log.info({
|
|
490
|
+
event: 'opencode.http.session_created',
|
|
491
|
+
sessionID: json.id,
|
|
492
|
+
cwd: resolveAgentCwd('opencode', opts),
|
|
493
|
+
}, 'session created');
|
|
494
|
+
return json.id;
|
|
495
|
+
}
|
|
496
|
+
async postPrompt(baseUrl, sessionID, prompt, opts) {
|
|
497
|
+
const body = {
|
|
498
|
+
parts: [{ type: 'text', text: prompt }],
|
|
499
|
+
};
|
|
500
|
+
if (opts.model)
|
|
501
|
+
body.model = opts.model;
|
|
502
|
+
if (opts.variant)
|
|
503
|
+
body.variant = opts.variant;
|
|
504
|
+
// planMode forces the plan agent on every turn, including resumed sessions
|
|
505
|
+
// that were originally created under build. opencode's per-message `agent`
|
|
506
|
+
// field overrides the session's default agent for this single turn.
|
|
507
|
+
if (opts.planMode)
|
|
508
|
+
body.agent = 'plan';
|
|
509
|
+
const res = await this.fetchImpl(`${baseUrl}/session/${sessionID}/message`, {
|
|
510
|
+
method: 'POST',
|
|
511
|
+
headers: { 'Content-Type': 'application/json' },
|
|
512
|
+
body: JSON.stringify(body),
|
|
513
|
+
});
|
|
514
|
+
if (!res.ok) {
|
|
515
|
+
throw new Error(`opencode prompt failed: HTTP ${res.status} ${await safeText(res)}`);
|
|
516
|
+
}
|
|
517
|
+
// On opencode 1.14.45+ the POST blocks until the assistant message is
|
|
518
|
+
// complete and returns the full body. We parse it (best-effort) so the
|
|
519
|
+
// caller can recover text + usage when /event SSE doesn't broadcast.
|
|
520
|
+
try {
|
|
521
|
+
return await res.json();
|
|
522
|
+
}
|
|
523
|
+
catch {
|
|
524
|
+
return {};
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
async replyPermission(baseUrl, requestId, reply, message) {
|
|
528
|
+
const body = { reply };
|
|
529
|
+
if (reply === 'reject' && message)
|
|
530
|
+
body.message = message;
|
|
531
|
+
const res = await this.fetchImpl(`${baseUrl}/permission/${requestId}/reply`, {
|
|
532
|
+
method: 'POST',
|
|
533
|
+
headers: { 'Content-Type': 'application/json' },
|
|
534
|
+
body: JSON.stringify(body),
|
|
535
|
+
});
|
|
536
|
+
if (!res.ok) {
|
|
537
|
+
throw new Error(`opencode permission reply HTTP ${res.status}`);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
async openEventStream(baseUrl) {
|
|
541
|
+
const res = await this.fetchImpl(`${baseUrl}/event`, {
|
|
542
|
+
headers: { Accept: 'text/event-stream' },
|
|
543
|
+
});
|
|
544
|
+
if (!res.ok || !res.body) {
|
|
545
|
+
throw new Error(`opencode event stream failed: HTTP ${res.status}`);
|
|
546
|
+
}
|
|
547
|
+
return new EventStream(res.body);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* Map a bus Decision to opencode's permission.reply schema.
|
|
552
|
+
*
|
|
553
|
+
* Mapping rules:
|
|
554
|
+
* - allow → once (one-shot grant; matches what TUI sends)
|
|
555
|
+
* - allow + autoAllowFurther → once on the wire; the bus has already
|
|
556
|
+
* registered an im-side auto-allow rule by side effect (see
|
|
557
|
+
* ApprovalBus.cancelPending). We deliberately do NOT promote to
|
|
558
|
+
* opencode's `always` because that would persist a ruleset addition on
|
|
559
|
+
* the opencode session, which is heavier scope than the IM rule.
|
|
560
|
+
* - deny + message → reject (+ message). opencode surfaces this as a
|
|
561
|
+
* CorrectedError ("user rejected … with feedback"), giving the LLM a
|
|
562
|
+
* useful hint instead of a bare reject.
|
|
563
|
+
* - deny → reject
|
|
564
|
+
*/
|
|
565
|
+
/**
|
|
566
|
+
* Compose the system instruction telling the agent how to identify the
|
|
567
|
+
* IM thread when it calls imhub MCP tools. Empty string when we don't
|
|
568
|
+
* have full IM context (web/scheduler/intent-llm calls) — those won't
|
|
569
|
+
* have reminder tools wired anyway.
|
|
570
|
+
*
|
|
571
|
+
* Single-user trust model: this prompt asks the agent to assert the
|
|
572
|
+
* caller's identity. A malicious or confused agent could pass another
|
|
573
|
+
* user's IDs. Acceptable for personal deployments; multi-tenant operators
|
|
574
|
+
* should use claude-code (per-spawn env via --mcp-config) or opencode
|
|
575
|
+
* stdio (per-spawn extraEnv) instead.
|
|
576
|
+
*/
|
|
577
|
+
function buildImContextInstruction(opts) {
|
|
578
|
+
const platform = opts.platform;
|
|
579
|
+
const threadId = opts.threadId;
|
|
580
|
+
const channelId = opts.channelId;
|
|
581
|
+
if (!platform || !threadId || !channelId)
|
|
582
|
+
return '';
|
|
583
|
+
const userId = opts.userId ?? '';
|
|
584
|
+
const ctx = JSON.stringify({ platform, threadId, channelId, userId });
|
|
585
|
+
return [
|
|
586
|
+
'[im-hub MCP routing — important]',
|
|
587
|
+
'When you call any imhub tool — reminder family (create_reminder /',
|
|
588
|
+
'list_reminders / cancel_reminder / snooze_reminder) or memo family',
|
|
589
|
+
'(save_memo / search_memos / update_memo / delete_memo /',
|
|
590
|
+
'request_location_capture) — you MUST include this exact `_im_context`',
|
|
591
|
+
'field in the tool input so the call routes to the correct IM thread:',
|
|
592
|
+
'',
|
|
593
|
+
` "_im_context": ${ctx}`,
|
|
594
|
+
'',
|
|
595
|
+
'Pass these values verbatim — do not invent or modify them.',
|
|
596
|
+
'',
|
|
597
|
+
'[memo timezone] All times in memo tools are Asia/Shanghai (UTC+8).',
|
|
598
|
+
'When the user says relative dates ("明天" / "下周三" / "下午3点"),',
|
|
599
|
+
'compute the absolute datetime in UTC+8 and emit "YYYY-MM-DD HH:MM:SS"',
|
|
600
|
+
'(no T, no offset suffix). The server normalizes if you slip an offset',
|
|
601
|
+
'in, but emit local directly to keep the chat consistent.',
|
|
602
|
+
].join('\n');
|
|
603
|
+
}
|
|
604
|
+
function decisionToOpencodeReply(decision) {
|
|
605
|
+
if (decision.behavior === 'allow')
|
|
606
|
+
return { reply: 'once' };
|
|
607
|
+
return decision.message
|
|
608
|
+
? { reply: 'reject', message: decision.message }
|
|
609
|
+
: { reply: 'reject' };
|
|
610
|
+
}
|
|
611
|
+
function resolveTimeout() {
|
|
612
|
+
const raw = process.env.OPENCODE_TIMEOUT_MS;
|
|
613
|
+
if (raw) {
|
|
614
|
+
const n = parseInt(raw, 10);
|
|
615
|
+
if (Number.isFinite(n) && n > 0)
|
|
616
|
+
return n;
|
|
617
|
+
}
|
|
618
|
+
return DEFAULT_TIMEOUT_MS;
|
|
619
|
+
}
|
|
620
|
+
async function safeText(res) {
|
|
621
|
+
try {
|
|
622
|
+
return (await res.text()).slice(0, 500);
|
|
623
|
+
}
|
|
624
|
+
catch {
|
|
625
|
+
return '';
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
export class EventStream {
|
|
629
|
+
reader;
|
|
630
|
+
decoder = new TextDecoder('utf-8');
|
|
631
|
+
buf = '';
|
|
632
|
+
closed = false;
|
|
633
|
+
constructor(body) {
|
|
634
|
+
this.reader = body.getReader();
|
|
635
|
+
}
|
|
636
|
+
close() {
|
|
637
|
+
if (this.closed)
|
|
638
|
+
return;
|
|
639
|
+
this.closed = true;
|
|
640
|
+
try {
|
|
641
|
+
void this.reader.cancel();
|
|
642
|
+
}
|
|
643
|
+
catch { /* ignore */ }
|
|
644
|
+
}
|
|
645
|
+
async *[Symbol.asyncIterator]() {
|
|
646
|
+
while (!this.closed) {
|
|
647
|
+
const { value, done } = await this.reader.read();
|
|
648
|
+
if (done)
|
|
649
|
+
return;
|
|
650
|
+
if (!value)
|
|
651
|
+
continue;
|
|
652
|
+
this.buf += this.decoder.decode(value, { stream: true });
|
|
653
|
+
while (true) {
|
|
654
|
+
const idx = this.buf.indexOf('\n\n');
|
|
655
|
+
if (idx === -1)
|
|
656
|
+
break;
|
|
657
|
+
const frame = this.buf.slice(0, idx);
|
|
658
|
+
this.buf = this.buf.slice(idx + 2);
|
|
659
|
+
const evt = parseSseFrame(frame);
|
|
660
|
+
if (evt)
|
|
661
|
+
yield evt;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
function parseSseFrame(frame) {
|
|
667
|
+
let payload = '';
|
|
668
|
+
for (const line of frame.split('\n')) {
|
|
669
|
+
if (line.startsWith('data:')) {
|
|
670
|
+
payload += line.slice(5).trimStart();
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
if (!payload)
|
|
674
|
+
return null;
|
|
675
|
+
try {
|
|
676
|
+
return JSON.parse(payload);
|
|
677
|
+
}
|
|
678
|
+
catch {
|
|
679
|
+
return null;
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
//# sourceMappingURL=opencode-http-adapter.js.map
|