@clinebot/core 0.0.35 → 0.0.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -2
- package/dist/ClineCore.d.ts +362 -39
- package/dist/ClineCore.d.ts.map +1 -1
- package/dist/account/cline-account-service.d.ts.map +1 -1
- package/dist/account/index.d.ts +1 -1
- package/dist/account/index.d.ts.map +1 -1
- package/dist/account/rpc.d.ts +6 -6
- package/dist/account/rpc.d.ts.map +1 -1
- package/dist/cron/cron-event-ingress.d.ts +38 -0
- package/dist/cron/cron-event-ingress.d.ts.map +1 -0
- package/dist/cron/cron-materializer.d.ts +36 -0
- package/dist/cron/cron-materializer.d.ts.map +1 -0
- package/dist/cron/cron-reconciler.d.ts +62 -0
- package/dist/cron/cron-reconciler.d.ts.map +1 -0
- package/dist/cron/cron-report-writer.d.ts +41 -0
- package/dist/cron/cron-report-writer.d.ts.map +1 -0
- package/dist/cron/cron-runner.d.ts +43 -0
- package/dist/cron/cron-runner.d.ts.map +1 -0
- package/dist/cron/cron-schema.d.ts +3 -0
- package/dist/cron/cron-schema.d.ts.map +1 -0
- package/dist/cron/cron-service.d.ts +57 -0
- package/dist/cron/cron-service.d.ts.map +1 -0
- package/dist/cron/cron-spec-parser.d.ts +27 -0
- package/dist/cron/cron-spec-parser.d.ts.map +1 -0
- package/dist/cron/cron-watcher.d.ts +23 -0
- package/dist/cron/cron-watcher.d.ts.map +1 -0
- package/dist/cron/resource-limiter.d.ts +9 -0
- package/dist/cron/resource-limiter.d.ts.map +1 -0
- package/dist/cron/schedule-command-service.d.ts +10 -0
- package/dist/cron/schedule-command-service.d.ts.map +1 -0
- package/dist/cron/schedule-service.d.ts +100 -0
- package/dist/cron/schedule-service.d.ts.map +1 -0
- package/dist/cron/scheduler.d.ts +68 -0
- package/dist/cron/scheduler.d.ts.map +1 -0
- package/dist/cron/sqlite-cron-store.d.ts +230 -0
- package/dist/cron/sqlite-cron-store.d.ts.map +1 -0
- package/dist/cron/sqlite-schedule-store.d.ts +52 -0
- package/dist/cron/sqlite-schedule-store.d.ts.map +1 -0
- package/dist/extensions/config/agent-config-loader.d.ts +4 -3
- package/dist/extensions/config/agent-config-loader.d.ts.map +1 -1
- package/dist/extensions/config/runtime-commands.d.ts +1 -0
- package/dist/extensions/config/runtime-commands.d.ts.map +1 -1
- package/dist/extensions/config/user-instruction-config-loader.d.ts +1 -0
- package/dist/extensions/config/user-instruction-config-loader.d.ts.map +1 -1
- package/dist/extensions/context/agentic-compaction.d.ts +2 -2
- package/dist/extensions/context/agentic-compaction.d.ts.map +1 -1
- package/dist/extensions/context/compaction-shared.d.ts +5 -4
- package/dist/extensions/context/compaction-shared.d.ts.map +1 -1
- package/dist/extensions/context/compaction.d.ts.map +1 -1
- package/dist/extensions/plugin/plugin-config-loader.d.ts +15 -2
- package/dist/extensions/plugin/plugin-config-loader.d.ts.map +1 -1
- package/dist/extensions/plugin/plugin-loader.d.ts +13 -7
- package/dist/extensions/plugin/plugin-loader.d.ts.map +1 -1
- package/dist/extensions/plugin/plugin-module-import.d.ts.map +1 -1
- package/dist/extensions/plugin/plugin-sandbox.d.ts +21 -2
- package/dist/extensions/plugin/plugin-sandbox.d.ts.map +1 -1
- package/dist/extensions/plugin/plugin-targeting.d.ts +7 -0
- package/dist/extensions/plugin/plugin-targeting.d.ts.map +1 -0
- package/dist/extensions/plugin-sandbox-bootstrap.js +237 -276
- package/dist/extensions/tools/constants.d.ts +1 -0
- package/dist/extensions/tools/constants.d.ts.map +1 -1
- package/dist/extensions/tools/definitions.d.ts +3 -4
- package/dist/extensions/tools/definitions.d.ts.map +1 -1
- package/dist/extensions/tools/executors/apply-patch.d.ts +3 -1
- package/dist/extensions/tools/executors/apply-patch.d.ts.map +1 -1
- package/dist/extensions/tools/executors/editor.d.ts.map +1 -1
- package/dist/extensions/tools/executors/search.d.ts +1 -1
- package/dist/extensions/tools/executors/search.d.ts.map +1 -1
- package/dist/extensions/tools/helpers.d.ts +1 -0
- package/dist/extensions/tools/helpers.d.ts.map +1 -1
- package/dist/extensions/tools/index.d.ts +3 -2
- package/dist/extensions/tools/index.d.ts.map +1 -1
- package/dist/extensions/tools/presets.d.ts +27 -44
- package/dist/extensions/tools/presets.d.ts.map +1 -1
- package/dist/extensions/tools/runtime.d.ts +25 -0
- package/dist/extensions/tools/runtime.d.ts.map +1 -0
- package/dist/extensions/tools/schemas.d.ts +25 -3
- package/dist/extensions/tools/schemas.d.ts.map +1 -1
- package/dist/extensions/tools/team/delegated-agent.d.ts +2 -2
- package/dist/extensions/tools/team/delegated-agent.d.ts.map +1 -1
- package/dist/extensions/tools/team/multi-agent.d.ts +7 -3
- package/dist/extensions/tools/team/multi-agent.d.ts.map +1 -1
- package/dist/extensions/tools/team/team-tools.d.ts +1 -0
- package/dist/extensions/tools/team/team-tools.d.ts.map +1 -1
- package/dist/extensions/tools/types.d.ts +0 -5
- package/dist/extensions/tools/types.d.ts.map +1 -1
- package/dist/hooks/hook-bridge.d.ts +118 -0
- package/dist/hooks/hook-bridge.d.ts.map +1 -0
- package/dist/hooks/hook-file-hooks.d.ts +6 -2
- package/dist/hooks/hook-file-hooks.d.ts.map +1 -1
- package/dist/hooks/hook-registry.d.ts +16 -0
- package/dist/hooks/hook-registry.d.ts.map +1 -0
- package/dist/hooks/index.d.ts +0 -1
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/subprocess.d.ts +8 -1
- package/dist/hooks/subprocess.d.ts.map +1 -1
- package/dist/hub/browser-websocket.d.ts +18 -0
- package/dist/hub/browser-websocket.d.ts.map +1 -0
- package/dist/hub/client.d.ts +51 -0
- package/dist/hub/client.d.ts.map +1 -0
- package/dist/hub/connect.d.ts +15 -0
- package/dist/hub/connect.d.ts.map +1 -0
- package/dist/hub/daemon-entry.d.ts +2 -0
- package/dist/hub/daemon-entry.d.ts.map +1 -0
- package/dist/hub/daemon-entry.js +1305 -0
- package/dist/hub/daemon.d.ts +5 -0
- package/dist/hub/daemon.d.ts.map +1 -0
- package/dist/hub/defaults.d.ts +17 -0
- package/dist/hub/defaults.d.ts.map +1 -0
- package/dist/hub/discovery.d.ts +29 -0
- package/dist/hub/discovery.d.ts.map +1 -0
- package/dist/hub/index.d.ts +15 -0
- package/dist/hub/index.d.ts.map +1 -0
- package/dist/hub/index.js +1294 -0
- package/dist/hub/native-transport.d.ts +17 -0
- package/dist/hub/native-transport.d.ts.map +1 -0
- package/dist/hub/runtime-handlers.d.ts +11 -0
- package/dist/hub/runtime-handlers.d.ts.map +1 -0
- package/dist/hub/server.d.ts +104 -0
- package/dist/hub/server.d.ts.map +1 -0
- package/dist/hub/session-client.d.ts +90 -0
- package/dist/hub/session-client.d.ts.map +1 -0
- package/dist/hub/start-shared-server.d.ts +19 -0
- package/dist/hub/start-shared-server.d.ts.map +1 -0
- package/dist/hub/transport.d.ts +8 -0
- package/dist/hub/transport.d.ts.map +1 -0
- package/dist/hub/ui-client.d.ts +45 -0
- package/dist/hub/ui-client.d.ts.map +1 -0
- package/dist/hub/workspace.d.ts +4 -0
- package/dist/hub/workspace.d.ts.map +1 -0
- package/dist/index.d.ts +29 -16
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +782 -471
- package/dist/llms/cline-recommended-models.d.ts +20 -0
- package/dist/llms/cline-recommended-models.d.ts.map +1 -0
- package/dist/llms/configured-provider-registry.d.ts +28 -0
- package/dist/llms/configured-provider-registry.d.ts.map +1 -0
- package/dist/llms/handler-factory.d.ts +16 -0
- package/dist/llms/handler-factory.d.ts.map +1 -0
- package/dist/llms/provider-defaults.d.ts +27 -0
- package/dist/llms/provider-defaults.d.ts.map +1 -0
- package/dist/llms/provider-settings.d.ts +245 -0
- package/dist/llms/provider-settings.d.ts.map +1 -0
- package/dist/llms/runtime-config.d.ts +4 -0
- package/dist/llms/runtime-config.d.ts.map +1 -0
- package/dist/llms/runtime-registry.d.ts +20 -0
- package/dist/llms/runtime-registry.d.ts.map +1 -0
- package/dist/llms/runtime-types.d.ts +85 -0
- package/dist/llms/runtime-types.d.ts.map +1 -0
- package/dist/runtime/agent-config-adapter.d.ts +148 -0
- package/dist/runtime/agent-config-adapter.d.ts.map +1 -0
- package/dist/runtime/agent-runtime-config-builder.d.ts +96 -0
- package/dist/runtime/agent-runtime-config-builder.d.ts.map +1 -0
- package/dist/runtime/history.d.ts +6 -0
- package/dist/runtime/history.d.ts.map +1 -1
- package/dist/runtime/host.d.ts +1 -2
- package/dist/runtime/host.d.ts.map +1 -1
- package/dist/runtime/loop-detection.d.ts +59 -0
- package/dist/runtime/loop-detection.d.ts.map +1 -0
- package/dist/runtime/mistake-tracker.d.ts +69 -0
- package/dist/runtime/mistake-tracker.d.ts.map +1 -0
- package/dist/runtime/rules.d.ts +1 -0
- package/dist/runtime/rules.d.ts.map +1 -1
- package/dist/runtime/runtime-builder.d.ts.map +1 -1
- package/dist/runtime/runtime-event-adapter.d.ts +102 -0
- package/dist/runtime/runtime-event-adapter.d.ts.map +1 -0
- package/dist/runtime/runtime-host.d.ts +49 -26
- package/dist/runtime/runtime-host.d.ts.map +1 -1
- package/dist/runtime/runtime-oauth-token-manager.d.ts.map +1 -1
- package/dist/runtime/session-runtime-orchestrator.d.ts +261 -0
- package/dist/runtime/session-runtime-orchestrator.d.ts.map +1 -0
- package/dist/runtime/session-runtime.d.ts +16 -21
- package/dist/runtime/session-runtime.d.ts.map +1 -1
- package/dist/runtime/user-input-builder.d.ts +24 -0
- package/dist/runtime/user-input-builder.d.ts.map +1 -0
- package/dist/services/global-settings.d.ts +12 -0
- package/dist/services/global-settings.d.ts.map +1 -0
- package/dist/services/index.js +28 -0
- package/dist/services/local-runtime-bootstrap.d.ts +9 -3
- package/dist/services/local-runtime-bootstrap.d.ts.map +1 -1
- package/dist/services/plugin-tools.d.ts +16 -0
- package/dist/services/plugin-tools.d.ts.map +1 -0
- package/dist/services/providers/local-provider-registry.d.ts +199 -23
- package/dist/services/providers/local-provider-registry.d.ts.map +1 -1
- package/dist/services/providers/local-provider-service.d.ts +15 -13
- package/dist/services/providers/local-provider-service.d.ts.map +1 -1
- package/dist/services/session-data.d.ts +1 -1
- package/dist/services/session-data.d.ts.map +1 -1
- package/dist/services/session-telemetry.d.ts +7 -2
- package/dist/services/session-telemetry.d.ts.map +1 -1
- package/dist/services/storage/file-team-store.d.ts.map +1 -1
- package/dist/services/storage/provider-settings-legacy-migration.d.ts +1 -1
- package/dist/services/storage/provider-settings-legacy-migration.d.ts.map +1 -1
- package/dist/services/storage/provider-settings-manager.d.ts +1 -0
- package/dist/services/storage/provider-settings-manager.d.ts.map +1 -1
- package/dist/services/storage/sqlite-team-store.d.ts.map +1 -1
- package/dist/services/workspace-manifest.d.ts +11 -0
- package/dist/services/workspace-manifest.d.ts.map +1 -1
- package/dist/session/conversation-store.d.ts +30 -0
- package/dist/session/conversation-store.d.ts.map +1 -0
- package/dist/session/message-builder.d.ts +65 -0
- package/dist/session/message-builder.d.ts.map +1 -0
- package/dist/session/persistence-service.d.ts +11 -23
- package/dist/session/persistence-service.d.ts.map +1 -1
- package/dist/session/session-manifest-store.d.ts +22 -0
- package/dist/session/session-manifest-store.d.ts.map +1 -0
- package/dist/session/session-manifest.d.ts +1 -1
- package/dist/session/session-row.d.ts +93 -0
- package/dist/session/session-row.d.ts.map +1 -0
- package/dist/session/session-service.d.ts +2 -102
- package/dist/session/session-service.d.ts.map +1 -1
- package/dist/session/subagent-session-manager.d.ts +36 -0
- package/dist/session/subagent-session-manager.d.ts.map +1 -0
- package/dist/session/team-persistence-store.d.ts +24 -0
- package/dist/session/team-persistence-store.d.ts.map +1 -0
- package/dist/transports/hub.d.ts +58 -0
- package/dist/transports/hub.d.ts.map +1 -0
- package/dist/transports/local.d.ts +23 -9
- package/dist/transports/local.d.ts.map +1 -1
- package/dist/transports/remote.d.ts +10 -0
- package/dist/transports/remote.d.ts.map +1 -0
- package/dist/transports/runtime-host-support.d.ts +3 -2
- package/dist/transports/runtime-host-support.d.ts.map +1 -1
- package/dist/types/chat-schema.d.ts +15 -17
- package/dist/types/chat-schema.d.ts.map +1 -1
- package/dist/types/config.d.ts +17 -7
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/events.d.ts +7 -6
- package/dist/types/events.d.ts.map +1 -1
- package/dist/types/provider-settings.d.ts +4 -5
- package/dist/types/provider-settings.d.ts.map +1 -1
- package/dist/types/session.d.ts +7 -3
- package/dist/types/session.d.ts.map +1 -1
- package/dist/types.d.ts +11 -4
- package/dist/types.d.ts.map +1 -1
- package/package.json +20 -6
- package/src/ClineCore.ts +757 -44
- package/src/account/cline-account-service.ts +44 -6
- package/src/account/index.ts +3 -3
- package/src/account/rpc.ts +12 -12
- package/src/cron/cron-event-ingress.ts +357 -0
- package/src/cron/cron-materializer.ts +97 -0
- package/src/cron/cron-reconciler.ts +241 -0
- package/src/cron/cron-report-writer.ts +153 -0
- package/src/cron/cron-runner.ts +495 -0
- package/src/cron/cron-schema.ts +127 -0
- package/src/cron/cron-service.ts +163 -0
- package/src/cron/cron-spec-parser.ts +489 -0
- package/src/cron/cron-watcher.ts +102 -0
- package/src/cron/index.ts +15 -0
- package/src/cron/resource-limiter.ts +46 -0
- package/src/cron/schedule-command-service.ts +193 -0
- package/src/cron/schedule-service.ts +703 -0
- package/src/cron/scheduler.ts +772 -0
- package/src/cron/sqlite-cron-store.ts +1286 -0
- package/src/cron/sqlite-schedule-store.ts +708 -0
- package/src/extensions/config/agent-config-loader.ts +17 -7
- package/src/extensions/config/runtime-commands.ts +6 -0
- package/src/extensions/config/user-instruction-config-loader.ts +1 -0
- package/src/extensions/context/agentic-compaction.ts +3 -3
- package/src/extensions/context/basic-compaction.ts +2 -2
- package/src/extensions/context/compaction-shared.ts +5 -4
- package/src/extensions/context/compaction.ts +3 -3
- package/src/extensions/plugin/plugin-config-loader.ts +37 -2
- package/src/extensions/plugin/plugin-loader.ts +69 -9
- package/src/extensions/plugin/plugin-module-import.ts +0 -2
- package/src/extensions/plugin/plugin-sandbox-bootstrap.ts +243 -39
- package/src/extensions/plugin/plugin-sandbox.ts +173 -29
- package/src/extensions/plugin/plugin-targeting.ts +32 -0
- package/src/extensions/tools/constants.ts +2 -0
- package/src/extensions/tools/definitions.ts +61 -71
- package/src/extensions/tools/executors/apply-patch.ts +69 -80
- package/src/extensions/tools/executors/editor.ts +4 -3
- package/src/extensions/tools/executors/search.ts +195 -3
- package/src/extensions/tools/helpers.ts +24 -0
- package/src/extensions/tools/index.ts +11 -2
- package/src/extensions/tools/presets.ts +32 -47
- package/src/extensions/tools/runtime.ts +261 -0
- package/src/extensions/tools/schemas.ts +17 -20
- package/src/extensions/tools/team/delegated-agent.ts +8 -3
- package/src/extensions/tools/team/multi-agent.ts +135 -19
- package/src/extensions/tools/team/team-tools.ts +172 -91
- package/src/extensions/tools/types.ts +0 -6
- package/src/hooks/hook-bridge.ts +489 -0
- package/src/hooks/hook-file-hooks.ts +66 -5
- package/src/hooks/hook-registry.ts +257 -0
- package/src/hooks/index.ts +0 -7
- package/src/hooks/subprocess-runner.ts +1 -1
- package/src/hooks/subprocess.ts +9 -0
- package/src/hub/browser-websocket.ts +159 -0
- package/src/hub/client.ts +633 -0
- package/src/hub/connect.ts +156 -0
- package/src/hub/daemon-entry.ts +122 -0
- package/src/hub/daemon.ts +284 -0
- package/src/hub/defaults.ts +70 -0
- package/src/hub/discovery.ts +247 -0
- package/src/hub/index.ts +14 -0
- package/src/hub/native-transport.ts +31 -0
- package/src/hub/runtime-handlers.ts +141 -0
- package/src/hub/server.ts +2317 -0
- package/src/hub/session-client.ts +502 -0
- package/src/hub/start-shared-server.ts +61 -0
- package/src/hub/transport.ts +14 -0
- package/src/hub/ui-client.ts +126 -0
- package/src/hub/workspace.ts +19 -0
- package/src/index.ts +169 -68
- package/src/llms/cline-recommended-models.ts +167 -0
- package/src/llms/configured-provider-registry.ts +193 -0
- package/src/llms/handler-factory.ts +56 -0
- package/src/llms/provider-defaults.ts +653 -0
- package/src/llms/provider-settings.ts +310 -0
- package/src/llms/runtime-config.ts +43 -0
- package/src/llms/runtime-registry.ts +172 -0
- package/src/llms/runtime-types.ts +121 -0
- package/src/runtime/agent-config-adapter.ts +636 -0
- package/src/runtime/agent-runtime-config-builder.ts +205 -0
- package/src/runtime/error-feedback.ts +142 -0
- package/src/runtime/history.ts +137 -0
- package/src/runtime/host.ts +127 -267
- package/src/runtime/index.ts +1 -0
- package/src/runtime/loop-detection.ts +162 -0
- package/src/runtime/mistake-tracker.ts +221 -0
- package/src/runtime/rules.ts +12 -0
- package/src/runtime/runtime-builder.ts +85 -13
- package/src/runtime/runtime-event-adapter.ts +412 -0
- package/src/runtime/runtime-host.ts +134 -62
- package/src/runtime/runtime-oauth-token-manager.ts +11 -15
- package/src/runtime/session-runtime-orchestrator.ts +1253 -0
- package/src/runtime/session-runtime.ts +16 -26
- package/src/runtime/user-input-builder.ts +167 -0
- package/src/services/global-settings.ts +122 -0
- package/src/services/local-runtime-bootstrap.ts +175 -31
- package/src/services/plugin-tools.ts +86 -0
- package/src/services/providers/local-provider-registry.ts +277 -61
- package/src/services/providers/local-provider-service.ts +109 -44
- package/src/services/session-data.ts +18 -10
- package/src/services/session-telemetry.ts +6 -15
- package/src/services/storage/file-team-store.ts +1 -5
- package/src/services/storage/provider-settings-legacy-migration.ts +14 -51
- package/src/services/storage/provider-settings-manager.ts +17 -2
- package/src/services/storage/sqlite-team-store.ts +1 -5
- package/src/services/workspace-manifest.ts +18 -0
- package/src/session/conversation-store.ts +77 -0
- package/src/session/file-session-service.ts +1 -1
- package/src/session/index.ts +6 -27
- package/src/session/message-builder.ts +941 -0
- package/src/session/persistence-service.ts +119 -504
- package/src/session/session-manifest-store.ts +158 -0
- package/src/session/session-row.ts +199 -0
- package/src/session/session-service.ts +17 -376
- package/src/session/session-team-coordination.ts +1 -1
- package/src/session/subagent-session-manager.ts +397 -0
- package/src/session/team-persistence-store.ts +176 -0
- package/src/transports/hub.ts +1081 -0
- package/src/transports/local.ts +419 -93
- package/src/transports/remote.ts +27 -0
- package/src/transports/runtime-host-support.ts +63 -9
- package/src/types/chat-schema.ts +4 -5
- package/src/types/config.ts +17 -7
- package/src/types/events.ts +8 -6
- package/src/types/index.ts +3 -0
- package/src/types/provider-settings.ts +18 -7
- package/src/types/session.ts +7 -6
- package/src/types.ts +42 -2
- package/dist/hooks/persistent.d.ts +0 -64
- package/dist/hooks/persistent.d.ts.map +0 -1
- package/dist/runtime/rpc-runtime-ensure.d.ts +0 -65
- package/dist/runtime/rpc-runtime-ensure.d.ts.map +0 -1
- package/dist/runtime/rpc-spawn-lease.d.ts +0 -8
- package/dist/runtime/rpc-spawn-lease.d.ts.map +0 -1
- package/dist/services/telemetry/index.js +0 -15
- package/dist/session/rpc-session-service.d.ts +0 -16
- package/dist/session/rpc-session-service.d.ts.map +0 -1
- package/dist/session/sqlite-rpc-session-backend.d.ts +0 -31
- package/dist/session/sqlite-rpc-session-backend.d.ts.map +0 -1
- package/dist/transports/rpc.d.ts +0 -51
- package/dist/transports/rpc.d.ts.map +0 -1
- package/src/ClineCore.test.ts +0 -226
- package/src/account/cline-account-service.test.ts +0 -185
- package/src/account/featurebase-token.test.ts +0 -175
- package/src/account/rpc.test.ts +0 -63
- package/src/auth/bounded-ttl-cache.test.ts +0 -38
- package/src/auth/client.test.ts +0 -69
- package/src/auth/cline.test.ts +0 -267
- package/src/auth/codex.test.ts +0 -170
- package/src/auth/oca.test.ts +0 -340
- package/src/auth/server.test.ts +0 -287
- package/src/auth/utils.test.ts +0 -128
- package/src/extensions/config/agent-config-loader.test.ts +0 -236
- package/src/extensions/config/hooks-config-loader.test.ts +0 -20
- package/src/extensions/config/runtime-commands.test.ts +0 -115
- package/src/extensions/config/unified-config-file-watcher.test.ts +0 -196
- package/src/extensions/config/user-instruction-config-loader.test.ts +0 -246
- package/src/extensions/context/compaction.test.ts +0 -483
- package/src/extensions/mcp/config-loader.test.ts +0 -238
- package/src/extensions/mcp/manager.test.ts +0 -105
- package/src/extensions/plugin/plugin-config-loader.test.ts +0 -184
- package/src/extensions/plugin/plugin-loader.test.ts +0 -292
- package/src/extensions/plugin/plugin-sandbox.test.ts +0 -423
- package/src/extensions/tools/definitions.test.ts +0 -780
- package/src/extensions/tools/executors/bash.test.ts +0 -87
- package/src/extensions/tools/executors/editor.test.ts +0 -35
- package/src/extensions/tools/executors/file-read.test.ts +0 -125
- package/src/extensions/tools/model-tool-routing.test.ts +0 -86
- package/src/extensions/tools/presets.test.ts +0 -70
- package/src/extensions/tools/team/multi-agent.lifecycle.test.ts +0 -455
- package/src/extensions/tools/team/spawn-agent-tool.test.ts +0 -381
- package/src/extensions/tools/team/team-tools.test.ts +0 -918
- package/src/hooks/checkpoint-hooks.test.ts +0 -168
- package/src/hooks/hook-file-hooks.test.ts +0 -311
- package/src/hooks/persistent.ts +0 -661
- package/src/runtime/history.test.ts +0 -114
- package/src/runtime/host.test.ts +0 -230
- package/src/runtime/rpc-runtime-ensure.test.ts +0 -123
- package/src/runtime/rpc-runtime-ensure.ts +0 -659
- package/src/runtime/rpc-spawn-lease.test.ts +0 -81
- package/src/runtime/rpc-spawn-lease.ts +0 -156
- package/src/runtime/runtime-builder.team-persistence.test.ts +0 -245
- package/src/runtime/runtime-builder.test.ts +0 -615
- package/src/runtime/runtime-oauth-token-manager.test.ts +0 -137
- package/src/runtime/runtime-parity.test.ts +0 -143
- package/src/services/providers/local-provider-service.test.ts +0 -1062
- package/src/services/session-data.test.ts +0 -160
- package/src/services/storage/provider-settings-legacy-migration.test.ts +0 -424
- package/src/services/storage/provider-settings-manager.test.ts +0 -191
- package/src/services/telemetry/OpenTelemetryAdapter.test.ts +0 -157
- package/src/services/telemetry/OpenTelemetryProvider.test.ts +0 -326
- package/src/services/telemetry/TelemetryLoggerSink.test.ts +0 -42
- package/src/services/telemetry/TelemetryService.test.ts +0 -134
- package/src/services/telemetry/distinct-id.test.ts +0 -57
- package/src/services/workspace/file-indexer.d.ts +0 -11
- package/src/services/workspace/file-indexer.test.ts +0 -156
- package/src/services/workspace/mention-enricher.test.ts +0 -106
- package/src/session/persistence-service.test.ts +0 -300
- package/src/session/rpc-session-service.ts +0 -114
- package/src/session/session-service.team-persistence.test.ts +0 -48
- package/src/session/sqlite-rpc-session-backend.ts +0 -301
- package/src/transports/local.e2e.test.ts +0 -380
- package/src/transports/local.test.ts +0 -2559
- package/src/transports/rpc.test.ts +0 -82
- package/src/transports/rpc.ts +0 -665
|
@@ -0,0 +1,1253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-session `SessionRuntime` orchestrator (PLAN.md §3.2.3).
|
|
3
|
+
*
|
|
4
|
+
* Owns all cross-turn state for one logical agent session:
|
|
5
|
+
*
|
|
6
|
+
* - `ConversationStore` — message transcript + session-started gate
|
|
7
|
+
* - `MistakeTracker` — per-session consecutive-mistake counter
|
|
8
|
+
* - `LoopDetectionTracker` — per-session repeated-tool-call detector
|
|
9
|
+
* - `MessageBuilder` — provider-message assembly cache
|
|
10
|
+
* - `HookEngine` + `HookBridge` — legacy `AgentHooks`/`AgentExtension[]`
|
|
11
|
+
* → runtime-hook synthesis
|
|
12
|
+
* - `RuntimeEventAdapter` — per-run stateful `AgentRuntimeEvent`
|
|
13
|
+
* → legacy `AgentEvent` translator
|
|
14
|
+
* - listener registry — host subscribers see legacy `AgentEvent`s
|
|
15
|
+
* - pending tool set, abort — per-run lifecycle housekeeping
|
|
16
|
+
*
|
|
17
|
+
* A fresh `AgentRuntime` is instantiated per run via
|
|
18
|
+
* `createAgentRuntime(createAgentRuntimeConfig({...}))`; it is the
|
|
19
|
+
* only class that depends on `@clinebot/agents`. All session-level
|
|
20
|
+
* state outlives any one `AgentRuntime`, making OAuth-retry + run
|
|
21
|
+
* replay feasible (§3.2.2 invariant #3).
|
|
22
|
+
*
|
|
23
|
+
* NOTE: this class lives alongside the pre-existing
|
|
24
|
+
* `packages/core/src/runtime/session-runtime.ts`, which exports
|
|
25
|
+
* transport-level `SessionRuntime`/`RuntimeBuilder` interfaces
|
|
26
|
+
* consumed by `LocalRuntimeHost`. Both coexist through Step 8/9; the
|
|
27
|
+
* transport-level interface is retired in Step 10 (§3.6 Step 10).
|
|
28
|
+
*
|
|
29
|
+
* @see PLAN.md §3.2.3 — public surface.
|
|
30
|
+
* @see PLAN.md §3.2.2 — call graph and invariants.
|
|
31
|
+
* @see PLAN.md §3.6 Step 8d — landing commit.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
import type { AgentRuntime } from "@clinebot/agents";
|
|
35
|
+
import { createAgentRuntime } from "@clinebot/agents";
|
|
36
|
+
import {
|
|
37
|
+
type AgentConfig,
|
|
38
|
+
type AgentEvent,
|
|
39
|
+
type AgentExtension,
|
|
40
|
+
type AgentExtensionRegistry,
|
|
41
|
+
type AgentFinishReason,
|
|
42
|
+
type AgentResult,
|
|
43
|
+
type AgentRunResult,
|
|
44
|
+
type AgentRuntimeEvent,
|
|
45
|
+
type BasicLogger,
|
|
46
|
+
type ContributionRegistry,
|
|
47
|
+
createContributionRegistry,
|
|
48
|
+
HookEngine,
|
|
49
|
+
type ITelemetryService,
|
|
50
|
+
type LegacyAgentUsage,
|
|
51
|
+
type LoopDetectionConfig,
|
|
52
|
+
type Message,
|
|
53
|
+
type MessageWithMetadata,
|
|
54
|
+
type ModelInfo,
|
|
55
|
+
type Tool,
|
|
56
|
+
type ToolCallRecord,
|
|
57
|
+
} from "@clinebot/shared";
|
|
58
|
+
import { HookBridge } from "../hooks/hook-bridge";
|
|
59
|
+
import {
|
|
60
|
+
createHandlerFromConfig,
|
|
61
|
+
resolveKnownModelsFromConfig,
|
|
62
|
+
} from "../llms/handler-factory";
|
|
63
|
+
import { ConversationStore } from "../session/conversation-store";
|
|
64
|
+
import { MessageBuilder } from "../session/message-builder";
|
|
65
|
+
import {
|
|
66
|
+
agentMessagesToMessagesWithMetadata,
|
|
67
|
+
apiHandlerToAgentModel,
|
|
68
|
+
messagesToAgentMessages,
|
|
69
|
+
toolsToAgentTools,
|
|
70
|
+
} from "./agent-config-adapter";
|
|
71
|
+
import { createAgentRuntimeConfig } from "./agent-runtime-config-builder";
|
|
72
|
+
import { LoopDetectionTracker } from "./loop-detection";
|
|
73
|
+
import { MistakeTracker } from "./mistake-tracker";
|
|
74
|
+
import { RuntimeEventAdapter } from "./runtime-event-adapter";
|
|
75
|
+
|
|
76
|
+
function formatToolResultError(output: unknown): string {
|
|
77
|
+
if (typeof output === "string") {
|
|
78
|
+
return output;
|
|
79
|
+
}
|
|
80
|
+
if (output instanceof Error) {
|
|
81
|
+
return output.message;
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
return JSON.stringify(output);
|
|
85
|
+
} catch {
|
|
86
|
+
return String(output);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// =============================================================================
|
|
91
|
+
// Public types
|
|
92
|
+
// =============================================================================
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Listener invoked for every legacy `AgentEvent` produced by the
|
|
96
|
+
* session runtime. Use `subscribeEvents(listener)` — it returns an
|
|
97
|
+
* `unsubscribe` function.
|
|
98
|
+
*/
|
|
99
|
+
export type SessionEventListener = (event: AgentEvent) => void;
|
|
100
|
+
|
|
101
|
+
/** Subset of host-side deps needed by the session orchestrator. */
|
|
102
|
+
export interface SessionRuntimeOrchestratorDeps {
|
|
103
|
+
readonly logger?: BasicLogger;
|
|
104
|
+
readonly telemetry?: ITelemetryService;
|
|
105
|
+
/** Optional pre-constructed `HookEngine`. A fresh one is built when omitted. */
|
|
106
|
+
readonly hookEngine?: HookEngine;
|
|
107
|
+
/**
|
|
108
|
+
* Test hook: override the `AgentRuntime` factory. Production
|
|
109
|
+
* callers leave this undefined and get the real `createAgentRuntime`.
|
|
110
|
+
*/
|
|
111
|
+
readonly createAgentRuntimeImpl?: (
|
|
112
|
+
config: Parameters<typeof createAgentRuntime>[0],
|
|
113
|
+
) => AgentRuntime;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Connection overrides applied via `updateConnection`. */
|
|
117
|
+
export interface ConnectionOverrides {
|
|
118
|
+
providerId?: string;
|
|
119
|
+
modelId?: string;
|
|
120
|
+
apiKey?: string;
|
|
121
|
+
baseUrl?: string;
|
|
122
|
+
headers?: Record<string, string>;
|
|
123
|
+
providerConfig?: unknown;
|
|
124
|
+
reasoningEffort?: AgentConfig["reasoningEffort"];
|
|
125
|
+
thinking?: boolean;
|
|
126
|
+
thinkingBudgetTokens?: number;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// =============================================================================
|
|
130
|
+
// SessionRuntime orchestrator
|
|
131
|
+
// =============================================================================
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Per-session orchestrator. Construct once per agent session; call
|
|
135
|
+
* `run` / `continue` repeatedly. The class matches the subset of
|
|
136
|
+
* legacy `Agent` surface that `legacy-agent-facade` needs in Step 8e.
|
|
137
|
+
*
|
|
138
|
+
* Named `SessionRuntime` to match PLAN.md §3.2.3. To avoid a symbol
|
|
139
|
+
* collision with the pre-existing transport-level `SessionRuntime`
|
|
140
|
+
* interface in `session-runtime.ts`, this file is **not** re-exported
|
|
141
|
+
* from `runtime/index.ts` or `src/index.ts`. Consumers (facade in
|
|
142
|
+
* Step 8e) import directly from this module.
|
|
143
|
+
*/
|
|
144
|
+
export class SessionRuntime {
|
|
145
|
+
private config: AgentConfig;
|
|
146
|
+
private readonly agentId: string;
|
|
147
|
+
private readonly parentAgentId?: string;
|
|
148
|
+
private readonly logger?: BasicLogger;
|
|
149
|
+
// Reserved for §3.4.4 telemetry parity (not yet consumed — §3.4.4
|
|
150
|
+
// listed as explicitly deferred until telemetry wiring is added).
|
|
151
|
+
// Typed as `readonly` to preserve the field slot for future use
|
|
152
|
+
// without re-touching the constructor.
|
|
153
|
+
readonly telemetry?: ITelemetryService;
|
|
154
|
+
private readonly hookEngine: HookEngine;
|
|
155
|
+
private readonly hookBridge: HookBridge;
|
|
156
|
+
private readonly conversation: ConversationStore;
|
|
157
|
+
private readonly mistakeTracker: MistakeTracker;
|
|
158
|
+
private readonly loopTracker: LoopDetectionTracker;
|
|
159
|
+
/**
|
|
160
|
+
* True when `execution.loopDetection === false` at construction
|
|
161
|
+
* time. Loop inspection is skipped entirely — the tracker still
|
|
162
|
+
* exists for API compatibility but is never fed.
|
|
163
|
+
*/
|
|
164
|
+
private readonly loopDetectionDisabled: boolean;
|
|
165
|
+
// Reserved for host-owned message assembly (currently handled
|
|
166
|
+
// inline by `agentMessagesToMessages` in the adapter). Kept as a
|
|
167
|
+
// ready slot so the compaction plugin can install a beforeModel
|
|
168
|
+
// hook that feeds an explicit `MessageBuilder` later.
|
|
169
|
+
readonly messageBuilder: MessageBuilder;
|
|
170
|
+
/**
|
|
171
|
+
* Contribution registry that hosts extension-provided tools,
|
|
172
|
+
* commands, message builders, and providers. Lazily initialized
|
|
173
|
+
* on first run (parity with legacy `Agent.ensureExtensionsInitialized`
|
|
174
|
+
* at `packages/agents/src/agent.ts:1122-1147`).
|
|
175
|
+
*/
|
|
176
|
+
private readonly contributionRegistry: ContributionRegistry<
|
|
177
|
+
AgentExtension,
|
|
178
|
+
Tool,
|
|
179
|
+
Message[]
|
|
180
|
+
>;
|
|
181
|
+
private extensionsInitialized = false;
|
|
182
|
+
private readonly listeners = new Set<SessionEventListener>();
|
|
183
|
+
private readonly createAgentRuntimeImpl: (
|
|
184
|
+
config: Parameters<typeof createAgentRuntime>[0],
|
|
185
|
+
) => AgentRuntime;
|
|
186
|
+
|
|
187
|
+
/** Stable run id shared with the HookBridge during an active run. */
|
|
188
|
+
private activeRunId: string | null = null;
|
|
189
|
+
/** True while a run is in flight. `canStartRun()` is the negation. */
|
|
190
|
+
private running = false;
|
|
191
|
+
/** True once `abort()` has been requested for the active run. */
|
|
192
|
+
private abortRequested = false;
|
|
193
|
+
/** Last abort reason requested for the active run. */
|
|
194
|
+
private abortReason: string | undefined;
|
|
195
|
+
/** Reference to the current run's `AgentRuntime` so `abort` can forward. */
|
|
196
|
+
private activeRuntime: AgentRuntime | null = null;
|
|
197
|
+
/** Promise for the current run so shutdown can await an aborted run's drain. */
|
|
198
|
+
private activeRunPromise: Promise<AgentResult> | null = null;
|
|
199
|
+
/** Per-run `Agent → AgentEvent` adapter; `reset()` each run. */
|
|
200
|
+
private readonly eventAdapter = new RuntimeEventAdapter();
|
|
201
|
+
/** Session-shutdown gate — rejects late runs. */
|
|
202
|
+
private shutdownCalled = false;
|
|
203
|
+
/** Running tally of tool-call records for `AgentResult.toolCalls`. */
|
|
204
|
+
private currentRunToolCalls: ToolCallRecord[] = [];
|
|
205
|
+
/** Aggregated usage across the current run. */
|
|
206
|
+
private currentRunUsage: LegacyAgentUsage = {
|
|
207
|
+
inputTokens: 0,
|
|
208
|
+
outputTokens: 0,
|
|
209
|
+
};
|
|
210
|
+
/** Tool-start timestamps for `ToolCallRecord.durationMs`. */
|
|
211
|
+
private toolStartedAt = new Map<string, Date>();
|
|
212
|
+
/** Tool-call input snapshot for `ToolCallRecord.input`. */
|
|
213
|
+
private toolInputs = new Map<string, unknown>();
|
|
214
|
+
/**
|
|
215
|
+
* Per-turn tool outcome counters used by the MistakeTracker wiring.
|
|
216
|
+
* Reset on every `turn-started` event; consumed on `turn-finished`
|
|
217
|
+
* to feed `mistakeTracker.record` when every tool call erred and no
|
|
218
|
+
* successful call landed. Matches legacy `agent.ts` tool-failure
|
|
219
|
+
* mistake-feed path (§3.4.6 + pre-Step-9 oracle lines 972-997).
|
|
220
|
+
*/
|
|
221
|
+
private currentTurnSuccessfulTools = 0;
|
|
222
|
+
private currentTurnFailedTools = 0;
|
|
223
|
+
private currentTurnFailureDetails: string[] = [];
|
|
224
|
+
/**
|
|
225
|
+
* Serial queue for `MistakeTracker.record(...)` + loop-detection
|
|
226
|
+
* side-effects fired from the sync `handleRuntimeEvent` stream. The
|
|
227
|
+
* tracker's `record()` is async but the runtime event stream is
|
|
228
|
+
* synchronous, so we chain tracker work onto a promise and await it
|
|
229
|
+
* in `executeRun` before returning the `AgentResult`.
|
|
230
|
+
*/
|
|
231
|
+
private activeTrackerWork: Promise<void> = Promise.resolve();
|
|
232
|
+
/** True when tracker logic has issued an abort for the active run. */
|
|
233
|
+
private trackerAbortInFlight = false;
|
|
234
|
+
|
|
235
|
+
constructor(config: AgentConfig, deps: SessionRuntimeOrchestratorDeps = {}) {
|
|
236
|
+
this.config = config;
|
|
237
|
+
this.agentId = `agent_${Date.now()}_${Math.random()
|
|
238
|
+
.toString(36)
|
|
239
|
+
.slice(2, 8)}`;
|
|
240
|
+
this.parentAgentId = config.parentAgentId;
|
|
241
|
+
this.logger = deps.logger ?? config.logger;
|
|
242
|
+
this.telemetry = deps.telemetry ?? config.telemetry;
|
|
243
|
+
this.hookEngine = deps.hookEngine ?? new HookEngine();
|
|
244
|
+
this.createAgentRuntimeImpl =
|
|
245
|
+
deps.createAgentRuntimeImpl ?? createAgentRuntime;
|
|
246
|
+
|
|
247
|
+
this.conversation = new ConversationStore(config.initialMessages);
|
|
248
|
+
this.messageBuilder = new MessageBuilder();
|
|
249
|
+
this.contributionRegistry = createContributionRegistry<
|
|
250
|
+
AgentExtension,
|
|
251
|
+
Tool,
|
|
252
|
+
Message[]
|
|
253
|
+
>({
|
|
254
|
+
extensions: config.extensions ? [...config.extensions] : [],
|
|
255
|
+
setupContext: {
|
|
256
|
+
session: config.extensionContext?.session,
|
|
257
|
+
client: config.extensionContext?.client,
|
|
258
|
+
user: config.extensionContext?.user,
|
|
259
|
+
workspaceInfo: config.extensionContext?.workspace,
|
|
260
|
+
automation: config.extensionContext?.automation,
|
|
261
|
+
logger: config.extensionContext?.logger ?? this.logger,
|
|
262
|
+
telemetry: config.extensionContext?.telemetry ?? this.telemetry,
|
|
263
|
+
},
|
|
264
|
+
});
|
|
265
|
+
// Resolve + validate eagerly so `getExtensionRegistry()` is
|
|
266
|
+
// callable before the first run (legacy parity with
|
|
267
|
+
// `Agent` constructor at packages/agents/src/agent.ts:158-159).
|
|
268
|
+
// `setup()` is deferred to `ensureExtensionsInitialized` on
|
|
269
|
+
// the first run so async extension setup can't block the
|
|
270
|
+
// constructor.
|
|
271
|
+
this.contributionRegistry.resolve();
|
|
272
|
+
this.contributionRegistry.validate();
|
|
273
|
+
this.hookBridge = new HookBridge({
|
|
274
|
+
agentId: this.agentId,
|
|
275
|
+
conversationId: this.conversation.getConversationId(),
|
|
276
|
+
parentAgentId: this.parentAgentId ?? null,
|
|
277
|
+
hookEngine: this.hookEngine,
|
|
278
|
+
hooks: config.hooks,
|
|
279
|
+
extensions: config.extensions,
|
|
280
|
+
getRunId: () => this.activeRunId ?? "",
|
|
281
|
+
onHookContext: (_source, context) => {
|
|
282
|
+
// Legacy behaviour: hook-returned context becomes a user
|
|
283
|
+
// message on the next turn. Stash it on the conversation
|
|
284
|
+
// store so subsequent turns pick it up.
|
|
285
|
+
this.conversation.appendMessage({
|
|
286
|
+
role: "user",
|
|
287
|
+
content: [{ type: "text", text: context }],
|
|
288
|
+
});
|
|
289
|
+
},
|
|
290
|
+
onDispatchError: (error) => {
|
|
291
|
+
this.logger?.error?.("SessionRuntime hook dispatch failed", {
|
|
292
|
+
agentId: this.agentId,
|
|
293
|
+
error,
|
|
294
|
+
});
|
|
295
|
+
},
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
const maxMistakes = config.execution?.maxConsecutiveMistakes ?? 6;
|
|
299
|
+
this.mistakeTracker = new MistakeTracker({
|
|
300
|
+
maxConsecutiveMistakes: maxMistakes,
|
|
301
|
+
onLimitReached: config.onConsecutiveMistakeLimitReached,
|
|
302
|
+
emit: (event) => this.emitLegacyEvent(event),
|
|
303
|
+
log: (level, message, metadata) =>
|
|
304
|
+
leveledLog(this.logger, level, message, metadata),
|
|
305
|
+
agentId: this.agentId,
|
|
306
|
+
getConversationId: () => this.conversation.getConversationId(),
|
|
307
|
+
getActiveRunId: () => this.activeRunId ?? "",
|
|
308
|
+
appendRecoveryNotice: (message, _reason) => {
|
|
309
|
+
this.conversation.appendMessage({
|
|
310
|
+
role: "user",
|
|
311
|
+
content: [{ type: "text", text: message }],
|
|
312
|
+
});
|
|
313
|
+
},
|
|
314
|
+
});
|
|
315
|
+
const loopDetectionInput = config.execution?.loopDetection;
|
|
316
|
+
this.loopDetectionDisabled = loopDetectionInput === false;
|
|
317
|
+
const loopConfig: Partial<LoopDetectionConfig> | undefined =
|
|
318
|
+
loopDetectionInput === false || loopDetectionInput === undefined
|
|
319
|
+
? undefined
|
|
320
|
+
: loopDetectionInput;
|
|
321
|
+
this.loopTracker = new LoopDetectionTracker(loopConfig);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// -------------------------------------------------------------------
|
|
325
|
+
// Accessors & state mutators
|
|
326
|
+
// -------------------------------------------------------------------
|
|
327
|
+
|
|
328
|
+
getAgentId(): string {
|
|
329
|
+
return this.agentId;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
getConversationId(): string {
|
|
333
|
+
return this.conversation.getConversationId();
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
getMessages(): MessageWithMetadata[] {
|
|
337
|
+
return this.conversation.getMessages();
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/** True when no run is currently active and the session is not shut down. */
|
|
341
|
+
canStartRun(): boolean {
|
|
342
|
+
return !this.running && !this.shutdownCalled;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Snapshot of the contribution registry (tools, commands, and other
|
|
347
|
+
* extension contributions).
|
|
348
|
+
*
|
|
349
|
+
* Before the first run, the registry is in the `validate` phase:
|
|
350
|
+
* extensions are validated but their `setup()` callbacks have not
|
|
351
|
+
* run yet, so the snapshot only reflects eagerly-declared
|
|
352
|
+
* contributions. After the first `run()`/`continue()`, the
|
|
353
|
+
* registry is initialized (§`ensureExtensionsInitialized`), and
|
|
354
|
+
* the snapshot reflects everything extensions registered via
|
|
355
|
+
* `api.registerTool` / `registerCommand` / `registerMessageBuilder`
|
|
356
|
+
* / `registerProvider` / `registerAutomationEventType`.
|
|
357
|
+
*/
|
|
358
|
+
getExtensionRegistry(): AgentExtensionRegistry<Tool, Message[]> {
|
|
359
|
+
return this.contributionRegistry.getRegistrySnapshot();
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/** Append additional tools to every subsequent turn's runtime config. */
|
|
363
|
+
addTools(tools: Tool[]): void {
|
|
364
|
+
if (tools.length === 0) {
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
const existing = new Set(this.config.tools.map((tool) => tool.name));
|
|
368
|
+
const merged = [...this.config.tools];
|
|
369
|
+
for (const tool of tools) {
|
|
370
|
+
if (!existing.has(tool.name)) {
|
|
371
|
+
merged.push(tool);
|
|
372
|
+
existing.add(tool.name);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
this.config = { ...this.config, tools: merged };
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/** Mutate provider / reasoning fields for subsequent runs. */
|
|
379
|
+
updateConnection(overrides: ConnectionOverrides): void {
|
|
380
|
+
const next: AgentConfig = { ...this.config };
|
|
381
|
+
if (overrides.providerId !== undefined)
|
|
382
|
+
next.providerId = overrides.providerId;
|
|
383
|
+
if (overrides.modelId !== undefined) next.modelId = overrides.modelId;
|
|
384
|
+
if (overrides.apiKey !== undefined) next.apiKey = overrides.apiKey;
|
|
385
|
+
if (overrides.baseUrl !== undefined) next.baseUrl = overrides.baseUrl;
|
|
386
|
+
if (overrides.headers !== undefined) next.headers = overrides.headers;
|
|
387
|
+
if (overrides.providerConfig !== undefined)
|
|
388
|
+
next.providerConfig = overrides.providerConfig;
|
|
389
|
+
if (overrides.reasoningEffort !== undefined)
|
|
390
|
+
next.reasoningEffort = overrides.reasoningEffort;
|
|
391
|
+
if (overrides.thinking !== undefined) next.thinking = overrides.thinking;
|
|
392
|
+
if (overrides.thinkingBudgetTokens !== undefined)
|
|
393
|
+
next.thinkingBudgetTokens = overrides.thinkingBudgetTokens;
|
|
394
|
+
this.config = next;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
clearHistory(): void {
|
|
398
|
+
this.conversation.clearHistory();
|
|
399
|
+
this.resetConversationBoundaryTrackers();
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
restore(messages: readonly MessageWithMetadata[]): void {
|
|
403
|
+
this.conversation.restore(messages);
|
|
404
|
+
this.resetConversationBoundaryTrackers();
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
private resetConversationBoundaryTrackers(): void {
|
|
408
|
+
this.mistakeTracker.reset();
|
|
409
|
+
this.loopTracker.reset();
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// -------------------------------------------------------------------
|
|
413
|
+
// Event subscription (legacy shape)
|
|
414
|
+
// -------------------------------------------------------------------
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Subscribe to **legacy** `AgentEvent`s. The session runtime
|
|
418
|
+
* translates the new `AgentRuntimeEvent` stream via
|
|
419
|
+
* `RuntimeEventAdapter` before fanout, so consumers see the
|
|
420
|
+
* pre-swap shape.
|
|
421
|
+
*/
|
|
422
|
+
subscribeEvents(listener: SessionEventListener): () => void {
|
|
423
|
+
this.listeners.add(listener);
|
|
424
|
+
return () => {
|
|
425
|
+
this.listeners.delete(listener);
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// -------------------------------------------------------------------
|
|
430
|
+
// Abort / shutdown
|
|
431
|
+
// -------------------------------------------------------------------
|
|
432
|
+
|
|
433
|
+
abort(reason?: unknown): void {
|
|
434
|
+
const message =
|
|
435
|
+
typeof reason === "string"
|
|
436
|
+
? reason
|
|
437
|
+
: reason instanceof Error
|
|
438
|
+
? reason.message
|
|
439
|
+
: reason === undefined
|
|
440
|
+
? undefined
|
|
441
|
+
: String(reason);
|
|
442
|
+
this.abortRequested = true;
|
|
443
|
+
this.abortReason = message;
|
|
444
|
+
if (this.activeRunPromise) {
|
|
445
|
+
/**
|
|
446
|
+
* Why this exists in hub mode:
|
|
447
|
+
*
|
|
448
|
+
* The TUI and the runtime are not always in the same process. In hub
|
|
449
|
+
* mode, the visible TUI talks to a shared daemon over websocket. When the
|
|
450
|
+
* user sends a prompt, the TUI sends a "start this run" command to the
|
|
451
|
+
* daemon. The daemon starts the AgentRuntime and stores the promise for
|
|
452
|
+
* that run as `activeRunPromise`.
|
|
453
|
+
*
|
|
454
|
+
* If the user presses Escape, the TUI sends a separate "cancel the
|
|
455
|
+
* current run" command to the daemon. Cancelling a run means aborting the
|
|
456
|
+
* AgentRuntime. That is supposed to interrupt the provider stream or any
|
|
457
|
+
* other in-flight async work, and the normal way that interruption shows
|
|
458
|
+
* up in JavaScript is a rejected promise. That rejection is not a bug by
|
|
459
|
+
* itself. It is the expected result of the user saying "stop this
|
|
460
|
+
* request."
|
|
461
|
+
*
|
|
462
|
+
* The important detail is that the rejection is already handled by the
|
|
463
|
+
* code path that started the run. The original "start this run" command
|
|
464
|
+
* is still awaiting `sessionHost.send(...)`, and that await is what
|
|
465
|
+
* should eventually turn the run result or run error into a reply/event
|
|
466
|
+
* for the client.
|
|
467
|
+
*
|
|
468
|
+
* The problem we hit was a timing gap inside the daemon process. The
|
|
469
|
+
* separate cancel command can call `activeRuntime.abort(message)` while
|
|
470
|
+
* the original start command is still waiting elsewhere. The abort can
|
|
471
|
+
* make `activeRunPromise` reject immediately. If the runtime reports that
|
|
472
|
+
* rejection before the original start command observes it, Node/Bun can
|
|
473
|
+
* briefly classify it as an `unhandledRejection`.
|
|
474
|
+
*
|
|
475
|
+
* In the hub daemon, `unhandledRejection` is fatal. That is normally the
|
|
476
|
+
* right policy because real unhandled errors should not be ignored. But
|
|
477
|
+
* for this cancellation path it meant Escape could kill the daemon even
|
|
478
|
+
* though the run error was expected and the original start command was
|
|
479
|
+
* still responsible for handling it. After the daemon died, the next
|
|
480
|
+
* prompt looked like it started loading, then silently stalled because
|
|
481
|
+
* the TUI was talking to a dead runtime process.
|
|
482
|
+
*
|
|
483
|
+
* This `.catch()` is not the real application-level error handling. It is
|
|
484
|
+
* only a local safety observer attached before we trigger the abort, so
|
|
485
|
+
* the daemon does not mistake an expected cancellation rejection for a
|
|
486
|
+
* process crash. We do not replace `activeRunPromise`, await this catch,
|
|
487
|
+
* or convert the rejection into success. The original start command, and
|
|
488
|
+
* any other caller awaiting `run()` / `continue()`, still receives the
|
|
489
|
+
* same result or error it would have received without this observer.
|
|
490
|
+
*/
|
|
491
|
+
void this.activeRunPromise.catch(() => {});
|
|
492
|
+
}
|
|
493
|
+
this.activeRuntime?.abort(message);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Shut the session down. Fires `hook.session_shutdown` exactly once
|
|
498
|
+
* (legacy parity with `Agent.shutdown` at pre-Step-9 `agent.ts:325`)
|
|
499
|
+
* and drains any in-flight async hooks through the hook engine.
|
|
500
|
+
*
|
|
501
|
+
* @param reason Optional caller-supplied reason string (e.g.
|
|
502
|
+
* `"session_complete"`, `"session_error"`,
|
|
503
|
+
* `"session_stop"`, `"ctrl_d"`). Propagated into
|
|
504
|
+
* the `session_shutdown` hook payload as
|
|
505
|
+
* `AgentHookSessionShutdownContext.reason` so hook
|
|
506
|
+
* handlers (e.g. hook-file runners) can route on
|
|
507
|
+
* it — this matches the legacy `Agent.shutdown(reason)`
|
|
508
|
+
* contract and keeps `isAbortReason(ctx.reason)`
|
|
509
|
+
* checks in `hook-file-hooks.ts` working.
|
|
510
|
+
* @param timeoutMs Optional drain timeout forwarded to
|
|
511
|
+
* `HookEngine.shutdown`. Defaults to the engine's
|
|
512
|
+
* own default (3000 ms) when omitted.
|
|
513
|
+
*/
|
|
514
|
+
async shutdown(reason?: string, timeoutMs?: number): Promise<void> {
|
|
515
|
+
if (this.running) {
|
|
516
|
+
if (!this.abortRequested || !this.activeRunPromise) {
|
|
517
|
+
throw new Error(
|
|
518
|
+
`SessionRuntime.shutdown called while a run is in progress (agentId=${this.agentId})`,
|
|
519
|
+
);
|
|
520
|
+
}
|
|
521
|
+
await this.activeRunPromise;
|
|
522
|
+
}
|
|
523
|
+
if (this.shutdownCalled) {
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
this.shutdownCalled = true;
|
|
527
|
+
await this.hookBridge.dispatch("hook.session_shutdown", {
|
|
528
|
+
stage: "session_shutdown",
|
|
529
|
+
payload: {
|
|
530
|
+
agentId: this.agentId,
|
|
531
|
+
conversationId: this.conversation.getConversationId(),
|
|
532
|
+
sessionId: this.config.sessionId,
|
|
533
|
+
parentAgentId: this.parentAgentId ?? null,
|
|
534
|
+
reason,
|
|
535
|
+
},
|
|
536
|
+
});
|
|
537
|
+
await this.hookBridge.shutdown(timeoutMs);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// -------------------------------------------------------------------
|
|
541
|
+
// Run / continue
|
|
542
|
+
// -------------------------------------------------------------------
|
|
543
|
+
|
|
544
|
+
async run(
|
|
545
|
+
userMessage: string,
|
|
546
|
+
userImages?: string[],
|
|
547
|
+
userFiles?: string[],
|
|
548
|
+
): Promise<AgentResult> {
|
|
549
|
+
this.conversation.resetForRun();
|
|
550
|
+
this.resetConversationBoundaryTrackers();
|
|
551
|
+
return this.executeRun({
|
|
552
|
+
userMessage,
|
|
553
|
+
userImages,
|
|
554
|
+
userFiles,
|
|
555
|
+
isContinue: false,
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
async continue(
|
|
560
|
+
userMessage?: string,
|
|
561
|
+
userImages?: string[],
|
|
562
|
+
userFiles?: string[],
|
|
563
|
+
): Promise<AgentResult> {
|
|
564
|
+
return this.executeRun({
|
|
565
|
+
userMessage,
|
|
566
|
+
userImages,
|
|
567
|
+
userFiles,
|
|
568
|
+
isContinue: true,
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// -------------------------------------------------------------------
|
|
573
|
+
// Private implementation
|
|
574
|
+
// -------------------------------------------------------------------
|
|
575
|
+
|
|
576
|
+
private async executeRun(input: {
|
|
577
|
+
userMessage?: string;
|
|
578
|
+
userImages?: string[];
|
|
579
|
+
userFiles?: string[];
|
|
580
|
+
isContinue: boolean;
|
|
581
|
+
}): Promise<AgentResult> {
|
|
582
|
+
const runPromise = this.executeRunInternal(input);
|
|
583
|
+
this.activeRunPromise = runPromise;
|
|
584
|
+
try {
|
|
585
|
+
return await runPromise;
|
|
586
|
+
} finally {
|
|
587
|
+
if (this.activeRunPromise === runPromise) {
|
|
588
|
+
this.activeRunPromise = null;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
private async executeRunInternal(input: {
|
|
594
|
+
userMessage?: string;
|
|
595
|
+
userImages?: string[];
|
|
596
|
+
userFiles?: string[];
|
|
597
|
+
isContinue: boolean;
|
|
598
|
+
}): Promise<AgentResult> {
|
|
599
|
+
if (this.shutdownCalled) {
|
|
600
|
+
throw new Error(
|
|
601
|
+
`SessionRuntime.run called after shutdown (agentId=${this.agentId})`,
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
if (this.running) {
|
|
605
|
+
throw new Error(
|
|
606
|
+
`SessionRuntime state is "running"; call canStartRun() first (agentId=${this.agentId})`,
|
|
607
|
+
);
|
|
608
|
+
}
|
|
609
|
+
this.running = true;
|
|
610
|
+
this.abortRequested = false;
|
|
611
|
+
this.abortReason = undefined;
|
|
612
|
+
this.activeRunId = `run_${Date.now()}_${Math.random()
|
|
613
|
+
.toString(36)
|
|
614
|
+
.slice(2, 8)}`;
|
|
615
|
+
// Lazily initialize contribution-registry extensions on the
|
|
616
|
+
// first run. Legacy parity with `Agent.ensureExtensionsInitialized`
|
|
617
|
+
// (pre-Step-9 `agent.ts:245`/:277), called before any
|
|
618
|
+
// session_start / input / run_start dispatch.
|
|
619
|
+
await this.ensureExtensionsInitialized();
|
|
620
|
+
this.eventAdapter.reset();
|
|
621
|
+
this.currentRunToolCalls = [];
|
|
622
|
+
this.currentRunUsage = { inputTokens: 0, outputTokens: 0 };
|
|
623
|
+
this.toolStartedAt.clear();
|
|
624
|
+
this.toolInputs.clear();
|
|
625
|
+
this.currentTurnSuccessfulTools = 0;
|
|
626
|
+
this.currentTurnFailedTools = 0;
|
|
627
|
+
this.currentTurnFailureDetails = [];
|
|
628
|
+
this.activeTrackerWork = Promise.resolve();
|
|
629
|
+
this.trackerAbortInFlight = false;
|
|
630
|
+
|
|
631
|
+
const startedAt = new Date();
|
|
632
|
+
const mode: "run" | "continue" = input.isContinue ? "continue" : "run";
|
|
633
|
+
|
|
634
|
+
// ------------------------------------------------------------------
|
|
635
|
+
// session_start — dispatched once per logical session, before any
|
|
636
|
+
// run-scoped work. Parity with legacy agent.ts pre-Step-9 (L503).
|
|
637
|
+
// ConversationStore owns the "started" gate and resets it on
|
|
638
|
+
// resetForRun()/clearHistory()/restore() — i.e. a fresh session
|
|
639
|
+
// boundary refires session_start, matching legacy semantics.
|
|
640
|
+
// ------------------------------------------------------------------
|
|
641
|
+
if (!this.conversation.isSessionStarted()) {
|
|
642
|
+
const sessionStartControl = await this.hookBridge.dispatch(
|
|
643
|
+
"hook.session_start",
|
|
644
|
+
{
|
|
645
|
+
stage: "session_start",
|
|
646
|
+
payload: {
|
|
647
|
+
agentId: this.agentId,
|
|
648
|
+
conversationId: this.conversation.getConversationId(),
|
|
649
|
+
parentAgentId: this.parentAgentId ?? null,
|
|
650
|
+
},
|
|
651
|
+
},
|
|
652
|
+
);
|
|
653
|
+
this.conversation.markSessionStarted();
|
|
654
|
+
if (sessionStartControl?.cancel === true) {
|
|
655
|
+
return this.finalizeAbortedRun(startedAt);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// ------------------------------------------------------------------
|
|
660
|
+
// hook.input — dispatched once per run/continue when the caller
|
|
661
|
+
// supplied a user message. Honors control.overrideInput (string)
|
|
662
|
+
// and control.cancel. Parity with legacy agent.ts pre-Step-9
|
|
663
|
+
// (L1154 `prepareUserInput`).
|
|
664
|
+
// ------------------------------------------------------------------
|
|
665
|
+
let effectiveUserMessage = input.userMessage;
|
|
666
|
+
if (effectiveUserMessage !== undefined) {
|
|
667
|
+
const inputControl = await this.hookBridge.dispatch("hook.input", {
|
|
668
|
+
stage: "input",
|
|
669
|
+
payload: {
|
|
670
|
+
agentId: this.agentId,
|
|
671
|
+
conversationId: this.conversation.getConversationId(),
|
|
672
|
+
parentAgentId: this.parentAgentId ?? null,
|
|
673
|
+
mode,
|
|
674
|
+
input: effectiveUserMessage,
|
|
675
|
+
},
|
|
676
|
+
});
|
|
677
|
+
if (inputControl?.cancel === true) {
|
|
678
|
+
return this.finalizeAbortedRun(startedAt);
|
|
679
|
+
}
|
|
680
|
+
if (typeof inputControl?.overrideInput === "string") {
|
|
681
|
+
effectiveUserMessage = inputControl.overrideInput;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// Append the user turn (if any) to the conversation store. This
|
|
686
|
+
// must happen BEFORE we snapshot `initialMessages` below so the
|
|
687
|
+
// runtime sees the user message as part of its seed — we then
|
|
688
|
+
// pass an empty input to `runtime.run()` so the runtime does not
|
|
689
|
+
// append the message a second time (AgentRuntime.execute treats
|
|
690
|
+
// a falsy input as "no additional messages", per
|
|
691
|
+
// packages/agents/src/agent-runtime.ts normalizeInput path).
|
|
692
|
+
if (effectiveUserMessage !== undefined) {
|
|
693
|
+
const content = await buildUserTurnContent(
|
|
694
|
+
effectiveUserMessage,
|
|
695
|
+
input.userImages,
|
|
696
|
+
input.userFiles,
|
|
697
|
+
this.config.userFileContentLoader,
|
|
698
|
+
);
|
|
699
|
+
this.conversation.appendMessage({ role: "user", content });
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// Build the AgentRuntime for this turn.
|
|
703
|
+
const handler = createHandlerFromConfig(this.config, this.logger);
|
|
704
|
+
const agentModel = apiHandlerToAgentModel(handler, {
|
|
705
|
+
prepareMessages: (messages) => this.prepareMessagesForApi(messages),
|
|
706
|
+
});
|
|
707
|
+
// Merge extension-contributed tools with the config-declared
|
|
708
|
+
// tools for this turn. Extensions register tools via
|
|
709
|
+
// `api.registerTool` during `setup()` — parity with legacy
|
|
710
|
+
// `Agent.ensureExtensionsInitialized` at pre-Step-9 `agent.ts:1140-1146`
|
|
711
|
+
// which merged `this.contributionRegistry.getRegisteredTools()`
|
|
712
|
+
// into `this.config.tools`. Dedupe by name so a config tool
|
|
713
|
+
// wins over a same-named extension tool (legacy behaviour:
|
|
714
|
+
// `validateTools` rejects duplicates; here we prefer the
|
|
715
|
+
// explicitly-declared config tool).
|
|
716
|
+
const extensionTools = this.contributionRegistry.getRegisteredTools();
|
|
717
|
+
const mergedToolsByName = new Map<string, Tool>();
|
|
718
|
+
for (const tool of extensionTools) {
|
|
719
|
+
mergedToolsByName.set(tool.name, tool);
|
|
720
|
+
}
|
|
721
|
+
for (const tool of this.config.tools) {
|
|
722
|
+
mergedToolsByName.set(tool.name, tool);
|
|
723
|
+
}
|
|
724
|
+
const conversationId = this.conversation.getConversationId();
|
|
725
|
+
const modelInfo = tryGetModelInfo(this.config);
|
|
726
|
+
const adaptedTools = toolsToAgentTools(
|
|
727
|
+
Array.from(mergedToolsByName.values()),
|
|
728
|
+
{
|
|
729
|
+
conversationId,
|
|
730
|
+
metadata: {
|
|
731
|
+
modelSupportsImages:
|
|
732
|
+
modelInfo?.capabilities?.includes("images") ?? true,
|
|
733
|
+
...this.config.toolContextMetadata,
|
|
734
|
+
},
|
|
735
|
+
},
|
|
736
|
+
);
|
|
737
|
+
// Seed initialMessages with the full prior transcript (including
|
|
738
|
+
// the user message we just appended) so multi-turn history is
|
|
739
|
+
// preserved across runs. Fixes P1 #1: prior turns were silently
|
|
740
|
+
// lost because `createAgentRuntimeConfig` received no seed and
|
|
741
|
+
// `replaceMessages(runResult.messages)` downstream overwrote the
|
|
742
|
+
// conversation with just the current-turn trail.
|
|
743
|
+
const initialMessages = messagesToAgentMessages(
|
|
744
|
+
this.conversation.getMessages(),
|
|
745
|
+
);
|
|
746
|
+
const runtimeConfig = createAgentRuntimeConfig({
|
|
747
|
+
agentConfig: this.config,
|
|
748
|
+
sessionId: this.config.sessionId,
|
|
749
|
+
agentId: this.agentId,
|
|
750
|
+
conversationId,
|
|
751
|
+
parentAgentId: this.parentAgentId,
|
|
752
|
+
model: agentModel,
|
|
753
|
+
logger: this.logger,
|
|
754
|
+
tools: adaptedTools,
|
|
755
|
+
hookBridge: this.hookBridge,
|
|
756
|
+
initialMessages,
|
|
757
|
+
});
|
|
758
|
+
const runtime = this.createAgentRuntimeImpl(runtimeConfig);
|
|
759
|
+
this.activeRuntime = runtime;
|
|
760
|
+
if (this.abortRequested) {
|
|
761
|
+
runtime.abort(this.abortReason);
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
// Subscribe to runtime events; fan out legacy events to listeners
|
|
765
|
+
// and keep private book-keeping for tool-call records / usage.
|
|
766
|
+
const unsubscribe = runtime.subscribe((event: AgentRuntimeEvent) => {
|
|
767
|
+
this.handleRuntimeEvent(event);
|
|
768
|
+
});
|
|
769
|
+
|
|
770
|
+
let runResult: AgentRunResult | undefined;
|
|
771
|
+
let thrownError: Error | undefined;
|
|
772
|
+
try {
|
|
773
|
+
// Pass empty input so AgentRuntime does not duplicate the
|
|
774
|
+
// user message we already seeded via `initialMessages`. The
|
|
775
|
+
// runtime's `normalizeInput` treats `""`/`undefined` as
|
|
776
|
+
// "no extra messages".
|
|
777
|
+
if (input.isContinue) {
|
|
778
|
+
runResult = await runtime.continue(undefined);
|
|
779
|
+
} else {
|
|
780
|
+
runResult = await runtime.run("");
|
|
781
|
+
}
|
|
782
|
+
} catch (error) {
|
|
783
|
+
thrownError = error instanceof Error ? error : new Error(String(error));
|
|
784
|
+
} finally {
|
|
785
|
+
unsubscribe();
|
|
786
|
+
// Drain any in-flight tracker work (mistake/loop side-effects
|
|
787
|
+
// queued from handleRuntimeEvent) before we clear state so a
|
|
788
|
+
// late abort can still reach the runtime if needed.
|
|
789
|
+
try {
|
|
790
|
+
await this.activeTrackerWork;
|
|
791
|
+
} catch (error) {
|
|
792
|
+
this.logger?.error?.(
|
|
793
|
+
"SessionRuntime tracker work failed during drain",
|
|
794
|
+
{ agentId: this.agentId, error },
|
|
795
|
+
);
|
|
796
|
+
}
|
|
797
|
+
this.activeRuntime = null;
|
|
798
|
+
this.running = false;
|
|
799
|
+
this.abortRequested = false;
|
|
800
|
+
this.abortReason = undefined;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
// Persist the runtime's message trail back into the conversation
|
|
804
|
+
// store so later turns see assistant output. The runtime state
|
|
805
|
+
// was seeded with the full transcript, so `runResult.messages`
|
|
806
|
+
// IS the complete new transcript (seed + newly-produced turn).
|
|
807
|
+
if (runResult && runResult.messages.length > 0) {
|
|
808
|
+
const replacement = agentMessagesToMessagesWithMetadata(
|
|
809
|
+
runResult.messages,
|
|
810
|
+
);
|
|
811
|
+
this.conversation.replaceMessages(replacement);
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
const endedAt = new Date();
|
|
815
|
+
try {
|
|
816
|
+
const result = this.buildLegacyResult({
|
|
817
|
+
runResult,
|
|
818
|
+
thrownError,
|
|
819
|
+
startedAt,
|
|
820
|
+
endedAt,
|
|
821
|
+
});
|
|
822
|
+
await this.dispatchRunEnd(result);
|
|
823
|
+
return result;
|
|
824
|
+
} finally {
|
|
825
|
+
this.activeRunId = null;
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
/**
|
|
830
|
+
* Build an aborted `AgentResult` without invoking the AgentRuntime.
|
|
831
|
+
* Used by the `hook.session_start` / `hook.input` cancel paths —
|
|
832
|
+
* legacy parity: a hook that returns `{ cancel: true }` short-circuits
|
|
833
|
+
* the run and yields `finishReason: "aborted"`.
|
|
834
|
+
*/
|
|
835
|
+
private finalizeAbortedRun(startedAt: Date): AgentResult {
|
|
836
|
+
this.activeRuntime = null;
|
|
837
|
+
this.activeRunId = null;
|
|
838
|
+
this.running = false;
|
|
839
|
+
this.abortRequested = false;
|
|
840
|
+
this.abortReason = undefined;
|
|
841
|
+
const endedAt = new Date();
|
|
842
|
+
const messages = this.conversation.getMessages();
|
|
843
|
+
const modelInfo = tryGetModelInfo(this.config);
|
|
844
|
+
return {
|
|
845
|
+
text: "",
|
|
846
|
+
usage: { inputTokens: 0, outputTokens: 0 },
|
|
847
|
+
messages,
|
|
848
|
+
toolCalls: [],
|
|
849
|
+
iterations: 0,
|
|
850
|
+
finishReason: "aborted",
|
|
851
|
+
model: {
|
|
852
|
+
id: this.config.modelId,
|
|
853
|
+
provider: this.config.providerId,
|
|
854
|
+
info: modelInfo,
|
|
855
|
+
},
|
|
856
|
+
startedAt,
|
|
857
|
+
endedAt,
|
|
858
|
+
durationMs: endedAt.getTime() - startedAt.getTime(),
|
|
859
|
+
};
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
/**
|
|
863
|
+
* Initialize the contribution registry once per session. Runs
|
|
864
|
+
* extension `setup()` callbacks so they can `registerTool`,
|
|
865
|
+
* `registerCommand`, `registerMessageBuilder`, and
|
|
866
|
+
* `registerProvider`. Matches legacy `Agent.ensureExtensionsInitialized`
|
|
867
|
+
* at pre-Step-9 `agent.ts:1122-1147`:
|
|
868
|
+
*
|
|
869
|
+
* - on `hookErrorMode === "throw"`, setup failures propagate;
|
|
870
|
+
* - otherwise setup failures emit a recoverable `error` event
|
|
871
|
+
* via the legacy event channel and leave the registry
|
|
872
|
+
* partially initialized.
|
|
873
|
+
*
|
|
874
|
+
* Idempotent: subsequent calls are no-ops once the registry has
|
|
875
|
+
* been activated.
|
|
876
|
+
*/
|
|
877
|
+
private async ensureExtensionsInitialized(): Promise<void> {
|
|
878
|
+
if (this.extensionsInitialized) {
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
try {
|
|
882
|
+
await this.contributionRegistry.initialize();
|
|
883
|
+
} catch (error) {
|
|
884
|
+
if (this.config.hookErrorMode === "throw") {
|
|
885
|
+
throw error;
|
|
886
|
+
}
|
|
887
|
+
this.emitLegacyEvent({
|
|
888
|
+
type: "error",
|
|
889
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
890
|
+
recoverable: true,
|
|
891
|
+
iteration: 0,
|
|
892
|
+
});
|
|
893
|
+
}
|
|
894
|
+
this.extensionsInitialized = true;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
private async prepareMessagesForApi(messages: Message[]): Promise<Message[]> {
|
|
898
|
+
let prepared = messages;
|
|
899
|
+
for (const builder of this.contributionRegistry.getRegistrySnapshot()
|
|
900
|
+
.messageBuilder) {
|
|
901
|
+
prepared = await builder.build(prepared);
|
|
902
|
+
}
|
|
903
|
+
return this.messageBuilder.buildForApi(prepared);
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
private handleRuntimeEvent(event: AgentRuntimeEvent): void {
|
|
907
|
+
// Track tool-call records before translation so the timing data
|
|
908
|
+
// is available to observers via `AgentResult.toolCalls`.
|
|
909
|
+
switch (event.type) {
|
|
910
|
+
case "turn-started": {
|
|
911
|
+
// Reset per-turn tool-outcome counters used by the
|
|
912
|
+
// MistakeTracker wiring. Parity with pre-Step-9
|
|
913
|
+
// agent.ts which accumulates per-iteration success/fail
|
|
914
|
+
// counts and feeds them into recordMistake at the
|
|
915
|
+
// turn boundary.
|
|
916
|
+
this.currentTurnSuccessfulTools = 0;
|
|
917
|
+
this.currentTurnFailedTools = 0;
|
|
918
|
+
this.currentTurnFailureDetails = [];
|
|
919
|
+
break;
|
|
920
|
+
}
|
|
921
|
+
case "tool-started": {
|
|
922
|
+
this.toolStartedAt.set(event.toolCall.toolCallId, new Date());
|
|
923
|
+
this.toolInputs.set(event.toolCall.toolCallId, event.toolCall.input);
|
|
924
|
+
// Loop-detection inspection: identical consecutive
|
|
925
|
+
// tool-call signatures trip the tracker. On "soft"
|
|
926
|
+
// verdict we append a recovery notice; on "hard"
|
|
927
|
+
// verdict we feed the mistake tracker with
|
|
928
|
+
// forceAtLimit:true and abort. Parity with pre-Step-9
|
|
929
|
+
// agent.ts L917-954.
|
|
930
|
+
this.inspectLoopForToolCall(
|
|
931
|
+
event.toolCall.toolName,
|
|
932
|
+
event.toolCall.input,
|
|
933
|
+
event.iteration,
|
|
934
|
+
);
|
|
935
|
+
break;
|
|
936
|
+
}
|
|
937
|
+
case "tool-finished": {
|
|
938
|
+
const startedAt = this.toolStartedAt.get(event.toolCall.toolCallId);
|
|
939
|
+
const endedAt = new Date();
|
|
940
|
+
const input = this.toolInputs.get(event.toolCall.toolCallId);
|
|
941
|
+
this.toolStartedAt.delete(event.toolCall.toolCallId);
|
|
942
|
+
this.toolInputs.delete(event.toolCall.toolCallId);
|
|
943
|
+
const resultPart = event.message.content.find(
|
|
944
|
+
(part) => part.type === "tool-result",
|
|
945
|
+
);
|
|
946
|
+
const isError =
|
|
947
|
+
resultPart?.type === "tool-result" && resultPart.isError === true;
|
|
948
|
+
const errorText = isError
|
|
949
|
+
? formatToolResultError(
|
|
950
|
+
resultPart?.type === "tool-result"
|
|
951
|
+
? resultPart.output
|
|
952
|
+
: undefined,
|
|
953
|
+
)
|
|
954
|
+
: undefined;
|
|
955
|
+
const record: ToolCallRecord = {
|
|
956
|
+
id: event.toolCall.toolCallId,
|
|
957
|
+
name: event.toolCall.toolName,
|
|
958
|
+
input,
|
|
959
|
+
output:
|
|
960
|
+
resultPart?.type === "tool-result" ? resultPart.output : undefined,
|
|
961
|
+
error: errorText,
|
|
962
|
+
durationMs:
|
|
963
|
+
startedAt === undefined
|
|
964
|
+
? 0
|
|
965
|
+
: endedAt.getTime() - startedAt.getTime(),
|
|
966
|
+
startedAt: startedAt ?? endedAt,
|
|
967
|
+
endedAt,
|
|
968
|
+
};
|
|
969
|
+
this.currentRunToolCalls.push(record);
|
|
970
|
+
// Per-turn success/failure bookkeeping for MistakeTracker.
|
|
971
|
+
if (isError) {
|
|
972
|
+
this.currentTurnFailedTools += 1;
|
|
973
|
+
if (errorText) {
|
|
974
|
+
this.currentTurnFailureDetails.push(
|
|
975
|
+
`[${event.toolCall.toolName}] ${errorText}`,
|
|
976
|
+
);
|
|
977
|
+
}
|
|
978
|
+
} else {
|
|
979
|
+
this.currentTurnSuccessfulTools += 1;
|
|
980
|
+
}
|
|
981
|
+
break;
|
|
982
|
+
}
|
|
983
|
+
case "turn-finished": {
|
|
984
|
+
// End-of-turn mistake evaluation: legacy parity (pre-Step-9
|
|
985
|
+
// agent.ts L972-997). When some tool calls failed and the
|
|
986
|
+
// turn had no successful tool calls, record a mistake;
|
|
987
|
+
// reset on productive turns.
|
|
988
|
+
const failed = this.currentTurnFailedTools;
|
|
989
|
+
const succeeded = this.currentTurnSuccessfulTools;
|
|
990
|
+
if (failed > 0 && succeeded === 0) {
|
|
991
|
+
const details = this.currentTurnFailureDetails.join("; ");
|
|
992
|
+
this.enqueueMistakeRecord({
|
|
993
|
+
iteration: event.iteration,
|
|
994
|
+
reason: "tool_execution_failed",
|
|
995
|
+
details: `${failed} tool call(s) failed${
|
|
996
|
+
details ? `: ${details}` : ""
|
|
997
|
+
}`,
|
|
998
|
+
});
|
|
999
|
+
} else if (succeeded > 0) {
|
|
1000
|
+
// Productive turn — reset the tracker so transient
|
|
1001
|
+
// failures don't accumulate across unrelated turns.
|
|
1002
|
+
this.mistakeTracker.reset();
|
|
1003
|
+
}
|
|
1004
|
+
break;
|
|
1005
|
+
}
|
|
1006
|
+
case "usage-updated": {
|
|
1007
|
+
this.currentRunUsage = {
|
|
1008
|
+
inputTokens: event.usage.inputTokens,
|
|
1009
|
+
outputTokens: event.usage.outputTokens,
|
|
1010
|
+
cacheReadTokens:
|
|
1011
|
+
event.usage.cacheReadTokens > 0
|
|
1012
|
+
? event.usage.cacheReadTokens
|
|
1013
|
+
: undefined,
|
|
1014
|
+
cacheWriteTokens:
|
|
1015
|
+
event.usage.cacheWriteTokens > 0
|
|
1016
|
+
? event.usage.cacheWriteTokens
|
|
1017
|
+
: undefined,
|
|
1018
|
+
totalCost: event.usage.totalCost,
|
|
1019
|
+
};
|
|
1020
|
+
break;
|
|
1021
|
+
}
|
|
1022
|
+
default:
|
|
1023
|
+
break;
|
|
1024
|
+
}
|
|
1025
|
+
for (const legacy of this.eventAdapter.translate(event)) {
|
|
1026
|
+
this.emitLegacyEvent(legacy);
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
private emitLegacyEvent(event: AgentEvent): void {
|
|
1031
|
+
for (const listener of this.listeners) {
|
|
1032
|
+
try {
|
|
1033
|
+
listener(event);
|
|
1034
|
+
} catch (error) {
|
|
1035
|
+
this.logger?.error?.("SessionRuntime event listener threw", {
|
|
1036
|
+
agentId: this.agentId,
|
|
1037
|
+
error,
|
|
1038
|
+
});
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
/**
|
|
1044
|
+
* Feed the `LoopDetectionTracker` with a tool-call and react to
|
|
1045
|
+
* the returned verdict. Parity with pre-Step-9 agent.ts L917-954:
|
|
1046
|
+
*
|
|
1047
|
+
* - `"soft"` → append a recovery notice telling the model to
|
|
1048
|
+
* change approach;
|
|
1049
|
+
* - `"hard"` → feed `MistakeTracker.record` with
|
|
1050
|
+
* `forceAtLimit:true`. When the tracker returns
|
|
1051
|
+
* `action: "stop"`, append the stop notice and
|
|
1052
|
+
* abort the active runtime.
|
|
1053
|
+
*/
|
|
1054
|
+
private inspectLoopForToolCall(
|
|
1055
|
+
toolName: string,
|
|
1056
|
+
input: unknown,
|
|
1057
|
+
iteration: number,
|
|
1058
|
+
): void {
|
|
1059
|
+
if (this.trackerAbortInFlight || this.loopDetectionDisabled) {
|
|
1060
|
+
return;
|
|
1061
|
+
}
|
|
1062
|
+
const verdict = this.loopTracker.inspect({ name: toolName, input });
|
|
1063
|
+
if (verdict.kind === "ok") {
|
|
1064
|
+
return;
|
|
1065
|
+
}
|
|
1066
|
+
if (verdict.kind === "soft") {
|
|
1067
|
+
if (verdict.message) {
|
|
1068
|
+
this.conversation.appendMessage({
|
|
1069
|
+
role: "user",
|
|
1070
|
+
content: [{ type: "text", text: verdict.message }],
|
|
1071
|
+
});
|
|
1072
|
+
}
|
|
1073
|
+
return;
|
|
1074
|
+
}
|
|
1075
|
+
// Hard escalation.
|
|
1076
|
+
this.enqueueMistakeRecord({
|
|
1077
|
+
iteration,
|
|
1078
|
+
reason: "tool_execution_failed",
|
|
1079
|
+
forceAtLimit: true,
|
|
1080
|
+
details:
|
|
1081
|
+
verdict.message ??
|
|
1082
|
+
`Detected repeated tool calls to \`${toolName}\`; stopping to avoid a loop.`,
|
|
1083
|
+
});
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
/**
|
|
1087
|
+
* Enqueue a mistake-record onto the serial tracker work chain. The
|
|
1088
|
+
* runtime event stream is synchronous but `MistakeTracker.record`
|
|
1089
|
+
* is async — chaining onto a shared promise preserves ordering
|
|
1090
|
+
* (legacy parity) and lets `executeRun` await draining before
|
|
1091
|
+
* returning the `AgentResult`.
|
|
1092
|
+
*
|
|
1093
|
+
* When the tracker returns `action: "stop"`, append the stop notice
|
|
1094
|
+
* to the conversation and abort the active runtime so the run ends
|
|
1095
|
+
* with `finishReason: "aborted"`.
|
|
1096
|
+
*/
|
|
1097
|
+
private enqueueMistakeRecord(input: {
|
|
1098
|
+
iteration: number;
|
|
1099
|
+
reason: "api_error" | "invalid_tool_call" | "tool_execution_failed";
|
|
1100
|
+
details?: string;
|
|
1101
|
+
forceAtLimit?: boolean;
|
|
1102
|
+
}): void {
|
|
1103
|
+
if (this.trackerAbortInFlight) {
|
|
1104
|
+
return;
|
|
1105
|
+
}
|
|
1106
|
+
this.activeTrackerWork = this.activeTrackerWork.then(async () => {
|
|
1107
|
+
if (this.trackerAbortInFlight) {
|
|
1108
|
+
return;
|
|
1109
|
+
}
|
|
1110
|
+
const outcome = await this.mistakeTracker.record(input);
|
|
1111
|
+
if (outcome.action === "stop") {
|
|
1112
|
+
this.trackerAbortInFlight = true;
|
|
1113
|
+
this.conversation.appendMessage({
|
|
1114
|
+
role: "user",
|
|
1115
|
+
content: [{ type: "text", text: outcome.message }],
|
|
1116
|
+
});
|
|
1117
|
+
this.activeRuntime?.abort(outcome.reason ?? outcome.message);
|
|
1118
|
+
}
|
|
1119
|
+
});
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
private buildLegacyResult(input: {
|
|
1123
|
+
runResult: AgentRunResult | undefined;
|
|
1124
|
+
thrownError: Error | undefined;
|
|
1125
|
+
startedAt: Date;
|
|
1126
|
+
endedAt: Date;
|
|
1127
|
+
}): AgentResult {
|
|
1128
|
+
const { runResult, thrownError, startedAt, endedAt } = input;
|
|
1129
|
+
const durationMs = endedAt.getTime() - startedAt.getTime();
|
|
1130
|
+
const finishReason: AgentFinishReason = thrownError
|
|
1131
|
+
? "error"
|
|
1132
|
+
: deriveFinishReason(runResult);
|
|
1133
|
+
const text =
|
|
1134
|
+
runResult?.outputText ||
|
|
1135
|
+
(runResult?.status === "failed" ? runResult.error?.message : undefined) ||
|
|
1136
|
+
"";
|
|
1137
|
+
const usage: LegacyAgentUsage = runResult
|
|
1138
|
+
? {
|
|
1139
|
+
inputTokens: runResult.usage.inputTokens,
|
|
1140
|
+
outputTokens: runResult.usage.outputTokens,
|
|
1141
|
+
cacheReadTokens:
|
|
1142
|
+
runResult.usage.cacheReadTokens > 0
|
|
1143
|
+
? runResult.usage.cacheReadTokens
|
|
1144
|
+
: undefined,
|
|
1145
|
+
cacheWriteTokens:
|
|
1146
|
+
runResult.usage.cacheWriteTokens > 0
|
|
1147
|
+
? runResult.usage.cacheWriteTokens
|
|
1148
|
+
: undefined,
|
|
1149
|
+
totalCost: runResult.usage.totalCost,
|
|
1150
|
+
}
|
|
1151
|
+
: this.currentRunUsage;
|
|
1152
|
+
const messages = runResult
|
|
1153
|
+
? agentMessagesToMessagesWithMetadata(runResult.messages)
|
|
1154
|
+
: this.conversation.getMessages();
|
|
1155
|
+
const modelInfo = tryGetModelInfo(this.config);
|
|
1156
|
+
if (thrownError) {
|
|
1157
|
+
throw thrownError;
|
|
1158
|
+
}
|
|
1159
|
+
return {
|
|
1160
|
+
text,
|
|
1161
|
+
usage,
|
|
1162
|
+
messages,
|
|
1163
|
+
toolCalls: this.currentRunToolCalls,
|
|
1164
|
+
iterations: runResult?.iterations ?? 0,
|
|
1165
|
+
finishReason,
|
|
1166
|
+
model: {
|
|
1167
|
+
id: this.config.modelId,
|
|
1168
|
+
provider: this.config.providerId,
|
|
1169
|
+
info: modelInfo,
|
|
1170
|
+
},
|
|
1171
|
+
startedAt,
|
|
1172
|
+
endedAt,
|
|
1173
|
+
durationMs,
|
|
1174
|
+
};
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
private async dispatchRunEnd(result: AgentResult): Promise<void> {
|
|
1178
|
+
await this.hookBridge.dispatch("hook.run_end", {
|
|
1179
|
+
stage: "run_end",
|
|
1180
|
+
iteration: result.iterations,
|
|
1181
|
+
payload: {
|
|
1182
|
+
agentId: this.agentId,
|
|
1183
|
+
conversationId: this.conversation.getConversationId(),
|
|
1184
|
+
parentAgentId: this.parentAgentId ?? null,
|
|
1185
|
+
result,
|
|
1186
|
+
},
|
|
1187
|
+
});
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
// =============================================================================
|
|
1192
|
+
// Module-level helpers
|
|
1193
|
+
// =============================================================================
|
|
1194
|
+
|
|
1195
|
+
function leveledLog(
|
|
1196
|
+
logger: BasicLogger | undefined,
|
|
1197
|
+
level: "debug" | "info" | "warn" | "error",
|
|
1198
|
+
message: string,
|
|
1199
|
+
metadata?: Record<string, unknown>,
|
|
1200
|
+
): void {
|
|
1201
|
+
if (!logger) {
|
|
1202
|
+
return;
|
|
1203
|
+
}
|
|
1204
|
+
if (level === "debug") {
|
|
1205
|
+
logger.debug(message, metadata);
|
|
1206
|
+
return;
|
|
1207
|
+
}
|
|
1208
|
+
if (level === "error" && logger.error) {
|
|
1209
|
+
logger.error(message, metadata);
|
|
1210
|
+
return;
|
|
1211
|
+
}
|
|
1212
|
+
const severity: "info" | "warn" | "error" =
|
|
1213
|
+
level === "warn" ? "warn" : level === "error" ? "error" : "info";
|
|
1214
|
+
logger.log(message, { ...metadata, severity });
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
function deriveFinishReason(
|
|
1218
|
+
runResult: AgentRunResult | undefined,
|
|
1219
|
+
): AgentFinishReason {
|
|
1220
|
+
if (!runResult) {
|
|
1221
|
+
return "error";
|
|
1222
|
+
}
|
|
1223
|
+
switch (runResult.status) {
|
|
1224
|
+
case "completed":
|
|
1225
|
+
return "completed";
|
|
1226
|
+
case "aborted":
|
|
1227
|
+
return "aborted";
|
|
1228
|
+
case "failed":
|
|
1229
|
+
return "error";
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
async function buildUserTurnContent(
|
|
1234
|
+
userMessage: string,
|
|
1235
|
+
userImages: string[] | undefined,
|
|
1236
|
+
userFiles: string[] | undefined,
|
|
1237
|
+
loader: AgentConfig["userFileContentLoader"],
|
|
1238
|
+
): Promise<Message["content"]> {
|
|
1239
|
+
// Import lazily to avoid a circular-import hazard via runtime barrels.
|
|
1240
|
+
const { buildInitialUserContent } = await import("./user-input-builder");
|
|
1241
|
+
return buildInitialUserContent(userMessage, userImages, userFiles, loader);
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
function tryGetModelInfo(config: AgentConfig): ModelInfo | undefined {
|
|
1245
|
+
if (config.knownModels?.[config.modelId]) {
|
|
1246
|
+
return config.knownModels[config.modelId];
|
|
1247
|
+
}
|
|
1248
|
+
const resolvedKnownModels = resolveKnownModelsFromConfig(config);
|
|
1249
|
+
if (resolvedKnownModels?.[config.modelId]) {
|
|
1250
|
+
return resolvedKnownModels[config.modelId];
|
|
1251
|
+
}
|
|
1252
|
+
return undefined;
|
|
1253
|
+
}
|