@aitne/daemon 0.1.6 → 0.1.8
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/adapters/dashboard-adapter.d.ts +18 -2
- package/dist/adapters/dashboard-adapter.d.ts.map +1 -1
- package/dist/adapters/dashboard-adapter.js +101 -51
- package/dist/adapters/dashboard-adapter.js.map +1 -1
- package/dist/adapters/discord.d.ts +8 -0
- package/dist/adapters/discord.d.ts.map +1 -1
- package/dist/adapters/discord.js +100 -21
- package/dist/adapters/discord.js.map +1 -1
- package/dist/adapters/message-hub.d.ts.map +1 -1
- package/dist/adapters/message-hub.js +7 -1
- package/dist/adapters/message-hub.js.map +1 -1
- package/dist/adapters/notification-manager.d.ts +102 -2
- package/dist/adapters/notification-manager.d.ts.map +1 -1
- package/dist/adapters/notification-manager.js +228 -8
- package/dist/adapters/notification-manager.js.map +1 -1
- package/dist/adapters/outbound-text.d.ts +16 -0
- package/dist/adapters/outbound-text.d.ts.map +1 -1
- package/dist/adapters/outbound-text.js +118 -1
- package/dist/adapters/outbound-text.js.map +1 -1
- package/dist/adapters/primary-platform-resolver.d.ts +69 -0
- package/dist/adapters/primary-platform-resolver.d.ts.map +1 -0
- package/dist/adapters/primary-platform-resolver.js +55 -0
- package/dist/adapters/primary-platform-resolver.js.map +1 -0
- package/dist/adapters/slack-adapter.d.ts +28 -0
- package/dist/adapters/slack-adapter.d.ts.map +1 -1
- package/dist/adapters/slack-adapter.js +134 -35
- package/dist/adapters/slack-adapter.js.map +1 -1
- package/dist/adapters/telegram-adapter.d.ts +7 -1
- package/dist/adapters/telegram-adapter.d.ts.map +1 -1
- package/dist/adapters/telegram-adapter.js +49 -18
- package/dist/adapters/telegram-adapter.js.map +1 -1
- package/dist/adapters/whatsapp-adapter.d.ts +33 -0
- package/dist/adapters/whatsapp-adapter.d.ts.map +1 -1
- package/dist/adapters/whatsapp-adapter.js +84 -0
- package/dist/adapters/whatsapp-adapter.js.map +1 -1
- package/dist/api/directory-picker.d.ts.map +1 -1
- package/dist/api/directory-picker.js +14 -0
- package/dist/api/directory-picker.js.map +1 -1
- package/dist/api/env-writer.d.ts.map +1 -1
- package/dist/api/env-writer.js +6 -1
- package/dist/api/env-writer.js.map +1 -1
- package/dist/api/helpers/agent-errors.d.ts +2600 -0
- package/dist/api/helpers/agent-errors.d.ts.map +1 -0
- package/dist/api/helpers/agent-errors.js +2506 -0
- package/dist/api/helpers/agent-errors.js.map +1 -0
- package/dist/api/integration-route-gate.d.ts.map +1 -1
- package/dist/api/integration-route-gate.js +10 -3
- package/dist/api/integration-route-gate.js.map +1 -1
- package/dist/api/routes/agent-schedule-plan-match.d.ts +5 -0
- package/dist/api/routes/agent-schedule-plan-match.d.ts.map +1 -0
- package/dist/api/routes/agent-schedule-plan-match.js +101 -0
- package/dist/api/routes/agent-schedule-plan-match.js.map +1 -0
- package/dist/api/routes/agent-schedule.d.ts +4 -0
- package/dist/api/routes/agent-schedule.d.ts.map +1 -0
- package/dist/api/routes/agent-schedule.js +750 -0
- package/dist/api/routes/agent-schedule.js.map +1 -0
- package/dist/api/routes/agent.d.ts.map +1 -1
- package/dist/api/routes/agent.js +209 -366
- package/dist/api/routes/agent.js.map +1 -1
- package/dist/api/routes/apple-calendar.d.ts.map +1 -1
- package/dist/api/routes/apple-calendar.js +109 -27
- package/dist/api/routes/apple-calendar.js.map +1 -1
- package/dist/api/routes/attachments.d.ts.map +1 -1
- package/dist/api/routes/attachments.js +113 -21
- package/dist/api/routes/attachments.js.map +1 -1
- package/dist/api/routes/backends.d.ts.map +1 -1
- package/dist/api/routes/backends.js +100 -4
- package/dist/api/routes/backends.js.map +1 -1
- package/dist/api/routes/books.d.ts.map +1 -1
- package/dist/api/routes/books.js +58 -18
- package/dist/api/routes/books.js.map +1 -1
- package/dist/api/routes/calendar.d.ts +3 -2
- package/dist/api/routes/calendar.d.ts.map +1 -1
- package/dist/api/routes/calendar.js +330 -55
- package/dist/api/routes/calendar.js.map +1 -1
- package/dist/api/routes/commands.d.ts.map +1 -1
- package/dist/api/routes/commands.js +20 -1
- package/dist/api/routes/commands.js.map +1 -1
- package/dist/api/routes/context/index.d.ts +33 -0
- package/dist/api/routes/context/index.d.ts.map +1 -0
- package/dist/api/routes/context/index.js +193 -0
- package/dist/api/routes/context/index.js.map +1 -0
- package/dist/api/routes/context/locks.d.ts +4 -0
- package/dist/api/routes/context/locks.d.ts.map +1 -0
- package/dist/api/routes/context/locks.js +136 -0
- package/dist/api/routes/context/locks.js.map +1 -0
- package/dist/api/routes/context/path-resolve.d.ts +15 -0
- package/dist/api/routes/context/path-resolve.d.ts.map +1 -0
- package/dist/api/routes/context/path-resolve.js +109 -0
- package/dist/api/routes/context/path-resolve.js.map +1 -0
- package/dist/api/routes/context/permissions.d.ts +35 -0
- package/dist/api/routes/context/permissions.d.ts.map +1 -0
- package/dist/api/routes/context/permissions.js +192 -0
- package/dist/api/routes/context/permissions.js.map +1 -0
- package/dist/api/routes/context/read.d.ts +4 -0
- package/dist/api/routes/context/read.d.ts.map +1 -0
- package/dist/api/routes/context/read.js +295 -0
- package/dist/api/routes/context/read.js.map +1 -0
- package/dist/api/routes/context/repair.d.ts +4 -0
- package/dist/api/routes/context/repair.d.ts.map +1 -0
- package/dist/api/routes/context/repair.js +114 -0
- package/dist/api/routes/context/repair.js.map +1 -0
- package/dist/api/routes/context/snapshots.d.ts +4 -0
- package/dist/api/routes/context/snapshots.d.ts.map +1 -0
- package/dist/api/routes/context/snapshots.js +177 -0
- package/dist/api/routes/context/snapshots.js.map +1 -0
- package/dist/api/routes/context/write.d.ts +4 -0
- package/dist/api/routes/context/write.d.ts.map +1 -0
- package/dist/api/routes/context/write.js +570 -0
- package/dist/api/routes/context/write.js.map +1 -0
- package/dist/api/routes/context.d.ts +2 -43
- package/dist/api/routes/context.d.ts.map +1 -1
- package/dist/api/routes/context.js +415 -558
- package/dist/api/routes/context.js.map +1 -1
- package/dist/api/routes/dashboard/config.d.ts +4 -0
- package/dist/api/routes/dashboard/config.d.ts.map +1 -0
- package/dist/api/routes/dashboard/config.js +499 -0
- package/dist/api/routes/dashboard/config.js.map +1 -0
- package/dist/api/routes/dashboard/conversations.d.ts +4 -0
- package/dist/api/routes/dashboard/conversations.d.ts.map +1 -0
- package/dist/api/routes/dashboard/conversations.js +309 -0
- package/dist/api/routes/dashboard/conversations.js.map +1 -0
- package/dist/api/routes/dashboard/cost-approvals.d.ts +29 -0
- package/dist/api/routes/dashboard/cost-approvals.d.ts.map +1 -0
- package/dist/api/routes/dashboard/cost-approvals.js +259 -0
- package/dist/api/routes/dashboard/cost-approvals.js.map +1 -0
- package/dist/api/routes/dashboard/index.d.ts +27 -0
- package/dist/api/routes/dashboard/index.d.ts.map +1 -0
- package/dist/api/routes/dashboard/index.js +47 -0
- package/dist/api/routes/dashboard/index.js.map +1 -0
- package/dist/api/routes/dashboard/messaging.d.ts +4 -0
- package/dist/api/routes/dashboard/messaging.d.ts.map +1 -0
- package/dist/api/routes/dashboard/messaging.js +351 -0
- package/dist/api/routes/dashboard/messaging.js.map +1 -0
- package/dist/api/routes/dashboard/notifications.d.ts +4 -0
- package/dist/api/routes/dashboard/notifications.d.ts.map +1 -0
- package/dist/api/routes/dashboard/notifications.js +109 -0
- package/dist/api/routes/dashboard/notifications.js.map +1 -0
- package/dist/api/routes/dashboard/oauth-google.d.ts +4 -0
- package/dist/api/routes/dashboard/oauth-google.d.ts.map +1 -0
- package/dist/api/routes/dashboard/oauth-google.js +293 -0
- package/dist/api/routes/dashboard/oauth-google.js.map +1 -0
- package/dist/api/routes/dashboard/schedule-readonly.d.ts +4 -0
- package/dist/api/routes/dashboard/schedule-readonly.d.ts.map +1 -0
- package/dist/api/routes/dashboard/schedule-readonly.js +46 -0
- package/dist/api/routes/dashboard/schedule-readonly.js.map +1 -0
- package/dist/api/routes/dashboard/secrets.d.ts +24 -0
- package/dist/api/routes/dashboard/secrets.d.ts.map +1 -0
- package/dist/api/routes/dashboard/secrets.js +307 -0
- package/dist/api/routes/dashboard/secrets.js.map +1 -0
- package/dist/api/routes/dashboard/snapshots.d.ts +4 -0
- package/dist/api/routes/dashboard/snapshots.d.ts.map +1 -0
- package/dist/api/routes/dashboard/snapshots.js +33 -0
- package/dist/api/routes/dashboard/snapshots.js.map +1 -0
- package/dist/api/routes/dashboard.d.ts.map +1 -1
- package/dist/api/routes/dashboard.js +20 -12
- package/dist/api/routes/dashboard.js.map +1 -1
- package/dist/api/routes/delegated.d.ts +5 -4
- package/dist/api/routes/delegated.d.ts.map +1 -1
- package/dist/api/routes/delegated.js +6 -5
- package/dist/api/routes/delegated.js.map +1 -1
- package/dist/api/routes/docs.d.ts.map +1 -1
- package/dist/api/routes/docs.js +72 -9
- package/dist/api/routes/docs.js.map +1 -1
- package/dist/api/routes/entities.d.ts.map +1 -1
- package/dist/api/routes/entities.js +112 -43
- package/dist/api/routes/entities.js.map +1 -1
- package/dist/api/routes/fs.d.ts.map +1 -1
- package/dist/api/routes/fs.js +27 -12
- package/dist/api/routes/fs.js.map +1 -1
- package/dist/api/routes/git-templates.d.ts.map +1 -1
- package/dist/api/routes/git-templates.js +107 -27
- package/dist/api/routes/git-templates.js.map +1 -1
- package/dist/api/routes/git.d.ts.map +1 -1
- package/dist/api/routes/git.js +55 -11
- package/dist/api/routes/git.js.map +1 -1
- package/dist/api/routes/github.d.ts.map +1 -1
- package/dist/api/routes/github.js +110 -17
- package/dist/api/routes/github.js.map +1 -1
- package/dist/api/routes/integrations/crud-patch.d.ts +17 -0
- package/dist/api/routes/integrations/crud-patch.d.ts.map +1 -0
- package/dist/api/routes/integrations/crud-patch.js +600 -0
- package/dist/api/routes/integrations/crud-patch.js.map +1 -0
- package/dist/api/routes/integrations/crud.d.ts +16 -0
- package/dist/api/routes/integrations/crud.d.ts.map +1 -0
- package/dist/api/routes/integrations/crud.js +158 -0
- package/dist/api/routes/integrations/crud.js.map +1 -0
- package/dist/api/routes/integrations/exec.d.ts +23 -0
- package/dist/api/routes/integrations/exec.d.ts.map +1 -0
- package/dist/api/routes/integrations/exec.js +356 -0
- package/dist/api/routes/integrations/exec.js.map +1 -0
- package/dist/api/routes/integrations/index.d.ts +62 -0
- package/dist/api/routes/integrations/index.d.ts.map +1 -0
- package/dist/api/routes/integrations/index.js +70 -0
- package/dist/api/routes/integrations/index.js.map +1 -0
- package/dist/api/routes/integrations/integrations/crud.d.ts +16 -0
- package/dist/api/routes/integrations/integrations/crud.d.ts.map +1 -0
- package/dist/api/routes/integrations/integrations/crud.js +158 -0
- package/dist/api/routes/integrations/integrations/crud.js.map +1 -0
- package/dist/api/routes/integrations/integrations/index.d.ts +55 -0
- package/dist/api/routes/integrations/integrations/index.d.ts.map +1 -0
- package/dist/api/routes/integrations/integrations/index.js +65 -0
- package/dist/api/routes/integrations/integrations/index.js.map +1 -0
- package/dist/api/routes/integrations/integrations/invoke.d.ts +38 -0
- package/dist/api/routes/integrations/integrations/invoke.d.ts.map +1 -0
- package/dist/api/routes/integrations/integrations/invoke.js +320 -0
- package/dist/api/routes/integrations/integrations/invoke.js.map +1 -0
- package/dist/api/routes/integrations/integrations/probe.d.ts +21 -0
- package/dist/api/routes/integrations/integrations/probe.d.ts.map +1 -0
- package/dist/api/routes/integrations/integrations/probe.js +247 -0
- package/dist/api/routes/integrations/integrations/probe.js.map +1 -0
- package/dist/api/routes/integrations/invoke.d.ts +38 -0
- package/dist/api/routes/integrations/invoke.d.ts.map +1 -0
- package/dist/api/routes/integrations/invoke.js +320 -0
- package/dist/api/routes/integrations/invoke.js.map +1 -0
- package/dist/api/routes/integrations/probe.d.ts +21 -0
- package/dist/api/routes/integrations/probe.d.ts.map +1 -0
- package/dist/api/routes/integrations/probe.js +247 -0
- package/dist/api/routes/integrations/probe.js.map +1 -0
- package/dist/api/routes/integrations.d.ts.map +1 -1
- package/dist/api/routes/integrations.js +65 -13
- package/dist/api/routes/integrations.js.map +1 -1
- package/dist/api/routes/knowledge.d.ts.map +1 -1
- package/dist/api/routes/knowledge.js +13 -2
- package/dist/api/routes/knowledge.js.map +1 -1
- package/dist/api/routes/mail/_pa_wip_mail/app-password.d.ts +4 -0
- package/dist/api/routes/mail/_pa_wip_mail/app-password.d.ts.map +1 -0
- package/dist/api/routes/mail/_pa_wip_mail/app-password.js +192 -0
- package/dist/api/routes/mail/_pa_wip_mail/app-password.js.map +1 -0
- package/dist/api/routes/mail/_pa_wip_mail/body-helpers.d.ts +55 -0
- package/dist/api/routes/mail/_pa_wip_mail/body-helpers.d.ts.map +1 -0
- package/dist/api/routes/mail/_pa_wip_mail/body-helpers.js +91 -0
- package/dist/api/routes/mail/_pa_wip_mail/body-helpers.js.map +1 -0
- package/dist/api/routes/mail/_pa_wip_mail/dependencies.d.ts +36 -0
- package/dist/api/routes/mail/_pa_wip_mail/dependencies.d.ts.map +1 -0
- package/dist/api/routes/mail/_pa_wip_mail/dependencies.js +2 -0
- package/dist/api/routes/mail/_pa_wip_mail/dependencies.js.map +1 -0
- package/dist/api/routes/mail/_pa_wip_mail/gating.d.ts +45 -0
- package/dist/api/routes/mail/_pa_wip_mail/gating.d.ts.map +1 -0
- package/dist/api/routes/mail/_pa_wip_mail/gating.js +98 -0
- package/dist/api/routes/mail/_pa_wip_mail/gating.js.map +1 -0
- package/dist/api/routes/mail/_pa_wip_mail/mail/accounts.d.ts +4 -0
- package/dist/api/routes/mail/_pa_wip_mail/mail/accounts.d.ts.map +1 -0
- package/dist/api/routes/mail/_pa_wip_mail/mail/accounts.js +289 -0
- package/dist/api/routes/mail/_pa_wip_mail/mail/accounts.js.map +1 -0
- package/dist/api/routes/mail/_pa_wip_mail/mail/app-password.d.ts +4 -0
- package/dist/api/routes/mail/_pa_wip_mail/mail/app-password.d.ts.map +1 -0
- package/dist/api/routes/mail/_pa_wip_mail/mail/app-password.js +192 -0
- package/dist/api/routes/mail/_pa_wip_mail/mail/app-password.js.map +1 -0
- package/dist/api/routes/mail/_pa_wip_mail/mail/body-helpers.d.ts +55 -0
- package/dist/api/routes/mail/_pa_wip_mail/mail/body-helpers.d.ts.map +1 -0
- package/dist/api/routes/mail/_pa_wip_mail/mail/body-helpers.js +91 -0
- package/dist/api/routes/mail/_pa_wip_mail/mail/body-helpers.js.map +1 -0
- package/dist/api/routes/mail/_pa_wip_mail/mail/dependencies.d.ts +36 -0
- package/dist/api/routes/mail/_pa_wip_mail/mail/dependencies.d.ts.map +1 -0
- package/dist/api/routes/mail/_pa_wip_mail/mail/dependencies.js +2 -0
- package/dist/api/routes/mail/_pa_wip_mail/mail/dependencies.js.map +1 -0
- package/dist/api/routes/mail/_pa_wip_mail/mail/drafts.d.ts +5 -0
- package/dist/api/routes/mail/_pa_wip_mail/mail/drafts.d.ts.map +1 -0
- package/dist/api/routes/mail/_pa_wip_mail/mail/drafts.js +139 -0
- package/dist/api/routes/mail/_pa_wip_mail/mail/drafts.js.map +1 -0
- package/dist/api/routes/mail/_pa_wip_mail/mail/gating.d.ts +45 -0
- package/dist/api/routes/mail/_pa_wip_mail/mail/gating.d.ts.map +1 -0
- package/dist/api/routes/mail/_pa_wip_mail/mail/gating.js +98 -0
- package/dist/api/routes/mail/_pa_wip_mail/mail/gating.js.map +1 -0
- package/dist/api/routes/mail/_pa_wip_mail/mail/index.d.ts +18 -0
- package/dist/api/routes/mail/_pa_wip_mail/mail/index.d.ts.map +1 -0
- package/dist/api/routes/mail/_pa_wip_mail/mail/index.js +40 -0
- package/dist/api/routes/mail/_pa_wip_mail/mail/index.js.map +1 -0
- package/dist/api/routes/mail/_pa_wip_mail/mail/messages.d.ts +15 -0
- package/dist/api/routes/mail/_pa_wip_mail/mail/messages.d.ts.map +1 -0
- package/dist/api/routes/mail/_pa_wip_mail/mail/messages.js +239 -0
- package/dist/api/routes/mail/_pa_wip_mail/mail/messages.js.map +1 -0
- package/dist/api/routes/mail/_pa_wip_mail/mail/outlook-config.d.ts +4 -0
- package/dist/api/routes/mail/_pa_wip_mail/mail/outlook-config.d.ts.map +1 -0
- package/dist/api/routes/mail/_pa_wip_mail/mail/outlook-config.js +73 -0
- package/dist/api/routes/mail/_pa_wip_mail/mail/outlook-config.js.map +1 -0
- package/dist/api/routes/mail/_pa_wip_mail/mail/provider-resolver.d.ts +64 -0
- package/dist/api/routes/mail/_pa_wip_mail/mail/provider-resolver.d.ts.map +1 -0
- package/dist/api/routes/mail/_pa_wip_mail/mail/provider-resolver.js +286 -0
- package/dist/api/routes/mail/_pa_wip_mail/mail/provider-resolver.js.map +1 -0
- package/dist/api/routes/mail/_pa_wip_mail/mail/providers.d.ts +4 -0
- package/dist/api/routes/mail/_pa_wip_mail/mail/providers.d.ts.map +1 -0
- package/dist/api/routes/mail/_pa_wip_mail/mail/providers.js +73 -0
- package/dist/api/routes/mail/_pa_wip_mail/mail/providers.js.map +1 -0
- package/dist/api/routes/mail/_pa_wip_mail/mail/tags-folders.d.ts +5 -0
- package/dist/api/routes/mail/_pa_wip_mail/mail/tags-folders.d.ts.map +1 -0
- package/dist/api/routes/mail/_pa_wip_mail/mail/tags-folders.js +35 -0
- package/dist/api/routes/mail/_pa_wip_mail/mail/tags-folders.js.map +1 -0
- package/dist/api/routes/mail/_pa_wip_mail/mail/validators.d.ts +23 -0
- package/dist/api/routes/mail/_pa_wip_mail/mail/validators.d.ts.map +1 -0
- package/dist/api/routes/mail/_pa_wip_mail/mail/validators.js +131 -0
- package/dist/api/routes/mail/_pa_wip_mail/mail/validators.js.map +1 -0
- package/dist/api/routes/mail/_pa_wip_mail/provider-resolver.d.ts +64 -0
- package/dist/api/routes/mail/_pa_wip_mail/provider-resolver.d.ts.map +1 -0
- package/dist/api/routes/mail/_pa_wip_mail/provider-resolver.js +286 -0
- package/dist/api/routes/mail/_pa_wip_mail/provider-resolver.js.map +1 -0
- package/dist/api/routes/mail/_pa_wip_mail/search-health.d.ts +4 -0
- package/dist/api/routes/mail/_pa_wip_mail/search-health.d.ts.map +1 -0
- package/dist/api/routes/mail/_pa_wip_mail/search-health.js +131 -0
- package/dist/api/routes/mail/_pa_wip_mail/search-health.js.map +1 -0
- package/dist/api/routes/mail/_pa_wip_mail/validators.d.ts +23 -0
- package/dist/api/routes/mail/_pa_wip_mail/validators.d.ts.map +1 -0
- package/dist/api/routes/mail/_pa_wip_mail/validators.js +131 -0
- package/dist/api/routes/mail/_pa_wip_mail/validators.js.map +1 -0
- package/dist/api/routes/mail/accounts.d.ts +4 -0
- package/dist/api/routes/mail/accounts.d.ts.map +1 -0
- package/dist/api/routes/mail/accounts.js +289 -0
- package/dist/api/routes/mail/accounts.js.map +1 -0
- package/dist/api/routes/mail/app-password.d.ts +4 -0
- package/dist/api/routes/mail/app-password.d.ts.map +1 -0
- package/dist/api/routes/mail/app-password.js +192 -0
- package/dist/api/routes/mail/app-password.js.map +1 -0
- package/dist/api/routes/mail/body-helpers.d.ts +56 -0
- package/dist/api/routes/mail/body-helpers.d.ts.map +1 -0
- package/dist/api/routes/mail/body-helpers.js +91 -0
- package/dist/api/routes/mail/body-helpers.js.map +1 -0
- package/dist/api/routes/mail/dependencies.d.ts +36 -0
- package/dist/api/routes/mail/dependencies.d.ts.map +1 -0
- package/dist/api/routes/mail/dependencies.js +2 -0
- package/dist/api/routes/mail/dependencies.js.map +1 -0
- package/dist/api/routes/mail/drafts.d.ts +5 -0
- package/dist/api/routes/mail/drafts.d.ts.map +1 -0
- package/dist/api/routes/mail/drafts.js +139 -0
- package/dist/api/routes/mail/drafts.js.map +1 -0
- package/dist/api/routes/mail/gating.d.ts +45 -0
- package/dist/api/routes/mail/gating.d.ts.map +1 -0
- package/dist/api/routes/mail/gating.js +99 -0
- package/dist/api/routes/mail/gating.js.map +1 -0
- package/dist/api/routes/mail/index.d.ts +18 -0
- package/dist/api/routes/mail/index.d.ts.map +1 -0
- package/dist/api/routes/mail/index.js +40 -0
- package/dist/api/routes/mail/index.js.map +1 -0
- package/dist/api/routes/mail/messages.d.ts +15 -0
- package/dist/api/routes/mail/messages.d.ts.map +1 -0
- package/dist/api/routes/mail/messages.js +239 -0
- package/dist/api/routes/mail/messages.js.map +1 -0
- package/dist/api/routes/mail/outlook-config.d.ts +4 -0
- package/dist/api/routes/mail/outlook-config.d.ts.map +1 -0
- package/dist/api/routes/mail/outlook-config.js +73 -0
- package/dist/api/routes/mail/outlook-config.js.map +1 -0
- package/dist/api/routes/mail/provider-resolver.d.ts +64 -0
- package/dist/api/routes/mail/provider-resolver.d.ts.map +1 -0
- package/dist/api/routes/mail/provider-resolver.js +286 -0
- package/dist/api/routes/mail/provider-resolver.js.map +1 -0
- package/dist/api/routes/mail/providers.d.ts +4 -0
- package/dist/api/routes/mail/providers.d.ts.map +1 -0
- package/dist/api/routes/mail/providers.js +73 -0
- package/dist/api/routes/mail/providers.js.map +1 -0
- package/dist/api/routes/mail/search-health.d.ts +4 -0
- package/dist/api/routes/mail/search-health.d.ts.map +1 -0
- package/dist/api/routes/mail/search-health.js +131 -0
- package/dist/api/routes/mail/search-health.js.map +1 -0
- package/dist/api/routes/mail/tags-folders.d.ts +5 -0
- package/dist/api/routes/mail/tags-folders.d.ts.map +1 -0
- package/dist/api/routes/mail/tags-folders.js +35 -0
- package/dist/api/routes/mail/tags-folders.js.map +1 -0
- package/dist/api/routes/mail/validators.d.ts +23 -0
- package/dist/api/routes/mail/validators.d.ts.map +1 -0
- package/dist/api/routes/mail/validators.js +131 -0
- package/dist/api/routes/mail/validators.js.map +1 -0
- package/dist/api/routes/mail.d.ts.map +1 -1
- package/dist/api/routes/mail.js +259 -67
- package/dist/api/routes/mail.js.map +1 -1
- package/dist/api/routes/managed-tasks.d.ts.map +1 -1
- package/dist/api/routes/managed-tasks.js +142 -54
- package/dist/api/routes/managed-tasks.js.map +1 -1
- package/dist/api/routes/mcp.d.ts.map +1 -1
- package/dist/api/routes/mcp.js +115 -47
- package/dist/api/routes/mcp.js.map +1 -1
- package/dist/api/routes/metrics.d.ts +1 -1
- package/dist/api/routes/metrics.js +2 -2
- package/dist/api/routes/notion.d.ts.map +1 -1
- package/dist/api/routes/notion.js +230 -54
- package/dist/api/routes/notion.js.map +1 -1
- package/dist/api/routes/observations.d.ts.map +1 -1
- package/dist/api/routes/observations.js +40 -169
- package/dist/api/routes/observations.js.map +1 -1
- package/dist/api/routes/obsidian.d.ts.map +1 -1
- package/dist/api/routes/obsidian.js +193 -32
- package/dist/api/routes/obsidian.js.map +1 -1
- package/dist/api/routes/profile-questions.d.ts.map +1 -1
- package/dist/api/routes/profile-questions.js +19 -3
- package/dist/api/routes/profile-questions.js.map +1 -1
- package/dist/api/routes/receipts.d.ts.map +1 -1
- package/dist/api/routes/receipts.js +64 -24
- package/dist/api/routes/receipts.js.map +1 -1
- package/dist/api/routes/recurring-schedules.d.ts.map +1 -1
- package/dist/api/routes/recurring-schedules.js +243 -13
- package/dist/api/routes/recurring-schedules.js.map +1 -1
- package/dist/api/routes/repositories.d.ts.map +1 -1
- package/dist/api/routes/repositories.js +278 -104
- package/dist/api/routes/repositories.js.map +1 -1
- package/dist/api/routes/schedule-model-resolver.d.ts +153 -0
- package/dist/api/routes/schedule-model-resolver.d.ts.map +1 -0
- package/dist/api/routes/schedule-model-resolver.js +282 -0
- package/dist/api/routes/schedule-model-resolver.js.map +1 -0
- package/dist/api/routes/schedule-options.d.ts +25 -0
- package/dist/api/routes/schedule-options.d.ts.map +1 -0
- package/dist/api/routes/schedule-options.js +77 -0
- package/dist/api/routes/schedule-options.js.map +1 -0
- package/dist/api/routes/schedule-validation.d.ts +146 -0
- package/dist/api/routes/schedule-validation.d.ts.map +1 -0
- package/dist/api/routes/schedule-validation.js +153 -0
- package/dist/api/routes/schedule-validation.js.map +1 -0
- package/dist/api/routes/setup.d.ts.map +1 -1
- package/dist/api/routes/setup.js +100 -26
- package/dist/api/routes/setup.js.map +1 -1
- package/dist/api/routes/skills.d.ts.map +1 -1
- package/dist/api/routes/skills.js +81 -30
- package/dist/api/routes/skills.js.map +1 -1
- package/dist/api/routes/sot-bindings.d.ts.map +1 -1
- package/dist/api/routes/sot-bindings.js +46 -11
- package/dist/api/routes/sot-bindings.js.map +1 -1
- package/dist/api/routes/sse.d.ts.map +1 -1
- package/dist/api/routes/sse.js +6 -1
- package/dist/api/routes/sse.js.map +1 -1
- package/dist/api/routes/system.d.ts.map +1 -1
- package/dist/api/routes/system.js +15 -2
- package/dist/api/routes/system.js.map +1 -1
- package/dist/api/routes/travel-bookings.d.ts.map +1 -1
- package/dist/api/routes/travel-bookings.js +26 -9
- package/dist/api/routes/travel-bookings.js.map +1 -1
- package/dist/api/routes/travel-time.d.ts.map +1 -1
- package/dist/api/routes/travel-time.js +50 -10
- package/dist/api/routes/travel-time.js.map +1 -1
- package/dist/api/routes/wiki.d.ts.map +1 -1
- package/dist/api/routes/wiki.js +49 -8
- package/dist/api/routes/wiki.js.map +1 -1
- package/dist/api/server.d.ts +15 -3
- package/dist/api/server.d.ts.map +1 -1
- package/dist/api/server.js +32 -10
- package/dist/api/server.js.map +1 -1
- package/dist/bootstrap/adapters.d.ts.map +1 -1
- package/dist/bootstrap/adapters.js +7 -4
- package/dist/bootstrap/adapters.js.map +1 -1
- package/dist/bootstrap/api.d.ts +205 -0
- package/dist/bootstrap/api.d.ts.map +1 -0
- package/dist/bootstrap/api.js +443 -0
- package/dist/bootstrap/api.js.map +1 -0
- package/dist/bootstrap/db.d.ts +119 -0
- package/dist/bootstrap/db.d.ts.map +1 -0
- package/dist/bootstrap/db.js +294 -0
- package/dist/bootstrap/db.js.map +1 -0
- package/dist/bootstrap/event-pipeline.d.ts +308 -0
- package/dist/bootstrap/event-pipeline.d.ts.map +1 -0
- package/dist/bootstrap/event-pipeline.js +704 -0
- package/dist/bootstrap/event-pipeline.js.map +1 -0
- package/dist/bootstrap/observers.d.ts +148 -0
- package/dist/bootstrap/observers.d.ts.map +1 -0
- package/dist/bootstrap/observers.js +558 -0
- package/dist/bootstrap/observers.js.map +1 -0
- package/dist/bootstrap/schedule-helpers.d.ts +122 -0
- package/dist/bootstrap/schedule-helpers.d.ts.map +1 -1
- package/dist/bootstrap/schedule-helpers.js +202 -4
- package/dist/bootstrap/schedule-helpers.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +20 -3
- package/dist/config.js.map +1 -1
- package/dist/core/abort-utils.d.ts +14 -0
- package/dist/core/abort-utils.d.ts.map +1 -0
- package/dist/core/abort-utils.js +36 -0
- package/dist/core/abort-utils.js.map +1 -0
- package/dist/core/agent-core.d.ts +22 -3
- package/dist/core/agent-core.d.ts.map +1 -1
- package/dist/core/agent-core.js +3 -1
- package/dist/core/agent-core.js.map +1 -1
- package/dist/core/backends/auth-health-monitor.js +2 -3
- package/dist/core/backends/auth-health-monitor.js.map +1 -1
- package/dist/core/backends/backend-router.d.ts +11 -1
- package/dist/core/backends/backend-router.d.ts.map +1 -1
- package/dist/core/backends/backend-router.js +97 -2
- package/dist/core/backends/backend-router.js.map +1 -1
- package/dist/core/backends/claude-code-core.d.ts +51 -0
- package/dist/core/backends/claude-code-core.d.ts.map +1 -1
- package/dist/core/backends/claude-code-core.js +134 -11
- package/dist/core/backends/claude-code-core.js.map +1 -1
- package/dist/core/backends/claude-tool-collection.js +1 -1
- package/dist/core/backends/codex-core.d.ts +13 -1
- package/dist/core/backends/codex-core.d.ts.map +1 -1
- package/dist/core/backends/codex-core.js +436 -22
- package/dist/core/backends/codex-core.js.map +1 -1
- package/dist/core/backends/gemini-cli-core.d.ts +5 -1
- package/dist/core/backends/gemini-cli-core.d.ts.map +1 -1
- package/dist/core/backends/gemini-cli-core.js +298 -15
- package/dist/core/backends/gemini-cli-core.js.map +1 -1
- package/dist/core/backends/install-methods.d.ts.map +1 -1
- package/dist/core/backends/install-methods.js +22 -0
- package/dist/core/backends/install-methods.js.map +1 -1
- package/dist/core/backends/model-registry.d.ts +9 -4
- package/dist/core/backends/model-registry.d.ts.map +1 -1
- package/dist/core/backends/model-registry.js +153 -23
- package/dist/core/backends/model-registry.js.map +1 -1
- package/dist/core/backends/native-skill-discovery-probe.d.ts +80 -0
- package/dist/core/backends/native-skill-discovery-probe.d.ts.map +1 -0
- package/dist/core/backends/native-skill-discovery-probe.js +175 -0
- package/dist/core/backends/native-skill-discovery-probe.js.map +1 -0
- package/dist/core/backends/opencode-basic-auth-fetch.d.ts +27 -0
- package/dist/core/backends/opencode-basic-auth-fetch.d.ts.map +1 -0
- package/dist/core/backends/opencode-basic-auth-fetch.js +40 -0
- package/dist/core/backends/opencode-basic-auth-fetch.js.map +1 -0
- package/dist/core/backends/opencode-config-builder.d.ts +86 -0
- package/dist/core/backends/opencode-config-builder.d.ts.map +1 -0
- package/dist/core/backends/opencode-config-builder.js +172 -0
- package/dist/core/backends/opencode-config-builder.js.map +1 -0
- package/dist/core/backends/opencode-core.d.ts +316 -0
- package/dist/core/backends/opencode-core.d.ts.map +1 -0
- package/dist/core/backends/opencode-core.js +1502 -0
- package/dist/core/backends/opencode-core.js.map +1 -0
- package/dist/core/backends/opencode-event-mapper.d.ts +133 -0
- package/dist/core/backends/opencode-event-mapper.d.ts.map +1 -0
- package/dist/core/backends/opencode-event-mapper.js +198 -0
- package/dist/core/backends/opencode-event-mapper.js.map +1 -0
- package/dist/core/backends/opencode-mcp.d.ts +82 -0
- package/dist/core/backends/opencode-mcp.d.ts.map +1 -0
- package/dist/core/backends/opencode-mcp.js +165 -0
- package/dist/core/backends/opencode-mcp.js.map +1 -0
- package/dist/core/backends/opencode-server-manager.d.ts +114 -0
- package/dist/core/backends/opencode-server-manager.d.ts.map +1 -0
- package/dist/core/backends/opencode-server-manager.js +222 -0
- package/dist/core/backends/opencode-server-manager.js.map +1 -0
- package/dist/core/backends/opencode-types.d.ts +46 -0
- package/dist/core/backends/opencode-types.d.ts.map +1 -0
- package/dist/core/backends/opencode-types.js +14 -0
- package/dist/core/backends/opencode-types.js.map +1 -0
- package/dist/core/backends/plan-presets.d.ts +18 -5
- package/dist/core/backends/plan-presets.d.ts.map +1 -1
- package/dist/core/backends/plan-presets.js +144 -23
- package/dist/core/backends/plan-presets.js.map +1 -1
- package/dist/core/backends/process-config-cascade.d.ts +35 -0
- package/dist/core/backends/process-config-cascade.d.ts.map +1 -1
- package/dist/core/backends/process-config-cascade.js +35 -1
- package/dist/core/backends/process-config-cascade.js.map +1 -1
- package/dist/core/backends/prompt-utils.d.ts.map +1 -1
- package/dist/core/backends/prompt-utils.js +0 -2
- package/dist/core/backends/prompt-utils.js.map +1 -1
- package/dist/core/backends/quota-reset-hints.d.ts +44 -0
- package/dist/core/backends/quota-reset-hints.d.ts.map +1 -0
- package/dist/core/backends/quota-reset-hints.js +117 -0
- package/dist/core/backends/quota-reset-hints.js.map +1 -0
- package/dist/core/bang-commands/commands-close.d.ts +24 -0
- package/dist/core/bang-commands/commands-close.d.ts.map +1 -0
- package/dist/core/bang-commands/commands-close.js +24 -0
- package/dist/core/bang-commands/commands-close.js.map +1 -0
- package/dist/core/bang-commands/commands-cost.d.ts.map +1 -1
- package/dist/core/bang-commands/commands-cost.js +13 -0
- package/dist/core/bang-commands/commands-cost.js.map +1 -1
- package/dist/core/bang-commands/commands-help.d.ts.map +1 -1
- package/dist/core/bang-commands/commands-help.js +44 -5
- package/dist/core/bang-commands/commands-help.js.map +1 -1
- package/dist/core/bang-commands/commands-report.d.ts.map +1 -1
- package/dist/core/bang-commands/commands-report.js +1 -0
- package/dist/core/bang-commands/commands-report.js.map +1 -1
- package/dist/core/bang-commands/commands-stop-start.d.ts.map +1 -1
- package/dist/core/bang-commands/commands-stop-start.js +2 -0
- package/dist/core/bang-commands/commands-stop-start.js.map +1 -1
- package/dist/core/bang-commands/commands-wiki.d.ts.map +1 -1
- package/dist/core/bang-commands/commands-wiki.js +12 -1
- package/dist/core/bang-commands/commands-wiki.js.map +1 -1
- package/dist/core/bang-commands/format-utils.d.ts +13 -1
- package/dist/core/bang-commands/format-utils.d.ts.map +1 -1
- package/dist/core/bang-commands/format-utils.js +21 -2
- package/dist/core/bang-commands/format-utils.js.map +1 -1
- package/dist/core/bang-commands/index.d.ts +1 -0
- package/dist/core/bang-commands/index.d.ts.map +1 -1
- package/dist/core/bang-commands/index.js +3 -0
- package/dist/core/bang-commands/index.js.map +1 -1
- package/dist/core/bang-commands/registry.d.ts +50 -0
- package/dist/core/bang-commands/registry.d.ts.map +1 -1
- package/dist/core/bang-commands/registry.js +164 -19
- package/dist/core/bang-commands/registry.js.map +1 -1
- package/dist/core/channel-timeline.d.ts +18 -1
- package/dist/core/channel-timeline.d.ts.map +1 -1
- package/dist/core/channel-timeline.js +44 -0
- package/dist/core/channel-timeline.js.map +1 -1
- package/dist/core/context/activity-view-runner.js +9 -1
- package/dist/core/context/activity-view-runner.js.map +1 -1
- package/dist/core/context/default-schedules-runner.js +44 -4
- package/dist/core/context/default-schedules-runner.js.map +1 -1
- package/dist/core/context/domain-index-runner.js +9 -1
- package/dist/core/context/domain-index-runner.js.map +1 -1
- package/dist/core/context/entity-source-rename.d.ts.map +1 -1
- package/dist/core/context/entity-source-rename.js +9 -1
- package/dist/core/context/entity-source-rename.js.map +1 -1
- package/dist/core/context/policy-index-runner.js +9 -1
- package/dist/core/context/policy-index-runner.js.map +1 -1
- package/dist/core/context/reconciler-runner.js +9 -1
- package/dist/core/context/reconciler-runner.js.map +1 -1
- package/dist/core/context-builder.d.ts +97 -2
- package/dist/core/context-builder.d.ts.map +1 -1
- package/dist/core/context-builder.js +303 -30
- package/dist/core/context-builder.js.map +1 -1
- package/dist/core/context-frontmatter.d.ts +6 -0
- package/dist/core/context-frontmatter.d.ts.map +1 -1
- package/dist/core/context-frontmatter.js +120 -8
- package/dist/core/context-frontmatter.js.map +1 -1
- package/dist/core/context-health.js +21 -9
- package/dist/core/context-health.js.map +1 -1
- package/dist/core/context-validation/_pa_wip_context_validation/index.d.ts +19 -0
- package/dist/core/context-validation/_pa_wip_context_validation/index.d.ts.map +1 -0
- package/dist/core/context-validation/_pa_wip_context_validation/index.js +19 -0
- package/dist/core/context-validation/_pa_wip_context_validation/index.js.map +1 -0
- package/dist/core/context-validation/_pa_wip_context_validation/prepare-write.d.ts +94 -0
- package/dist/core/context-validation/_pa_wip_context_validation/prepare-write.d.ts.map +1 -0
- package/dist/core/context-validation/_pa_wip_context_validation/prepare-write.js +130 -0
- package/dist/core/context-validation/_pa_wip_context_validation/prepare-write.js.map +1 -0
- package/dist/core/context-validation/_pa_wip_context_validation/routine-rulebook.d.ts +60 -0
- package/dist/core/context-validation/_pa_wip_context_validation/routine-rulebook.d.ts.map +1 -0
- package/dist/core/context-validation/_pa_wip_context_validation/routine-rulebook.js +156 -0
- package/dist/core/context-validation/_pa_wip_context_validation/routine-rulebook.js.map +1 -0
- package/dist/core/context-validation/_pa_wip_context_validation/section.d.ts +41 -0
- package/dist/core/context-validation/_pa_wip_context_validation/section.d.ts.map +1 -0
- package/dist/core/context-validation/_pa_wip_context_validation/section.js +82 -0
- package/dist/core/context-validation/_pa_wip_context_validation/section.js.map +1 -0
- package/dist/core/context-validation/_pa_wip_context_validation/snapshot-debounce.d.ts +52 -0
- package/dist/core/context-validation/_pa_wip_context_validation/snapshot-debounce.d.ts.map +1 -0
- package/dist/core/context-validation/_pa_wip_context_validation/snapshot-debounce.js +110 -0
- package/dist/core/context-validation/_pa_wip_context_validation/snapshot-debounce.js.map +1 -0
- package/dist/core/context-validation/_pa_wip_context_validation/today.d.ts +85 -0
- package/dist/core/context-validation/_pa_wip_context_validation/today.d.ts.map +1 -0
- package/dist/core/context-validation/_pa_wip_context_validation/today.js +162 -0
- package/dist/core/context-validation/_pa_wip_context_validation/today.js.map +1 -0
- package/dist/core/context-validation/index.d.ts +19 -0
- package/dist/core/context-validation/index.d.ts.map +1 -0
- package/dist/core/context-validation/index.js +19 -0
- package/dist/core/context-validation/index.js.map +1 -0
- package/dist/core/context-validation/prepare-write.d.ts +95 -0
- package/dist/core/context-validation/prepare-write.d.ts.map +1 -0
- package/dist/core/context-validation/prepare-write.js +135 -0
- package/dist/core/context-validation/prepare-write.js.map +1 -0
- package/dist/core/context-validation/routine-rulebook.d.ts +60 -0
- package/dist/core/context-validation/routine-rulebook.d.ts.map +1 -0
- package/dist/core/context-validation/routine-rulebook.js +156 -0
- package/dist/core/context-validation/routine-rulebook.js.map +1 -0
- package/dist/core/context-validation/section.d.ts +41 -0
- package/dist/core/context-validation/section.d.ts.map +1 -0
- package/dist/core/context-validation/section.js +82 -0
- package/dist/core/context-validation/section.js.map +1 -0
- package/dist/core/context-validation/snapshot-debounce.d.ts +52 -0
- package/dist/core/context-validation/snapshot-debounce.d.ts.map +1 -0
- package/dist/core/context-validation/snapshot-debounce.js +110 -0
- package/dist/core/context-validation/snapshot-debounce.js.map +1 -0
- package/dist/core/context-validation/today.d.ts +85 -0
- package/dist/core/context-validation/today.d.ts.map +1 -0
- package/dist/core/context-validation/today.js +168 -0
- package/dist/core/context-validation/today.js.map +1 -0
- package/dist/core/daemon-api-cli.d.ts +6 -5
- package/dist/core/daemon-api-cli.d.ts.map +1 -1
- package/dist/core/daemon-api-cli.js +73 -32
- package/dist/core/daemon-api-cli.js.map +1 -1
- package/dist/core/dispatcher-error-handling.d.ts +13 -0
- package/dist/core/dispatcher-error-handling.d.ts.map +1 -1
- package/dist/core/dispatcher-error-handling.js +69 -0
- package/dist/core/dispatcher-error-handling.js.map +1 -1
- package/dist/core/dispatcher-hourly-check.d.ts +73 -1
- package/dist/core/dispatcher-hourly-check.d.ts.map +1 -1
- package/dist/core/dispatcher-hourly-check.js +249 -151
- package/dist/core/dispatcher-hourly-check.js.map +1 -1
- package/dist/core/dispatcher-message-handler.d.ts +11 -1
- package/dist/core/dispatcher-message-handler.d.ts.map +1 -1
- package/dist/core/dispatcher-message-handler.js +165 -47
- package/dist/core/dispatcher-message-handler.js.map +1 -1
- package/dist/core/dispatcher-morning-routine.d.ts +38 -30
- package/dist/core/dispatcher-morning-routine.d.ts.map +1 -1
- package/dist/core/dispatcher-morning-routine.js +162 -104
- package/dist/core/dispatcher-morning-routine.js.map +1 -1
- package/dist/core/dispatcher-prompt.d.ts +4 -1
- package/dist/core/dispatcher-prompt.d.ts.map +1 -1
- package/dist/core/dispatcher-prompt.js +18 -2
- package/dist/core/dispatcher-prompt.js.map +1 -1
- package/dist/core/dispatcher-repository-helpers.d.ts.map +1 -1
- package/dist/core/dispatcher-repository-helpers.js +5 -1
- package/dist/core/dispatcher-repository-helpers.js.map +1 -1
- package/dist/core/dispatcher-result-processor.d.ts.map +1 -1
- package/dist/core/dispatcher-result-processor.js +9 -4
- package/dist/core/dispatcher-result-processor.js.map +1 -1
- package/dist/core/dispatcher-scheduled-tasks.d.ts +1 -1
- package/dist/core/dispatcher-scheduled-tasks.d.ts.map +1 -1
- package/dist/core/dispatcher-scheduled-tasks.js +79 -16
- package/dist/core/dispatcher-scheduled-tasks.js.map +1 -1
- package/dist/core/dispatcher-types.d.ts +84 -12
- package/dist/core/dispatcher-types.d.ts.map +1 -1
- package/dist/core/dispatcher-types.js.map +1 -1
- package/dist/core/dispatcher.d.ts +50 -1
- package/dist/core/dispatcher.d.ts.map +1 -1
- package/dist/core/dispatcher.js +143 -8
- package/dist/core/dispatcher.js.map +1 -1
- package/dist/core/dm-freshness-metrics.d.ts +6 -5
- package/dist/core/dm-freshness-metrics.d.ts.map +1 -1
- package/dist/core/dm-freshness-metrics.js +7 -6
- package/dist/core/dm-freshness-metrics.js.map +1 -1
- package/dist/core/evening-review-verify.d.ts +164 -0
- package/dist/core/evening-review-verify.d.ts.map +1 -0
- package/dist/core/evening-review-verify.js +637 -0
- package/dist/core/evening-review-verify.js.map +1 -0
- package/dist/core/fetch-window-prompt-loader.d.ts +47 -0
- package/dist/core/fetch-window-prompt-loader.d.ts.map +1 -0
- package/dist/core/fetch-window-prompt-loader.js +72 -0
- package/dist/core/fetch-window-prompt-loader.js.map +1 -0
- package/dist/core/management-md.d.ts.map +1 -1
- package/dist/core/management-md.js +23 -9
- package/dist/core/management-md.js.map +1 -1
- package/dist/core/management-registry.d.ts +13 -21
- package/dist/core/management-registry.d.ts.map +1 -1
- package/dist/core/management-registry.js +27 -48
- package/dist/core/management-registry.js.map +1 -1
- package/dist/core/metrics.d.ts +88 -1
- package/dist/core/metrics.d.ts.map +1 -1
- package/dist/core/metrics.js +78 -2
- package/dist/core/metrics.js.map +1 -1
- package/dist/core/morning/agent-journal-appender.d.ts +197 -0
- package/dist/core/morning/agent-journal-appender.d.ts.map +1 -0
- package/dist/core/morning/agent-journal-appender.js +458 -0
- package/dist/core/morning/agent-journal-appender.js.map +1 -0
- package/dist/core/morning/handoff-parser.d.ts +45 -0
- package/dist/core/morning/handoff-parser.d.ts.map +1 -0
- package/dist/core/morning/handoff-parser.js +117 -0
- package/dist/core/morning/handoff-parser.js.map +1 -0
- package/dist/core/morning/journal-skeleton-builder.d.ts +157 -0
- package/dist/core/morning/journal-skeleton-builder.d.ts.map +1 -0
- package/dist/core/morning/journal-skeleton-builder.js +303 -0
- package/dist/core/morning/journal-skeleton-builder.js.map +1 -0
- package/dist/core/morning/orchestrator.d.ts +312 -0
- package/dist/core/morning/orchestrator.d.ts.map +1 -0
- package/dist/core/morning/orchestrator.js +827 -0
- package/dist/core/morning/orchestrator.js.map +1 -0
- package/dist/core/morning/parent-audit-emitter.d.ts +82 -0
- package/dist/core/morning/parent-audit-emitter.d.ts.map +1 -0
- package/dist/core/morning/parent-audit-emitter.js +120 -0
- package/dist/core/morning/parent-audit-emitter.js.map +1 -0
- package/dist/core/morning/roadmap-skeleton-builder.d.ts +159 -0
- package/dist/core/morning/roadmap-skeleton-builder.d.ts.map +1 -0
- package/dist/core/morning/roadmap-skeleton-builder.js +338 -0
- package/dist/core/morning/roadmap-skeleton-builder.js.map +1 -0
- package/dist/core/output-language-policy.js +1 -1
- package/dist/core/output-language-policy.js.map +1 -1
- package/dist/core/policy-files.d.ts +19 -2
- package/dist/core/policy-files.d.ts.map +1 -1
- package/dist/core/policy-files.js +65 -7
- package/dist/core/policy-files.js.map +1 -1
- package/dist/core/pre-pass-freshness.d.ts +28 -0
- package/dist/core/pre-pass-freshness.d.ts.map +1 -0
- package/dist/core/pre-pass-freshness.js +10 -0
- package/dist/core/pre-pass-freshness.js.map +1 -0
- package/dist/core/previous-week-digest.d.ts +130 -0
- package/dist/core/previous-week-digest.d.ts.map +1 -0
- package/dist/core/previous-week-digest.js +257 -0
- package/dist/core/previous-week-digest.js.map +1 -0
- package/dist/core/prompts.js +3 -3
- package/dist/core/prompts.js.map +1 -1
- package/dist/core/quiet-hours-sync.d.ts.map +1 -1
- package/dist/core/quiet-hours-sync.js +7 -0
- package/dist/core/quiet-hours-sync.js.map +1 -1
- package/dist/core/recurrence.d.ts +13 -0
- package/dist/core/recurrence.d.ts.map +1 -1
- package/dist/core/recurrence.js +108 -13
- package/dist/core/recurrence.js.map +1 -1
- package/dist/core/release-assets.d.ts +21 -1
- package/dist/core/release-assets.d.ts.map +1 -1
- package/dist/core/release-assets.js +58 -3
- package/dist/core/release-assets.js.map +1 -1
- package/dist/core/repository-management-docs.d.ts.map +1 -1
- package/dist/core/repository-management-docs.js +14 -4
- package/dist/core/repository-management-docs.js.map +1 -1
- package/dist/core/review-context.d.ts.map +1 -1
- package/dist/core/review-context.js +29 -1
- package/dist/core/review-context.js.map +1 -1
- package/dist/core/roadmap-maintenance.d.ts +213 -0
- package/dist/core/roadmap-maintenance.d.ts.map +1 -0
- package/dist/core/roadmap-maintenance.js +706 -0
- package/dist/core/roadmap-maintenance.js.map +1 -0
- package/dist/core/roadmap-validate.d.ts +5 -0
- package/dist/core/roadmap-validate.d.ts.map +1 -1
- package/dist/core/roadmap-validate.js +6 -69
- package/dist/core/roadmap-validate.js.map +1 -1
- package/dist/core/routine-acquisition-plan.d.ts +43 -7
- package/dist/core/routine-acquisition-plan.d.ts.map +1 -1
- package/dist/core/routine-acquisition-plan.js +99 -8
- package/dist/core/routine-acquisition-plan.js.map +1 -1
- package/dist/core/routine-fetch-window-retry.d.ts +41 -2
- package/dist/core/routine-fetch-window-retry.d.ts.map +1 -1
- package/dist/core/routine-fetch-window-retry.js +91 -8
- package/dist/core/routine-fetch-window-retry.js.map +1 -1
- package/dist/core/routine-fetch-window-runner.d.ts +55 -21
- package/dist/core/routine-fetch-window-runner.d.ts.map +1 -1
- package/dist/core/routine-fetch-window-runner.js +258 -35
- package/dist/core/routine-fetch-window-runner.js.map +1 -1
- package/dist/core/routine-windows.d.ts +17 -13
- package/dist/core/routine-windows.d.ts.map +1 -1
- package/dist/core/routine-windows.js +78 -36
- package/dist/core/routine-windows.js.map +1 -1
- package/dist/core/scheduler.d.ts +121 -37
- package/dist/core/scheduler.d.ts.map +1 -1
- package/dist/core/scheduler.js +359 -80
- package/dist/core/scheduler.js.map +1 -1
- package/dist/core/session-manager.d.ts +25 -6
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +32 -15
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/skeleton.d.ts.map +1 -1
- package/dist/core/skeleton.js +23 -23
- package/dist/core/skeleton.js.map +1 -1
- package/dist/core/skills-compiler.d.ts +275 -25
- package/dist/core/skills-compiler.d.ts.map +1 -1
- package/dist/core/skills-compiler.js +844 -205
- package/dist/core/skills-compiler.js.map +1 -1
- package/dist/core/skills-manifest.d.ts +104 -0
- package/dist/core/skills-manifest.d.ts.map +1 -1
- package/dist/core/skills-manifest.js +350 -39
- package/dist/core/skills-manifest.js.map +1 -1
- package/dist/core/wiki/git-precompile.d.ts +9 -0
- package/dist/core/wiki/git-precompile.d.ts.map +1 -1
- package/dist/core/wiki/git-precompile.js +7 -1
- package/dist/core/wiki/git-precompile.js.map +1 -1
- package/dist/core/workdir.d.ts +30 -1
- package/dist/core/workdir.d.ts.map +1 -1
- package/dist/core/workdir.js +140 -15
- package/dist/core/workdir.js.map +1 -1
- package/dist/db/entities-store.d.ts +5 -5
- package/dist/db/entities-store.js +5 -5
- package/dist/db/hourly-check-signals.d.ts.map +1 -1
- package/dist/db/hourly-check-signals.js +121 -35
- package/dist/db/hourly-check-signals.js.map +1 -1
- package/dist/db/observations.d.ts +1 -1
- package/dist/db/observations.js +1 -1
- package/dist/db/observations.js.map +1 -1
- package/dist/db/recurring-schedules.d.ts +32 -1
- package/dist/db/recurring-schedules.d.ts.map +1 -1
- package/dist/db/recurring-schedules.js +29 -10
- package/dist/db/recurring-schedules.js.map +1 -1
- package/dist/db/repositories-store.d.ts +2 -1
- package/dist/db/repositories-store.d.ts.map +1 -1
- package/dist/db/repositories-store.js +38 -3
- package/dist/db/repositories-store.js.map +1 -1
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/db/schema.js +157 -51
- package/dist/db/schema.js.map +1 -1
- package/dist/db/wiki-store.d.ts.map +1 -1
- package/dist/db/wiki-store.js +3 -0
- package/dist/db/wiki-store.js.map +1 -1
- package/dist/index.js +308 -1473
- package/dist/index.js.map +1 -1
- package/dist/messaging/magic-phrase.d.ts +16 -0
- package/dist/messaging/magic-phrase.d.ts.map +1 -1
- package/dist/messaging/magic-phrase.js +53 -0
- package/dist/messaging/magic-phrase.js.map +1 -1
- package/dist/messaging/owner-channels.d.ts +24 -0
- package/dist/messaging/owner-channels.d.ts.map +1 -1
- package/dist/messaging/owner-channels.js +38 -0
- package/dist/messaging/owner-channels.js.map +1 -1
- package/dist/messaging/setup-welcome-dm.d.ts +30 -0
- package/dist/messaging/setup-welcome-dm.d.ts.map +1 -0
- package/dist/messaging/setup-welcome-dm.js +86 -0
- package/dist/messaging/setup-welcome-dm.js.map +1 -0
- package/dist/observers/calendar-poller.d.ts +2 -0
- package/dist/observers/calendar-poller.d.ts.map +1 -1
- package/dist/observers/calendar-poller.js +76 -54
- package/dist/observers/calendar-poller.js.map +1 -1
- package/dist/observers/delegated-sync-worker.d.ts +62 -0
- package/dist/observers/delegated-sync-worker.d.ts.map +1 -1
- package/dist/observers/delegated-sync-worker.js +128 -1
- package/dist/observers/delegated-sync-worker.js.map +1 -1
- package/dist/observers/git-watcher.d.ts +22 -0
- package/dist/observers/git-watcher.d.ts.map +1 -1
- package/dist/observers/git-watcher.js +31 -7
- package/dist/observers/git-watcher.js.map +1 -1
- package/dist/observers/imminent-event-scheduler.d.ts +2 -0
- package/dist/observers/imminent-event-scheduler.d.ts.map +1 -1
- package/dist/observers/imminent-event-scheduler.js +29 -0
- package/dist/observers/imminent-event-scheduler.js.map +1 -1
- package/dist/observers/notion-poller.d.ts +2 -0
- package/dist/observers/notion-poller.d.ts.map +1 -1
- package/dist/observers/notion-poller.js +44 -6
- package/dist/observers/notion-poller.js.map +1 -1
- package/dist/observers/poll-guard.d.ts +63 -0
- package/dist/observers/poll-guard.d.ts.map +1 -0
- package/dist/observers/poll-guard.js +89 -0
- package/dist/observers/poll-guard.js.map +1 -0
- package/dist/safety/absolute-block-audit.d.ts +17 -0
- package/dist/safety/absolute-block-audit.d.ts.map +1 -1
- package/dist/safety/absolute-block-audit.js +28 -2
- package/dist/safety/absolute-block-audit.js.map +1 -1
- package/dist/safety/agent-write-tracker.d.ts +42 -1
- package/dist/safety/agent-write-tracker.d.ts.map +1 -1
- package/dist/safety/agent-write-tracker.js +81 -1
- package/dist/safety/agent-write-tracker.js.map +1 -1
- package/dist/safety/always-disallowed.d.ts +34 -0
- package/dist/safety/always-disallowed.d.ts.map +1 -1
- package/dist/safety/always-disallowed.js +114 -7
- package/dist/safety/always-disallowed.js.map +1 -1
- package/dist/safety/audit.d.ts +20 -0
- package/dist/safety/audit.d.ts.map +1 -1
- package/dist/safety/audit.js +126 -0
- package/dist/safety/audit.js.map +1 -1
- package/dist/safety/risk-classifier.d.ts +17 -1
- package/dist/safety/risk-classifier.d.ts.map +1 -1
- package/dist/safety/risk-classifier.js +75 -7
- package/dist/safety/risk-classifier.js.map +1 -1
- package/dist/safety/subprocess-block-scanner.d.ts +89 -0
- package/dist/safety/subprocess-block-scanner.d.ts.map +1 -0
- package/dist/safety/subprocess-block-scanner.js +177 -0
- package/dist/safety/subprocess-block-scanner.js.map +1 -0
- package/dist/scheduler/hourly-check-gate.d.ts +23 -9
- package/dist/scheduler/hourly-check-gate.d.ts.map +1 -1
- package/dist/scheduler/hourly-check-gate.js +11 -6
- package/dist/scheduler/hourly-check-gate.js.map +1 -1
- package/dist/secrets/backend-api-key-env.d.ts.map +1 -1
- package/dist/secrets/backend-api-key-env.js +1 -0
- package/dist/secrets/backend-api-key-env.js.map +1 -1
- package/dist/services/delegated-backend-invoker.d.ts.map +1 -1
- package/dist/services/delegated-backend-invoker.js +8 -1
- package/dist/services/delegated-backend-invoker.js.map +1 -1
- package/dist/services/delegated-invoker-audit.d.ts.map +1 -1
- package/dist/services/delegated-invoker-audit.js +5 -1
- package/dist/services/delegated-invoker-audit.js.map +1 -1
- package/dist/services/delegated-proxy-config.d.ts +3 -2
- package/dist/services/delegated-proxy-config.d.ts.map +1 -1
- package/dist/services/delegated-proxy-config.js.map +1 -1
- package/dist/services/integrations/extract-write-item-id.d.ts +5 -2
- package/dist/services/integrations/extract-write-item-id.d.ts.map +1 -1
- package/dist/services/integrations/extract-write-item-id.js.map +1 -1
- package/dist/services/mcp/generators/index.d.ts +1 -0
- package/dist/services/mcp/generators/index.d.ts.map +1 -1
- package/dist/services/mcp/generators/index.js +3 -0
- package/dist/services/mcp/generators/index.js.map +1 -1
- package/dist/services/mcp/sdk-observations-server.d.ts +60 -0
- package/dist/services/mcp/sdk-observations-server.d.ts.map +1 -0
- package/dist/services/mcp/sdk-observations-server.js +161 -0
- package/dist/services/mcp/sdk-observations-server.js.map +1 -0
- package/dist/services/mcp/session-materializer.d.ts.map +1 -1
- package/dist/services/mcp/session-materializer.js +13 -9
- package/dist/services/mcp/session-materializer.js.map +1 -1
- package/dist/services/mcp/types.d.ts +1 -0
- package/dist/services/mcp/types.d.ts.map +1 -1
- package/dist/services/notion.d.ts +19 -1
- package/dist/services/notion.d.ts.map +1 -1
- package/dist/services/notion.js +41 -2
- package/dist/services/notion.js.map +1 -1
- package/dist/services/observations-batch.d.ts +100 -0
- package/dist/services/observations-batch.d.ts.map +1 -0
- package/dist/services/observations-batch.js +258 -0
- package/dist/services/observations-batch.js.map +1 -0
- package/dist/services/voice/transcriber.d.ts +23 -0
- package/dist/services/voice/transcriber.d.ts.map +1 -1
- package/dist/services/voice/transcriber.js +55 -0
- package/dist/services/voice/transcriber.js.map +1 -1
- package/dist/settings/runtime-settings.d.ts +9 -6
- package/dist/settings/runtime-settings.d.ts.map +1 -1
- package/dist/settings/runtime-settings.js +50 -17
- package/dist/settings/runtime-settings.js.map +1 -1
- package/package.json +3 -2
- package/dist/api/delegated-proxy-helper.d.ts +0 -33
- package/dist/api/delegated-proxy-helper.d.ts.map +0 -1
- package/dist/api/delegated-proxy-helper.js +0 -54
- package/dist/api/delegated-proxy-helper.js.map +0 -1
- package/dist/core/roadmap-merge.d.ts +0 -7
- package/dist/core/roadmap-merge.d.ts.map +0 -1
- package/dist/core/roadmap-merge.js +0 -187
- package/dist/core/roadmap-merge.js.map +0 -1
- package/dist/db/test-schemas.d.ts +0 -23
- package/dist/db/test-schemas.d.ts.map +0 -1
- package/dist/db/test-schemas.js +0 -111
- package/dist/db/test-schemas.js.map +0 -1
|
@@ -0,0 +1,1502 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* docs/design/appendices/opencode-backend.md §6.2 — `IAgentCore` implementation backed by
|
|
3
|
+
* the `@opencode-ai/sdk` HTTP server.
|
|
4
|
+
*
|
|
5
|
+
* Surface:
|
|
6
|
+
* - `execute()` end-to-end against the managed server (loopback)
|
|
7
|
+
* - `executeResume()` reuses an existing opencode session id so the
|
|
8
|
+
* dispatcher's multi-turn DM / dashboard-chat / docs-QA path can
|
|
9
|
+
* own opencode as main backend (see
|
|
10
|
+
* `docs/design/appendices/opencode-execute-resume.md`)
|
|
11
|
+
* - Streaming text deltas relayed through the StreamCallbacks
|
|
12
|
+
* - Cost / token telemetry from `client.session.get(...).data`,
|
|
13
|
+
* scoped to a per-turn delta when resuming (pre-turn snapshot
|
|
14
|
+
* subtracted from post-turn cumulative)
|
|
15
|
+
* - `summarize()` V11 round-trip
|
|
16
|
+
* - `runDelegatedTask()` via spawnEphemeral (Path A)
|
|
17
|
+
* - Stub for `runDelegatedTool` (by design — opencode delegation
|
|
18
|
+
* routes through `runDelegatedTask`)
|
|
19
|
+
* - Per-execute wall-clock abort via `AbortController` →
|
|
20
|
+
* `client.session.abort()`
|
|
21
|
+
*/
|
|
22
|
+
import { randomUUID } from "node:crypto";
|
|
23
|
+
import { defaultApiKeyProvider, isAutonomousProcessKey, isMessageEvent, } from "@aitne/shared";
|
|
24
|
+
import { getContextDir } from "../../config.js";
|
|
25
|
+
import { cleanupSessionWorkdir, createSessionWorkdir, } from "../workdir.js";
|
|
26
|
+
import { auditStreamObservation, extractOpencodeToolUseTarget, } from "../../safety/subprocess-block-scanner.js";
|
|
27
|
+
import { BackendDecisiveFailure, DelegatedToolUnsupportedError, LiveProbeUnsupportedError, TaskModeUnsupportedError, classifyAbortReason, } from "../agent-core.js";
|
|
28
|
+
import { matchRunAllowedToolPattern } from "@aitne/shared";
|
|
29
|
+
import { emptyCost, withDurationMs, } from "../../services/delegated-tool-runtime.js";
|
|
30
|
+
import { DEFAULT_OPENCODE_LITE_MODEL, findRegisteredModel, getModelsForBackend, } from "./model-registry.js";
|
|
31
|
+
import { PriceFetcher } from "./price-fetcher.js";
|
|
32
|
+
import { buildExecutionPrompt } from "./prompt-utils.js";
|
|
33
|
+
import { extractAssistantTextFromParts, extractToolUsesFromParts, isMessageAborted, isTerminal, normalize, } from "./opencode-event-mapper.js";
|
|
34
|
+
import { buildOpencodeRuntimeConfig, defensiveInstructionsFromEnv, } from "./opencode-config-builder.js";
|
|
35
|
+
import { renderOpencodeMcp } from "./opencode-mcp.js";
|
|
36
|
+
import { listMcpServers, resolveMcpSecrets } from "../../services/mcp/registry.js";
|
|
37
|
+
import { buildMcpDisallowedTools } from "../../services/mcp/risk.js";
|
|
38
|
+
import { createLogger } from "../../logging.js";
|
|
39
|
+
const logger = createLogger("opencode-core");
|
|
40
|
+
const EMPTY_USAGE = {
|
|
41
|
+
inputTokens: 0,
|
|
42
|
+
outputTokens: 0,
|
|
43
|
+
cacheCreationInputTokens: 0,
|
|
44
|
+
cacheReadInputTokens: 0,
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Provider/model parse for the SDK's `body.model = { providerID, modelID }`
|
|
48
|
+
* shape. opencode encodes the composite as `providerID/modelID` — note
|
|
49
|
+
* the model id itself may contain slashes (e.g. `openai/gpt-oss-20b:free`
|
|
50
|
+
* on OpenRouter), so we only split on the FIRST `/`.
|
|
51
|
+
*/
|
|
52
|
+
export function parseModelComposite(composite) {
|
|
53
|
+
const slash = composite.indexOf("/");
|
|
54
|
+
if (slash <= 0 || slash === composite.length - 1) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
providerID: composite.slice(0, slash),
|
|
59
|
+
modelID: composite.slice(slash + 1),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/** Five-minute TTL for the live-models cache. The live picker hits this
|
|
63
|
+
* on every dashboard load; 5 min matches the `client.config.providers()`
|
|
64
|
+
* poll cadence and keeps the SDK call out of the hot path. */
|
|
65
|
+
const LIVE_MODELS_CACHE_TTL_MS = 5 * 60 * 1000;
|
|
66
|
+
/**
|
|
67
|
+
* Heuristic tier inference for live models. opencode's catalogue has no
|
|
68
|
+
* native tier concept, so we mirror the daemon's registry thresholds
|
|
69
|
+
* (in/out per 1kTok):
|
|
70
|
+
* - haiku-4.5: $0.001 / $0.005 → lite
|
|
71
|
+
* - gpt-5.4: $0.0025 / $0.015 → medium
|
|
72
|
+
* - sonnet-4.6: $0.003 / $0.015 → medium
|
|
73
|
+
* - gpt-5.5: $0.005 / $0.03 → high (Opus-class, see registry comment)
|
|
74
|
+
* - opus-4.7: $0.015 / $0.075 → high
|
|
75
|
+
*
|
|
76
|
+
* A pure input-rate threshold mis-buckets gpt-5.5 (in=$0.005 sits at the
|
|
77
|
+
* medium boundary but its $30/MTok output is Opus-class). We therefore
|
|
78
|
+
* check input AND output — promote to the higher tier if either rate
|
|
79
|
+
* crosses its threshold.
|
|
80
|
+
*
|
|
81
|
+
* `null` input means cost is unknown (SDK omitted `cost.input`). Treat
|
|
82
|
+
* unknown ≠ free: default to "medium" so an undocumented model is not
|
|
83
|
+
* silently routed to lite-tier surfaces. Free → lite is preserved.
|
|
84
|
+
*/
|
|
85
|
+
function inferModelTier(usdPer1kIn, usdPer1kOut, isFree) {
|
|
86
|
+
if (isFree)
|
|
87
|
+
return "lite";
|
|
88
|
+
if (usdPer1kIn === null && usdPer1kOut === null)
|
|
89
|
+
return "medium";
|
|
90
|
+
const inRate = usdPer1kIn ?? 0;
|
|
91
|
+
const outRate = usdPer1kOut ?? 0;
|
|
92
|
+
// High: Opus-class. gpt-5.5 (in $0.005, out $0.03) qualifies on out alone.
|
|
93
|
+
if (inRate >= 0.005 || outRate >= 0.03)
|
|
94
|
+
return "high";
|
|
95
|
+
// Medium: Sonnet-class. Sonnet (in $0.003, out $0.015) qualifies on either.
|
|
96
|
+
if (inRate >= 0.002 || outRate >= 0.01)
|
|
97
|
+
return "medium";
|
|
98
|
+
return "lite";
|
|
99
|
+
}
|
|
100
|
+
export class OpencodeCore {
|
|
101
|
+
config;
|
|
102
|
+
writeTracker;
|
|
103
|
+
serverManager;
|
|
104
|
+
priceFetcher;
|
|
105
|
+
backendId = "opencode";
|
|
106
|
+
liveModelsCache = null;
|
|
107
|
+
/** Coalesce parallel `listLiveModels()` calls onto a single SDK round-trip.
|
|
108
|
+
* Mirrors the inflight pattern in `ManagedOpencodeServerManager.spawn()`
|
|
109
|
+
* so a dashboard burst (picker open + RQ background refresh) doesn't
|
|
110
|
+
* multiply `client.config.providers()` calls. */
|
|
111
|
+
liveModelsInflight = null;
|
|
112
|
+
readToken;
|
|
113
|
+
readTokenManager;
|
|
114
|
+
mcpContext;
|
|
115
|
+
constructor(config, writeTracker, serverManager, priceFetcher = new PriceFetcher(config.dataDir)) {
|
|
116
|
+
this.config = config;
|
|
117
|
+
this.writeTracker = writeTracker;
|
|
118
|
+
this.serverManager = serverManager;
|
|
119
|
+
this.priceFetcher = priceFetcher;
|
|
120
|
+
}
|
|
121
|
+
setReadToken(token) {
|
|
122
|
+
this.readToken = token;
|
|
123
|
+
}
|
|
124
|
+
setReadTokenManager(manager) {
|
|
125
|
+
this.readTokenManager = manager;
|
|
126
|
+
}
|
|
127
|
+
setMcpContext(context) {
|
|
128
|
+
this.mcpContext = context;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Materialises a session workdir, ensures the server is running with
|
|
132
|
+
* the desired runtime config, opens a fresh session via the SDK, then
|
|
133
|
+
* runs prompt + event-stream consumption in parallel. Per-execute
|
|
134
|
+
* timeout cancels the inflight prompt via `client.session.abort()`.
|
|
135
|
+
*/
|
|
136
|
+
async execute(params, streamCallbacks) {
|
|
137
|
+
return this.runTurn(params, streamCallbacks);
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Resume a prior turn against an existing opencode session id. The
|
|
141
|
+
* dispatcher reaches this when `conversation_sessions.backend_session_id`
|
|
142
|
+
* was previously written by `execute()`. opencode persists sessions on
|
|
143
|
+
* disk, so the same id stays addressable across server restarts; we
|
|
144
|
+
* skip `session.create` and re-enter via `session.prompt({ path: { id } })`.
|
|
145
|
+
*
|
|
146
|
+
* Design: `docs/design/appendices/opencode-execute-resume.md`.
|
|
147
|
+
*/
|
|
148
|
+
async executeResume(params, streamCallbacks) {
|
|
149
|
+
if (!params.sessionDir) {
|
|
150
|
+
throw new Error("sessionDir is required for executeResume — workdir holds MCP / skill files the server reads");
|
|
151
|
+
}
|
|
152
|
+
return this.runTurn({
|
|
153
|
+
prompt: params.message,
|
|
154
|
+
context: "",
|
|
155
|
+
// Synthetic event for log telemetry only — resume never re-renders
|
|
156
|
+
// a task-flow prompt (the bareMessage branch sends params.message
|
|
157
|
+
// verbatim through `session.prompt`).
|
|
158
|
+
event: {
|
|
159
|
+
type: "message.received",
|
|
160
|
+
source: "platform",
|
|
161
|
+
priority: 2,
|
|
162
|
+
timestamp: new Date(),
|
|
163
|
+
data: {},
|
|
164
|
+
correlationId: params.eventCorrelationId ?? "resume",
|
|
165
|
+
},
|
|
166
|
+
modelId: params.modelId,
|
|
167
|
+
maxTurns: params.maxTurns ?? 50,
|
|
168
|
+
maxBudgetUsd: params.maxBudgetUsd ?? 1.0,
|
|
169
|
+
sessionDir: params.sessionDir,
|
|
170
|
+
...(params.sessionDbId !== undefined
|
|
171
|
+
? { sessionDbId: params.sessionDbId }
|
|
172
|
+
: {}),
|
|
173
|
+
...(params.turnToken ? { turnToken: params.turnToken } : {}),
|
|
174
|
+
...(params.stagedAttachments && params.stagedAttachments.length > 0
|
|
175
|
+
? { stagedAttachments: params.stagedAttachments }
|
|
176
|
+
: {}),
|
|
177
|
+
webSearchEnabled: params.webSearchEnabled ?? false,
|
|
178
|
+
resumeSessionId: params.sessionId,
|
|
179
|
+
}, streamCallbacks);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* docs/design/appendices/opencode-backend.md §6.2 / §10 D5 / V11 — opencode-side
|
|
183
|
+
* summarisation flow.
|
|
184
|
+
*
|
|
185
|
+
* 1. Open a transient session so the server has a conversation it
|
|
186
|
+
* can summarise (opencode requires non-empty message history
|
|
187
|
+
* before `session.summarize` will produce a body).
|
|
188
|
+
* 2. Prompt with the conversation text under the lite-tier model
|
|
189
|
+
* (`DEFAULT_OPENCODE_LITE_MODEL`) — small_model isn't honoured
|
|
190
|
+
* here because Aitne disables `task` (V8) so opencode's own
|
|
191
|
+
* subagent path never picks small_model up. Cheap & quick on the
|
|
192
|
+
* same provider as the active turn.
|
|
193
|
+
* 3. Call `client.session.summarize({ path: { id }, body: { providerID, modelID } })`
|
|
194
|
+
* — body is FLAT, NOT nested in a `model` wrapper (V11 contract).
|
|
195
|
+
* Returns `{ data: true }` on HTTP 200; the actual summary lands
|
|
196
|
+
* as a new assistant message with `info.summary === true`.
|
|
197
|
+
* 4. Read `client.session.messages({ path: { id } })` and pick the
|
|
198
|
+
* LAST assistant message with `info.summary === true`. Join its
|
|
199
|
+
* text parts as the markdown summary. (Do NOT read
|
|
200
|
+
* `session.get(...).data.summary` — V11 fixture confirms that
|
|
201
|
+
* field is the diff stat block `{ additions, deletions, files }`,
|
|
202
|
+
* not text.)
|
|
203
|
+
* 5. Best-effort `session.delete()` in a `finally` so the transient
|
|
204
|
+
* session row doesn't accumulate on disk.
|
|
205
|
+
*
|
|
206
|
+
* Falls back to a truncated slice on any failure so a caller flowing
|
|
207
|
+
* through a fallback chain still gets *something* — matching the
|
|
208
|
+
* Codex/Gemini summarize contract (return string, never throw).
|
|
209
|
+
*/
|
|
210
|
+
async summarize(text) {
|
|
211
|
+
const truncated = text.length > 4096 ? text.slice(0, 4096) : text;
|
|
212
|
+
if (!text.trim()) {
|
|
213
|
+
return text;
|
|
214
|
+
}
|
|
215
|
+
let client;
|
|
216
|
+
try {
|
|
217
|
+
client = await this.serverManager.client();
|
|
218
|
+
}
|
|
219
|
+
catch (err) {
|
|
220
|
+
logger.warn({ err }, "opencode summarize: server unavailable; returning truncated text");
|
|
221
|
+
return truncated;
|
|
222
|
+
}
|
|
223
|
+
const lite = DEFAULT_OPENCODE_LITE_MODEL;
|
|
224
|
+
const liteParts = parseModelComposite(lite);
|
|
225
|
+
if (!liteParts) {
|
|
226
|
+
// Defensive — DEFAULT_OPENCODE_LITE_MODEL ships in `provider/model`
|
|
227
|
+
// form; a future regression that breaks the format should not
|
|
228
|
+
// wedge summarisation, just degrade to the truncation fallback.
|
|
229
|
+
logger.warn({ lite }, "opencode summarize: lite-model id is malformed; falling back to truncation");
|
|
230
|
+
return truncated;
|
|
231
|
+
}
|
|
232
|
+
let sessionId = null;
|
|
233
|
+
try {
|
|
234
|
+
const created = await client.session.create({
|
|
235
|
+
body: { title: `aitne:summarize:${randomUUID().slice(0, 8)}` },
|
|
236
|
+
});
|
|
237
|
+
sessionId = created.data?.id ?? null;
|
|
238
|
+
if (!sessionId) {
|
|
239
|
+
return truncated;
|
|
240
|
+
}
|
|
241
|
+
// Prime the session with the text we want summarised. `noReply`
|
|
242
|
+
// would be ideal but opencode 1.14.50 still requires a model turn
|
|
243
|
+
// before `session.summarize` produces output, so let the model
|
|
244
|
+
// produce a short ack — its body is discarded, only the
|
|
245
|
+
// subsequent summary message is read.
|
|
246
|
+
await client.session.prompt({
|
|
247
|
+
path: { id: sessionId },
|
|
248
|
+
body: {
|
|
249
|
+
model: liteParts,
|
|
250
|
+
parts: [
|
|
251
|
+
{
|
|
252
|
+
type: "text",
|
|
253
|
+
text: `Acknowledge with "ok" and stop. Conversation to summarise next:\n\n${text}`,
|
|
254
|
+
},
|
|
255
|
+
],
|
|
256
|
+
},
|
|
257
|
+
});
|
|
258
|
+
// V11 contract — flat body, NOT nested under `model`.
|
|
259
|
+
await client.session.summarize({
|
|
260
|
+
path: { id: sessionId },
|
|
261
|
+
body: {
|
|
262
|
+
providerID: liteParts.providerID,
|
|
263
|
+
modelID: liteParts.modelID,
|
|
264
|
+
},
|
|
265
|
+
});
|
|
266
|
+
const messagesResp = await client.session.messages({
|
|
267
|
+
path: { id: sessionId },
|
|
268
|
+
});
|
|
269
|
+
const messages = messagesResp.data ?? [];
|
|
270
|
+
const summaryMsg = [...messages]
|
|
271
|
+
.reverse()
|
|
272
|
+
.find((m) => m.info?.role === "assistant" && m.info?.summary === true);
|
|
273
|
+
if (!summaryMsg) {
|
|
274
|
+
logger.debug({ sessionId }, "opencode summarize: no assistant summary message found");
|
|
275
|
+
return truncated;
|
|
276
|
+
}
|
|
277
|
+
const summaryText = extractAssistantTextFromParts(summaryMsg.parts).trim();
|
|
278
|
+
return summaryText.length > 0 ? summaryText : truncated;
|
|
279
|
+
}
|
|
280
|
+
catch (err) {
|
|
281
|
+
logger.warn({ err, sessionId }, "opencode summarize: SDK call failed; returning truncated text");
|
|
282
|
+
return truncated;
|
|
283
|
+
}
|
|
284
|
+
finally {
|
|
285
|
+
if (sessionId) {
|
|
286
|
+
try {
|
|
287
|
+
await client.session.delete({ path: { id: sessionId } });
|
|
288
|
+
}
|
|
289
|
+
catch (err) {
|
|
290
|
+
logger.debug({ err, sessionId }, "opencode summarize: transient session.delete failed");
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Lightweight auth presence check. Mirrors Codex's pattern: prefer a
|
|
297
|
+
* keychain-mirrored env var (`OPENCODE_SERVER_PASSWORD` for remote);
|
|
298
|
+
* for the managed loopback server, the daemon owns the credentials so
|
|
299
|
+
* "configured" is sufficient. The deeper "is the provider key
|
|
300
|
+
* accepted?" check is exercised by `checkAuthDetailed`.
|
|
301
|
+
*/
|
|
302
|
+
async checkAuth() {
|
|
303
|
+
try {
|
|
304
|
+
const client = await this.serverManager.client();
|
|
305
|
+
const providersResult = await client.config.providers();
|
|
306
|
+
if (!providersResult.data) {
|
|
307
|
+
return { ok: false, reason: "opencode server did not return providers" };
|
|
308
|
+
}
|
|
309
|
+
const providerCount = providersResult.data.providers.length;
|
|
310
|
+
if (providerCount === 0) {
|
|
311
|
+
return {
|
|
312
|
+
ok: false,
|
|
313
|
+
reason: "No providers configured on the opencode server. Run `opencode auth login` to add one.",
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
// Managed loopback ⇒ the daemon owns the server; an external API key
|
|
317
|
+
// is configured on the opencode side, so we surface that as `api_key`.
|
|
318
|
+
return { ok: true, method: "api_key" };
|
|
319
|
+
}
|
|
320
|
+
catch (err) {
|
|
321
|
+
return {
|
|
322
|
+
ok: false,
|
|
323
|
+
reason: err instanceof Error ? err.message : "opencode auth probe failed",
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
async checkAuthDetailed() {
|
|
328
|
+
const inner = await this.checkAuth();
|
|
329
|
+
if (inner.ok) {
|
|
330
|
+
return { ok: true, status: "ok", method: inner.method };
|
|
331
|
+
}
|
|
332
|
+
return {
|
|
333
|
+
ok: false,
|
|
334
|
+
status: "expired",
|
|
335
|
+
method: defaultApiKeyProvider(this.backendId) === "opencode-server"
|
|
336
|
+
? "api_key"
|
|
337
|
+
: "cli_login",
|
|
338
|
+
detail: inner.reason,
|
|
339
|
+
recoveryCommand: "opencode auth login",
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Phase 5 §4.11 — opencode does NOT expose namespaced `mcp__*` tool
|
|
344
|
+
* names through `client.tool.ids()` in 1.14.50 (V7 fixture confirms).
|
|
345
|
+
* Live probe is therefore unsupported on this backend in v1; the
|
|
346
|
+
* route surfaces this as a 501 with a targeted message. Phase 5+ may
|
|
347
|
+
* unlock once `mcp.status()`/`tool.list()` surfaces MCP tools.
|
|
348
|
+
*/
|
|
349
|
+
async probeTools() {
|
|
350
|
+
throw new LiveProbeUnsupportedError(this.backendId, "opencode 1.14.50 does not surface MCP tool ids via tool.ids()");
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Static-registry first; Phase 6 (§6.6.2 wizard probe) extends this
|
|
354
|
+
* via `serverManager.listModels()` once the live-server enumeration
|
|
355
|
+
* lands. Today the registry seed is sufficient for `BackendRouter`
|
|
356
|
+
* bindings.
|
|
357
|
+
*/
|
|
358
|
+
listModels() {
|
|
359
|
+
return getModelsForBackend(this.backendId);
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Live model enumeration over `client.config.providers()`. Returns the
|
|
363
|
+
* full catalogue an operator can pick from — every provider the live
|
|
364
|
+
* opencode server has credentials for, with capability + cost metadata.
|
|
365
|
+
* Cached in-memory for 5 minutes so the picker doesn't re-bounce the
|
|
366
|
+
* server on every keystroke.
|
|
367
|
+
*
|
|
368
|
+
* Security: the raw `client.config.providers()` response includes
|
|
369
|
+
* `provider.key` (plaintext API key). This method NEVER returns that
|
|
370
|
+
* field — the caller surfaces what's projected here, not the raw SDK
|
|
371
|
+
* response.
|
|
372
|
+
*/
|
|
373
|
+
async listLiveModels(options = {}) {
|
|
374
|
+
const now = Date.now();
|
|
375
|
+
const cached = this.liveModelsCache;
|
|
376
|
+
if (!options.forceRefresh
|
|
377
|
+
&& cached
|
|
378
|
+
&& now - cached.fetchedAtMs < LIVE_MODELS_CACHE_TTL_MS) {
|
|
379
|
+
return { ...cached.payload, cached: true };
|
|
380
|
+
}
|
|
381
|
+
// Coalesce: if a fetch is already in flight, share its result. This
|
|
382
|
+
// applies to refresh=true as well — getting the freshest in-flight
|
|
383
|
+
// result is strictly better than spawning a redundant SDK call.
|
|
384
|
+
if (this.liveModelsInflight) {
|
|
385
|
+
return this.liveModelsInflight;
|
|
386
|
+
}
|
|
387
|
+
this.liveModelsInflight = this.fetchLiveModels(now);
|
|
388
|
+
try {
|
|
389
|
+
return await this.liveModelsInflight;
|
|
390
|
+
}
|
|
391
|
+
finally {
|
|
392
|
+
this.liveModelsInflight = null;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
async fetchLiveModels(requestedAtMs) {
|
|
396
|
+
const client = await this.serverManager.client();
|
|
397
|
+
const res = await client.config.providers();
|
|
398
|
+
const providers = res.data?.providers ?? [];
|
|
399
|
+
const projected = providers.map((p) => ({
|
|
400
|
+
id: p.id,
|
|
401
|
+
name: p.name,
|
|
402
|
+
source: p.source ?? "unknown",
|
|
403
|
+
models: Object.entries(p.models ?? {})
|
|
404
|
+
.filter(([, m]) => m.status !== "deprecated")
|
|
405
|
+
.map(([mid, mRaw]) => {
|
|
406
|
+
const m = mRaw;
|
|
407
|
+
const usdPer1kIn = typeof m.cost?.input === "number" ? m.cost.input / 1000 : null;
|
|
408
|
+
const usdPer1kOut = typeof m.cost?.output === "number" ? m.cost.output / 1000 : null;
|
|
409
|
+
const isFree = (m.cost?.input ?? 0) === 0 && (m.cost?.output ?? 0) === 0;
|
|
410
|
+
return {
|
|
411
|
+
modelId: `${p.id}/${mid}`,
|
|
412
|
+
shortId: mid,
|
|
413
|
+
name: m.name ?? mid,
|
|
414
|
+
family: m.family ?? "",
|
|
415
|
+
tier: inferModelTier(usdPer1kIn, usdPer1kOut, isFree),
|
|
416
|
+
supportsToolUse: m.capabilities?.toolcall === true,
|
|
417
|
+
supportsAttachment: m.capabilities?.attachment === true,
|
|
418
|
+
supportsReasoning: m.capabilities?.reasoning === true,
|
|
419
|
+
maxInputTokens: m.limit?.context ?? null,
|
|
420
|
+
maxOutputTokens: m.limit?.output ?? null,
|
|
421
|
+
usdPer1kIn,
|
|
422
|
+
usdPer1kOut,
|
|
423
|
+
isFree,
|
|
424
|
+
status: m.status ?? "active",
|
|
425
|
+
};
|
|
426
|
+
})
|
|
427
|
+
.sort((a, b) => a.shortId.localeCompare(b.shortId)),
|
|
428
|
+
}));
|
|
429
|
+
const payload = {
|
|
430
|
+
providers: projected,
|
|
431
|
+
fetchedAt: new Date(requestedAtMs).toISOString(),
|
|
432
|
+
cached: false,
|
|
433
|
+
};
|
|
434
|
+
this.liveModelsCache = { fetchedAtMs: requestedAtMs, payload };
|
|
435
|
+
return payload;
|
|
436
|
+
}
|
|
437
|
+
async runDelegatedTool(_params) {
|
|
438
|
+
throw new DelegatedToolUnsupportedError(this.backendId, "use runDelegatedTask (Phase 4)");
|
|
439
|
+
}
|
|
440
|
+
async runDelegatedTask(params) {
|
|
441
|
+
if (this.serverManager.mode === "remote") {
|
|
442
|
+
// docs/design/appendices/opencode-backend.md §5.9 — Remote mode cannot host
|
|
443
|
+
// tight-permission ephemeral servers and the daemon does not own
|
|
444
|
+
// the agent-file write seat. Surface the canonical sentinel so the
|
|
445
|
+
// delegated-task invoker treats this as 501 / `task_mode_unsupported`
|
|
446
|
+
// and the BackendRouter falls back to a Managed backend.
|
|
447
|
+
throw new TaskModeUnsupportedError(this.backendId);
|
|
448
|
+
}
|
|
449
|
+
return this.runDelegatedTaskAgainstEphemeral(params);
|
|
450
|
+
}
|
|
451
|
+
// ── private ──
|
|
452
|
+
async runTurn(params, streamCallbacks) {
|
|
453
|
+
this.assertPromptWithinMaxBudget(params.prompt, params.maxBudgetUsd, params.modelId);
|
|
454
|
+
const resumeSessionId = params.resumeSessionId;
|
|
455
|
+
const isResume = resumeSessionId !== undefined;
|
|
456
|
+
if (isResume && !params.sessionDir) {
|
|
457
|
+
// executeResume already gates this, but be defensive — runTurn is
|
|
458
|
+
// private and may grow new callers.
|
|
459
|
+
throw new Error("runTurn(resumeSessionId): sessionDir is required when resuming");
|
|
460
|
+
}
|
|
461
|
+
const startMs = Date.now();
|
|
462
|
+
const sessionDir = params.sessionDir ?? createSessionWorkdir(this.config.workspaceDir, params.event.type, `${this.config.dataDir}/skills`, {
|
|
463
|
+
backendId: this.backendId,
|
|
464
|
+
...(params.processKey ? { processKey: params.processKey } : {}),
|
|
465
|
+
character: this.config.character,
|
|
466
|
+
contextDir: getContextDir(this.config),
|
|
467
|
+
// docs/design/appendices/skills-unification.md Phase 4 — feed the conditional
|
|
468
|
+
// manifest predicates. `db` comes through `setMcpContext`; the
|
|
469
|
+
// inbound DM text (when present) lets the *ForDm trigger-phrase
|
|
470
|
+
// fallback drop `gmail-lifestyle` / `managed-tasks` for DMs that
|
|
471
|
+
// have no DB rows AND no trigger phrase.
|
|
472
|
+
...(this.mcpContext?.db ? { db: this.mcpContext.db } : {}),
|
|
473
|
+
...(isMessageEvent(params.event) ? { messageText: params.event.content } : {}),
|
|
474
|
+
});
|
|
475
|
+
const ownsSessionDir = !params.sessionDir;
|
|
476
|
+
// NOTE: opencode SDK runs the server in-process (createOpencode does
|
|
477
|
+
// not spawn a subprocess and exposes no per-tool env hook), so the
|
|
478
|
+
// issued token is not yet injected into bash-tool invocations the way
|
|
479
|
+
// Claude / Codex / Gemini do via `buildDaemonApiCliEnv`. The
|
|
480
|
+
// issue/revoke pair stays correct because event-pipeline.ts wires
|
|
481
|
+
// `setReadTokenManager` on all four cores — when an env-injection
|
|
482
|
+
// path lands here, replace this comment with the wiring rather than
|
|
483
|
+
// re-introducing the silent-undefined gap.
|
|
484
|
+
const issuedReadToken = this.readTokenManager?.issue(sessionDir) ?? this.readToken;
|
|
485
|
+
try {
|
|
486
|
+
const { config: runtimeConfig, warnings: configWarnings } = await this.buildRuntimeConfig(params);
|
|
487
|
+
if (configWarnings.length > 0) {
|
|
488
|
+
// Surface translator + MCP-renderer notices so a same-envelope
|
|
489
|
+
// bounce that drops Edit patterns or rejects MCP server names
|
|
490
|
+
// shows up in operator logs, not just buried in the dashboard
|
|
491
|
+
// tile (which materialises later via /health).
|
|
492
|
+
logger.info({ warnings: configWarnings, eventType: params.event.type }, "opencode runtime config emitted with translator warnings");
|
|
493
|
+
}
|
|
494
|
+
await this.serverManager.ensureConfig(runtimeConfig);
|
|
495
|
+
const client = await this.serverManager.client();
|
|
496
|
+
// Pre-flight: a missing provider key is the most common boot-time
|
|
497
|
+
// failure and surfaces as `session.error` mid-stream. Catch it here
|
|
498
|
+
// so the router fails over to a fallback backend rather than
|
|
499
|
+
// streaming a half-complete response.
|
|
500
|
+
const auth = await this.checkAuth();
|
|
501
|
+
if (!auth.ok) {
|
|
502
|
+
throw new BackendDecisiveFailure(this.backendId, "auth", new Error(auth.reason));
|
|
503
|
+
}
|
|
504
|
+
const modelParts = parseModelComposite(params.modelId);
|
|
505
|
+
if (!modelParts) {
|
|
506
|
+
throw new BackendDecisiveFailure(this.backendId, "model_unavailable", new Error(`opencode model id must be in 'provider/model' form, got '${params.modelId}'`));
|
|
507
|
+
}
|
|
508
|
+
// Session lifecycle — opencode keeps sessions on disk; we delete
|
|
509
|
+
// when we own the workdir (transient sessions). `title` is best-
|
|
510
|
+
// effort metadata only. On resume we re-enter an existing session
|
|
511
|
+
// id; `session.create` is skipped entirely.
|
|
512
|
+
let sessionId;
|
|
513
|
+
if (isResume) {
|
|
514
|
+
sessionId = resumeSessionId;
|
|
515
|
+
}
|
|
516
|
+
else {
|
|
517
|
+
const sessionTitle = `aitne:${params.event.type}:${randomUUID().slice(0, 8)}`;
|
|
518
|
+
const created = await client.session.create({
|
|
519
|
+
body: { title: sessionTitle },
|
|
520
|
+
});
|
|
521
|
+
const createdId = created.data?.id ?? null;
|
|
522
|
+
if (!createdId) {
|
|
523
|
+
throw new BackendDecisiveFailure(this.backendId, "other_non_retryable", new Error("opencode session.create returned no id"));
|
|
524
|
+
}
|
|
525
|
+
sessionId = createdId;
|
|
526
|
+
}
|
|
527
|
+
// Resume cost-delta — snapshot the session's cumulative cost +
|
|
528
|
+
// tokens before sending the new prompt. After the turn completes
|
|
529
|
+
// the post-turn `session.get()` aggregate is subtracted from this
|
|
530
|
+
// baseline so `AgentResult.costUsd` reflects this turn's
|
|
531
|
+
// contribution rather than the whole-session total. For first
|
|
532
|
+
// execute these stay zero and the math collapses.
|
|
533
|
+
let preTurnCost = 0;
|
|
534
|
+
let preTurnUsage = { ...EMPTY_USAGE };
|
|
535
|
+
if (isResume) {
|
|
536
|
+
try {
|
|
537
|
+
const preInfo = await client.session.get({ path: { id: sessionId } });
|
|
538
|
+
const preAgg = preInfo.data;
|
|
539
|
+
if (typeof preAgg?.cost === "number") {
|
|
540
|
+
preTurnCost = preAgg.cost;
|
|
541
|
+
}
|
|
542
|
+
if (preAgg?.tokens) {
|
|
543
|
+
preTurnUsage = {
|
|
544
|
+
inputTokens: preAgg.tokens.input ?? 0,
|
|
545
|
+
outputTokens: preAgg.tokens.output ?? 0,
|
|
546
|
+
cacheCreationInputTokens: preAgg.tokens.cache?.write ?? 0,
|
|
547
|
+
cacheReadInputTokens: preAgg.tokens.cache?.read ?? 0,
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
catch (err) {
|
|
552
|
+
// 404 / stale session — let session.prompt below surface the
|
|
553
|
+
// real failure with a clean classification. Pre-snapshot
|
|
554
|
+
// failure is a best-effort cost-attribution miss only.
|
|
555
|
+
logger.debug({ err, sessionId }, "opencode resume: pre-turn session.get failed; cost may include prior turns");
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
// AbortController owns both the event stream and the prompt call
|
|
559
|
+
// so per-execute timeout collapses them together.
|
|
560
|
+
const abortController = new AbortController();
|
|
561
|
+
const timeoutMs = this.config.executeTimeoutMinutes * 60 * 1000;
|
|
562
|
+
const timeoutId = setTimeout(async () => {
|
|
563
|
+
abortController.abort(new Error("opencode execute wall-clock timeout"));
|
|
564
|
+
try {
|
|
565
|
+
await client.session.abort({ path: { id: sessionId } });
|
|
566
|
+
}
|
|
567
|
+
catch (err) {
|
|
568
|
+
logger.warn({ err, sessionId }, "opencode session abort failed");
|
|
569
|
+
}
|
|
570
|
+
}, timeoutMs);
|
|
571
|
+
// §5.1 ordering invariant — `event.subscribe()` MUST complete before
|
|
572
|
+
// `session.prompt()` is sent, otherwise early `message.part.delta`
|
|
573
|
+
// events are emitted into a not-yet-bound stream and lost. Awaiting
|
|
574
|
+
// the subscription here (rather than letting `consumeEventStream`
|
|
575
|
+
// do it concurrently with the prompt below) collapses the race.
|
|
576
|
+
let subscription;
|
|
577
|
+
try {
|
|
578
|
+
subscription = await client.event.subscribe({
|
|
579
|
+
signal: abortController.signal,
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
catch (err) {
|
|
583
|
+
clearTimeout(timeoutId);
|
|
584
|
+
throw new BackendDecisiveFailure(this.backendId, "other_non_retryable", err instanceof Error
|
|
585
|
+
? err
|
|
586
|
+
: new Error("opencode event.subscribe failed"));
|
|
587
|
+
}
|
|
588
|
+
let streamedAnyText = false;
|
|
589
|
+
const eventConsumer = this.consumeEventStream({
|
|
590
|
+
stream: subscription.stream,
|
|
591
|
+
sessionId,
|
|
592
|
+
signal: abortController.signal,
|
|
593
|
+
onText: (text) => {
|
|
594
|
+
streamedAnyText = true;
|
|
595
|
+
streamCallbacks?.onText?.(text);
|
|
596
|
+
},
|
|
597
|
+
});
|
|
598
|
+
// Resume sends the user's reply verbatim — the system prompt and
|
|
599
|
+
// conversation history are already inside the opencode server
|
|
600
|
+
// session, so wrapping with `buildExecutionPrompt` would re-emit
|
|
601
|
+
// the task-flow framing and confuse the model.
|
|
602
|
+
const renderedPrompt = isResume
|
|
603
|
+
? params.prompt
|
|
604
|
+
: buildExecutionPrompt(params.prompt, params.context, params.event, params.conversationHistory);
|
|
605
|
+
// docs/design/appendices/opencode-backend.md §4 / Phase 4 — `routine.hourly_check.triage`
|
|
606
|
+
// returns a strict JSON envelope (`{ "action": "log_only" |
|
|
607
|
+
// "escalate", … }`) parsed by `parseStage2Verdict`. opencode's
|
|
608
|
+
// `format: { type: "json_schema", … }` honours the schema with
|
|
609
|
+
// built-in retryCount=2, and the parsed object lands at
|
|
610
|
+
// `info.structured` (V4 contract — NOT in text parts). We
|
|
611
|
+
// stringify it back to text so the dispatcher's existing parser
|
|
612
|
+
// (text-based across all backends) keeps working.
|
|
613
|
+
const structuredFormat = formatForProcessKey(params.processKey);
|
|
614
|
+
try {
|
|
615
|
+
let promptResult;
|
|
616
|
+
try {
|
|
617
|
+
// Cast through OpencodeAugmentedPromptBody — the SDK's body
|
|
618
|
+
// type omits `format` even though opencode 1.14.50 honours it
|
|
619
|
+
// at runtime (V4). Without the cast TypeScript rejects the
|
|
620
|
+
// optional field.
|
|
621
|
+
const promptBody = {
|
|
622
|
+
model: modelParts,
|
|
623
|
+
parts: [{ type: "text", text: renderedPrompt }],
|
|
624
|
+
...(structuredFormat ? { format: structuredFormat } : {}),
|
|
625
|
+
};
|
|
626
|
+
promptResult = await client.session.prompt({
|
|
627
|
+
path: { id: sessionId },
|
|
628
|
+
body: promptBody,
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
catch (promptErr) {
|
|
632
|
+
// Fast-fail (network error, transport-level failure): without
|
|
633
|
+
// this branch the consumer would await `session.idle` for the
|
|
634
|
+
// full `executeTimeoutMinutes` because the server never started
|
|
635
|
+
// the turn. Abort the subscribe stream so the for-await loop
|
|
636
|
+
// exits and the consumer's promise resolves; THEN drain so any
|
|
637
|
+
// already-buffered tool calls are still observed before we
|
|
638
|
+
// re-throw.
|
|
639
|
+
abortController.abort(promptErr instanceof Error
|
|
640
|
+
? promptErr
|
|
641
|
+
: new Error("opencode session.prompt failed"));
|
|
642
|
+
await eventConsumer.catch(() => undefined);
|
|
643
|
+
throw promptErr;
|
|
644
|
+
}
|
|
645
|
+
// The stream usually terminates on `session.idle` before the
|
|
646
|
+
// prompt resolves, but in race orderings the prompt response can
|
|
647
|
+
// arrive first. Wait for the stream to drain so we observe every
|
|
648
|
+
// tool call.
|
|
649
|
+
const collected = await eventConsumer;
|
|
650
|
+
clearTimeout(timeoutId);
|
|
651
|
+
// Distinguish a clean abort from a server-side error or successful
|
|
652
|
+
// turn. opencode signals abort via `info.error.name`, not a
|
|
653
|
+
// dedicated event.
|
|
654
|
+
const assistantMessage = promptResult.data?.info;
|
|
655
|
+
if (assistantMessage?.error?.name === "MessageAbortedError"
|
|
656
|
+
|| isMessageAborted(collected.terminalEvent)) {
|
|
657
|
+
throw new BackendDecisiveFailure(this.backendId, "timeout", new Error("opencode session aborted (likely wall-clock timeout)"));
|
|
658
|
+
}
|
|
659
|
+
if (assistantMessage?.error) {
|
|
660
|
+
throw classifyAssistantError(assistantMessage.error, this.backendId);
|
|
661
|
+
}
|
|
662
|
+
if (collected.terminalEvent?.kind === "session_error") {
|
|
663
|
+
throw classifyStreamError(collected.terminalEvent.error, this.backendId);
|
|
664
|
+
}
|
|
665
|
+
const partsForExtraction = promptResult.data?.parts ?? [];
|
|
666
|
+
// V4 — when `format.type === "json_schema"` was sent, the
|
|
667
|
+
// validated parsed object lives at `info.structured`, NOT in
|
|
668
|
+
// the text parts (which are empty in that mode). Stringify it
|
|
669
|
+
// back to JSON so the dispatcher's existing text-based parser
|
|
670
|
+
// (`parseStage2Verdict`) sees a clean envelope. A missing or
|
|
671
|
+
// null `structured` after a json_schema request is a terminal
|
|
672
|
+
// failure — opencode tried `retryCount=2` server-side and
|
|
673
|
+
// still couldn't satisfy the schema, so escalating to the
|
|
674
|
+
// model again from our side would just burn budget.
|
|
675
|
+
let structuredText = null;
|
|
676
|
+
if (structuredFormat) {
|
|
677
|
+
const structured = assistantMessage?.structured;
|
|
678
|
+
if (structured == null) {
|
|
679
|
+
throw new BackendDecisiveFailure(this.backendId, "other_non_retryable", new Error("opencode json_schema turn returned no structured payload (info.structured was null)"));
|
|
680
|
+
}
|
|
681
|
+
structuredText = JSON.stringify(structured);
|
|
682
|
+
}
|
|
683
|
+
const finalText = structuredText
|
|
684
|
+
?? (extractAssistantTextFromParts(partsForExtraction).trim()
|
|
685
|
+
|| collected.streamedText.trim());
|
|
686
|
+
// Tool-call audit — Phase 3 wires AgentWriteTracker via the tool
|
|
687
|
+
// extraction path. Phase 2 surfaces the structured list so the
|
|
688
|
+
// dispatcher can decide whether the agent touched files.
|
|
689
|
+
const tools = extractToolUsesFromParts(partsForExtraction);
|
|
690
|
+
recordAgentWritesFromTools(tools, this.writeTracker);
|
|
691
|
+
// EXECUTION-MODE-DESIGN.md §6.3 / OPENCODE_BACKEND_DESIGN §5.8
|
|
692
|
+
// synthetic absolute-block audit. opencode has no PreToolUse hook;
|
|
693
|
+
// each tool call surfaced in the final response is run through the
|
|
694
|
+
// classifier so an `agent_actions.blocked_absolute` row is written
|
|
695
|
+
// with `result='partial'` on a hit. The underlying enforcement
|
|
696
|
+
// happens server-side via the permission JSON — this row is
|
|
697
|
+
// observability only.
|
|
698
|
+
auditOpencodeTools(tools, {
|
|
699
|
+
db: this.mcpContext?.db,
|
|
700
|
+
mode: this.config.opencodeExecutionPermissionMode,
|
|
701
|
+
sessionId: params.sessionDbId ?? null,
|
|
702
|
+
});
|
|
703
|
+
// Cost / token aggregation — V11: `client.session.get()` exposes
|
|
704
|
+
// session-level aggregates that avoid summing per-message rows.
|
|
705
|
+
// Default tag is "sdk" because the primary path is the SDK's
|
|
706
|
+
// typed `info.cost`. Free-tier reconciliation below may override
|
|
707
|
+
// to "litellm" or "hardcoded" depending on the registry source.
|
|
708
|
+
let costSourceTag = "sdk";
|
|
709
|
+
let costUsd = assistantMessage?.cost ?? 0;
|
|
710
|
+
let usage = assistantMessage
|
|
711
|
+
? assistantMessageToUsage(assistantMessage)
|
|
712
|
+
: { ...EMPTY_USAGE };
|
|
713
|
+
try {
|
|
714
|
+
const sessionInfo = await client.session.get({
|
|
715
|
+
path: { id: sessionId },
|
|
716
|
+
});
|
|
717
|
+
const sessAgg = sessionInfo.data;
|
|
718
|
+
if (sessAgg?.tokens) {
|
|
719
|
+
// Subtract the pre-turn snapshot so resume usage reflects
|
|
720
|
+
// this turn only. Clamp to zero in case opencode reports a
|
|
721
|
+
// non-monotonic counter — never charge negative tokens.
|
|
722
|
+
usage = {
|
|
723
|
+
inputTokens: Math.max(0, (sessAgg.tokens.input ?? 0) - preTurnUsage.inputTokens),
|
|
724
|
+
outputTokens: Math.max(0, (sessAgg.tokens.output ?? 0) - preTurnUsage.outputTokens),
|
|
725
|
+
cacheCreationInputTokens: Math.max(0, (sessAgg.tokens.cache?.write ?? 0)
|
|
726
|
+
- preTurnUsage.cacheCreationInputTokens),
|
|
727
|
+
cacheReadInputTokens: Math.max(0, (sessAgg.tokens.cache?.read ?? 0)
|
|
728
|
+
- preTurnUsage.cacheReadInputTokens),
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
if (typeof sessAgg?.cost === "number") {
|
|
732
|
+
costUsd = Math.max(0, sessAgg.cost - preTurnCost);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
catch (err) {
|
|
736
|
+
logger.debug({ err, sessionId }, "opencode session.get failed; falling back to assistant-message tokens");
|
|
737
|
+
}
|
|
738
|
+
// Free-tier reconciliation (§5.7 layer 2): when opencode reports
|
|
739
|
+
// cost=0 but tokens flowed, fall back to registry pricing so the
|
|
740
|
+
// dashboard sees a non-zero cost figure for budgeting.
|
|
741
|
+
if (costUsd === 0
|
|
742
|
+
&& usage.inputTokens + usage.outputTokens > 0) {
|
|
743
|
+
const estimate = this.priceFetcher.estimateUsageCost({
|
|
744
|
+
backendId: this.backendId,
|
|
745
|
+
modelId: params.modelId,
|
|
746
|
+
usage,
|
|
747
|
+
fallbackModel: findRegisteredModel(this.backendId, params.modelId),
|
|
748
|
+
});
|
|
749
|
+
costUsd = estimate.costUsd;
|
|
750
|
+
// Preserve `litellm` vs `hardcoded` provenance verbatim — see
|
|
751
|
+
// CodexCore's same pattern (`codex-core.ts:761`). Earlier rev
|
|
752
|
+
// collapsed `litellm` into `sdk`, hiding which estimates came
|
|
753
|
+
// from the LiteLLM cache vs the registry fallback.
|
|
754
|
+
costSourceTag = estimate.costSource;
|
|
755
|
+
}
|
|
756
|
+
this.assertWithinMaxBudget(costUsd, params.maxBudgetUsd, params.modelId);
|
|
757
|
+
if (finalText && !streamedAnyText) {
|
|
758
|
+
streamCallbacks?.onText?.(finalText);
|
|
759
|
+
}
|
|
760
|
+
const durationMs = Date.now() - startMs;
|
|
761
|
+
const stopReason = assistantMessage?.finish ?? null;
|
|
762
|
+
// Best-effort cleanup of the on-disk opencode session row when we
|
|
763
|
+
// own the workdir — keeps disk usage bounded under hourly_check.
|
|
764
|
+
// On resume we must never delete: the dispatcher's next turn
|
|
765
|
+
// needs the same session id to resolve to a live server-side
|
|
766
|
+
// history. (`ownsSessionDir` is already false when sessionDir is
|
|
767
|
+
// provided, but the explicit isResume guard is defense-in-depth
|
|
768
|
+
// against a future caller passing a transient workdir on resume.)
|
|
769
|
+
if (ownsSessionDir && !isResume) {
|
|
770
|
+
try {
|
|
771
|
+
await client.session.delete({ path: { id: sessionId } });
|
|
772
|
+
}
|
|
773
|
+
catch (err) {
|
|
774
|
+
logger.debug({ err, sessionId }, "opencode session.delete failed");
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
logger.info({
|
|
778
|
+
eventType: params.event.type,
|
|
779
|
+
model: params.modelId,
|
|
780
|
+
durationMs,
|
|
781
|
+
costUsd,
|
|
782
|
+
sessionId,
|
|
783
|
+
}, "opencode execute completed");
|
|
784
|
+
const actualModelId = assistantMessage?.providerID && assistantMessage?.modelID
|
|
785
|
+
? `${assistantMessage.providerID}/${assistantMessage.modelID}`
|
|
786
|
+
: params.modelId;
|
|
787
|
+
return {
|
|
788
|
+
output: finalText,
|
|
789
|
+
sessionId,
|
|
790
|
+
backendId: this.backendId,
|
|
791
|
+
modelId: actualModelId,
|
|
792
|
+
costSource: costSourceTag,
|
|
793
|
+
costUsd,
|
|
794
|
+
usage,
|
|
795
|
+
modelUsage: usage.inputTokens || usage.outputTokens
|
|
796
|
+
? {
|
|
797
|
+
[actualModelId]: {
|
|
798
|
+
inputTokens: usage.inputTokens,
|
|
799
|
+
outputTokens: usage.outputTokens,
|
|
800
|
+
costUsd,
|
|
801
|
+
},
|
|
802
|
+
}
|
|
803
|
+
: {},
|
|
804
|
+
numTurns: 1,
|
|
805
|
+
durationMs,
|
|
806
|
+
durationApiMs: durationMs,
|
|
807
|
+
model: actualModelId,
|
|
808
|
+
isError: false,
|
|
809
|
+
stopReason,
|
|
810
|
+
contextUpdated: false,
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
finally {
|
|
814
|
+
clearTimeout(timeoutId);
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
finally {
|
|
818
|
+
streamCallbacks?.onEnd?.();
|
|
819
|
+
if (ownsSessionDir) {
|
|
820
|
+
if (issuedReadToken) {
|
|
821
|
+
this.readTokenManager?.revoke(sessionDir);
|
|
822
|
+
}
|
|
823
|
+
cleanupSessionWorkdir(sessionDir);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
/**
|
|
828
|
+
* docs/design/appendices/opencode-backend.md §5.9 / Phase 4 — opencode delegated-task
|
|
829
|
+
* execution.
|
|
830
|
+
*
|
|
831
|
+
* Implementation choice (v1): spawn an isolated ephemeral server
|
|
832
|
+
* with tight permission JSON for each delegated call. The design's
|
|
833
|
+
* preferred default is Path B (long-lived primary + per-agent
|
|
834
|
+
* permission frontmatter written mid-session), but Phase 0's V5
|
|
835
|
+
* fixture left the agent-file hot-reload behaviour an open question
|
|
836
|
+
* (§5.9 "Open item" — agent file was pre-created in V5). spawnEphemeral
|
|
837
|
+
* sidesteps the hot-reload concern entirely with a deterministic
|
|
838
|
+
* ~900 ms p50 spawn cost (V6 measurement); volume on delegated paths
|
|
839
|
+
* is low enough that the cost-isolation trade-off favours
|
|
840
|
+
* predictability. The router's `params.isolation === "ephemeral"`
|
|
841
|
+
* intent is the same as our default here, so callers that explicitly
|
|
842
|
+
* request ephemeral isolation get exactly what they asked for.
|
|
843
|
+
*
|
|
844
|
+
* Path B (long-lived primary + per-agent file reuse) can land
|
|
845
|
+
* incrementally once V5's open question is resolved against a real
|
|
846
|
+
* opencode server — see §5.9 Phase 3-blocked checklist row.
|
|
847
|
+
*
|
|
848
|
+
* Per-call envelope:
|
|
849
|
+
* - Tight `permission` JSON: every triple-keyed write tool
|
|
850
|
+
* (`edit`, `bash`, `webfetch`, `doom_loop`, `external_directory`)
|
|
851
|
+
* defaults to `deny`. The absolute-block layer's bash
|
|
852
|
+
* pattern-map merges on top so destructive shapes stay denied.
|
|
853
|
+
* - `tools.task: false` (V8 — kill subagent spawning) plus
|
|
854
|
+
* `tools.read: false` and `tools.write: false` for the
|
|
855
|
+
* non-MCP read/write tools (no permission triple exists for
|
|
856
|
+
* `read`; hard-disable is the only opencode 1.14.50 surface).
|
|
857
|
+
* - MCP map carries only the integration's connectors per
|
|
858
|
+
* `setMcpContext`; per-tool MCP deny is server-level only in v1
|
|
859
|
+
* (§5.6 v1 strategy) — disallowed connectors are simply not
|
|
860
|
+
* materialised.
|
|
861
|
+
* - `model` set to `params.modelId`.
|
|
862
|
+
*
|
|
863
|
+
* Stream pre-emption: opencode 1.14.50 does NOT emit
|
|
864
|
+
* `message.part.updated` events during a turn (V9 — see §5.3), so
|
|
865
|
+
* tool-call data is only available from the FINAL `session.prompt`
|
|
866
|
+
* response. The pre-emption check therefore happens AFTER the turn
|
|
867
|
+
* completes — `policy_violation` / `loop_aborted` classifications
|
|
868
|
+
* still surface, but the subprocess has already executed any
|
|
869
|
+
* out-of-envelope MCP call by the time we see it. Mitigation:
|
|
870
|
+
* 1. Server-level permission denies non-MCP write tools at
|
|
871
|
+
* enforcement time (the model receives a tool error and stops).
|
|
872
|
+
* 2. The MCP map only carries the integration's allowed
|
|
873
|
+
* connectors, so non-allowed MCP tools are unreachable.
|
|
874
|
+
* 3. The post-turn pre-emption check is the audit layer — it
|
|
875
|
+
* classifies whether the turn violated policy for telemetry.
|
|
876
|
+
* If opencode begins emitting streamed tool-call events, this method
|
|
877
|
+
* migrates to the live pre-emption pattern Codex uses; the post-
|
|
878
|
+
* extraction check stays as the back-stop.
|
|
879
|
+
*/
|
|
880
|
+
async runDelegatedTaskAgainstEphemeral(params) {
|
|
881
|
+
const startMs = Date.now();
|
|
882
|
+
const trace = [];
|
|
883
|
+
let writeClassToolFired = false;
|
|
884
|
+
const modelParts = parseModelComposite(params.modelId);
|
|
885
|
+
if (!modelParts) {
|
|
886
|
+
return {
|
|
887
|
+
ok: false,
|
|
888
|
+
errorClass: "subprocess_crashed",
|
|
889
|
+
message: `opencode model id must be in 'provider/model' form, got '${params.modelId}'`,
|
|
890
|
+
cost: withDurationMs(emptyCost(), startMs),
|
|
891
|
+
trace,
|
|
892
|
+
writeClassToolFired,
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
// Pre-flight auth gate — same chokepoint runTurn uses; spares us a
|
|
896
|
+
// server spawn on the obvious-401 path.
|
|
897
|
+
const auth = await this.checkAuth();
|
|
898
|
+
if (!auth.ok) {
|
|
899
|
+
return {
|
|
900
|
+
ok: false,
|
|
901
|
+
errorClass: "auth_error",
|
|
902
|
+
message: auth.reason,
|
|
903
|
+
cost: withDurationMs(emptyCost(), startMs),
|
|
904
|
+
trace,
|
|
905
|
+
writeClassToolFired,
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
// Build the tight runtime config envelope. Reuses the regular
|
|
909
|
+
// builder so absolute-block + per-session disallowed merges land
|
|
910
|
+
// identically; the per-task tightening goes on top via
|
|
911
|
+
// `extraHardDisable` (`read`/`write` off) and a tightened
|
|
912
|
+
// permission override (every triple key denied).
|
|
913
|
+
const mcpRender = await this.materializeMcp(undefined);
|
|
914
|
+
const built = buildOpencodeRuntimeConfig({
|
|
915
|
+
modelId: params.modelId,
|
|
916
|
+
executionMode: "strict",
|
|
917
|
+
disallowedTools: this.config.disallowedTools ?? [],
|
|
918
|
+
allowedToolsOverride: null,
|
|
919
|
+
mcpDisallowed: mcpRender.mcpDisallowed,
|
|
920
|
+
mcp: mcpRender.mcp,
|
|
921
|
+
defensiveInstructions: defensiveInstructionsFromEnv(),
|
|
922
|
+
extraHardDisable: { read: false, write: false },
|
|
923
|
+
});
|
|
924
|
+
const tightConfig = {
|
|
925
|
+
...built.config,
|
|
926
|
+
permission: {
|
|
927
|
+
// Triple-keyed denies — the bash pattern-map from absolute-block
|
|
928
|
+
// already merged into built.config.permission.bash, so taking
|
|
929
|
+
// its value here preserves the wildcards. The other four flip
|
|
930
|
+
// to wholesale "deny" because no per-task narrowing applies.
|
|
931
|
+
...(built.config.permission ?? {}),
|
|
932
|
+
edit: "deny",
|
|
933
|
+
webfetch: "deny",
|
|
934
|
+
doom_loop: "deny",
|
|
935
|
+
external_directory: "deny",
|
|
936
|
+
},
|
|
937
|
+
};
|
|
938
|
+
let handle = null;
|
|
939
|
+
try {
|
|
940
|
+
handle = await this.serverManager.spawnEphemeral(tightConfig);
|
|
941
|
+
}
|
|
942
|
+
catch (err) {
|
|
943
|
+
return {
|
|
944
|
+
ok: false,
|
|
945
|
+
errorClass: "subprocess_crashed",
|
|
946
|
+
message: err instanceof Error
|
|
947
|
+
? `opencode ephemeral server spawn failed: ${err.message}`
|
|
948
|
+
: "opencode ephemeral server spawn failed",
|
|
949
|
+
cost: withDurationMs(emptyCost(), startMs),
|
|
950
|
+
trace,
|
|
951
|
+
writeClassToolFired,
|
|
952
|
+
};
|
|
953
|
+
}
|
|
954
|
+
const aborter = new AbortController();
|
|
955
|
+
const callerListener = () => {
|
|
956
|
+
aborter.abort(params.abortSignal?.reason);
|
|
957
|
+
};
|
|
958
|
+
if (params.abortSignal) {
|
|
959
|
+
if (params.abortSignal.aborted) {
|
|
960
|
+
aborter.abort(params.abortSignal.reason);
|
|
961
|
+
}
|
|
962
|
+
else {
|
|
963
|
+
params.abortSignal.addEventListener("abort", callerListener, {
|
|
964
|
+
once: true,
|
|
965
|
+
});
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
const localTimeout = setTimeout(() => aborter.abort(new Error("opencode delegated task local timeout")), params.timeoutMs + 60_000);
|
|
969
|
+
let sessionId = null;
|
|
970
|
+
try {
|
|
971
|
+
const created = await handle.client.session.create({
|
|
972
|
+
body: {
|
|
973
|
+
title: `aitne:delegated:${randomUUID().slice(0, 8)}`,
|
|
974
|
+
},
|
|
975
|
+
});
|
|
976
|
+
sessionId = created.data?.id ?? null;
|
|
977
|
+
if (!sessionId) {
|
|
978
|
+
return {
|
|
979
|
+
ok: false,
|
|
980
|
+
errorClass: "subprocess_crashed",
|
|
981
|
+
message: "opencode session.create returned no id",
|
|
982
|
+
cost: withDurationMs(emptyCost(), startMs),
|
|
983
|
+
trace,
|
|
984
|
+
writeClassToolFired,
|
|
985
|
+
};
|
|
986
|
+
}
|
|
987
|
+
const promptResult = await handle.client.session.prompt({
|
|
988
|
+
path: { id: sessionId },
|
|
989
|
+
body: {
|
|
990
|
+
model: modelParts,
|
|
991
|
+
parts: [{ type: "text", text: params.systemPrompt }],
|
|
992
|
+
},
|
|
993
|
+
});
|
|
994
|
+
if (aborter.signal.aborted) {
|
|
995
|
+
const errorClass = classifyAbortReason(params.abortSignal?.reason ?? aborter.signal.reason);
|
|
996
|
+
return {
|
|
997
|
+
ok: false,
|
|
998
|
+
errorClass,
|
|
999
|
+
message: errorClass === "timeout"
|
|
1000
|
+
? "opencode delegated task timed out (wall-clock)"
|
|
1001
|
+
: "opencode delegated task cancelled",
|
|
1002
|
+
cost: withDurationMs(emptyCost(), startMs),
|
|
1003
|
+
trace,
|
|
1004
|
+
writeClassToolFired,
|
|
1005
|
+
};
|
|
1006
|
+
}
|
|
1007
|
+
const assistantMessage = promptResult.data?.info;
|
|
1008
|
+
if (assistantMessage?.error?.name === "MessageAbortedError") {
|
|
1009
|
+
return {
|
|
1010
|
+
ok: false,
|
|
1011
|
+
errorClass: "timeout",
|
|
1012
|
+
message: "opencode delegated session aborted",
|
|
1013
|
+
cost: withDurationMs(emptyCost(), startMs),
|
|
1014
|
+
trace,
|
|
1015
|
+
writeClassToolFired,
|
|
1016
|
+
};
|
|
1017
|
+
}
|
|
1018
|
+
if (assistantMessage?.error) {
|
|
1019
|
+
return {
|
|
1020
|
+
ok: false,
|
|
1021
|
+
errorClass: "tool_failed",
|
|
1022
|
+
message: `opencode assistant error: ${assistantMessage.error.name}`,
|
|
1023
|
+
cost: withDurationMs(emptyCost(), startMs),
|
|
1024
|
+
trace,
|
|
1025
|
+
writeClassToolFired,
|
|
1026
|
+
};
|
|
1027
|
+
}
|
|
1028
|
+
const partsRaw = promptResult.data?.parts ?? [];
|
|
1029
|
+
const finalText = extractAssistantTextFromParts(partsRaw).trim();
|
|
1030
|
+
const toolUses = extractToolUsesFromParts(partsRaw);
|
|
1031
|
+
// Post-extraction policy gate. opencode 1.14.50 cannot stream
|
|
1032
|
+
// tool calls (V9 — see method-level comment), so the check is
|
|
1033
|
+
// best-effort audit + classification, not real-time pre-emption.
|
|
1034
|
+
let policyViolationTool = null;
|
|
1035
|
+
let toolCallCount = 0;
|
|
1036
|
+
const isAllowedTool = (name) => params.allowedTools.some((pattern) => matchRunAllowedToolPattern(pattern, name));
|
|
1037
|
+
const destructiveSet = params.allowDestructive
|
|
1038
|
+
? new Set()
|
|
1039
|
+
: new Set(params.destructiveTools);
|
|
1040
|
+
const writeClassMatcher = (name) => params.writeClassTools.some((pattern) => matchRunAllowedToolPattern(pattern, name));
|
|
1041
|
+
for (const tool of toolUses) {
|
|
1042
|
+
if (tool.status === "pending" || tool.status === "running")
|
|
1043
|
+
continue;
|
|
1044
|
+
toolCallCount += 1;
|
|
1045
|
+
// opencode 1.14.50 surfaces opencode-built-in tool names without
|
|
1046
|
+
// the `mcp__server__` prefix (the SDK v1 limitation §5.6 v2 will
|
|
1047
|
+
// address). For the allowedTools/destructive comparison we
|
|
1048
|
+
// inspect the verbatim name first, falling back to a synthesised
|
|
1049
|
+
// prefix when the integration key is known. Either way the audit
|
|
1050
|
+
// remains accurate for the same-name match.
|
|
1051
|
+
const fullName = tool.toolName;
|
|
1052
|
+
if (!isAllowedTool(fullName) || destructiveSet.has(fullName)) {
|
|
1053
|
+
policyViolationTool = fullName;
|
|
1054
|
+
}
|
|
1055
|
+
if (writeClassMatcher(fullName)) {
|
|
1056
|
+
writeClassToolFired = true;
|
|
1057
|
+
}
|
|
1058
|
+
const step = {
|
|
1059
|
+
toolName: fullName,
|
|
1060
|
+
toolArgs: tool.input,
|
|
1061
|
+
durationMs: tool.durationMs ?? 0,
|
|
1062
|
+
status: tool.status === "completed" ? "ok" : "error",
|
|
1063
|
+
costUsd: null,
|
|
1064
|
+
tokensInput: null,
|
|
1065
|
+
tokensOutput: null,
|
|
1066
|
+
};
|
|
1067
|
+
if (tool.output !== undefined) {
|
|
1068
|
+
try {
|
|
1069
|
+
step.toolResult = JSON.parse(tool.output);
|
|
1070
|
+
}
|
|
1071
|
+
catch {
|
|
1072
|
+
step.toolResult = tool.output;
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
trace.push(step);
|
|
1076
|
+
params.onToolStep?.(step);
|
|
1077
|
+
}
|
|
1078
|
+
// Synthetic absolute-block audit on tool calls — same chokepoint
|
|
1079
|
+
// runTurn uses for execute() (§5.8). Records `blocked_absolute`
|
|
1080
|
+
// rows when a tool target matches the absolute-block layer; the
|
|
1081
|
+
// permission JSON denied the call server-side, this row is
|
|
1082
|
+
// observability only.
|
|
1083
|
+
auditOpencodeTools(toolUses, {
|
|
1084
|
+
db: this.mcpContext?.db,
|
|
1085
|
+
mode: "strict",
|
|
1086
|
+
sessionId: null,
|
|
1087
|
+
});
|
|
1088
|
+
const usage = assistantMessage
|
|
1089
|
+
? {
|
|
1090
|
+
inputTokens: assistantMessage.tokens?.input ?? 0,
|
|
1091
|
+
outputTokens: assistantMessage.tokens?.output ?? 0,
|
|
1092
|
+
cacheCreationInputTokens: assistantMessage.tokens?.cache?.write ?? 0,
|
|
1093
|
+
cacheReadInputTokens: assistantMessage.tokens?.cache?.read ?? 0,
|
|
1094
|
+
}
|
|
1095
|
+
: { ...EMPTY_USAGE };
|
|
1096
|
+
const costUsd = assistantMessage?.cost ?? 0;
|
|
1097
|
+
const cost = withDurationMs({
|
|
1098
|
+
tokensInput: usage.inputTokens,
|
|
1099
|
+
tokensOutput: usage.outputTokens,
|
|
1100
|
+
cacheCreationTokens: usage.cacheCreationInputTokens,
|
|
1101
|
+
cacheReadTokens: usage.cacheReadInputTokens,
|
|
1102
|
+
costUsd,
|
|
1103
|
+
durationMs: 0,
|
|
1104
|
+
numTurns: 1,
|
|
1105
|
+
}, startMs);
|
|
1106
|
+
// Best-effort cleanup of the on-disk session row. Failure is
|
|
1107
|
+
// benign — the ephemeral server itself will be torn down in
|
|
1108
|
+
// `finally` and its disk state goes with it.
|
|
1109
|
+
try {
|
|
1110
|
+
await handle.client.session.delete({ path: { id: sessionId } });
|
|
1111
|
+
}
|
|
1112
|
+
catch (err) {
|
|
1113
|
+
logger.debug({ err, sessionId }, "opencode delegated session.delete failed");
|
|
1114
|
+
}
|
|
1115
|
+
if (policyViolationTool) {
|
|
1116
|
+
return {
|
|
1117
|
+
ok: false,
|
|
1118
|
+
errorClass: "policy_violation",
|
|
1119
|
+
message: `opencode invoked '${policyViolationTool}' which is outside the per-task allowlist`,
|
|
1120
|
+
rawAssistantText: finalText,
|
|
1121
|
+
cost,
|
|
1122
|
+
trace,
|
|
1123
|
+
writeClassToolFired,
|
|
1124
|
+
};
|
|
1125
|
+
}
|
|
1126
|
+
if (toolCallCount > params.maxToolCalls) {
|
|
1127
|
+
return {
|
|
1128
|
+
ok: false,
|
|
1129
|
+
errorClass: "loop_aborted",
|
|
1130
|
+
message: `opencode exceeded maxToolCalls=${params.maxToolCalls} (observed=${toolCallCount})`,
|
|
1131
|
+
rawAssistantText: finalText,
|
|
1132
|
+
cost,
|
|
1133
|
+
trace,
|
|
1134
|
+
writeClassToolFired,
|
|
1135
|
+
};
|
|
1136
|
+
}
|
|
1137
|
+
if (finalText.length === 0) {
|
|
1138
|
+
return {
|
|
1139
|
+
ok: false,
|
|
1140
|
+
errorClass: "parse_error",
|
|
1141
|
+
message: "opencode assistant produced empty text",
|
|
1142
|
+
rawAssistantText: finalText,
|
|
1143
|
+
cost,
|
|
1144
|
+
trace,
|
|
1145
|
+
writeClassToolFired,
|
|
1146
|
+
};
|
|
1147
|
+
}
|
|
1148
|
+
return {
|
|
1149
|
+
ok: true,
|
|
1150
|
+
rawAssistantText: finalText,
|
|
1151
|
+
cost,
|
|
1152
|
+
trace,
|
|
1153
|
+
writeClassToolFired,
|
|
1154
|
+
};
|
|
1155
|
+
}
|
|
1156
|
+
catch (err) {
|
|
1157
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1158
|
+
const cost = withDurationMs(emptyCost(), startMs);
|
|
1159
|
+
if (aborter.signal.aborted || params.abortSignal?.aborted) {
|
|
1160
|
+
return {
|
|
1161
|
+
ok: false,
|
|
1162
|
+
errorClass: classifyAbortReason(params.abortSignal?.reason ?? aborter.signal.reason),
|
|
1163
|
+
message,
|
|
1164
|
+
cost,
|
|
1165
|
+
trace,
|
|
1166
|
+
writeClassToolFired,
|
|
1167
|
+
};
|
|
1168
|
+
}
|
|
1169
|
+
return {
|
|
1170
|
+
ok: false,
|
|
1171
|
+
errorClass: "subprocess_crashed",
|
|
1172
|
+
message,
|
|
1173
|
+
cost,
|
|
1174
|
+
trace,
|
|
1175
|
+
writeClassToolFired,
|
|
1176
|
+
};
|
|
1177
|
+
}
|
|
1178
|
+
finally {
|
|
1179
|
+
clearTimeout(localTimeout);
|
|
1180
|
+
if (params.abortSignal && callerListener) {
|
|
1181
|
+
params.abortSignal.removeEventListener("abort", callerListener);
|
|
1182
|
+
}
|
|
1183
|
+
if (handle) {
|
|
1184
|
+
try {
|
|
1185
|
+
await handle.close();
|
|
1186
|
+
}
|
|
1187
|
+
catch (err) {
|
|
1188
|
+
logger.debug({ err }, "opencode delegated ephemeral close threw");
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
/**
|
|
1194
|
+
* OPENCODE_BACKEND_DESIGN §5.1 / §5.8 — assemble the full per-session
|
|
1195
|
+
* `OpencodeRuntimeConfig` envelope. Async because MCP materialization
|
|
1196
|
+
* resolves secrets via the encrypted blob store.
|
|
1197
|
+
*
|
|
1198
|
+
* Self-contained: every field is set explicitly (or deliberately
|
|
1199
|
+
* omitted), so the server-manager's bounce hash is stable across
|
|
1200
|
+
* same-envelope turns. The §5.1 invariant.
|
|
1201
|
+
*/
|
|
1202
|
+
async buildRuntimeConfig(params) {
|
|
1203
|
+
const mcpRender = await this.materializeMcp(params.processKey);
|
|
1204
|
+
const built = buildOpencodeRuntimeConfig({
|
|
1205
|
+
modelId: params.modelId,
|
|
1206
|
+
executionMode: this.config.opencodeExecutionPermissionMode,
|
|
1207
|
+
disallowedTools: this.config.disallowedTools ?? [],
|
|
1208
|
+
allowedToolsOverride: params.allowedToolsOverride ?? this.config.allowedToolsOverride ?? null,
|
|
1209
|
+
mcpDisallowed: mcpRender.mcpDisallowed,
|
|
1210
|
+
mcp: mcpRender.mcp,
|
|
1211
|
+
defensiveInstructions: defensiveInstructionsFromEnv(),
|
|
1212
|
+
});
|
|
1213
|
+
// MCP renderer warnings (server-name lint failures, missing
|
|
1214
|
+
// command/url) must flow through so the dashboard's runtime-config
|
|
1215
|
+
// preview tile sees them alongside permission-translator warnings —
|
|
1216
|
+
// the §5.6 contract says warnings are "collected from both
|
|
1217
|
+
// translators". Without this concatenation they only land in
|
|
1218
|
+
// `logger.warn`, invisible to operators not tailing daemon logs.
|
|
1219
|
+
return {
|
|
1220
|
+
config: built.config,
|
|
1221
|
+
warnings: [...built.warnings, ...mcpRender.warnings],
|
|
1222
|
+
};
|
|
1223
|
+
}
|
|
1224
|
+
/**
|
|
1225
|
+
* Per-session MCP materialization. Unlike Claude / Codex / Gemini —
|
|
1226
|
+
* which write a backend-specific file under `<sessionDir>/.mcp.json`
|
|
1227
|
+
* or `.codex/config.toml` — OpenCode receives MCP servers inline via
|
|
1228
|
+
* `OPENCODE_CONFIG_CONTENT` (§5.1). The daemon therefore renders
|
|
1229
|
+
* directly into a `Record<string, McpLocalConfig | McpRemoteConfig>`
|
|
1230
|
+
* map and feeds it into `buildOpencodeRuntimeConfig`.
|
|
1231
|
+
*
|
|
1232
|
+
* Returns the rendered map + the autonomous-strip disallowedTools so
|
|
1233
|
+
* the config builder can surface those as MCP-server warnings
|
|
1234
|
+
* (per-tool MCP deny isn't expressible in opencode 1.14.50 — §5.6).
|
|
1235
|
+
*/
|
|
1236
|
+
async materializeMcp(processKey) {
|
|
1237
|
+
if (!this.mcpContext) {
|
|
1238
|
+
return { mcp: {}, mcpDisallowed: [], warnings: [] };
|
|
1239
|
+
}
|
|
1240
|
+
const allServers = listMcpServers(this.mcpContext.db);
|
|
1241
|
+
const forBackend = allServers.filter((s) => s.enabled && s.backends.includes(this.backendId));
|
|
1242
|
+
if (forBackend.length === 0) {
|
|
1243
|
+
return { mcp: {}, mcpDisallowed: [], warnings: [] };
|
|
1244
|
+
}
|
|
1245
|
+
// Resolve secrets the same way materializeMcpForSession does — keyed
|
|
1246
|
+
// by `<serverId>:<keyName>` so the renderer can pick the subset it
|
|
1247
|
+
// needs without collisions across servers.
|
|
1248
|
+
const scopedSecrets = {};
|
|
1249
|
+
for (const server of forBackend) {
|
|
1250
|
+
const raw = await resolveMcpSecrets(this.mcpContext.blobStore, server);
|
|
1251
|
+
for (const [keyName, value] of Object.entries(raw)) {
|
|
1252
|
+
if (value == null)
|
|
1253
|
+
continue;
|
|
1254
|
+
scopedSecrets[`${server.id}:${keyName}`] = value;
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
// Allow mode bypasses the approve-tier strip — mirroring the Codex
|
|
1258
|
+
// / Gemini wiring (see codex-core.ts:230).
|
|
1259
|
+
const allowMode = this.config.opencodeExecutionPermissionMode === "allow";
|
|
1260
|
+
const autonomous = !allowMode && (processKey ? isAutonomousProcessKey(processKey) : false);
|
|
1261
|
+
const mcpDisallowed = buildMcpDisallowedTools({
|
|
1262
|
+
servers: forBackend,
|
|
1263
|
+
autonomous,
|
|
1264
|
+
});
|
|
1265
|
+
const rendered = renderOpencodeMcp({
|
|
1266
|
+
servers: forBackend,
|
|
1267
|
+
secrets: scopedSecrets,
|
|
1268
|
+
});
|
|
1269
|
+
if (rendered.warnings.length > 0) {
|
|
1270
|
+
logger.warn({ warnings: rendered.warnings, contextDir: getContextDir(this.config) }, "opencode-mcp renderer surfaced warnings");
|
|
1271
|
+
}
|
|
1272
|
+
return { mcp: rendered.mcp, mcpDisallowed, warnings: rendered.warnings };
|
|
1273
|
+
}
|
|
1274
|
+
async consumeEventStream(args) {
|
|
1275
|
+
const { stream, sessionId, signal, onText } = args;
|
|
1276
|
+
let streamedText = "";
|
|
1277
|
+
let terminalEvent;
|
|
1278
|
+
try {
|
|
1279
|
+
for await (const rawEvent of stream) {
|
|
1280
|
+
if (signal.aborted)
|
|
1281
|
+
break;
|
|
1282
|
+
const normalized = normalize(rawEvent);
|
|
1283
|
+
// Filter events that don't belong to our session — `server.heartbeat`
|
|
1284
|
+
// and `server.connected` carry no sessionId, but other typed
|
|
1285
|
+
// events do; drop foreign sessions to avoid cross-talk on a
|
|
1286
|
+
// long-lived server.
|
|
1287
|
+
const eventSessionId = "sessionId" in normalized ? normalized.sessionId : "";
|
|
1288
|
+
if (eventSessionId && eventSessionId !== sessionId)
|
|
1289
|
+
continue;
|
|
1290
|
+
if (normalized.kind === "text_delta" && normalized.field === "text") {
|
|
1291
|
+
// Only the `text` field is user-visible final output. opencode
|
|
1292
|
+
// also emits `field: "reasoning"` (and other non-text part
|
|
1293
|
+
// fields) — they must not reach `onText` (chat bubble) and
|
|
1294
|
+
// must not pollute `streamedText`, which is the
|
|
1295
|
+
// `extractAssistantTextFromParts` fallback used when the parts
|
|
1296
|
+
// array carries no `type: "text"` entries.
|
|
1297
|
+
streamedText += normalized.delta;
|
|
1298
|
+
if (normalized.delta) {
|
|
1299
|
+
onText(normalized.delta);
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
if (isTerminal(normalized)) {
|
|
1303
|
+
terminalEvent = normalized;
|
|
1304
|
+
break;
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
catch (err) {
|
|
1309
|
+
if (signal.aborted) {
|
|
1310
|
+
// Caller surfaces the timeout (or the prompt fast-fail) via the
|
|
1311
|
+
// abort path; don't escalate the iterator's AbortError to a warn.
|
|
1312
|
+
return { streamedText, terminalEvent };
|
|
1313
|
+
}
|
|
1314
|
+
logger.warn({ err, sessionId }, "opencode event-stream consumption error");
|
|
1315
|
+
}
|
|
1316
|
+
return { streamedText, terminalEvent };
|
|
1317
|
+
}
|
|
1318
|
+
assertPromptWithinMaxBudget(prompt, maxBudgetUsd, modelId) {
|
|
1319
|
+
if (!maxBudgetUsd)
|
|
1320
|
+
return;
|
|
1321
|
+
// Same envelope check Codex uses — defensive only; opencode also
|
|
1322
|
+
// enforces a context limit server-side.
|
|
1323
|
+
const approxInputTokens = Math.ceil(prompt.length / 3.5);
|
|
1324
|
+
const fallback = findRegisteredModel(this.backendId, modelId);
|
|
1325
|
+
if (!fallback?.usdPer1kIn)
|
|
1326
|
+
return;
|
|
1327
|
+
const projected = (approxInputTokens / 1000) * fallback.usdPer1kIn;
|
|
1328
|
+
if (projected > maxBudgetUsd * 2) {
|
|
1329
|
+
throw new BackendDecisiveFailure(this.backendId, "model_unavailable", new Error(`Prompt projected at $${projected.toFixed(4)} exceeds 2× budget cap of $${maxBudgetUsd.toFixed(4)}`));
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
assertWithinMaxBudget(costUsd, maxBudgetUsd, modelId) {
|
|
1333
|
+
if (!maxBudgetUsd)
|
|
1334
|
+
return;
|
|
1335
|
+
if (costUsd > maxBudgetUsd) {
|
|
1336
|
+
throw new BackendDecisiveFailure(this.backendId, "model_unavailable", new Error(`opencode execute cost $${costUsd.toFixed(4)} exceeds budget $${maxBudgetUsd.toFixed(4)} (${modelId})`));
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
function assistantMessageToUsage(msg) {
|
|
1341
|
+
const tokens = msg.tokens;
|
|
1342
|
+
return {
|
|
1343
|
+
inputTokens: tokens?.input ?? 0,
|
|
1344
|
+
outputTokens: tokens?.output ?? 0,
|
|
1345
|
+
cacheCreationInputTokens: tokens?.cache?.write ?? 0,
|
|
1346
|
+
cacheReadInputTokens: tokens?.cache?.read ?? 0,
|
|
1347
|
+
};
|
|
1348
|
+
}
|
|
1349
|
+
/**
|
|
1350
|
+
* Best-effort write attribution. Permanent limitation in v1 (V9 / §5.3):
|
|
1351
|
+
*
|
|
1352
|
+
* opencode 1.14.50 does NOT emit `message.part.updated` events during a
|
|
1353
|
+
* turn — tool-call data is only available from the FINAL session.prompt
|
|
1354
|
+
* response. By the time we read it here, the file watcher has already
|
|
1355
|
+
* fired (chokidar latency: milliseconds), so a marker added now is too
|
|
1356
|
+
* late to influence the immediate `actor` classification.
|
|
1357
|
+
*
|
|
1358
|
+
* The practical value of this call is therefore the SECOND-ORDER
|
|
1359
|
+
* protection it gives observers polling slower than 30s (the
|
|
1360
|
+
* AgentWriteTracker default TTL): if the same path is re-observed
|
|
1361
|
+
* within that window, attribution is correct. Real-time chokidar
|
|
1362
|
+
* observers (Obsidian / Git) see opencode tool-driven file writes
|
|
1363
|
+
* without an `actor='agent'` mark — accepted gap, contained because the
|
|
1364
|
+
* context-MD chokepoint forbids `edit`/`write` against
|
|
1365
|
+
* `~/.personal-agent/context/**` server-side (the absolute-block
|
|
1366
|
+
* permission JSON denies the canonical bash exfiltration paths;
|
|
1367
|
+
* `tools.read` hard-disable plus per-session permission denies bar
|
|
1368
|
+
* the opencode `read`/`write` tools from those paths in strict mode).
|
|
1369
|
+
*
|
|
1370
|
+
* If opencode begins emitting streamed `tool_call` events upstream
|
|
1371
|
+
* (gated on a future release), this function migrates to the event
|
|
1372
|
+
* mapper's stream path so the marker lands before the file watcher
|
|
1373
|
+
* fires; the existing extraction logic below remains as the
|
|
1374
|
+
* final-response back-stop.
|
|
1375
|
+
*/
|
|
1376
|
+
function recordAgentWritesFromTools(tools, writeTracker) {
|
|
1377
|
+
for (const tool of tools) {
|
|
1378
|
+
if (tool.status !== "completed")
|
|
1379
|
+
continue;
|
|
1380
|
+
const writePath = extractWritePathFromInput(tool.toolName, tool.input);
|
|
1381
|
+
if (writePath) {
|
|
1382
|
+
writeTracker.markWriting(writePath);
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
function extractWritePathFromInput(toolName, input) {
|
|
1387
|
+
if (!input)
|
|
1388
|
+
return null;
|
|
1389
|
+
if (toolName === "write" || toolName === "edit" || toolName === "apply_patch") {
|
|
1390
|
+
const candidate = input.path ?? input.filePath ?? input.file;
|
|
1391
|
+
if (typeof candidate === "string" && candidate.length > 0)
|
|
1392
|
+
return candidate;
|
|
1393
|
+
}
|
|
1394
|
+
return null;
|
|
1395
|
+
}
|
|
1396
|
+
/**
|
|
1397
|
+
* docs/design/appendices/opencode-backend.md §5.8 synthetic absolute-block audit. For
|
|
1398
|
+
* every tool call extracted from the final `session.prompt` response,
|
|
1399
|
+
* route through the shared `auditStreamObservation` chokepoint so a
|
|
1400
|
+
* `blocked_absolute` row with `result='partial'` is written on a match.
|
|
1401
|
+
*
|
|
1402
|
+
* Two-step pipeline keeps the classifier centralised:
|
|
1403
|
+
* 1. `extractOpencodeToolUseTarget` — opencode-specific tool/arg
|
|
1404
|
+
* normalisation (`bash` / `read` / `write` / `edit` / `apply_patch`).
|
|
1405
|
+
* 2. `auditStreamObservation` — runs `classifyAbsoluteBlock` against
|
|
1406
|
+
* the normalised target and writes the audit row when matched.
|
|
1407
|
+
*
|
|
1408
|
+
* Why the `partial` result code: opencode enforces the deny inside the
|
|
1409
|
+
* permission JSON at server side, which the daemon does not directly
|
|
1410
|
+
* observe. The audit row records *that the agent attempted* a pattern
|
|
1411
|
+
* the absolute-block layer covers — useful even if the underlying
|
|
1412
|
+
* enforcement happened correctly server-side, and *critical* if a future
|
|
1413
|
+
* permission-JSON regression silently let the call through.
|
|
1414
|
+
*
|
|
1415
|
+
* Exported for `opencode-core.test.ts` so the synthetic audit can be
|
|
1416
|
+
* exercised without spinning up a full server.
|
|
1417
|
+
*/
|
|
1418
|
+
export function auditOpencodeTools(tools, deps) {
|
|
1419
|
+
if (!deps.db)
|
|
1420
|
+
return;
|
|
1421
|
+
for (const tool of tools) {
|
|
1422
|
+
// Match on every tool we can normalise — both completed and errored
|
|
1423
|
+
// calls. Pending/running calls don't carry final input yet.
|
|
1424
|
+
if (tool.status === "pending" || tool.status === "running")
|
|
1425
|
+
continue;
|
|
1426
|
+
const target = extractOpencodeToolUseTarget(tool.toolName, tool.input);
|
|
1427
|
+
if (!target)
|
|
1428
|
+
continue;
|
|
1429
|
+
const match = auditStreamObservation(target, {
|
|
1430
|
+
db: deps.db,
|
|
1431
|
+
backend: "opencode",
|
|
1432
|
+
mode: deps.mode,
|
|
1433
|
+
sessionId: deps.sessionId,
|
|
1434
|
+
});
|
|
1435
|
+
// `auditStreamObservation` already writes the row when match is
|
|
1436
|
+
// non-null; nothing else to do here. The void return lets callers
|
|
1437
|
+
// ignore matches in the hot path while keeping the row in the DB.
|
|
1438
|
+
void match;
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
function classifyAssistantError(error, backendId) {
|
|
1442
|
+
if (error.name === "ProviderAuthError") {
|
|
1443
|
+
return new BackendDecisiveFailure(backendId, "auth", new Error("opencode provider auth rejected"));
|
|
1444
|
+
}
|
|
1445
|
+
if (error.name === "MessageOutputLengthError") {
|
|
1446
|
+
return new BackendDecisiveFailure(backendId, "max_turns", new Error("opencode message output length exceeded"));
|
|
1447
|
+
}
|
|
1448
|
+
return new BackendDecisiveFailure(backendId, "other_non_retryable", new Error(`opencode assistant error: ${error.name}`));
|
|
1449
|
+
}
|
|
1450
|
+
function classifyStreamError(payload, backendId) {
|
|
1451
|
+
const status = payload.data?.statusCode;
|
|
1452
|
+
const message = payload.data?.message ?? `opencode stream error: ${payload.name}`;
|
|
1453
|
+
if (status === 401 || status === 403 || payload.name === "ProviderAuthError") {
|
|
1454
|
+
return new BackendDecisiveFailure(backendId, "auth", new Error(message));
|
|
1455
|
+
}
|
|
1456
|
+
if (status === 429) {
|
|
1457
|
+
return new BackendDecisiveFailure(backendId, "quota", new Error(message));
|
|
1458
|
+
}
|
|
1459
|
+
return new BackendDecisiveFailure(backendId, "other_non_retryable", new Error(message));
|
|
1460
|
+
}
|
|
1461
|
+
/**
|
|
1462
|
+
* docs/design/appendices/opencode-backend.md Phase 4 — Stage 2 hourly-check triage
|
|
1463
|
+
* schema. Mirrors the `parseStage2Verdict` text contract
|
|
1464
|
+
* (`dispatcher-types.ts`): the agent must return exactly
|
|
1465
|
+
* `{ "action": "log_only" | "escalate", "reason": string }`. Opencode
|
|
1466
|
+
* validates against this schema with `retryCount: 2` server-side, then
|
|
1467
|
+
* surfaces the parsed object at `info.structured`.
|
|
1468
|
+
*
|
|
1469
|
+
* Exported for the regression test that exercises the json_schema
|
|
1470
|
+
* round-trip without spinning up a real opencode server.
|
|
1471
|
+
*/
|
|
1472
|
+
export const STAGE2_TRIAGE_JSON_SCHEMA = {
|
|
1473
|
+
type: "object",
|
|
1474
|
+
properties: {
|
|
1475
|
+
action: {
|
|
1476
|
+
type: "string",
|
|
1477
|
+
enum: ["log_only", "escalate"],
|
|
1478
|
+
},
|
|
1479
|
+
reason: {
|
|
1480
|
+
type: "string",
|
|
1481
|
+
},
|
|
1482
|
+
},
|
|
1483
|
+
required: ["action", "reason"],
|
|
1484
|
+
additionalProperties: false,
|
|
1485
|
+
};
|
|
1486
|
+
/**
|
|
1487
|
+
* Returns the opencode `format` envelope to apply when a given process
|
|
1488
|
+
* key has a strict structured-output contract; null otherwise. v1
|
|
1489
|
+
* covers `routine.hourly_check.triage`; future strict-JSON process
|
|
1490
|
+
* keys (e.g. delegated classifiers) extend this map.
|
|
1491
|
+
*/
|
|
1492
|
+
function formatForProcessKey(processKey) {
|
|
1493
|
+
if (processKey === "routine.hourly_check.triage") {
|
|
1494
|
+
return {
|
|
1495
|
+
type: "json_schema",
|
|
1496
|
+
schema: STAGE2_TRIAGE_JSON_SCHEMA,
|
|
1497
|
+
retryCount: 2,
|
|
1498
|
+
};
|
|
1499
|
+
}
|
|
1500
|
+
return null;
|
|
1501
|
+
}
|
|
1502
|
+
//# sourceMappingURL=opencode-core.js.map
|