@roj-ai/sdk 0.1.3 → 0.1.4
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/bootstrap.js +191 -0
- package/dist/bootstrap.js.map +1 -0
- package/dist/builtin-events.js +8 -0
- package/dist/builtin-events.js.map +1 -0
- package/dist/bun-platform/fs.js +39 -0
- package/dist/bun-platform/fs.js.map +1 -0
- package/dist/bun-platform/index.js +18 -0
- package/dist/bun-platform/index.js.map +1 -0
- package/dist/bun-platform/process.js +21 -0
- package/dist/bun-platform/process.js.map +1 -0
- package/dist/config.js +54 -0
- package/dist/config.js.map +1 -0
- package/dist/config.test.js +155 -0
- package/dist/config.test.js.map +1 -0
- package/dist/core/agent-loop.integration.test.js +414 -0
- package/dist/core/agent-loop.integration.test.js.map +1 -0
- package/dist/core/agents/agent-config.test.js +194 -0
- package/dist/core/agents/agent-config.test.js.map +1 -0
- package/dist/core/agents/agent-roles.js +25 -0
- package/dist/core/agents/agent-roles.js.map +1 -0
- package/dist/core/agents/agent-shutdown.test.js +180 -0
- package/dist/core/agents/agent-shutdown.test.js.map +1 -0
- package/dist/core/agents/agent.js +1205 -0
- package/dist/core/agents/agent.js.map +1 -0
- package/dist/core/agents/agent.test.js +313 -0
- package/dist/core/agents/agent.test.js.map +1 -0
- package/dist/core/agents/communicator.js +13 -0
- package/dist/core/agents/communicator.js.map +1 -0
- package/dist/core/agents/config.js +5 -0
- package/dist/core/agents/config.js.map +1 -0
- package/dist/core/agents/context.js +2 -0
- package/dist/core/agents/context.js.map +1 -0
- package/dist/core/agents/debounce.js +74 -0
- package/dist/core/agents/debounce.js.map +1 -0
- package/dist/core/agents/handler-events.test.js +115 -0
- package/dist/core/agents/handler-events.test.js.map +1 -0
- package/dist/core/agents/index.js +2 -0
- package/dist/core/agents/index.js.map +1 -0
- package/dist/core/agents/response-sanitizer.js +46 -0
- package/dist/core/agents/response-sanitizer.js.map +1 -0
- package/dist/core/agents/response-sanitizer.test.js +101 -0
- package/dist/core/agents/response-sanitizer.test.js.map +1 -0
- package/dist/core/agents/retry.js +105 -0
- package/dist/core/agents/retry.js.map +1 -0
- package/dist/core/agents/schema.js +39 -0
- package/dist/core/agents/schema.js.map +1 -0
- package/dist/core/agents/state.js +90 -0
- package/dist/core/agents/state.js.map +1 -0
- package/dist/core/context/state.js +23 -0
- package/dist/core/context/state.js.map +1 -0
- package/dist/core/errors.js +38 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/event-sourcing.integration.test.js +154 -0
- package/dist/core/event-sourcing.integration.test.js.map +1 -0
- package/dist/core/events/base-event-store.js +201 -0
- package/dist/core/events/base-event-store.js.map +1 -0
- package/dist/core/events/event-store.js +26 -0
- package/dist/core/events/event-store.js.map +1 -0
- package/dist/core/events/file.js +320 -0
- package/dist/core/events/file.js.map +1 -0
- package/dist/core/events/file.test.js +284 -0
- package/dist/core/events/file.test.js.map +1 -0
- package/dist/core/events/index.js +3 -0
- package/dist/core/events/index.js.map +1 -0
- package/dist/core/events/memory.js +101 -0
- package/dist/core/events/memory.js.map +1 -0
- package/dist/core/events/memory.test.js +502 -0
- package/dist/core/events/memory.test.js.map +1 -0
- package/dist/core/events/metadata-utils.js +107 -0
- package/dist/core/events/metadata-utils.js.map +1 -0
- package/dist/core/events/test-helpers.js +15 -0
- package/dist/core/events/test-helpers.js.map +1 -0
- package/dist/core/events/types.js +21 -0
- package/dist/core/events/types.js.map +1 -0
- package/dist/core/file-store/file-store.js +250 -0
- package/dist/core/file-store/file-store.js.map +1 -0
- package/dist/core/file-store/types.js +7 -0
- package/dist/core/file-store/types.js.map +1 -0
- package/dist/core/image/image-processor.js +106 -0
- package/dist/core/image/image-processor.js.map +1 -0
- package/dist/core/image/image-processor.test.js +171 -0
- package/dist/core/image/image-processor.test.js.map +1 -0
- package/dist/core/image/index.js +4 -0
- package/dist/core/image/index.js.map +1 -0
- package/dist/core/image/noop-resizer.js +6 -0
- package/dist/core/image/noop-resizer.js.map +1 -0
- package/dist/core/image/types.js +2 -0
- package/dist/core/image/types.js.map +1 -0
- package/dist/core/image/vips-resizer.js +100 -0
- package/dist/core/image/vips-resizer.js.map +1 -0
- package/dist/core/image/vips-resizer.test.js +324 -0
- package/dist/core/image/vips-resizer.test.js.map +1 -0
- package/dist/core/llm/anthropic.js +396 -0
- package/dist/core/llm/anthropic.js.map +1 -0
- package/dist/core/llm/anthropic.test.js +434 -0
- package/dist/core/llm/anthropic.test.js.map +1 -0
- package/dist/core/llm/cache-breakpoints.js +37 -0
- package/dist/core/llm/cache-breakpoints.js.map +1 -0
- package/dist/core/llm/cache-live.test.js +137 -0
- package/dist/core/llm/cache-live.test.js.map +1 -0
- package/dist/core/llm/index.js +9 -0
- package/dist/core/llm/index.js.map +1 -0
- package/dist/core/llm/llm-log-types.js +12 -0
- package/dist/core/llm/llm-log-types.js.map +1 -0
- package/dist/core/llm/logger.js +241 -0
- package/dist/core/llm/logger.js.map +1 -0
- package/dist/core/llm/logger.test.js +228 -0
- package/dist/core/llm/logger.test.js.map +1 -0
- package/dist/core/llm/logging-provider.js +49 -0
- package/dist/core/llm/logging-provider.js.map +1 -0
- package/dist/core/llm/middleware.js +114 -0
- package/dist/core/llm/middleware.js.map +1 -0
- package/dist/core/llm/mock.js +186 -0
- package/dist/core/llm/mock.js.map +1 -0
- package/dist/core/llm/mock.test.js +318 -0
- package/dist/core/llm/mock.test.js.map +1 -0
- package/dist/core/llm/openrouter-mapping.test.js +125 -0
- package/dist/core/llm/openrouter-mapping.test.js.map +1 -0
- package/dist/core/llm/openrouter.js +298 -0
- package/dist/core/llm/openrouter.js.map +1 -0
- package/dist/core/llm/openrouter.test.js +377 -0
- package/dist/core/llm/openrouter.test.js.map +1 -0
- package/dist/core/llm/provider-integration.test.js +350 -0
- package/dist/core/llm/provider-integration.test.js.map +1 -0
- package/dist/core/llm/provider.js +18 -0
- package/dist/core/llm/provider.js.map +1 -0
- package/dist/core/llm/routing-provider.js +52 -0
- package/dist/core/llm/routing-provider.js.map +1 -0
- package/dist/core/llm/routing-provider.test.js +94 -0
- package/dist/core/llm/routing-provider.test.js.map +1 -0
- package/dist/core/llm/schema.js +31 -0
- package/dist/core/llm/schema.js.map +1 -0
- package/dist/core/llm/snapshot-fetch.js +122 -0
- package/dist/core/llm/snapshot-fetch.js.map +1 -0
- package/dist/core/llm/snapshot-middleware.js +142 -0
- package/dist/core/llm/snapshot-middleware.js.map +1 -0
- package/dist/core/llm/snapshot-middleware.test.js +144 -0
- package/dist/core/llm/snapshot-middleware.test.js.map +1 -0
- package/dist/core/llm/state.js +48 -0
- package/dist/core/llm/state.js.map +1 -0
- package/dist/core/llm/tokens.js +40 -0
- package/dist/core/llm/tokens.js.map +1 -0
- package/dist/core/multi-agent.integration.test.js +298 -0
- package/dist/core/multi-agent.integration.test.js.map +1 -0
- package/dist/core/plugin-hooks.integration.test.js +344 -0
- package/dist/core/plugin-hooks.integration.test.js.map +1 -0
- package/dist/core/plugins/hook-types.js +5 -0
- package/dist/core/plugins/hook-types.js.map +1 -0
- package/dist/core/plugins/index.js +5 -0
- package/dist/core/plugins/index.js.map +1 -0
- package/dist/core/plugins/plugin-builder.js +321 -0
- package/dist/core/plugins/plugin-builder.js.map +1 -0
- package/dist/core/preset/config.js +54 -0
- package/dist/core/preset/config.js.map +1 -0
- package/dist/core/preset/index.js +6 -0
- package/dist/core/preset/index.js.map +1 -0
- package/dist/core/preset/preset-builder.js +63 -0
- package/dist/core/preset/preset-builder.js.map +1 -0
- package/dist/core/session-lifecycle.integration.test.js +159 -0
- package/dist/core/session-lifecycle.integration.test.js.map +1 -0
- package/dist/core/sessions/apply-event.js +41 -0
- package/dist/core/sessions/apply-event.js.map +1 -0
- package/dist/core/sessions/context.js +2 -0
- package/dist/core/sessions/context.js.map +1 -0
- package/dist/core/sessions/fork-utils.js +42 -0
- package/dist/core/sessions/fork-utils.js.map +1 -0
- package/dist/core/sessions/fork-utils.test.js +129 -0
- package/dist/core/sessions/fork-utils.test.js.map +1 -0
- package/dist/core/sessions/reducer.js +55 -0
- package/dist/core/sessions/reducer.js.map +1 -0
- package/dist/core/sessions/schema.js +66 -0
- package/dist/core/sessions/schema.js.map +1 -0
- package/dist/core/sessions/session-environment.js +2 -0
- package/dist/core/sessions/session-environment.js.map +1 -0
- package/dist/core/sessions/session-manager.js +650 -0
- package/dist/core/sessions/session-manager.js.map +1 -0
- package/dist/core/sessions/session-store.js +118 -0
- package/dist/core/sessions/session-store.js.map +1 -0
- package/dist/core/sessions/session.js +675 -0
- package/dist/core/sessions/session.js.map +1 -0
- package/dist/core/sessions/session.test.js +1095 -0
- package/dist/core/sessions/session.test.js.map +1 -0
- package/dist/core/sessions/state.js +377 -0
- package/dist/core/sessions/state.js.map +1 -0
- package/dist/core/system.js +66 -0
- package/dist/core/system.js.map +1 -0
- package/dist/core/tools/context.js +2 -0
- package/dist/core/tools/context.js.map +1 -0
- package/dist/core/tools/definition.js +4 -0
- package/dist/core/tools/definition.js.map +1 -0
- package/dist/core/tools/executor.js +82 -0
- package/dist/core/tools/executor.js.map +1 -0
- package/dist/core/tools/executor.test.js +143 -0
- package/dist/core/tools/executor.test.js.map +1 -0
- package/dist/core/tools/index.js +4 -0
- package/dist/core/tools/index.js.map +1 -0
- package/dist/core/tools/schema.js +20 -0
- package/dist/core/tools/schema.js.map +1 -0
- package/dist/core/tools/state.js +29 -0
- package/dist/core/tools/state.js.map +1 -0
- package/dist/index.js +70 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/json/index.js +5 -0
- package/dist/lib/json/index.js.map +1 -0
- package/dist/lib/logger/console.js +147 -0
- package/dist/lib/logger/console.js.map +1 -0
- package/dist/lib/logger/console.test.js +258 -0
- package/dist/lib/logger/console.test.js.map +1 -0
- package/dist/lib/logger/file.js +54 -0
- package/dist/lib/logger/file.js.map +1 -0
- package/dist/lib/logger/index.js +4 -0
- package/dist/lib/logger/index.js.map +1 -0
- package/dist/lib/logger/logger.js +28 -0
- package/dist/lib/logger/logger.js.map +1 -0
- package/dist/lib/logger/ring-buffer.js +61 -0
- package/dist/lib/logger/ring-buffer.js.map +1 -0
- package/dist/lib/logger/tee.js +43 -0
- package/dist/lib/logger/tee.js.map +1 -0
- package/dist/lib/mime.js +22 -0
- package/dist/lib/mime.js.map +1 -0
- package/dist/lib/never.js +4 -0
- package/dist/lib/never.js.map +1 -0
- package/dist/lib/utils/hash.js +35 -0
- package/dist/lib/utils/hash.js.map +1 -0
- package/dist/lib/utils/result.js +21 -0
- package/dist/lib/utils/result.js.map +1 -0
- package/dist/platform/fs.js +8 -0
- package/dist/platform/fs.js.map +1 -0
- package/dist/platform/index.js +9 -0
- package/dist/platform/index.js.map +1 -0
- package/dist/platform/process.js +8 -0
- package/dist/platform/process.js.map +1 -0
- package/dist/plugins/agent-status/plugin.js +77 -0
- package/dist/plugins/agent-status/plugin.js.map +1 -0
- package/dist/plugins/agents/agents.integration.test.js +683 -0
- package/dist/plugins/agents/agents.integration.test.js.map +1 -0
- package/dist/plugins/agents/index.js +2 -0
- package/dist/plugins/agents/index.js.map +1 -0
- package/dist/plugins/agents/plugin.js +199 -0
- package/dist/plugins/agents/plugin.js.map +1 -0
- package/dist/plugins/context-compact/context-compact.integration.test.js +174 -0
- package/dist/plugins/context-compact/context-compact.integration.test.js.map +1 -0
- package/dist/plugins/context-compact/context-compactor.js +238 -0
- package/dist/plugins/context-compact/context-compactor.js.map +1 -0
- package/dist/plugins/context-compact/context-compactor.test.js +763 -0
- package/dist/plugins/context-compact/context-compactor.test.js.map +1 -0
- package/dist/plugins/context-compact/history-offloader.js +42 -0
- package/dist/plugins/context-compact/history-offloader.js.map +1 -0
- package/dist/plugins/context-compact/history-offloader.test.js +77 -0
- package/dist/plugins/context-compact/history-offloader.test.js.map +1 -0
- package/dist/plugins/context-compact/index.js +4 -0
- package/dist/plugins/context-compact/index.js.map +1 -0
- package/dist/plugins/context-compact/plugin.js +37 -0
- package/dist/plugins/context-compact/plugin.js.map +1 -0
- package/dist/plugins/filesystem/filesystem.integration.test.js +411 -0
- package/dist/plugins/filesystem/filesystem.integration.test.js.map +1 -0
- package/dist/plugins/filesystem/helpers.js +170 -0
- package/dist/plugins/filesystem/helpers.js.map +1 -0
- package/dist/plugins/filesystem/index.js +3 -0
- package/dist/plugins/filesystem/index.js.map +1 -0
- package/dist/plugins/filesystem/listing.js +247 -0
- package/dist/plugins/filesystem/listing.js.map +1 -0
- package/dist/plugins/filesystem/plugin.js +364 -0
- package/dist/plugins/filesystem/plugin.js.map +1 -0
- package/dist/plugins/filesystem/schema.js +2 -0
- package/dist/plugins/filesystem/schema.js.map +1 -0
- package/dist/plugins/git-status/index.js +2 -0
- package/dist/plugins/git-status/index.js.map +1 -0
- package/dist/plugins/git-status/plugin.js +144 -0
- package/dist/plugins/git-status/plugin.js.map +1 -0
- package/dist/plugins/limits-guard/config.js +5 -0
- package/dist/plugins/limits-guard/config.js.map +1 -0
- package/dist/plugins/limits-guard/index.js +3 -0
- package/dist/plugins/limits-guard/index.js.map +1 -0
- package/dist/plugins/limits-guard/limit-guard.js +125 -0
- package/dist/plugins/limits-guard/limit-guard.js.map +1 -0
- package/dist/plugins/limits-guard/limit-guard.test.js +121 -0
- package/dist/plugins/limits-guard/limit-guard.test.js.map +1 -0
- package/dist/plugins/limits-guard/limits-guard.integration.test.js +378 -0
- package/dist/plugins/limits-guard/limits-guard.integration.test.js.map +1 -0
- package/dist/plugins/limits-guard/plugin.js +240 -0
- package/dist/plugins/limits-guard/plugin.js.map +1 -0
- package/dist/plugins/llm-debug/index.js +2 -0
- package/dist/plugins/llm-debug/index.js.map +1 -0
- package/dist/plugins/llm-debug/llm-debug.integration.test.js +157 -0
- package/dist/plugins/llm-debug/llm-debug.integration.test.js.map +1 -0
- package/dist/plugins/llm-debug/plugin.js +148 -0
- package/dist/plugins/llm-debug/plugin.js.map +1 -0
- package/dist/plugins/logs/index.js +2 -0
- package/dist/plugins/logs/index.js.map +1 -0
- package/dist/plugins/logs/plugin.js +38 -0
- package/dist/plugins/logs/plugin.js.map +1 -0
- package/dist/plugins/mailbox/helpers.js +66 -0
- package/dist/plugins/mailbox/helpers.js.map +1 -0
- package/dist/plugins/mailbox/index.js +9 -0
- package/dist/plugins/mailbox/index.js.map +1 -0
- package/dist/plugins/mailbox/mailbox.integration.test.js +605 -0
- package/dist/plugins/mailbox/mailbox.integration.test.js.map +1 -0
- package/dist/plugins/mailbox/plugin.js +204 -0
- package/dist/plugins/mailbox/plugin.js.map +1 -0
- package/dist/plugins/mailbox/prompts.js +93 -0
- package/dist/plugins/mailbox/prompts.js.map +1 -0
- package/dist/plugins/mailbox/query.js +38 -0
- package/dist/plugins/mailbox/query.js.map +1 -0
- package/dist/plugins/mailbox/schema.js +32 -0
- package/dist/plugins/mailbox/schema.js.map +1 -0
- package/dist/plugins/mailbox/state.js +41 -0
- package/dist/plugins/mailbox/state.js.map +1 -0
- package/dist/plugins/resources/index.js +4 -0
- package/dist/plugins/resources/index.js.map +1 -0
- package/dist/plugins/resources/manifest.js +20 -0
- package/dist/plugins/resources/manifest.js.map +1 -0
- package/dist/plugins/resources/plugin.js +171 -0
- package/dist/plugins/resources/plugin.js.map +1 -0
- package/dist/plugins/resources/post-inject.js +32 -0
- package/dist/plugins/resources/post-inject.js.map +1 -0
- package/dist/plugins/resources/state.js +16 -0
- package/dist/plugins/resources/state.js.map +1 -0
- package/dist/plugins/result-eviction/index.js +2 -0
- package/dist/plugins/result-eviction/index.js.map +1 -0
- package/dist/plugins/result-eviction/plugin.js +43 -0
- package/dist/plugins/result-eviction/plugin.js.map +1 -0
- package/dist/plugins/result-eviction/result-eviction.integration.test.js +217 -0
- package/dist/plugins/result-eviction/result-eviction.integration.test.js.map +1 -0
- package/dist/plugins/services/plugin.js +453 -0
- package/dist/plugins/services/plugin.js.map +1 -0
- package/dist/plugins/services/port-pool.js +70 -0
- package/dist/plugins/services/port-pool.js.map +1 -0
- package/dist/plugins/services/prompt.js +40 -0
- package/dist/plugins/services/prompt.js.map +1 -0
- package/dist/plugins/services/schema.js +9 -0
- package/dist/plugins/services/schema.js.map +1 -0
- package/dist/plugins/services/service.js +470 -0
- package/dist/plugins/services/service.js.map +1 -0
- package/dist/plugins/services/services.integration.test.js +485 -0
- package/dist/plugins/services/services.integration.test.js.map +1 -0
- package/dist/plugins/session-lifecycle/index.js +2 -0
- package/dist/plugins/session-lifecycle/index.js.map +1 -0
- package/dist/plugins/session-lifecycle/plugin.js +273 -0
- package/dist/plugins/session-lifecycle/plugin.js.map +1 -0
- package/dist/plugins/session-lifecycle/session-lifecycle.integration.test.js +498 -0
- package/dist/plugins/session-lifecycle/session-lifecycle.integration.test.js.map +1 -0
- package/dist/plugins/session-state/plugin.js +159 -0
- package/dist/plugins/session-state/plugin.js.map +1 -0
- package/dist/plugins/session-stats/index.js +3 -0
- package/dist/plugins/session-stats/index.js.map +1 -0
- package/dist/plugins/session-stats/plugin.js +81 -0
- package/dist/plugins/session-stats/plugin.js.map +1 -0
- package/dist/plugins/shell/executor.js +339 -0
- package/dist/plugins/shell/executor.js.map +1 -0
- package/dist/plugins/shell/index.js +6 -0
- package/dist/plugins/shell/index.js.map +1 -0
- package/dist/plugins/shell/plugin.js +66 -0
- package/dist/plugins/shell/plugin.js.map +1 -0
- package/dist/plugins/shell/shell.integration.test.js +234 -0
- package/dist/plugins/shell/shell.integration.test.js.map +1 -0
- package/dist/plugins/shell/shell.test.js +236 -0
- package/dist/plugins/shell/shell.test.js.map +1 -0
- package/dist/plugins/skills/discovery.js +205 -0
- package/dist/plugins/skills/discovery.js.map +1 -0
- package/dist/plugins/skills/discovery.test.js +312 -0
- package/dist/plugins/skills/discovery.test.js.map +1 -0
- package/dist/plugins/skills/index.js +12 -0
- package/dist/plugins/skills/index.js.map +1 -0
- package/dist/plugins/skills/plugin.js +293 -0
- package/dist/plugins/skills/plugin.js.map +1 -0
- package/dist/plugins/skills/prompts.js +70 -0
- package/dist/plugins/skills/prompts.js.map +1 -0
- package/dist/plugins/skills/schema.js +18 -0
- package/dist/plugins/skills/schema.js.map +1 -0
- package/dist/plugins/skills/skills.integration.test.js +475 -0
- package/dist/plugins/skills/skills.integration.test.js.map +1 -0
- package/dist/plugins/snapshotting/index.js +3 -0
- package/dist/plugins/snapshotting/index.js.map +1 -0
- package/dist/plugins/snapshotting/jj-snapshotter.js +106 -0
- package/dist/plugins/snapshotting/jj-snapshotter.js.map +1 -0
- package/dist/plugins/snapshotting/plugin.js +28 -0
- package/dist/plugins/snapshotting/plugin.js.map +1 -0
- package/dist/plugins/snapshotting/snapshotter.js +2 -0
- package/dist/plugins/snapshotting/snapshotter.js.map +1 -0
- package/dist/plugins/todo/index.js +7 -0
- package/dist/plugins/todo/index.js.map +1 -0
- package/dist/plugins/todo/plugin.js +319 -0
- package/dist/plugins/todo/plugin.js.map +1 -0
- package/dist/plugins/todo/prompts.js +54 -0
- package/dist/plugins/todo/prompts.js.map +1 -0
- package/dist/plugins/todo/schema.js +18 -0
- package/dist/plugins/todo/schema.js.map +1 -0
- package/dist/plugins/todo/todo.integration.test.js +605 -0
- package/dist/plugins/todo/todo.integration.test.js.map +1 -0
- package/dist/plugins/uploads/index.js +8 -0
- package/dist/plugins/uploads/index.js.map +1 -0
- package/dist/plugins/uploads/plugin.js +346 -0
- package/dist/plugins/uploads/plugin.js.map +1 -0
- package/dist/plugins/uploads/preprocessor.js +44 -0
- package/dist/plugins/uploads/preprocessor.js.map +1 -0
- package/dist/plugins/uploads/preprocessors/image-classifier.js +127 -0
- package/dist/plugins/uploads/preprocessors/image-classifier.js.map +1 -0
- package/dist/plugins/uploads/preprocessors/index.js +7 -0
- package/dist/plugins/uploads/preprocessors/index.js.map +1 -0
- package/dist/plugins/uploads/preprocessors/markitdown-preprocessor.js +204 -0
- package/dist/plugins/uploads/preprocessors/markitdown-preprocessor.js.map +1 -0
- package/dist/plugins/uploads/preprocessors/zip-preprocessor.js +172 -0
- package/dist/plugins/uploads/preprocessors/zip-preprocessor.js.map +1 -0
- package/dist/plugins/uploads/schema.js +20 -0
- package/dist/plugins/uploads/schema.js.map +1 -0
- package/dist/plugins/uploads/state.js +22 -0
- package/dist/plugins/uploads/state.js.map +1 -0
- package/dist/plugins/uploads/uploads.integration.test.js +496 -0
- package/dist/plugins/uploads/uploads.integration.test.js.map +1 -0
- package/dist/plugins/user-chat/index.js +5 -0
- package/dist/plugins/user-chat/index.js.map +1 -0
- package/dist/plugins/user-chat/plugin.js +544 -0
- package/dist/plugins/user-chat/plugin.js.map +1 -0
- package/dist/plugins/user-chat/prompts.js +29 -0
- package/dist/plugins/user-chat/prompts.js.map +1 -0
- package/dist/plugins/user-chat/schema.js +46 -0
- package/dist/plugins/user-chat/schema.js.map +1 -0
- package/dist/plugins/user-chat/user-chat.integration.test.js +668 -0
- package/dist/plugins/user-chat/user-chat.integration.test.js.map +1 -0
- package/dist/plugins/workers/context.js +143 -0
- package/dist/plugins/workers/context.js.map +1 -0
- package/dist/plugins/workers/definition.js +30 -0
- package/dist/plugins/workers/definition.js.map +1 -0
- package/dist/plugins/workers/index.js +7 -0
- package/dist/plugins/workers/index.js.map +1 -0
- package/dist/plugins/workers/plugin.js +578 -0
- package/dist/plugins/workers/plugin.js.map +1 -0
- package/dist/plugins/workers/worker.js +18 -0
- package/dist/plugins/workers/worker.js.map +1 -0
- package/dist/plugins/workers/workers.integration.test.js +629 -0
- package/dist/plugins/workers/workers.integration.test.js.map +1 -0
- package/dist/prompts/base.js +239 -0
- package/dist/prompts/base.js.map +1 -0
- package/dist/prompts/builder.js +131 -0
- package/dist/prompts/builder.js.map +1 -0
- package/dist/prompts/index.js +20 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/prompts/macros.js +26 -0
- package/dist/prompts/macros.js.map +1 -0
- package/dist/prompts/macros.test.js +80 -0
- package/dist/prompts/macros.test.js.map +1 -0
- package/dist/testing/bootstrap-for-testing.js +28 -0
- package/dist/testing/bootstrap-for-testing.js.map +1 -0
- package/dist/testing/index.js +7 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/node-platform.js +65 -0
- package/dist/testing/node-platform.js.map +1 -0
- package/dist/testing/notification-collector.js +82 -0
- package/dist/testing/notification-collector.js.map +1 -0
- package/dist/testing/preset-helpers.js +37 -0
- package/dist/testing/preset-helpers.js.map +1 -0
- package/dist/testing/test-harness.js +226 -0
- package/dist/testing/test-harness.js.map +1 -0
- package/dist/testing/test-harness.test.js +51 -0
- package/dist/testing/test-harness.test.js.map +1 -0
- package/dist/testing/wait-helpers.js +64 -0
- package/dist/testing/wait-helpers.js.map +1 -0
- package/dist/transport/adapter/client-adapter.js +64 -0
- package/dist/transport/adapter/client-adapter.js.map +1 -0
- package/dist/transport/adapter/index.js +24 -0
- package/dist/transport/adapter/index.js.map +1 -0
- package/dist/transport/adapter/server-adapter.js +73 -0
- package/dist/transport/adapter/server-adapter.js.map +1 -0
- package/dist/transport/adapter/types.js +8 -0
- package/dist/transport/adapter/types.js.map +1 -0
- package/dist/transport/http/app.js +86 -0
- package/dist/transport/http/app.js.map +1 -0
- package/dist/transport/http/index.js +6 -0
- package/dist/transport/http/index.js.map +1 -0
- package/dist/transport/http/middleware/bearer-auth.js +33 -0
- package/dist/transport/http/middleware/bearer-auth.js.map +1 -0
- package/dist/transport/http/middleware/error-handler.js +56 -0
- package/dist/transport/http/middleware/error-handler.js.map +1 -0
- package/dist/transport/http/routes/files.js +237 -0
- package/dist/transport/http/routes/files.js.map +1 -0
- package/dist/transport/http/routes/resources.js +77 -0
- package/dist/transport/http/routes/resources.js.map +1 -0
- package/dist/transport/http/routes/rpc.integration.test.js +189 -0
- package/dist/transport/http/routes/rpc.integration.test.js.map +1 -0
- package/dist/transport/http/routes/rpc.js +110 -0
- package/dist/transport/http/routes/rpc.js.map +1 -0
- package/dist/transport/http/routes/rpc.test.js +316 -0
- package/dist/transport/http/routes/rpc.test.js.map +1 -0
- package/dist/transport/http/routes/upload.js +205 -0
- package/dist/transport/http/routes/upload.js.map +1 -0
- package/dist/transport/rpc/index.js +7 -0
- package/dist/transport/rpc/index.js.map +1 -0
- package/dist/transport/rpc/methods.js +8 -0
- package/dist/transport/rpc/methods.js.map +1 -0
- package/dist/user-config.js +14 -0
- package/dist/user-config.js.map +1 -0
- package/package.json +47 -57
|
@@ -0,0 +1,1205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent - OOP wrapper for agent orchestration.
|
|
3
|
+
*
|
|
4
|
+
* Responsibilities:
|
|
5
|
+
* - Process mailbox (inference + tool execution loop)
|
|
6
|
+
* - Embedded scheduling with debounce
|
|
7
|
+
* - Spawn child agents
|
|
8
|
+
*/
|
|
9
|
+
import { agentEvents } from '../../core/agents/state.js';
|
|
10
|
+
import { withSessionId } from '../../core/events/test-helpers.js';
|
|
11
|
+
import { applyCacheBreakpoint } from '../../core/llm/cache-breakpoints.js';
|
|
12
|
+
import { LLMCallId } from '../../core/llm/schema.js';
|
|
13
|
+
import { llmEvents } from '../../core/llm/state.js';
|
|
14
|
+
import { buildPluginDeps } from '../../core/plugins/plugin-builder.js';
|
|
15
|
+
import { ToolCallId } from '../../core/tools/schema.js';
|
|
16
|
+
import { toolEvents } from '../../core/tools/state.js';
|
|
17
|
+
import { getAgentUnconsumedMailbox, selectMailboxState } from '../../plugins/mailbox/query.js';
|
|
18
|
+
import { AGENT_BASE_BRIEFING } from '../../prompts/base.js';
|
|
19
|
+
import { buildEnvironmentSection } from '../../prompts/builder.js';
|
|
20
|
+
import { sanitizeLLMResponse } from './response-sanitizer.js';
|
|
21
|
+
import { withLLMRetry } from './retry.js';
|
|
22
|
+
// ============================================================================
|
|
23
|
+
// Agent
|
|
24
|
+
// ============================================================================
|
|
25
|
+
/**
|
|
26
|
+
* Agent handles mailbox processing, inference, and tool execution.
|
|
27
|
+
*
|
|
28
|
+
* Features:
|
|
29
|
+
* - Debounced processing (timer or callback-based)
|
|
30
|
+
* - LLM inference with retry
|
|
31
|
+
* - Context compaction
|
|
32
|
+
* - Tool execution
|
|
33
|
+
*/
|
|
34
|
+
export class Agent {
|
|
35
|
+
id;
|
|
36
|
+
config;
|
|
37
|
+
sessionContext;
|
|
38
|
+
store;
|
|
39
|
+
logger;
|
|
40
|
+
llmProvider;
|
|
41
|
+
llmProviders;
|
|
42
|
+
toolExecutor;
|
|
43
|
+
plugins;
|
|
44
|
+
environment;
|
|
45
|
+
fileStore;
|
|
46
|
+
pluginContexts;
|
|
47
|
+
sendNotification;
|
|
48
|
+
pluginMethodCaller;
|
|
49
|
+
scheduleCallback;
|
|
50
|
+
/** Merged tools map: config tools + plugin tools (plugins override). Rebuilt each processing cycle. */
|
|
51
|
+
tools;
|
|
52
|
+
// Scheduler state (embedded)
|
|
53
|
+
debounceTimer;
|
|
54
|
+
processing = false;
|
|
55
|
+
scheduled = false;
|
|
56
|
+
pendingReschedule = false;
|
|
57
|
+
abortController = new AbortController();
|
|
58
|
+
/** Track conversation turn number for handler context */
|
|
59
|
+
turnNumber = 0;
|
|
60
|
+
constructor(deps) {
|
|
61
|
+
this.id = deps.id;
|
|
62
|
+
this.config = deps.config;
|
|
63
|
+
this.sessionContext = deps.sessionContext;
|
|
64
|
+
this.store = deps.store;
|
|
65
|
+
this.logger = deps.logger;
|
|
66
|
+
this.llmProvider = deps.llmProvider;
|
|
67
|
+
this.llmProviders = deps.llmProviders ?? new Map();
|
|
68
|
+
this.toolExecutor = deps.toolExecutor;
|
|
69
|
+
this.plugins = deps.plugins;
|
|
70
|
+
this.environment = deps.environment;
|
|
71
|
+
this.fileStore = deps.fileStore;
|
|
72
|
+
this.pluginContexts = deps.pluginContexts ?? new Map();
|
|
73
|
+
this.sendNotification = deps.sendNotification;
|
|
74
|
+
this.pluginMethodCaller = deps.pluginMethodCaller;
|
|
75
|
+
this.scheduleCallback = deps.schedule;
|
|
76
|
+
this.tools = this.buildToolsMap();
|
|
77
|
+
// Initialize turn number from conversation history
|
|
78
|
+
const state = this.state;
|
|
79
|
+
if (state) {
|
|
80
|
+
this.turnNumber = state.conversationHistory.filter((m) => m.role === 'assistant').length;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Get the current agent state from store.
|
|
85
|
+
*/
|
|
86
|
+
get state() {
|
|
87
|
+
return this.store.getAgentState(this.id);
|
|
88
|
+
}
|
|
89
|
+
// ============================================================================
|
|
90
|
+
// Public API
|
|
91
|
+
// ============================================================================
|
|
92
|
+
/**
|
|
93
|
+
* Unified processing entry point - decides what to do next.
|
|
94
|
+
* Safe to call multiple times; skips if already processing.
|
|
95
|
+
*/
|
|
96
|
+
async continue() {
|
|
97
|
+
if (this.processing)
|
|
98
|
+
return;
|
|
99
|
+
if (this.store.isClosed())
|
|
100
|
+
return;
|
|
101
|
+
this.processing = true;
|
|
102
|
+
this.scheduled = false;
|
|
103
|
+
try {
|
|
104
|
+
while (true) {
|
|
105
|
+
const agentState = this.state;
|
|
106
|
+
if (!agentState)
|
|
107
|
+
break;
|
|
108
|
+
// Rebuild tools map each cycle so plugin-dynamic tools reflect current state
|
|
109
|
+
this.tools = this.buildToolsMap();
|
|
110
|
+
const decision = this.decide(agentState);
|
|
111
|
+
switch (decision) {
|
|
112
|
+
case 'idle':
|
|
113
|
+
this.logger.debug('Agent has nothing to do', {
|
|
114
|
+
agentId: this.id,
|
|
115
|
+
status: agentState.status,
|
|
116
|
+
});
|
|
117
|
+
return;
|
|
118
|
+
case 'paused':
|
|
119
|
+
this.logger.debug('Agent is paused, skipping processing', { agentId: this.id });
|
|
120
|
+
return;
|
|
121
|
+
case 'on_start':
|
|
122
|
+
await this.executeOnStart(agentState);
|
|
123
|
+
continue;
|
|
124
|
+
case 'tool_exec':
|
|
125
|
+
this.logger.info('Executing pending tool calls', {
|
|
126
|
+
agentId: this.id,
|
|
127
|
+
count: agentState.pendingToolCalls.length,
|
|
128
|
+
});
|
|
129
|
+
for (const toolCall of agentState.pendingToolCalls) {
|
|
130
|
+
await this.executeToolCall(toolCall);
|
|
131
|
+
}
|
|
132
|
+
// Schedule re-entry via debounce after tool execution
|
|
133
|
+
// (allows debounce callback to wait for child responses, etc.)
|
|
134
|
+
this.scheduleProcessing();
|
|
135
|
+
return;
|
|
136
|
+
case 'resume_from_error':
|
|
137
|
+
await this.store.emit(withSessionId(this.store.sessionId, agentEvents.create('agent_resumed', { agentId: this.id })));
|
|
138
|
+
continue;
|
|
139
|
+
case 'infer':
|
|
140
|
+
await this.runInference(agentState);
|
|
141
|
+
continue;
|
|
142
|
+
case 'complete':
|
|
143
|
+
await this.executeOnComplete(agentState);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
catch (err) {
|
|
149
|
+
if (this.abortController.signal.aborted) {
|
|
150
|
+
this.logger.debug('Agent processing aborted', { agentId: this.id });
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
this.logger.error('Unexpected error in agent processing', err instanceof Error ? err : new Error(String(err)), {
|
|
154
|
+
agentId: this.id,
|
|
155
|
+
sessionId: this.store.sessionId,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
finally {
|
|
159
|
+
this.processing = false;
|
|
160
|
+
if (this.pendingReschedule) {
|
|
161
|
+
this.pendingReschedule = false;
|
|
162
|
+
this.scheduleProcessing();
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Schedule processing with debounce.
|
|
168
|
+
* Use this when receiving new messages or after tool execution.
|
|
169
|
+
*/
|
|
170
|
+
scheduleProcessing() {
|
|
171
|
+
if (this.scheduled)
|
|
172
|
+
return;
|
|
173
|
+
if (this.store.isClosed())
|
|
174
|
+
return;
|
|
175
|
+
if (this.processing) {
|
|
176
|
+
this.pendingReschedule = true;
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
this.cancelSchedule();
|
|
180
|
+
this.scheduled = true;
|
|
181
|
+
const agentState = this.state;
|
|
182
|
+
if (!agentState)
|
|
183
|
+
return;
|
|
184
|
+
if (this.config.debounceCallback) {
|
|
185
|
+
// Callback-based debounce using recursive setTimeout
|
|
186
|
+
const checkInterval = this.config.checkIntervalMs ?? 100;
|
|
187
|
+
const scheduleCheck = () => {
|
|
188
|
+
this.debounceTimer = setTimeout(async () => {
|
|
189
|
+
// Re-read state fresh each check — no stale data
|
|
190
|
+
const currentState = this.state;
|
|
191
|
+
if (!currentState) {
|
|
192
|
+
this.cancelSchedule();
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
// Guard: schedule could be cancelled between timer fire and here
|
|
196
|
+
if (!this.scheduled)
|
|
197
|
+
return;
|
|
198
|
+
const sessionState = this.store.getState();
|
|
199
|
+
const unconsumed = getUnconsumedMessages(sessionState, this.id);
|
|
200
|
+
const pendingToolResults = currentState.pendingToolResults;
|
|
201
|
+
// If no messages, no pending tool results, and no plugin pending, nothing to do
|
|
202
|
+
if (unconsumed.length === 0 && pendingToolResults.length === 0 && !this.hasPluginPendingMessages()) {
|
|
203
|
+
this.cancelSchedule();
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
const oldestTimestamp = unconsumed.length > 0
|
|
207
|
+
? Math.min(...unconsumed.map((m) => m.timestamp))
|
|
208
|
+
: Date.now();
|
|
209
|
+
const oldestWaitingMs = Date.now() - oldestTimestamp;
|
|
210
|
+
const decision = await this.config.debounceCallback({
|
|
211
|
+
messages: unconsumed,
|
|
212
|
+
oldestWaitingMs,
|
|
213
|
+
totalPending: unconsumed.length,
|
|
214
|
+
pendingToolResults,
|
|
215
|
+
});
|
|
216
|
+
// Re-check after async callback — schedule could be cancelled during await
|
|
217
|
+
if (!this.scheduled)
|
|
218
|
+
return;
|
|
219
|
+
if (decision === 'process_now') {
|
|
220
|
+
this.cancelSchedule();
|
|
221
|
+
this.continue().catch((err) => {
|
|
222
|
+
this.logger.error('Unhandled error in continue()', err instanceof Error ? err : undefined, { agentId: this.id });
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
// Callback said "wait" — schedule next check.
|
|
227
|
+
// Fresh state will be read on next iteration.
|
|
228
|
+
scheduleCheck();
|
|
229
|
+
}
|
|
230
|
+
}, checkInterval);
|
|
231
|
+
};
|
|
232
|
+
scheduleCheck();
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
// Timer-based debounce (default: 500ms)
|
|
236
|
+
const debounceMs = this.config.debounceMs ?? 500;
|
|
237
|
+
this.debounceTimer = setTimeout(() => {
|
|
238
|
+
this.scheduled = false;
|
|
239
|
+
this.debounceTimer = undefined;
|
|
240
|
+
this.continue().catch((err) => {
|
|
241
|
+
this.logger.error('Unhandled error in continue()', err instanceof Error ? err : undefined, { agentId: this.id });
|
|
242
|
+
});
|
|
243
|
+
}, debounceMs);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Shutdown the agent - cancel any scheduled processing.
|
|
248
|
+
*/
|
|
249
|
+
shutdown() {
|
|
250
|
+
try {
|
|
251
|
+
this.abortController.abort();
|
|
252
|
+
}
|
|
253
|
+
catch {
|
|
254
|
+
// AbortError may be thrown synchronously by abort signal listeners
|
|
255
|
+
}
|
|
256
|
+
this.cancelSchedule();
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Check if processing is scheduled.
|
|
260
|
+
*/
|
|
261
|
+
isScheduled() {
|
|
262
|
+
return this.scheduled;
|
|
263
|
+
}
|
|
264
|
+
// ============================================================================
|
|
265
|
+
// Private methods - Processing
|
|
266
|
+
// ============================================================================
|
|
267
|
+
/**
|
|
268
|
+
* Determine the next action based on current agent state.
|
|
269
|
+
*/
|
|
270
|
+
decide(state) {
|
|
271
|
+
if (state.status === 'paused')
|
|
272
|
+
return 'paused';
|
|
273
|
+
if (!state.onStartCalled)
|
|
274
|
+
return 'on_start';
|
|
275
|
+
if (state.status === 'tool_exec' && state.pendingToolCalls.length > 0)
|
|
276
|
+
return 'tool_exec';
|
|
277
|
+
if (state.status === 'pending') {
|
|
278
|
+
if (hasWork(state) || this.hasPluginPendingMessages())
|
|
279
|
+
return 'infer';
|
|
280
|
+
return 'complete';
|
|
281
|
+
}
|
|
282
|
+
if (state.status === 'errored') {
|
|
283
|
+
// Errored with new messages (e.g. user sent a message): emit resume event, then retry
|
|
284
|
+
// Only check for new messages, not stale pendingToolResults — those were present when inference failed
|
|
285
|
+
if (this.hasPluginPendingMessages())
|
|
286
|
+
return 'resume_from_error';
|
|
287
|
+
return 'complete';
|
|
288
|
+
}
|
|
289
|
+
return 'idle';
|
|
290
|
+
}
|
|
291
|
+
static MAX_INFERENCE_RETRIES = 3;
|
|
292
|
+
/**
|
|
293
|
+
* Run inference on agent's mailbox.
|
|
294
|
+
* Runs when there are unconsumed messages OR pending tool results.
|
|
295
|
+
*/
|
|
296
|
+
async runInference(initialAgentState, retryCount = 0) {
|
|
297
|
+
let agentState = initialAgentState;
|
|
298
|
+
const hasToolResults = agentState.pendingToolResults.length > 0;
|
|
299
|
+
// Collect plugin dequeue messages (includes mailbox messages)
|
|
300
|
+
const pluginDequeued = this.collectPluginMessages();
|
|
301
|
+
// Need either tool results or plugin messages to process
|
|
302
|
+
if (!hasToolResults && pluginDequeued.length === 0)
|
|
303
|
+
return;
|
|
304
|
+
this.turnNumber++;
|
|
305
|
+
// 0. beforeInference handler - can skip LLM entirely or pause
|
|
306
|
+
const beforeResult = await this.executeBeforeInference(agentState);
|
|
307
|
+
if (beforeResult !== null) {
|
|
308
|
+
if (beforeResult.action === 'skip') {
|
|
309
|
+
// Skip LLM, use provided response directly
|
|
310
|
+
await this.emitInferenceCompleted(beforeResult.response, undefined);
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
if (beforeResult.action === 'pause') {
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
// 1. Context compaction is now handled by the context-compact plugin's beforeInference hook
|
|
318
|
+
// (which runs above). If compaction occurred, agent state was updated via context_compacted event.
|
|
319
|
+
// Re-read agent state to pick up any compacted history.
|
|
320
|
+
const postHookState = this.state;
|
|
321
|
+
if (postHookState) {
|
|
322
|
+
agentState = postHookState;
|
|
323
|
+
}
|
|
324
|
+
// 2. Build pending messages (tool results only)
|
|
325
|
+
const pendingMessages = this.buildPendingMessages(agentState);
|
|
326
|
+
// 2b. Append plugin dequeued messages (includes mailbox messages)
|
|
327
|
+
for (const dequeued of pluginDequeued) {
|
|
328
|
+
pendingMessages.push(...dequeued.messages);
|
|
329
|
+
}
|
|
330
|
+
// 3. Inference start - emit with pending messages
|
|
331
|
+
await this.store.emit(withSessionId(this.store.sessionId, llmEvents.create('inference_started', {
|
|
332
|
+
agentId: this.id,
|
|
333
|
+
messages: pendingMessages,
|
|
334
|
+
consumedMessageIds: [],
|
|
335
|
+
})));
|
|
336
|
+
// 4. Build LLM messages — re-read state to include inference_started changes
|
|
337
|
+
const preInferenceState = this.state;
|
|
338
|
+
if (preInferenceState) {
|
|
339
|
+
agentState = preInferenceState;
|
|
340
|
+
}
|
|
341
|
+
const messages = this.buildLLMMessages(agentState, pendingMessages);
|
|
342
|
+
// 4b. Append ephemeral context (not stored in history, recreated each inference)
|
|
343
|
+
const ephemeralParts = [];
|
|
344
|
+
// Collect status messages from all plugins
|
|
345
|
+
const pluginStatus = this.getPluginStatus();
|
|
346
|
+
if (pluginStatus)
|
|
347
|
+
ephemeralParts.push(pluginStatus);
|
|
348
|
+
if (ephemeralParts.length > 0) {
|
|
349
|
+
messages.push({
|
|
350
|
+
role: 'user',
|
|
351
|
+
content: `<session-context>\n${ephemeralParts.join('\n\n')}\n</session-context>`,
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
// Mark cache breakpoint — ephemeral session-context suffix is excluded
|
|
355
|
+
// so it doesn't invalidate the cache on every inference.
|
|
356
|
+
const cachedMessages = applyCacheBreakpoint(messages, ephemeralParts.length > 0 ? 1 : 0);
|
|
357
|
+
// 5. LLM inference (with retry)
|
|
358
|
+
const request = {
|
|
359
|
+
model: this.config.model,
|
|
360
|
+
systemPrompt: this.buildSystemPrompt(),
|
|
361
|
+
messages: cachedMessages,
|
|
362
|
+
tools: this.tools.size > 0 ? [...this.tools.values()] : undefined,
|
|
363
|
+
// Stop sequences to prevent hallucination of message tags
|
|
364
|
+
stopSequences: ['<message'],
|
|
365
|
+
};
|
|
366
|
+
this.logger.debug('Running inference', {
|
|
367
|
+
sessionId: this.store.sessionId,
|
|
368
|
+
agentId: this.id,
|
|
369
|
+
messageCount: messages.length,
|
|
370
|
+
});
|
|
371
|
+
// Capture llmCallId from the logging provider
|
|
372
|
+
let llmCallId;
|
|
373
|
+
const llmResponse = await withLLMRetry(() => this.llmProvider.inference(request, {
|
|
374
|
+
sessionId: this.store.sessionId,
|
|
375
|
+
agentId: this.id,
|
|
376
|
+
onLLMCallCreated: (callId) => {
|
|
377
|
+
llmCallId = LLMCallId(callId);
|
|
378
|
+
},
|
|
379
|
+
signal: this.abortController.signal,
|
|
380
|
+
fileStore: this.fileStore,
|
|
381
|
+
providers: this.llmProviders,
|
|
382
|
+
}), { logger: this.logger, signal: this.abortController.signal });
|
|
383
|
+
// Mark plugin messages as consumed (regardless of inference outcome —
|
|
384
|
+
// messages are already appended to conversationHistory via inference_started)
|
|
385
|
+
{
|
|
386
|
+
const currentAgentState = this.state;
|
|
387
|
+
if (currentAgentState) {
|
|
388
|
+
const ctx = this.buildAgentContext(currentAgentState);
|
|
389
|
+
for (const dequeued of pluginDequeued) {
|
|
390
|
+
if (!dequeued.plugin.dequeue)
|
|
391
|
+
continue;
|
|
392
|
+
const pluginCtx = this.buildPluginHookContext(dequeued.plugin, ctx);
|
|
393
|
+
await dequeued.plugin.dequeue.markConsumed(pluginCtx, dequeued.token);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
if (!llmResponse.ok) {
|
|
398
|
+
// 4a. Inference failed
|
|
399
|
+
await this.store.emit(withSessionId(this.store.sessionId, llmEvents.create('inference_failed', {
|
|
400
|
+
agentId: this.id,
|
|
401
|
+
error: llmResponse.error.message,
|
|
402
|
+
llmCallId,
|
|
403
|
+
})));
|
|
404
|
+
// Notify plugins (e.g. mailbox sends error message to parent)
|
|
405
|
+
const errorState = this.state;
|
|
406
|
+
if (errorState) {
|
|
407
|
+
await this.executeOnError(errorState, llmResponse.error.message);
|
|
408
|
+
}
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
// 4c. Sanitize response to prevent hallucination
|
|
412
|
+
const sanitized = sanitizeLLMResponse(llmResponse.value.content);
|
|
413
|
+
if (sanitized.wasTruncated) {
|
|
414
|
+
this.logger.warn('LLM response was truncated (potential hallucination)', {
|
|
415
|
+
agentId: this.id,
|
|
416
|
+
sessionId: this.store.sessionId,
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
// Build response object
|
|
420
|
+
let response = {
|
|
421
|
+
content: sanitized.content,
|
|
422
|
+
toolCalls: llmResponse.value.toolCalls.map((tc) => ({
|
|
423
|
+
id: tc.id,
|
|
424
|
+
name: tc.name,
|
|
425
|
+
input: tc.input,
|
|
426
|
+
})),
|
|
427
|
+
};
|
|
428
|
+
// 4c. afterInference handler - can modify response, request retry, or pause
|
|
429
|
+
// Re-read state: inference events have been processed since last read
|
|
430
|
+
const postInferenceState = this.state;
|
|
431
|
+
if (postInferenceState) {
|
|
432
|
+
agentState = postInferenceState;
|
|
433
|
+
}
|
|
434
|
+
const afterResult = await this.executeAfterInference(agentState, response);
|
|
435
|
+
if (afterResult !== null) {
|
|
436
|
+
if (afterResult.action === 'pause') {
|
|
437
|
+
// Inference completed and messages were consumed — commit the turn
|
|
438
|
+
// before pausing so pendingMessages move to conversationHistory.
|
|
439
|
+
await this.emitInferenceCompleted(response, llmCallId, llmResponse.value.metrics);
|
|
440
|
+
await this.emitHandlerPause(afterResult.reason);
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
if (afterResult.action === 'retry') {
|
|
444
|
+
if (retryCount >= Agent.MAX_INFERENCE_RETRIES) {
|
|
445
|
+
this.logger.warn('afterInference retry limit reached, continuing with current response', {
|
|
446
|
+
agentId: this.id,
|
|
447
|
+
retryCount,
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
else {
|
|
451
|
+
// Retry inference - decrement turn number and recursively call with fresh state
|
|
452
|
+
this.turnNumber--;
|
|
453
|
+
const freshState = this.state;
|
|
454
|
+
if (!freshState)
|
|
455
|
+
return;
|
|
456
|
+
await this.runInference(freshState, retryCount + 1);
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
else if (afterResult.action === 'modify') {
|
|
461
|
+
response = afterResult.response;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
// 4d. Inference completed
|
|
465
|
+
// Tool calls will be executed in the next continue() cycle
|
|
466
|
+
await this.emitInferenceCompleted(response, llmCallId, llmResponse.value.metrics);
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Emit inference_completed event.
|
|
470
|
+
*/
|
|
471
|
+
async emitInferenceCompleted(response, llmCallId, metrics) {
|
|
472
|
+
await this.store.emit(withSessionId(this.store.sessionId, llmEvents.create('inference_completed', {
|
|
473
|
+
agentId: this.id,
|
|
474
|
+
consumedMessageIds: [],
|
|
475
|
+
response: {
|
|
476
|
+
content: response.content,
|
|
477
|
+
toolCalls: response.toolCalls.map((tc) => ({
|
|
478
|
+
id: ToolCallId(tc.id),
|
|
479
|
+
name: tc.name,
|
|
480
|
+
input: tc.input,
|
|
481
|
+
})),
|
|
482
|
+
},
|
|
483
|
+
metrics: metrics ?? {
|
|
484
|
+
promptTokens: 0,
|
|
485
|
+
completionTokens: 0,
|
|
486
|
+
totalTokens: 0,
|
|
487
|
+
latencyMs: 0,
|
|
488
|
+
model: 'handler-skip',
|
|
489
|
+
},
|
|
490
|
+
llmCallId,
|
|
491
|
+
})));
|
|
492
|
+
}
|
|
493
|
+
// ============================================================================
|
|
494
|
+
// Private methods - Scheduling
|
|
495
|
+
// ============================================================================
|
|
496
|
+
/**
|
|
497
|
+
* Cancel any scheduled processing.
|
|
498
|
+
*/
|
|
499
|
+
cancelSchedule() {
|
|
500
|
+
if (this.debounceTimer) {
|
|
501
|
+
clearTimeout(this.debounceTimer);
|
|
502
|
+
this.debounceTimer = undefined;
|
|
503
|
+
}
|
|
504
|
+
this.scheduled = false;
|
|
505
|
+
this.pendingReschedule = false;
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Execute a single tool call.
|
|
509
|
+
*/
|
|
510
|
+
async executeToolCall(toolCall) {
|
|
511
|
+
const agentState = this.state;
|
|
512
|
+
if (!agentState)
|
|
513
|
+
return;
|
|
514
|
+
// beforeToolCall handler - can block, replace, or pause the tool call
|
|
515
|
+
let effectiveToolCall = toolCall;
|
|
516
|
+
const beforeResult = await this.executeBeforeToolCall(agentState, toolCall);
|
|
517
|
+
if (beforeResult !== null) {
|
|
518
|
+
if (beforeResult.action === 'pause') {
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
if (beforeResult.action === 'block') {
|
|
522
|
+
// Emit tool_failed with the block reason
|
|
523
|
+
await this.store.emit(withSessionId(this.store.sessionId, toolEvents.create('tool_started', {
|
|
524
|
+
agentId: this.id,
|
|
525
|
+
toolCallId: toolCall.id,
|
|
526
|
+
toolName: toolCall.name,
|
|
527
|
+
input: toInputRecord(toolCall.input),
|
|
528
|
+
})));
|
|
529
|
+
await this.store.emit(withSessionId(this.store.sessionId, toolEvents.create('tool_failed', {
|
|
530
|
+
agentId: this.id,
|
|
531
|
+
toolCallId: toolCall.id,
|
|
532
|
+
error: `Tool blocked by handler: ${beforeResult.reason}`,
|
|
533
|
+
})));
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
else if (beforeResult.action === 'replace') {
|
|
537
|
+
effectiveToolCall = {
|
|
538
|
+
id: ToolCallId(beforeResult.toolCall.id),
|
|
539
|
+
name: beforeResult.toolCall.name,
|
|
540
|
+
input: beforeResult.toolCall.input,
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
// Start event
|
|
545
|
+
await this.store.emit(withSessionId(this.store.sessionId, toolEvents.create('tool_started', {
|
|
546
|
+
agentId: this.id,
|
|
547
|
+
toolCallId: effectiveToolCall.id,
|
|
548
|
+
toolName: effectiveToolCall.name,
|
|
549
|
+
input: toInputRecord(effectiveToolCall.input),
|
|
550
|
+
})));
|
|
551
|
+
const tool = this.tools.get(effectiveToolCall.name);
|
|
552
|
+
if (!tool) {
|
|
553
|
+
await this.store.emit(withSessionId(this.store.sessionId, toolEvents.create('tool_failed', {
|
|
554
|
+
agentId: this.id,
|
|
555
|
+
toolCallId: effectiveToolCall.id,
|
|
556
|
+
error: `Unknown tool: ${effectiveToolCall.name}`,
|
|
557
|
+
})));
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
const context = {
|
|
561
|
+
...this.buildAgentContext(agentState),
|
|
562
|
+
logger: this.logger.child({ toolName: toolCall.name }),
|
|
563
|
+
};
|
|
564
|
+
const result = await this.toolExecutor.execute(tool, effectiveToolCall.input, context);
|
|
565
|
+
// Build initial tool result with ToolResultContent
|
|
566
|
+
let toolResult = result.ok
|
|
567
|
+
? { isError: false, content: result.value }
|
|
568
|
+
: { isError: true, content: result.error.message };
|
|
569
|
+
// afterToolCall handler - can modify result or pause
|
|
570
|
+
const currentAgentState = this.state;
|
|
571
|
+
if (currentAgentState) {
|
|
572
|
+
const afterResult = await this.executeAfterToolCall(currentAgentState, effectiveToolCall, toolResult);
|
|
573
|
+
if (afterResult !== null) {
|
|
574
|
+
if (afterResult.action === 'pause') {
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
if (afterResult.action === 'modify') {
|
|
578
|
+
toolResult = afterResult.result;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
// Result event
|
|
583
|
+
if (!toolResult.isError) {
|
|
584
|
+
await this.store.emit(withSessionId(this.store.sessionId, toolEvents.create('tool_completed', {
|
|
585
|
+
agentId: this.id,
|
|
586
|
+
toolCallId: effectiveToolCall.id,
|
|
587
|
+
result: toolResult.content,
|
|
588
|
+
})));
|
|
589
|
+
}
|
|
590
|
+
else {
|
|
591
|
+
// Convert content to string for tool_failed error field
|
|
592
|
+
const errorMessage = typeof toolResult.content === 'string'
|
|
593
|
+
? toolResult.content
|
|
594
|
+
: JSON.stringify(toolResult.content);
|
|
595
|
+
await this.store.emit(withSessionId(this.store.sessionId, toolEvents.create('tool_failed', {
|
|
596
|
+
agentId: this.id,
|
|
597
|
+
toolCallId: effectiveToolCall.id,
|
|
598
|
+
error: errorMessage,
|
|
599
|
+
})));
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
// ============================================================================
|
|
603
|
+
// Handler execution methods
|
|
604
|
+
// ============================================================================
|
|
605
|
+
/**
|
|
606
|
+
* Emit agent_paused event with reason 'handler'.
|
|
607
|
+
*/
|
|
608
|
+
async emitHandlerPause(message) {
|
|
609
|
+
await this.store.emit(withSessionId(this.store.sessionId, agentEvents.create('agent_paused', {
|
|
610
|
+
agentId: this.id,
|
|
611
|
+
reason: 'handler',
|
|
612
|
+
message,
|
|
613
|
+
})));
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Build base AgentContext for handler/hook calls.
|
|
617
|
+
*/
|
|
618
|
+
buildAgentContext(agentState) {
|
|
619
|
+
return {
|
|
620
|
+
// SessionContext fields (refreshed from store for up-to-date state)
|
|
621
|
+
sessionId: this.sessionContext.sessionId,
|
|
622
|
+
sessionState: this.store.getState(),
|
|
623
|
+
sessionInput: this.sessionContext.sessionInput,
|
|
624
|
+
environment: this.sessionContext.environment,
|
|
625
|
+
llm: this.sessionContext.llm,
|
|
626
|
+
files: this.sessionContext.files,
|
|
627
|
+
eventStore: this.sessionContext.eventStore,
|
|
628
|
+
llmLogger: this.sessionContext.llmLogger,
|
|
629
|
+
platform: this.sessionContext.platform,
|
|
630
|
+
logger: this.logger,
|
|
631
|
+
emitEvent: this.sessionContext.emitEvent,
|
|
632
|
+
notify: this.sessionContext.notify,
|
|
633
|
+
// AgentContext fields
|
|
634
|
+
agentId: this.id,
|
|
635
|
+
agentState,
|
|
636
|
+
agentConfig: this.config,
|
|
637
|
+
input: agentState.typedInput,
|
|
638
|
+
parentId: agentState.parentId,
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* Build PluginHookContext for a specific plugin.
|
|
643
|
+
* Adds pluginConfig, pluginAgentConfig, pluginContext, pluginState, self.
|
|
644
|
+
*/
|
|
645
|
+
buildPluginHookContext(plugin, agentContext) {
|
|
646
|
+
const pluginState = plugin.slice
|
|
647
|
+
? plugin.slice.select(this.store.getState())
|
|
648
|
+
: undefined;
|
|
649
|
+
const self = {};
|
|
650
|
+
for (const [methodName] of Object.entries(plugin.methods)) {
|
|
651
|
+
self[methodName] = async (input) => {
|
|
652
|
+
if (!this.pluginMethodCaller) {
|
|
653
|
+
throw new Error('pluginMethodCaller not available');
|
|
654
|
+
}
|
|
655
|
+
return this.pluginMethodCaller(plugin.name, methodName, input);
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
const sendNotification = this.sendNotification;
|
|
659
|
+
const pluginName = plugin.name;
|
|
660
|
+
const deps = this.pluginMethodCaller
|
|
661
|
+
? buildPluginDeps(plugin.dependencyNames, this.plugins, this.pluginMethodCaller)
|
|
662
|
+
: {};
|
|
663
|
+
const schedule = this.scheduleCallback ?? (() => { });
|
|
664
|
+
return {
|
|
665
|
+
...agentContext,
|
|
666
|
+
pluginConfig: undefined, // injected by plugin builder wrapper
|
|
667
|
+
pluginAgentConfig: this.config.plugins?.find(c => c.pluginName === plugin.name)?.config,
|
|
668
|
+
pluginContext: this.pluginContexts.get(plugin.name),
|
|
669
|
+
pluginState,
|
|
670
|
+
self,
|
|
671
|
+
schedule,
|
|
672
|
+
notify: (type, payload) => {
|
|
673
|
+
sendNotification?.({ pluginName, type, payload });
|
|
674
|
+
},
|
|
675
|
+
deps,
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
/**
|
|
679
|
+
* Emit handler_completed event.
|
|
680
|
+
*
|
|
681
|
+
* Skipped when the handler produced no action (null result) — those events are
|
|
682
|
+
* pure noise (64%+ of a typical session log). onStart is the one exception: the
|
|
683
|
+
* reducer uses its completion event to flip `onStartCalled`, so we always emit
|
|
684
|
+
* it even with a null result.
|
|
685
|
+
*/
|
|
686
|
+
async emitHandlerCompleted(handlerName, result) {
|
|
687
|
+
if (result === null && handlerName !== 'onStart')
|
|
688
|
+
return;
|
|
689
|
+
await this.store.emit(withSessionId(this.store.sessionId, agentEvents.create('handler_completed', {
|
|
690
|
+
agentId: this.id,
|
|
691
|
+
handlerName,
|
|
692
|
+
result,
|
|
693
|
+
})));
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Execute onStart handler - called once on first inference.
|
|
697
|
+
*/
|
|
698
|
+
async executeOnStart(agentState) {
|
|
699
|
+
this.logger.debug('Executing onStart handlers', { agentId: this.id });
|
|
700
|
+
const agentContext = this.buildAgentContext(agentState);
|
|
701
|
+
// Run all plugin handlers with per-plugin isolation
|
|
702
|
+
// Note: Handlers emit preamble_added events directly via ctx.emitEvent()
|
|
703
|
+
let pauseResult = null;
|
|
704
|
+
for (const plugin of this.plugins) {
|
|
705
|
+
if (!plugin.agentHooks?.onStart)
|
|
706
|
+
continue;
|
|
707
|
+
try {
|
|
708
|
+
const ctx = this.buildPluginHookContext(plugin, agentContext);
|
|
709
|
+
const result = await plugin.agentHooks.onStart(ctx);
|
|
710
|
+
if (result !== null && result.action === 'pause') {
|
|
711
|
+
// Record first pause, but continue running other handlers
|
|
712
|
+
if (pauseResult === null) {
|
|
713
|
+
pauseResult = result;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
catch (error) {
|
|
718
|
+
this.logger.error(`Plugin '${plugin.name}' onStart hook failed`, error instanceof Error ? error : undefined, {
|
|
719
|
+
agentId: this.id,
|
|
720
|
+
plugin: plugin.name,
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
await this.emitHandlerCompleted('onStart', pauseResult);
|
|
725
|
+
if (pauseResult !== null) {
|
|
726
|
+
await this.emitHandlerPause(pauseResult.reason);
|
|
727
|
+
}
|
|
728
|
+
return pauseResult;
|
|
729
|
+
}
|
|
730
|
+
/**
|
|
731
|
+
* Execute beforeInference handler.
|
|
732
|
+
*/
|
|
733
|
+
async executeBeforeInference(agentState) {
|
|
734
|
+
this.logger.debug('Executing beforeInference handlers', {
|
|
735
|
+
agentId: this.id,
|
|
736
|
+
turnNumber: this.turnNumber,
|
|
737
|
+
});
|
|
738
|
+
const agentContext = this.buildAgentContext(agentState);
|
|
739
|
+
// Run plugin handlers with per-plugin isolation - first skip/pause wins
|
|
740
|
+
for (const plugin of this.plugins) {
|
|
741
|
+
if (!plugin.agentHooks?.beforeInference)
|
|
742
|
+
continue;
|
|
743
|
+
try {
|
|
744
|
+
const ctx = {
|
|
745
|
+
...this.buildPluginHookContext(plugin, agentContext),
|
|
746
|
+
pendingMessages: getUnconsumedMessages(this.store.getState(), this.id),
|
|
747
|
+
turnNumber: this.turnNumber,
|
|
748
|
+
};
|
|
749
|
+
const result = await plugin.agentHooks.beforeInference(ctx);
|
|
750
|
+
if (result === null) {
|
|
751
|
+
continue;
|
|
752
|
+
}
|
|
753
|
+
switch (result.action) {
|
|
754
|
+
case 'skip':
|
|
755
|
+
await this.emitHandlerCompleted('beforeInference', result);
|
|
756
|
+
return result;
|
|
757
|
+
case 'pause':
|
|
758
|
+
await this.emitHandlerCompleted('beforeInference', result);
|
|
759
|
+
await this.emitHandlerPause(result.reason);
|
|
760
|
+
return result;
|
|
761
|
+
default:
|
|
762
|
+
throw new Error(`Unhandled beforeInference action: ${result.action}`);
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
catch (error) {
|
|
766
|
+
this.logger.error(`Plugin '${plugin.name}' beforeInference hook failed`, error instanceof Error ? error : undefined, {
|
|
767
|
+
agentId: this.id,
|
|
768
|
+
plugin: plugin.name,
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
await this.emitHandlerCompleted('beforeInference', null);
|
|
773
|
+
return null;
|
|
774
|
+
}
|
|
775
|
+
/**
|
|
776
|
+
* Execute afterInference handler.
|
|
777
|
+
*/
|
|
778
|
+
async executeAfterInference(agentState, response) {
|
|
779
|
+
this.logger.debug('Executing afterInference handlers', {
|
|
780
|
+
agentId: this.id,
|
|
781
|
+
turnNumber: this.turnNumber,
|
|
782
|
+
});
|
|
783
|
+
const agentContext = this.buildAgentContext(agentState);
|
|
784
|
+
// Run plugin handlers with per-plugin isolation - first retry/modify/pause wins
|
|
785
|
+
for (const plugin of this.plugins) {
|
|
786
|
+
if (!plugin.agentHooks?.afterInference)
|
|
787
|
+
continue;
|
|
788
|
+
try {
|
|
789
|
+
const ctx = {
|
|
790
|
+
...this.buildPluginHookContext(plugin, agentContext),
|
|
791
|
+
response,
|
|
792
|
+
turnNumber: this.turnNumber,
|
|
793
|
+
};
|
|
794
|
+
const result = await plugin.agentHooks.afterInference(ctx);
|
|
795
|
+
if (result === null) {
|
|
796
|
+
continue;
|
|
797
|
+
}
|
|
798
|
+
switch (result.action) {
|
|
799
|
+
case 'retry':
|
|
800
|
+
await this.emitHandlerCompleted('afterInference', result);
|
|
801
|
+
return result;
|
|
802
|
+
case 'modify':
|
|
803
|
+
await this.emitHandlerCompleted('afterInference', result);
|
|
804
|
+
return result;
|
|
805
|
+
case 'pause':
|
|
806
|
+
await this.emitHandlerCompleted('afterInference', result);
|
|
807
|
+
// Don't emit agent_paused here — caller commits inference first,
|
|
808
|
+
// then pauses (so conversationHistory includes the completed turn).
|
|
809
|
+
return result;
|
|
810
|
+
default:
|
|
811
|
+
throw new Error(`Unhandled afterInference action: ${result.action}`);
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
catch (error) {
|
|
815
|
+
this.logger.error(`Plugin '${plugin.name}' afterInference hook failed`, error instanceof Error ? error : undefined, {
|
|
816
|
+
agentId: this.id,
|
|
817
|
+
plugin: plugin.name,
|
|
818
|
+
});
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
await this.emitHandlerCompleted('afterInference', null);
|
|
822
|
+
return null;
|
|
823
|
+
}
|
|
824
|
+
/**
|
|
825
|
+
* Execute beforeToolCall handler.
|
|
826
|
+
*/
|
|
827
|
+
async executeBeforeToolCall(agentState, toolCall) {
|
|
828
|
+
this.logger.debug('Executing beforeToolCall handlers', {
|
|
829
|
+
agentId: this.id,
|
|
830
|
+
toolName: toolCall.name,
|
|
831
|
+
});
|
|
832
|
+
const agentContext = this.buildAgentContext(agentState);
|
|
833
|
+
// Run plugin handlers with per-plugin isolation - first block/replace/pause wins
|
|
834
|
+
for (const plugin of this.plugins) {
|
|
835
|
+
if (!plugin.agentHooks?.beforeToolCall)
|
|
836
|
+
continue;
|
|
837
|
+
try {
|
|
838
|
+
const ctx = {
|
|
839
|
+
...this.buildPluginHookContext(plugin, agentContext),
|
|
840
|
+
toolCall: {
|
|
841
|
+
id: toolCall.id,
|
|
842
|
+
name: toolCall.name,
|
|
843
|
+
input: toolCall.input,
|
|
844
|
+
},
|
|
845
|
+
};
|
|
846
|
+
const result = await plugin.agentHooks.beforeToolCall(ctx);
|
|
847
|
+
if (result === null) {
|
|
848
|
+
continue;
|
|
849
|
+
}
|
|
850
|
+
switch (result.action) {
|
|
851
|
+
case 'block':
|
|
852
|
+
await this.emitHandlerCompleted('beforeToolCall', result);
|
|
853
|
+
return result;
|
|
854
|
+
case 'replace':
|
|
855
|
+
await this.emitHandlerCompleted('beforeToolCall', result);
|
|
856
|
+
return {
|
|
857
|
+
action: 'replace',
|
|
858
|
+
toolCall: {
|
|
859
|
+
id: result.toolCall.id,
|
|
860
|
+
name: result.toolCall.name,
|
|
861
|
+
input: result.toolCall.input,
|
|
862
|
+
},
|
|
863
|
+
};
|
|
864
|
+
case 'pause':
|
|
865
|
+
await this.emitHandlerCompleted('beforeToolCall', result);
|
|
866
|
+
await this.emitHandlerPause(result.reason);
|
|
867
|
+
return result;
|
|
868
|
+
default:
|
|
869
|
+
throw new Error(`Unhandled beforeToolCall action: ${result.action}`);
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
catch (error) {
|
|
873
|
+
this.logger.error(`Plugin '${plugin.name}' beforeToolCall hook failed`, error instanceof Error ? error : undefined, {
|
|
874
|
+
agentId: this.id,
|
|
875
|
+
plugin: plugin.name,
|
|
876
|
+
toolName: toolCall.name,
|
|
877
|
+
});
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
await this.emitHandlerCompleted('beforeToolCall', null);
|
|
881
|
+
return null;
|
|
882
|
+
}
|
|
883
|
+
/**
|
|
884
|
+
* Execute afterToolCall handler.
|
|
885
|
+
*/
|
|
886
|
+
async executeAfterToolCall(agentState, toolCall, toolResult) {
|
|
887
|
+
this.logger.debug('Executing afterToolCall handlers', {
|
|
888
|
+
agentId: this.id,
|
|
889
|
+
toolName: toolCall.name,
|
|
890
|
+
});
|
|
891
|
+
const agentContext = this.buildAgentContext(agentState);
|
|
892
|
+
// Run plugin handlers with per-plugin isolation - first modify/pause wins
|
|
893
|
+
for (const plugin of this.plugins) {
|
|
894
|
+
if (!plugin.agentHooks?.afterToolCall)
|
|
895
|
+
continue;
|
|
896
|
+
try {
|
|
897
|
+
const ctx = {
|
|
898
|
+
...this.buildPluginHookContext(plugin, agentContext),
|
|
899
|
+
toolCall: {
|
|
900
|
+
id: toolCall.id,
|
|
901
|
+
name: toolCall.name,
|
|
902
|
+
input: toolCall.input,
|
|
903
|
+
},
|
|
904
|
+
result: toolResult,
|
|
905
|
+
};
|
|
906
|
+
const result = await plugin.agentHooks.afterToolCall(ctx);
|
|
907
|
+
if (result === null) {
|
|
908
|
+
continue;
|
|
909
|
+
}
|
|
910
|
+
switch (result.action) {
|
|
911
|
+
case 'modify':
|
|
912
|
+
await this.emitHandlerCompleted('afterToolCall', result);
|
|
913
|
+
return result;
|
|
914
|
+
case 'pause':
|
|
915
|
+
await this.emitHandlerCompleted('afterToolCall', result);
|
|
916
|
+
await this.emitHandlerPause(result.reason);
|
|
917
|
+
return result;
|
|
918
|
+
default:
|
|
919
|
+
throw new Error(`Unhandled afterToolCall action: ${result.action}`);
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
catch (error) {
|
|
923
|
+
this.logger.error(`Plugin '${plugin.name}' afterToolCall hook failed`, error instanceof Error ? error : undefined, {
|
|
924
|
+
agentId: this.id,
|
|
925
|
+
plugin: plugin.name,
|
|
926
|
+
toolName: toolCall.name,
|
|
927
|
+
});
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
await this.emitHandlerCompleted('afterToolCall', null);
|
|
931
|
+
return null;
|
|
932
|
+
}
|
|
933
|
+
/**
|
|
934
|
+
* Execute onComplete handler.
|
|
935
|
+
*/
|
|
936
|
+
async executeOnComplete(agentState) {
|
|
937
|
+
this.logger.debug('Executing onComplete handlers', { agentId: this.id });
|
|
938
|
+
const agentContext = this.buildAgentContext(agentState);
|
|
939
|
+
// Run all plugin handlers with per-plugin isolation - first pause wins
|
|
940
|
+
for (const plugin of this.plugins) {
|
|
941
|
+
if (!plugin.agentHooks?.onComplete)
|
|
942
|
+
continue;
|
|
943
|
+
try {
|
|
944
|
+
const ctx = this.buildPluginHookContext(plugin, agentContext);
|
|
945
|
+
const pluginResult = await plugin.agentHooks.onComplete(ctx);
|
|
946
|
+
if (pluginResult === null) {
|
|
947
|
+
continue;
|
|
948
|
+
}
|
|
949
|
+
switch (pluginResult.action) {
|
|
950
|
+
case 'pause':
|
|
951
|
+
await this.emitHandlerCompleted('onComplete', pluginResult);
|
|
952
|
+
await this.emitHandlerPause(pluginResult.reason);
|
|
953
|
+
return;
|
|
954
|
+
default:
|
|
955
|
+
throw new Error(`Unhandled onComplete action: ${pluginResult.action}`);
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
catch (error) {
|
|
959
|
+
this.logger.error(`Plugin '${plugin.name}' onComplete hook failed`, error instanceof Error ? error : undefined, {
|
|
960
|
+
agentId: this.id,
|
|
961
|
+
plugin: plugin.name,
|
|
962
|
+
});
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
await this.emitHandlerCompleted('onComplete', null);
|
|
966
|
+
}
|
|
967
|
+
/**
|
|
968
|
+
* Execute onError handler.
|
|
969
|
+
*/
|
|
970
|
+
async executeOnError(agentState, error) {
|
|
971
|
+
this.logger.debug('Executing onError handlers', { agentId: this.id });
|
|
972
|
+
const agentContext = this.buildAgentContext(agentState);
|
|
973
|
+
// Run all plugin handlers with per-plugin isolation - first pause wins
|
|
974
|
+
for (const plugin of this.plugins) {
|
|
975
|
+
if (!plugin.agentHooks?.onError)
|
|
976
|
+
continue;
|
|
977
|
+
try {
|
|
978
|
+
const ctx = this.buildPluginHookContext(plugin, agentContext);
|
|
979
|
+
const pluginResult = await plugin.agentHooks.onError({ ...ctx, error });
|
|
980
|
+
if (pluginResult === null) {
|
|
981
|
+
continue;
|
|
982
|
+
}
|
|
983
|
+
switch (pluginResult.action) {
|
|
984
|
+
case 'pause':
|
|
985
|
+
await this.emitHandlerCompleted('onError', pluginResult);
|
|
986
|
+
await this.emitHandlerPause(pluginResult.reason);
|
|
987
|
+
return;
|
|
988
|
+
default:
|
|
989
|
+
throw new Error(`Unhandled onError action: ${pluginResult.action}`);
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
catch (error) {
|
|
993
|
+
this.logger.error(`Plugin '${plugin.name}' onError hook failed`, error instanceof Error ? error : undefined, {
|
|
994
|
+
agentId: this.id,
|
|
995
|
+
plugin: plugin.name,
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
await this.emitHandlerCompleted('onError', null);
|
|
1000
|
+
}
|
|
1001
|
+
// ============================================================================
|
|
1002
|
+
// Message building
|
|
1003
|
+
// ============================================================================
|
|
1004
|
+
/**
|
|
1005
|
+
* Build pending messages from tool results.
|
|
1006
|
+
* Mailbox messages are handled by the mailbox plugin's dequeue mechanism.
|
|
1007
|
+
*/
|
|
1008
|
+
buildPendingMessages(agentState) {
|
|
1009
|
+
const pending = [];
|
|
1010
|
+
for (const ptr of agentState.pendingToolResults) {
|
|
1011
|
+
pending.push({
|
|
1012
|
+
role: 'tool',
|
|
1013
|
+
toolCallId: ptr.toolCallId,
|
|
1014
|
+
toolName: ptr.toolName,
|
|
1015
|
+
content: ptr.content,
|
|
1016
|
+
isError: ptr.isError,
|
|
1017
|
+
timestamp: ptr.timestamp,
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
1020
|
+
return pending;
|
|
1021
|
+
}
|
|
1022
|
+
/**
|
|
1023
|
+
* Build LLM messages from agent state and pending messages.
|
|
1024
|
+
* Order: [preamble, conversation history, pending messages]
|
|
1025
|
+
* - Preamble is never compacted (includes skills injected by plugin)
|
|
1026
|
+
* - Conversation history may be compacted
|
|
1027
|
+
* - Pending messages are ephemeral (for current turn)
|
|
1028
|
+
*/
|
|
1029
|
+
buildLLMMessages(agentState, pendingMessages) {
|
|
1030
|
+
const messages = [];
|
|
1031
|
+
// 1. Preamble (ALWAYS prepended, NEVER compacted — includes skills from plugin)
|
|
1032
|
+
messages.push(...agentState.preamble);
|
|
1033
|
+
// 2. Conversation history (may be compacted)
|
|
1034
|
+
messages.push(...agentState.conversationHistory);
|
|
1035
|
+
// 3. Pending messages
|
|
1036
|
+
messages.push(...pendingMessages);
|
|
1037
|
+
return messages;
|
|
1038
|
+
}
|
|
1039
|
+
// ============================================================================
|
|
1040
|
+
// Plugin system helpers
|
|
1041
|
+
// ============================================================================
|
|
1042
|
+
/**
|
|
1043
|
+
* Build merged tools map from config (preset-level) and plugin tools.
|
|
1044
|
+
* Plugin tools override config tools with the same name.
|
|
1045
|
+
*/
|
|
1046
|
+
buildToolsMap() {
|
|
1047
|
+
const tools = new Map();
|
|
1048
|
+
/** Track which source registered each tool name for collision detection */
|
|
1049
|
+
const toolSources = new Map();
|
|
1050
|
+
// 1. Static tools from config (preset-level)
|
|
1051
|
+
for (const tool of this.config.tools ?? []) {
|
|
1052
|
+
tools.set(tool.name, tool);
|
|
1053
|
+
toolSources.set(tool.name, 'config');
|
|
1054
|
+
}
|
|
1055
|
+
// 2. Plugin tools (override static)
|
|
1056
|
+
const agentState = this.state;
|
|
1057
|
+
if (agentState) {
|
|
1058
|
+
const agentContext = this.buildAgentContext(agentState);
|
|
1059
|
+
for (const plugin of this.plugins) {
|
|
1060
|
+
if (!plugin.getTools)
|
|
1061
|
+
continue;
|
|
1062
|
+
const ctx = this.buildPluginHookContext(plugin, agentContext);
|
|
1063
|
+
for (const tool of plugin.getTools(ctx)) {
|
|
1064
|
+
const existing = toolSources.get(tool.name);
|
|
1065
|
+
if (existing) {
|
|
1066
|
+
this.logger.warn(`Tool name collision: '${tool.name}' from plugin '${plugin.name}' overrides '${existing}'`, {
|
|
1067
|
+
agentId: this.id,
|
|
1068
|
+
toolName: tool.name,
|
|
1069
|
+
});
|
|
1070
|
+
}
|
|
1071
|
+
tools.set(tool.name, tool);
|
|
1072
|
+
toolSources.set(tool.name, `plugin:${plugin.name}`);
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
return tools;
|
|
1077
|
+
}
|
|
1078
|
+
/**
|
|
1079
|
+
* Build composed system prompt from base briefing, plugin sections, environment, and preset prompt.
|
|
1080
|
+
*/
|
|
1081
|
+
buildSystemPrompt() {
|
|
1082
|
+
const sections = [];
|
|
1083
|
+
// 1. Framework base briefing (always first)
|
|
1084
|
+
sections.push(AGENT_BASE_BRIEFING);
|
|
1085
|
+
// 2. Plugin system prompt sections
|
|
1086
|
+
const agentState = this.state;
|
|
1087
|
+
if (agentState) {
|
|
1088
|
+
const agentContext = this.buildAgentContext(agentState);
|
|
1089
|
+
for (const plugin of this.plugins) {
|
|
1090
|
+
if (!plugin.getSystemPrompt)
|
|
1091
|
+
continue;
|
|
1092
|
+
const ctx = this.buildPluginHookContext(plugin, agentContext);
|
|
1093
|
+
const section = plugin.getSystemPrompt(ctx);
|
|
1094
|
+
if (section)
|
|
1095
|
+
sections.push(section);
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
// 3. Environment section
|
|
1099
|
+
const roots = this.fileStore.getRoots();
|
|
1100
|
+
sections.push(buildEnvironmentSection({
|
|
1101
|
+
sessionPath: roots.session,
|
|
1102
|
+
workspacePath: roots.workspace,
|
|
1103
|
+
}));
|
|
1104
|
+
// 4. Custom prompt from preset (last)
|
|
1105
|
+
if (this.config.systemPrompt) {
|
|
1106
|
+
let customPrompt = this.config.systemPrompt;
|
|
1107
|
+
customPrompt = customPrompt.replaceAll('{{sessionDir}}', roots.session);
|
|
1108
|
+
if (roots.workspace) {
|
|
1109
|
+
customPrompt = customPrompt.replaceAll('{{workspaceDir}}', roots.workspace);
|
|
1110
|
+
}
|
|
1111
|
+
sections.push(customPrompt);
|
|
1112
|
+
}
|
|
1113
|
+
return sections.filter(Boolean).join('\n\n').trim();
|
|
1114
|
+
}
|
|
1115
|
+
/**
|
|
1116
|
+
* Get combined status from all plugins.
|
|
1117
|
+
*/
|
|
1118
|
+
getPluginStatus() {
|
|
1119
|
+
const agentState = this.state;
|
|
1120
|
+
if (!agentState)
|
|
1121
|
+
return null;
|
|
1122
|
+
const agentContext = this.buildAgentContext(agentState);
|
|
1123
|
+
const parts = [];
|
|
1124
|
+
for (const plugin of this.plugins) {
|
|
1125
|
+
if (!plugin.getStatus)
|
|
1126
|
+
continue;
|
|
1127
|
+
const ctx = this.buildPluginHookContext(plugin, agentContext);
|
|
1128
|
+
const status = plugin.getStatus(ctx);
|
|
1129
|
+
if (status)
|
|
1130
|
+
parts.push(status);
|
|
1131
|
+
}
|
|
1132
|
+
return parts.length > 0 ? parts.join('\n\n') : null;
|
|
1133
|
+
}
|
|
1134
|
+
// ============================================================================
|
|
1135
|
+
// Plugin dequeue helpers
|
|
1136
|
+
// ============================================================================
|
|
1137
|
+
/**
|
|
1138
|
+
* Check if any plugin has pending messages for this agent.
|
|
1139
|
+
*/
|
|
1140
|
+
hasPluginPendingMessages() {
|
|
1141
|
+
const agentState = this.state;
|
|
1142
|
+
if (!agentState)
|
|
1143
|
+
return false;
|
|
1144
|
+
const agentContext = this.buildAgentContext(agentState);
|
|
1145
|
+
for (const plugin of this.plugins) {
|
|
1146
|
+
if (!plugin.dequeue)
|
|
1147
|
+
continue;
|
|
1148
|
+
const ctx = this.buildPluginHookContext(plugin, agentContext);
|
|
1149
|
+
if (plugin.dequeue.hasPendingMessages(ctx))
|
|
1150
|
+
return true;
|
|
1151
|
+
}
|
|
1152
|
+
return false;
|
|
1153
|
+
}
|
|
1154
|
+
/**
|
|
1155
|
+
* Collect pending messages from all plugins that have dequeue hooks.
|
|
1156
|
+
* Returns array of { plugin, messages, token } for each plugin with pending messages.
|
|
1157
|
+
*/
|
|
1158
|
+
collectPluginMessages() {
|
|
1159
|
+
const agentState = this.state;
|
|
1160
|
+
if (!agentState)
|
|
1161
|
+
return [];
|
|
1162
|
+
const agentContext = this.buildAgentContext(agentState);
|
|
1163
|
+
const collected = [];
|
|
1164
|
+
for (const plugin of this.plugins) {
|
|
1165
|
+
if (!plugin.dequeue)
|
|
1166
|
+
continue;
|
|
1167
|
+
const ctx = this.buildPluginHookContext(plugin, agentContext);
|
|
1168
|
+
const result = plugin.dequeue.getPendingMessages(ctx);
|
|
1169
|
+
if (result) {
|
|
1170
|
+
collected.push({
|
|
1171
|
+
plugin,
|
|
1172
|
+
messages: result.messages,
|
|
1173
|
+
token: result.token,
|
|
1174
|
+
});
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
return collected;
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
// ============================================================================
|
|
1181
|
+
// Local helpers (inlined from @roj-ai/core)
|
|
1182
|
+
// ============================================================================
|
|
1183
|
+
function getUnconsumedMessages(sessionState, agentId) {
|
|
1184
|
+
return getAgentUnconsumedMailbox(selectMailboxState(sessionState), agentId);
|
|
1185
|
+
}
|
|
1186
|
+
function hasWork(agent) {
|
|
1187
|
+
// Has pending tool results - needs LLM to process
|
|
1188
|
+
if (agent.pendingToolResults.length > 0)
|
|
1189
|
+
return true;
|
|
1190
|
+
return false;
|
|
1191
|
+
}
|
|
1192
|
+
/**
|
|
1193
|
+
* Narrow tool call input from `unknown` to `Record<string, unknown>`.
|
|
1194
|
+
* Tool inputs are always validated objects from Zod schemas.
|
|
1195
|
+
*/
|
|
1196
|
+
function isRecord(value) {
|
|
1197
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
1198
|
+
}
|
|
1199
|
+
function toInputRecord(input) {
|
|
1200
|
+
if (isRecord(input)) {
|
|
1201
|
+
return input;
|
|
1202
|
+
}
|
|
1203
|
+
return {};
|
|
1204
|
+
}
|
|
1205
|
+
//# sourceMappingURL=agent.js.map
|