@agentick/core 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +875 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/.tsbuildinfo.build +1 -0
- package/dist/agent.d.ts +32 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +26 -0
- package/dist/agent.js.map +1 -0
- package/dist/agentick-instance.d.ts +285 -0
- package/dist/agentick-instance.d.ts.map +1 -0
- package/dist/agentick-instance.js +700 -0
- package/dist/agentick-instance.js.map +1 -0
- package/dist/aidk-instance.d.ts +294 -0
- package/dist/aidk-instance.d.ts.map +1 -0
- package/dist/aidk-instance.js +340 -0
- package/dist/aidk-instance.js.map +1 -0
- package/dist/app/session-store.d.ts +57 -0
- package/dist/app/session-store.d.ts.map +1 -0
- package/dist/app/session-store.js +87 -0
- package/dist/app/session-store.js.map +1 -0
- package/dist/app/session.d.ts +209 -0
- package/dist/app/session.d.ts.map +1 -0
- package/dist/app/session.js +2131 -0
- package/dist/app/session.js.map +1 -0
- package/dist/app/sqlite-session-store.d.ts +60 -0
- package/dist/app/sqlite-session-store.d.ts.map +1 -0
- package/dist/app/sqlite-session-store.js +234 -0
- package/dist/app/sqlite-session-store.js.map +1 -0
- package/dist/app/types.d.ts +1461 -0
- package/dist/app/types.d.ts.map +1 -0
- package/dist/app/types.js +14 -0
- package/dist/app/types.js.map +1 -0
- package/dist/app.d.ts +79 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +83 -0
- package/dist/app.js.map +1 -0
- package/dist/channels/adapters/index.d.ts +2 -0
- package/dist/channels/adapters/index.d.ts.map +1 -0
- package/dist/channels/adapters/index.js +2 -0
- package/dist/channels/adapters/index.js.map +1 -0
- package/dist/channels/adapters/redis.d.ts +77 -0
- package/dist/channels/adapters/redis.d.ts.map +1 -0
- package/dist/channels/adapters/redis.js +259 -0
- package/dist/channels/adapters/redis.js.map +1 -0
- package/dist/channels/index.d.ts +38 -0
- package/dist/channels/index.d.ts.map +1 -0
- package/dist/channels/index.js +38 -0
- package/dist/channels/index.js.map +1 -0
- package/dist/channels/service.d.ts +684 -0
- package/dist/channels/service.d.ts.map +1 -0
- package/dist/channels/service.js +870 -0
- package/dist/channels/service.js.map +1 -0
- package/dist/channels/transports/index.d.ts +4 -0
- package/dist/channels/transports/index.d.ts.map +1 -0
- package/dist/channels/transports/index.js +4 -0
- package/dist/channels/transports/index.js.map +1 -0
- package/dist/channels/transports/socketio.d.ts +98 -0
- package/dist/channels/transports/socketio.d.ts.map +1 -0
- package/dist/channels/transports/socketio.js +246 -0
- package/dist/channels/transports/socketio.js.map +1 -0
- package/dist/channels/transports/streamable-http.d.ts +107 -0
- package/dist/channels/transports/streamable-http.d.ts.map +1 -0
- package/dist/channels/transports/streamable-http.js +353 -0
- package/dist/channels/transports/streamable-http.js.map +1 -0
- package/dist/channels/transports/websocket.d.ts +117 -0
- package/dist/channels/transports/websocket.d.ts.map +1 -0
- package/dist/channels/transports/websocket.js +416 -0
- package/dist/channels/transports/websocket.js.map +1 -0
- package/dist/com/index.d.ts +29 -0
- package/dist/com/index.d.ts.map +1 -0
- package/dist/com/index.js +29 -0
- package/dist/com/index.js.map +1 -0
- package/dist/com/object-model.d.ts +634 -0
- package/dist/com/object-model.d.ts.map +1 -0
- package/dist/com/object-model.js +963 -0
- package/dist/com/object-model.js.map +1 -0
- package/dist/com/types.d.ts +192 -0
- package/dist/com/types.d.ts.map +1 -0
- package/dist/com/types.js +1 -0
- package/dist/com/types.js.map +1 -0
- package/dist/compiler/collector.d.ts +16 -0
- package/dist/compiler/collector.d.ts.map +1 -0
- package/dist/compiler/collector.js +388 -0
- package/dist/compiler/collector.js.map +1 -0
- package/dist/compiler/content-block-registry.d.ts +11 -0
- package/dist/compiler/content-block-registry.d.ts.map +1 -0
- package/dist/compiler/content-block-registry.js +312 -0
- package/dist/compiler/content-block-registry.js.map +1 -0
- package/dist/compiler/extractors.d.ts +68 -0
- package/dist/compiler/extractors.d.ts.map +1 -0
- package/dist/compiler/extractors.js +547 -0
- package/dist/compiler/extractors.js.map +1 -0
- package/dist/compiler/fiber-compiler.d.ts +203 -0
- package/dist/compiler/fiber-compiler.d.ts.map +1 -0
- package/dist/compiler/fiber-compiler.js +498 -0
- package/dist/compiler/fiber-compiler.js.map +1 -0
- package/dist/compiler/fiber.d.ts +61 -0
- package/dist/compiler/fiber.d.ts.map +1 -0
- package/dist/compiler/fiber.js +244 -0
- package/dist/compiler/fiber.js.map +1 -0
- package/dist/compiler/index.d.ts +18 -0
- package/dist/compiler/index.d.ts.map +1 -0
- package/dist/compiler/index.js +38 -0
- package/dist/compiler/index.js.map +1 -0
- package/dist/compiler/scheduler.d.ts +95 -0
- package/dist/compiler/scheduler.d.ts.map +1 -0
- package/dist/compiler/scheduler.js +138 -0
- package/dist/compiler/scheduler.js.map +1 -0
- package/dist/compiler/structure-renderer.d.ts +42 -0
- package/dist/compiler/structure-renderer.d.ts.map +1 -0
- package/dist/compiler/structure-renderer.js +189 -0
- package/dist/compiler/structure-renderer.js.map +1 -0
- package/dist/compiler/types.d.ts +96 -0
- package/dist/compiler/types.d.ts.map +1 -0
- package/dist/compiler/types.js +19 -0
- package/dist/compiler/types.js.map +1 -0
- package/dist/component/component-hooks.d.ts +68 -0
- package/dist/component/component-hooks.d.ts.map +1 -0
- package/dist/component/component-hooks.js +112 -0
- package/dist/component/component-hooks.js.map +1 -0
- package/dist/component/component.d.ts +314 -0
- package/dist/component/component.d.ts.map +1 -0
- package/dist/component/component.js +64 -0
- package/dist/component/component.js.map +1 -0
- package/dist/component/index.d.ts +47 -0
- package/dist/component/index.d.ts.map +1 -0
- package/dist/component/index.js +47 -0
- package/dist/component/index.js.map +1 -0
- package/dist/component/tentickle-component.d.ts +185 -0
- package/dist/component/tentickle-component.d.ts.map +1 -0
- package/dist/component/tentickle-component.js +182 -0
- package/dist/component/tentickle-component.js.map +1 -0
- package/dist/content/index.d.ts +12 -0
- package/dist/content/index.d.ts.map +1 -0
- package/dist/content/index.js +17 -0
- package/dist/content/index.js.map +1 -0
- package/dist/context/index.d.ts +51 -0
- package/dist/context/index.d.ts.map +1 -0
- package/dist/context/index.js +69 -0
- package/dist/context/index.js.map +1 -0
- package/dist/core/channel-helpers.d.ts +31 -0
- package/dist/core/channel-helpers.d.ts.map +1 -0
- package/dist/core/channel-helpers.js +62 -0
- package/dist/core/channel-helpers.js.map +1 -0
- package/dist/core/channel.d.ts +164 -0
- package/dist/core/channel.d.ts.map +1 -0
- package/dist/core/channel.js +199 -0
- package/dist/core/channel.js.map +1 -0
- package/dist/core/context.d.ts +412 -0
- package/dist/core/context.d.ts.map +1 -0
- package/dist/core/context.js +290 -0
- package/dist/core/context.js.map +1 -0
- package/dist/core/event-buffer.d.ts +212 -0
- package/dist/core/event-buffer.d.ts.map +1 -0
- package/dist/core/event-buffer.js +346 -0
- package/dist/core/event-buffer.js.map +1 -0
- package/dist/core/execution-helpers.d.ts +179 -0
- package/dist/core/execution-helpers.d.ts.map +1 -0
- package/dist/core/execution-helpers.js +212 -0
- package/dist/core/execution-helpers.js.map +1 -0
- package/dist/core/execution-tracker.d.ts +53 -0
- package/dist/core/execution-tracker.d.ts.map +1 -0
- package/dist/core/execution-tracker.js +309 -0
- package/dist/core/execution-tracker.js.map +1 -0
- package/dist/core/index.d.ts +58 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +58 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/logger.d.ts +341 -0
- package/dist/core/logger.d.ts.map +1 -0
- package/dist/core/logger.js +346 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/metrics-helpers.d.ts +40 -0
- package/dist/core/metrics-helpers.d.ts.map +1 -0
- package/dist/core/metrics-helpers.js +72 -0
- package/dist/core/metrics-helpers.js.map +1 -0
- package/dist/core/otel-provider.d.ts +54 -0
- package/dist/core/otel-provider.d.ts.map +1 -0
- package/dist/core/otel-provider.js +107 -0
- package/dist/core/otel-provider.js.map +1 -0
- package/dist/core/procedure-graph.d.ts +136 -0
- package/dist/core/procedure-graph.d.ts.map +1 -0
- package/dist/core/procedure-graph.js +272 -0
- package/dist/core/procedure-graph.js.map +1 -0
- package/dist/core/procedure.d.ts +755 -0
- package/dist/core/procedure.d.ts.map +1 -0
- package/dist/core/procedure.js +899 -0
- package/dist/core/procedure.js.map +1 -0
- package/dist/core/stream.d.ts +106 -0
- package/dist/core/stream.d.ts.map +1 -0
- package/dist/core/stream.js +186 -0
- package/dist/core/stream.js.map +1 -0
- package/dist/core/telemetry.d.ts +182 -0
- package/dist/core/telemetry.d.ts.map +1 -0
- package/dist/core/telemetry.js +124 -0
- package/dist/core/telemetry.js.map +1 -0
- package/dist/engine/client-tool-coordinator.d.ts +50 -0
- package/dist/engine/client-tool-coordinator.d.ts.map +1 -0
- package/dist/engine/client-tool-coordinator.js +121 -0
- package/dist/engine/client-tool-coordinator.js.map +1 -0
- package/dist/engine/engine-events.d.ts +117 -0
- package/dist/engine/engine-events.d.ts.map +1 -0
- package/dist/engine/engine-events.js +178 -0
- package/dist/engine/engine-events.js.map +1 -0
- package/dist/engine/engine-response.d.ts +48 -0
- package/dist/engine/engine-response.d.ts.map +1 -0
- package/dist/engine/engine-response.js +2 -0
- package/dist/engine/engine-response.js.map +1 -0
- package/dist/engine/execution-graph.d.ts +104 -0
- package/dist/engine/execution-graph.d.ts.map +1 -0
- package/dist/engine/execution-graph.js +257 -0
- package/dist/engine/execution-graph.js.map +1 -0
- package/dist/engine/execution-handle.d.ts +212 -0
- package/dist/engine/execution-handle.d.ts.map +1 -0
- package/dist/engine/execution-handle.js +602 -0
- package/dist/engine/execution-handle.js.map +1 -0
- package/dist/engine/execution-types.d.ts +248 -0
- package/dist/engine/execution-types.d.ts.map +1 -0
- package/dist/engine/execution-types.js +23 -0
- package/dist/engine/execution-types.js.map +1 -0
- package/dist/engine/index.d.ts +21 -0
- package/dist/engine/index.d.ts.map +1 -0
- package/dist/engine/index.js +23 -0
- package/dist/engine/index.js.map +1 -0
- package/dist/engine/tool-confirmation-coordinator.d.ts +74 -0
- package/dist/engine/tool-confirmation-coordinator.d.ts.map +1 -0
- package/dist/engine/tool-confirmation-coordinator.js +137 -0
- package/dist/engine/tool-confirmation-coordinator.js.map +1 -0
- package/dist/engine/tool-executor.d.ts +127 -0
- package/dist/engine/tool-executor.d.ts.map +1 -0
- package/dist/engine/tool-executor.js +363 -0
- package/dist/engine/tool-executor.js.map +1 -0
- package/dist/hibernation/index.d.ts +126 -0
- package/dist/hibernation/index.d.ts.map +1 -0
- package/dist/hibernation/index.js +127 -0
- package/dist/hibernation/index.js.map +1 -0
- package/dist/hooks/base-hook-registry.d.ts +41 -0
- package/dist/hooks/base-hook-registry.d.ts.map +1 -0
- package/dist/hooks/base-hook-registry.js +76 -0
- package/dist/hooks/base-hook-registry.js.map +1 -0
- package/dist/hooks/com-state.d.ts +40 -0
- package/dist/hooks/com-state.d.ts.map +1 -0
- package/dist/hooks/com-state.js +90 -0
- package/dist/hooks/com-state.js.map +1 -0
- package/dist/hooks/context-info.d.ts +139 -0
- package/dist/hooks/context-info.d.ts.map +1 -0
- package/dist/hooks/context-info.js +115 -0
- package/dist/hooks/context-info.js.map +1 -0
- package/dist/hooks/context-internal.d.ts +21 -0
- package/dist/hooks/context-internal.d.ts.map +1 -0
- package/dist/hooks/context-internal.js +20 -0
- package/dist/hooks/context-internal.js.map +1 -0
- package/dist/hooks/context.d.ts +64 -0
- package/dist/hooks/context.d.ts.map +1 -0
- package/dist/hooks/context.js +83 -0
- package/dist/hooks/context.js.map +1 -0
- package/dist/hooks/data.d.ts +33 -0
- package/dist/hooks/data.d.ts.map +1 -0
- package/dist/hooks/data.js +84 -0
- package/dist/hooks/data.js.map +1 -0
- package/dist/hooks/formatter-context.d.ts +34 -0
- package/dist/hooks/formatter-context.d.ts.map +1 -0
- package/dist/hooks/formatter-context.js +34 -0
- package/dist/hooks/formatter-context.js.map +1 -0
- package/dist/hooks/hook-registry.d.ts +45 -0
- package/dist/hooks/hook-registry.d.ts.map +1 -0
- package/dist/hooks/hook-registry.js +109 -0
- package/dist/hooks/hook-registry.js.map +1 -0
- package/dist/hooks/index.d.ts +20 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +47 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/knob.d.ts +87 -0
- package/dist/hooks/knob.d.ts.map +1 -0
- package/dist/hooks/knob.js +129 -0
- package/dist/hooks/knob.js.map +1 -0
- package/dist/hooks/knobs-component.d.ts +70 -0
- package/dist/hooks/knobs-component.d.ts.map +1 -0
- package/dist/hooks/knobs-component.js +300 -0
- package/dist/hooks/knobs-component.js.map +1 -0
- package/dist/hooks/lifecycle.d.ts +158 -0
- package/dist/hooks/lifecycle.d.ts.map +1 -0
- package/dist/hooks/lifecycle.js +217 -0
- package/dist/hooks/lifecycle.js.map +1 -0
- package/dist/hooks/message-context.d.ts +101 -0
- package/dist/hooks/message-context.d.ts.map +1 -0
- package/dist/hooks/message-context.js +145 -0
- package/dist/hooks/message-context.js.map +1 -0
- package/dist/hooks/policy-context.d.ts.map +1 -0
- package/dist/hooks/runtime-context.d.ts +122 -0
- package/dist/hooks/runtime-context.d.ts.map +1 -0
- package/dist/hooks/runtime-context.js +149 -0
- package/dist/hooks/runtime-context.js.map +1 -0
- package/dist/hooks/signal.d.ts +267 -0
- package/dist/hooks/signal.d.ts.map +1 -0
- package/dist/hooks/signal.js +825 -0
- package/dist/hooks/signal.js.map +1 -0
- package/dist/hooks/types.d.ts +179 -0
- package/dist/hooks/types.d.ts.map +1 -0
- package/dist/hooks/types.js +5 -0
- package/dist/hooks/types.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +50 -0
- package/dist/index.js.map +1 -0
- package/dist/jsx/components/agent.d.ts +64 -0
- package/dist/jsx/components/agent.d.ts.map +1 -0
- package/dist/jsx/components/agent.js +80 -0
- package/dist/jsx/components/agent.js.map +1 -0
- package/dist/jsx/components/complete.d.ts +65 -0
- package/dist/jsx/components/complete.d.ts.map +1 -0
- package/dist/jsx/components/complete.js +64 -0
- package/dist/jsx/components/complete.js.map +1 -0
- package/dist/jsx/components/content.d.ts +98 -0
- package/dist/jsx/components/content.d.ts.map +1 -0
- package/dist/jsx/components/content.js +51 -0
- package/dist/jsx/components/content.js.map +1 -0
- package/dist/jsx/components/harness.d.ts +118 -0
- package/dist/jsx/components/harness.d.ts.map +1 -0
- package/dist/jsx/components/harness.js +117 -0
- package/dist/jsx/components/harness.js.map +1 -0
- package/dist/jsx/components/index.d.ts +11 -0
- package/dist/jsx/components/index.d.ts.map +1 -0
- package/dist/jsx/components/index.js +11 -0
- package/dist/jsx/components/index.js.map +1 -0
- package/dist/jsx/components/markdown.d.ts +31 -0
- package/dist/jsx/components/markdown.d.ts.map +1 -0
- package/dist/jsx/components/markdown.js +17 -0
- package/dist/jsx/components/markdown.js.map +1 -0
- package/dist/jsx/components/messages.d.ts +283 -0
- package/dist/jsx/components/messages.d.ts.map +1 -0
- package/dist/jsx/components/messages.js +257 -0
- package/dist/jsx/components/messages.js.map +1 -0
- package/dist/jsx/components/model.d.ts +94 -0
- package/dist/jsx/components/model.d.ts.map +1 -0
- package/dist/jsx/components/model.js +96 -0
- package/dist/jsx/components/model.js.map +1 -0
- package/dist/jsx/components/primitives.d.ts +117 -0
- package/dist/jsx/components/primitives.d.ts.map +1 -0
- package/dist/jsx/components/primitives.js +83 -0
- package/dist/jsx/components/primitives.js.map +1 -0
- package/dist/jsx/components/renderer.d.ts +24 -0
- package/dist/jsx/components/renderer.d.ts.map +1 -0
- package/dist/jsx/components/renderer.js +11 -0
- package/dist/jsx/components/renderer.js.map +1 -0
- package/dist/jsx/components/semantic.d.ts +155 -0
- package/dist/jsx/components/semantic.d.ts.map +1 -0
- package/dist/jsx/components/semantic.js +39 -0
- package/dist/jsx/components/semantic.js.map +1 -0
- package/dist/jsx/components/timeline.d.ts +157 -0
- package/dist/jsx/components/timeline.d.ts.map +1 -0
- package/dist/jsx/components/timeline.js +357 -0
- package/dist/jsx/components/timeline.js.map +1 -0
- package/dist/jsx/components/token-budget.d.ts +70 -0
- package/dist/jsx/components/token-budget.d.ts.map +1 -0
- package/dist/jsx/components/token-budget.js +135 -0
- package/dist/jsx/components/token-budget.js.map +1 -0
- package/dist/jsx/components/xml.d.ts +27 -0
- package/dist/jsx/components/xml.d.ts.map +1 -0
- package/dist/jsx/components/xml.js +17 -0
- package/dist/jsx/components/xml.js.map +1 -0
- package/dist/jsx/index.d.ts +58 -0
- package/dist/jsx/index.d.ts.map +1 -0
- package/dist/jsx/index.js +59 -0
- package/dist/jsx/index.js.map +1 -0
- package/dist/jsx/jsx-runtime.d.ts +370 -0
- package/dist/jsx/jsx-runtime.d.ts.map +1 -0
- package/dist/jsx/jsx-runtime.js +79 -0
- package/dist/jsx/jsx-runtime.js.map +1 -0
- package/dist/jsx/jsx-types.d.ts +23 -0
- package/dist/jsx/jsx-types.d.ts.map +1 -0
- package/dist/jsx/jsx-types.js +1 -0
- package/dist/jsx/jsx-types.js.map +1 -0
- package/dist/mcp/client.d.ts +46 -0
- package/dist/mcp/client.d.ts.map +1 -0
- package/dist/mcp/client.js +138 -0
- package/dist/mcp/client.js.map +1 -0
- package/dist/mcp/component.d.ts +95 -0
- package/dist/mcp/component.d.ts.map +1 -0
- package/dist/mcp/component.js +185 -0
- package/dist/mcp/component.js.map +1 -0
- package/dist/mcp/create-mcp-tool.d.ts +191 -0
- package/dist/mcp/create-mcp-tool.d.ts.map +1 -0
- package/dist/mcp/create-mcp-tool.js +228 -0
- package/dist/mcp/create-mcp-tool.js.map +1 -0
- package/dist/mcp/index.d.ts +49 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +48 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/service.d.ts +39 -0
- package/dist/mcp/service.d.ts.map +1 -0
- package/dist/mcp/service.js +77 -0
- package/dist/mcp/service.js.map +1 -0
- package/dist/mcp/tool.d.ts +55 -0
- package/dist/mcp/tool.d.ts.map +1 -0
- package/dist/mcp/tool.js +119 -0
- package/dist/mcp/tool.js.map +1 -0
- package/dist/mcp/types.d.ts +72 -0
- package/dist/mcp/types.d.ts.map +1 -0
- package/dist/mcp/types.js +6 -0
- package/dist/mcp/types.js.map +1 -0
- package/dist/middleware/defaults.d.ts +9 -0
- package/dist/middleware/defaults.d.ts.map +1 -0
- package/dist/middleware/defaults.js +47 -0
- package/dist/middleware/defaults.js.map +1 -0
- package/dist/model/adapter-helpers.d.ts +161 -0
- package/dist/model/adapter-helpers.d.ts.map +1 -0
- package/dist/model/adapter-helpers.js +351 -0
- package/dist/model/adapter-helpers.js.map +1 -0
- package/dist/model/adapter.d.ts +399 -0
- package/dist/model/adapter.d.ts.map +1 -0
- package/dist/model/adapter.js +497 -0
- package/dist/model/adapter.js.map +1 -0
- package/dist/model/index.d.ts +54 -0
- package/dist/model/index.d.ts.map +1 -0
- package/dist/model/index.js +55 -0
- package/dist/model/index.js.map +1 -0
- package/dist/model/model-hooks.d.ts +45 -0
- package/dist/model/model-hooks.d.ts.map +1 -0
- package/dist/model/model-hooks.js +24 -0
- package/dist/model/model-hooks.js.map +1 -0
- package/dist/model/model.d.ts +302 -0
- package/dist/model/model.d.ts.map +1 -0
- package/dist/model/model.js +20 -0
- package/dist/model/model.js.map +1 -0
- package/dist/model/simple-adapter.d.ts +176 -0
- package/dist/model/simple-adapter.d.ts.map +1 -0
- package/dist/model/simple-adapter.js +264 -0
- package/dist/model/simple-adapter.js.map +1 -0
- package/dist/model/stream-accumulator.d.ts +284 -0
- package/dist/model/stream-accumulator.d.ts.map +1 -0
- package/dist/model/stream-accumulator.js +532 -0
- package/dist/model/stream-accumulator.js.map +1 -0
- package/dist/model/utils/index.d.ts +2 -0
- package/dist/model/utils/index.d.ts.map +1 -0
- package/dist/model/utils/index.js +2 -0
- package/dist/model/utils/index.js.map +1 -0
- package/dist/model/utils/language-model.d.ts +26 -0
- package/dist/model/utils/language-model.d.ts.map +1 -0
- package/dist/model/utils/language-model.js +706 -0
- package/dist/model/utils/language-model.js.map +1 -0
- package/dist/procedure/index.d.ts +20 -0
- package/dist/procedure/index.d.ts.map +1 -0
- package/dist/procedure/index.js +19 -0
- package/dist/procedure/index.js.map +1 -0
- package/dist/reconciler/devtools-bridge.d.ts +40 -0
- package/dist/reconciler/devtools-bridge.d.ts.map +1 -0
- package/dist/reconciler/devtools-bridge.js +79 -0
- package/dist/reconciler/devtools-bridge.js.map +1 -0
- package/dist/reconciler/host-config.d.ts +39 -0
- package/dist/reconciler/host-config.d.ts.map +1 -0
- package/dist/reconciler/host-config.js +195 -0
- package/dist/reconciler/host-config.js.map +1 -0
- package/dist/reconciler/index.d.ts +7 -0
- package/dist/reconciler/index.d.ts.map +1 -0
- package/dist/reconciler/index.js +7 -0
- package/dist/reconciler/index.js.map +1 -0
- package/dist/reconciler/reconciler.d.ts +47 -0
- package/dist/reconciler/reconciler.d.ts.map +1 -0
- package/dist/reconciler/reconciler.js +89 -0
- package/dist/reconciler/reconciler.js.map +1 -0
- package/dist/reconciler/types.d.ts +86 -0
- package/dist/reconciler/types.d.ts.map +1 -0
- package/dist/reconciler/types.js +37 -0
- package/dist/reconciler/types.js.map +1 -0
- package/dist/renderers/base.d.ts +98 -0
- package/dist/renderers/base.d.ts.map +1 -0
- package/dist/renderers/base.js +82 -0
- package/dist/renderers/base.js.map +1 -0
- package/dist/renderers/index.d.ts +31 -0
- package/dist/renderers/index.d.ts.map +1 -0
- package/dist/renderers/index.js +31 -0
- package/dist/renderers/index.js.map +1 -0
- package/dist/renderers/markdown.d.ts +48 -0
- package/dist/renderers/markdown.d.ts.map +1 -0
- package/dist/renderers/markdown.js +432 -0
- package/dist/renderers/markdown.js.map +1 -0
- package/dist/renderers/types.d.ts +7 -0
- package/dist/renderers/types.d.ts.map +1 -0
- package/dist/renderers/types.js +7 -0
- package/dist/renderers/types.js.map +1 -0
- package/dist/renderers/xml.d.ts +49 -0
- package/dist/renderers/xml.d.ts.map +1 -0
- package/dist/renderers/xml.js +444 -0
- package/dist/renderers/xml.js.map +1 -0
- package/dist/state/boundary.d.ts +347 -0
- package/dist/state/boundary.d.ts.map +1 -0
- package/dist/state/boundary.js +341 -0
- package/dist/state/boundary.js.map +1 -0
- package/dist/state/context.d.ts +138 -0
- package/dist/state/context.d.ts.map +1 -0
- package/dist/state/context.js +139 -0
- package/dist/state/context.js.map +1 -0
- package/dist/state/hooks.d.ts +798 -0
- package/dist/state/hooks.d.ts.map +1 -0
- package/dist/state/hooks.js +1437 -0
- package/dist/state/hooks.js.map +1 -0
- package/dist/state/index.d.ts +72 -0
- package/dist/state/index.d.ts.map +1 -0
- package/dist/state/index.js +73 -0
- package/dist/state/index.js.map +1 -0
- package/dist/state/signal.d.ts +223 -0
- package/dist/state/signal.d.ts.map +1 -0
- package/dist/state/signal.js +699 -0
- package/dist/state/signal.js.map +1 -0
- package/dist/state/use-state.d.ts +210 -0
- package/dist/state/use-state.d.ts.map +1 -0
- package/dist/state/use-state.js +327 -0
- package/dist/state/use-state.js.map +1 -0
- package/dist/tentickle-instance.d.ts +285 -0
- package/dist/tentickle-instance.d.ts.map +1 -0
- package/dist/tentickle-instance.js +700 -0
- package/dist/tentickle-instance.js.map +1 -0
- package/dist/testing/act.d.ts +59 -0
- package/dist/testing/act.d.ts.map +1 -0
- package/dist/testing/act.js +92 -0
- package/dist/testing/act.js.map +1 -0
- package/dist/testing/async-helpers.d.ts +99 -0
- package/dist/testing/async-helpers.d.ts.map +1 -0
- package/dist/testing/async-helpers.js +193 -0
- package/dist/testing/async-helpers.js.map +1 -0
- package/dist/testing/compile-agent.d.ts +101 -0
- package/dist/testing/compile-agent.d.ts.map +1 -0
- package/dist/testing/compile-agent.js +136 -0
- package/dist/testing/compile-agent.js.map +1 -0
- package/dist/testing/index.d.ts +57 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +59 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/mock-app.d.ts +163 -0
- package/dist/testing/mock-app.d.ts.map +1 -0
- package/dist/testing/mock-app.js +393 -0
- package/dist/testing/mock-app.js.map +1 -0
- package/dist/testing/mocks.d.ts +142 -0
- package/dist/testing/mocks.d.ts.map +1 -0
- package/dist/testing/mocks.js +191 -0
- package/dist/testing/mocks.js.map +1 -0
- package/dist/testing/render-agent.d.ts +146 -0
- package/dist/testing/render-agent.d.ts.map +1 -0
- package/dist/testing/render-agent.js +200 -0
- package/dist/testing/render-agent.js.map +1 -0
- package/dist/testing/test-adapter.d.ts +157 -0
- package/dist/testing/test-adapter.d.ts.map +1 -0
- package/dist/testing/test-adapter.js +297 -0
- package/dist/testing/test-adapter.js.map +1 -0
- package/dist/testing/test-model.d.ts +132 -0
- package/dist/testing/test-model.d.ts.map +1 -0
- package/dist/testing/test-model.js +260 -0
- package/dist/testing/test-model.js.map +1 -0
- package/dist/tool/index.d.ts +61 -0
- package/dist/tool/index.d.ts.map +1 -0
- package/dist/tool/index.js +63 -0
- package/dist/tool/index.js.map +1 -0
- package/dist/tool/tool-hooks.d.ts +45 -0
- package/dist/tool/tool-hooks.d.ts.map +1 -0
- package/dist/tool/tool-hooks.js +35 -0
- package/dist/tool/tool-hooks.js.map +1 -0
- package/dist/tool/tool.d.ts +403 -0
- package/dist/tool/tool.d.ts.map +1 -0
- package/dist/tool/tool.js +176 -0
- package/dist/tool/tool.js.map +1 -0
- package/dist/types.d.ts +442 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +97 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/abort-utils.d.ts +5 -0
- package/dist/utils/abort-utils.d.ts.map +1 -0
- package/dist/utils/abort-utils.js +50 -0
- package/dist/utils/abort-utils.js.map +1 -0
- package/dist/utils/classify-error.d.ts +19 -0
- package/dist/utils/classify-error.d.ts.map +1 -0
- package/dist/utils/classify-error.js +77 -0
- package/dist/utils/classify-error.js.map +1 -0
- package/dist/utils/index.d.ts +21 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +21 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/normalization.d.ts +6 -0
- package/dist/utils/normalization.d.ts.map +1 -0
- package/dist/utils/normalization.js +103 -0
- package/dist/utils/normalization.js.map +1 -0
- package/dist/utils/registry.d.ts +15 -0
- package/dist/utils/registry.d.ts.map +1 -0
- package/dist/utils/registry.js +28 -0
- package/dist/utils/registry.js.map +1 -0
- package/dist/utils/schema.d.ts +7 -0
- package/dist/utils/schema.d.ts.map +1 -0
- package/dist/utils/schema.js +13 -0
- package/dist/utils/schema.js.map +1 -0
- package/dist/utils/token-estimate.d.ts +87 -0
- package/dist/utils/token-estimate.d.ts.map +1 -0
- package/dist/utils/token-estimate.js +199 -0
- package/dist/utils/token-estimate.js.map +1 -0
- package/dist/v2/reconciler/host-config.d.ts +31 -0
- package/dist/v2/reconciler/host-config.d.ts.map +1 -0
- package/dist/v2/reconciler/host-config.js +197 -0
- package/dist/v2/reconciler/host-config.js.map +1 -0
- package/dist/v2/reconciler/index.d.ts +7 -0
- package/dist/v2/reconciler/index.d.ts.map +1 -0
- package/dist/v2/reconciler/index.js +7 -0
- package/dist/v2/reconciler/index.js.map +1 -0
- package/dist/v2/reconciler/reconciler.d.ts +39 -0
- package/dist/v2/reconciler/reconciler.d.ts.map +1 -0
- package/dist/v2/reconciler/reconciler.js +54 -0
- package/dist/v2/reconciler/reconciler.js.map +1 -0
- package/dist/v2/reconciler/types.d.ts +64 -0
- package/dist/v2/reconciler/types.d.ts.map +1 -0
- package/dist/v2/reconciler/types.js +20 -0
- package/dist/v2/reconciler/types.js.map +1 -0
- package/dist/v2/renderers/index.d.ts +7 -0
- package/dist/v2/renderers/index.d.ts.map +1 -0
- package/dist/v2/renderers/index.js +7 -0
- package/dist/v2/renderers/index.js.map +1 -0
- package/dist/v2/renderers/markdown.d.ts +16 -0
- package/dist/v2/renderers/markdown.d.ts.map +1 -0
- package/dist/v2/renderers/markdown.js +65 -0
- package/dist/v2/renderers/markdown.js.map +1 -0
- package/dist/v2/renderers/types.d.ts +26 -0
- package/dist/v2/renderers/types.d.ts.map +1 -0
- package/dist/v2/renderers/types.js +6 -0
- package/dist/v2/renderers/types.js.map +1 -0
- package/dist/v2/renderers/xml.d.ts +17 -0
- package/dist/v2/renderers/xml.d.ts.map +1 -0
- package/dist/v2/renderers/xml.js +73 -0
- package/dist/v2/renderers/xml.js.map +1 -0
- package/package.json +49 -0
|
@@ -0,0 +1,2131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session - The Execution Unit
|
|
3
|
+
*
|
|
4
|
+
* A session holds component state (fiber tree) across ticks.
|
|
5
|
+
* Each tick: compile JSX -> call model -> execute tools -> decide continue?
|
|
6
|
+
*
|
|
7
|
+
* Design principles:
|
|
8
|
+
* - Single class, no intermediate layers
|
|
9
|
+
* - Minimal state - just what's needed for execution
|
|
10
|
+
* - render() is a Procedure returning SessionExecutionHandle (AsyncIterable)
|
|
11
|
+
* - Clean, elegant code over feature completeness
|
|
12
|
+
*
|
|
13
|
+
* @module agentick/app/session
|
|
14
|
+
*/
|
|
15
|
+
import { EventEmitter } from "node:events";
|
|
16
|
+
import { randomUUID } from "node:crypto";
|
|
17
|
+
import { Context, createProcedure, Channel, EventBuffer, ExecutionHandleBrand, Logger, } from "@agentick/kernel";
|
|
18
|
+
import { FiberCompiler, StructureRenderer, ReconciliationScheduler, } from "../compiler";
|
|
19
|
+
import { COM } from "../com/object-model";
|
|
20
|
+
import { MarkdownRenderer } from "../renderers/index";
|
|
21
|
+
import { ToolExecutor } from "../engine/tool-executor";
|
|
22
|
+
import { AbortError } from "../utils/abort-utils";
|
|
23
|
+
import { jsx } from "../jsx/jsx-runtime";
|
|
24
|
+
import { devToolsEmitter, forwardToDevTools, FrameworkChannels, getEffectiveModelInfo, getContextUtilization, } from "@agentick/shared";
|
|
25
|
+
import { computeTokenSummary } from "../utils/token-estimate";
|
|
26
|
+
import React from "react";
|
|
27
|
+
/**
|
|
28
|
+
* Get session context from the current ALS context.
|
|
29
|
+
*/
|
|
30
|
+
function getSessionContext() {
|
|
31
|
+
const ctx = Context.tryGet();
|
|
32
|
+
return {
|
|
33
|
+
sessionId: ctx?.sessionId,
|
|
34
|
+
rootComponent: ctx?.rootComponent,
|
|
35
|
+
devToolsEnabled: ctx?.devToolsEnabled,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Ensure a message has an ID. If no ID exists, generate one.
|
|
40
|
+
* This is critical for deduplication in reactive clients.
|
|
41
|
+
*/
|
|
42
|
+
function ensureMessageId(message) {
|
|
43
|
+
if (message.id)
|
|
44
|
+
return message;
|
|
45
|
+
return { ...message, id: randomUUID() };
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Session implementation.
|
|
49
|
+
*
|
|
50
|
+
* A session manages the execution lifecycle: compile JSX, call model, execute tools.
|
|
51
|
+
* Component state (hooks, signals) persists across ticks within a session.
|
|
52
|
+
*/
|
|
53
|
+
export class SessionImpl extends EventEmitter {
|
|
54
|
+
id;
|
|
55
|
+
log = Logger.for("SessionImpl");
|
|
56
|
+
// Core execution state
|
|
57
|
+
_status = "idle";
|
|
58
|
+
_tick = 1;
|
|
59
|
+
_isAborted = false;
|
|
60
|
+
_currentExecutionId = null;
|
|
61
|
+
// Hydration state (pending fiber tree data to restore)
|
|
62
|
+
_pendingHydrationData = null;
|
|
63
|
+
// Compilation infrastructure (no intermediate layer)
|
|
64
|
+
compiler = null;
|
|
65
|
+
ctx = null;
|
|
66
|
+
structureRenderer = null;
|
|
67
|
+
scheduler = null;
|
|
68
|
+
// State that persists across ticks
|
|
69
|
+
_previousOutput = null;
|
|
70
|
+
_currentOutput = null;
|
|
71
|
+
// Track timeline sent to model (for combining with response in complete())
|
|
72
|
+
_lastSentTimeline = [];
|
|
73
|
+
// Estimated context tokens from last compilation (pre-model-call)
|
|
74
|
+
_estimatedContextTokens;
|
|
75
|
+
// Track last published timeline length for delta publishing
|
|
76
|
+
_lastPublishedTimelineLength = 0;
|
|
77
|
+
// Message queue
|
|
78
|
+
_queuedMessages = [];
|
|
79
|
+
// Abort handling
|
|
80
|
+
sessionAbortController;
|
|
81
|
+
executionAbortController = null;
|
|
82
|
+
executionAbortCleanup = [];
|
|
83
|
+
// Event streaming
|
|
84
|
+
_eventQueue = [];
|
|
85
|
+
_eventResolvers = [];
|
|
86
|
+
_executionComplete = false;
|
|
87
|
+
_sequence = 0; // Monotonically increasing sequence number for durable streams
|
|
88
|
+
// Usage tracking
|
|
89
|
+
_totalUsage = {
|
|
90
|
+
inputTokens: 0,
|
|
91
|
+
outputTokens: 0,
|
|
92
|
+
totalTokens: 0,
|
|
93
|
+
ticks: 0,
|
|
94
|
+
};
|
|
95
|
+
// Last model output tracking
|
|
96
|
+
_lastModelOutput = null;
|
|
97
|
+
// Recording support
|
|
98
|
+
_recording = null;
|
|
99
|
+
_recordingMode = null;
|
|
100
|
+
_recordingStartedAt = null;
|
|
101
|
+
_snapshots = [];
|
|
102
|
+
// Configuration
|
|
103
|
+
Component;
|
|
104
|
+
appOptions;
|
|
105
|
+
sessionOptions;
|
|
106
|
+
// Last props for hot-update support
|
|
107
|
+
_lastProps = null;
|
|
108
|
+
// Captured context from session creation
|
|
109
|
+
_capturedContext;
|
|
110
|
+
// Channels for pub/sub communication
|
|
111
|
+
_channels = new Map();
|
|
112
|
+
// Current execution handle (for concurrent send idempotency)
|
|
113
|
+
_currentHandle = null;
|
|
114
|
+
_currentResultResolve = null;
|
|
115
|
+
_currentResultReject = null;
|
|
116
|
+
// Hibernate callback (set by App when registering session)
|
|
117
|
+
_hibernateCallback = null;
|
|
118
|
+
// Spawn hierarchy
|
|
119
|
+
_parent = null;
|
|
120
|
+
_children = [];
|
|
121
|
+
_spawnDepth = 0;
|
|
122
|
+
static MAX_SPAWN_DEPTH = 10;
|
|
123
|
+
constructor(Component, appOptions, sessionOptions = {}) {
|
|
124
|
+
super();
|
|
125
|
+
this.id = sessionOptions.sessionId ?? randomUUID();
|
|
126
|
+
this.Component = Component;
|
|
127
|
+
this.appOptions = appOptions;
|
|
128
|
+
this.sessionOptions = sessionOptions;
|
|
129
|
+
// Capture ALS context at session creation time
|
|
130
|
+
// Include Agentick middleware registry if available (for procedure middleware support)
|
|
131
|
+
const currentContext = Context.tryGet();
|
|
132
|
+
const agentickInstance = appOptions
|
|
133
|
+
._agentickInstance;
|
|
134
|
+
if (agentickInstance && currentContext) {
|
|
135
|
+
this._capturedContext = { ...currentContext, middleware: agentickInstance };
|
|
136
|
+
}
|
|
137
|
+
else if (agentickInstance && !currentContext) {
|
|
138
|
+
this._capturedContext = Context.create({ middleware: agentickInstance });
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
this._capturedContext = currentContext;
|
|
142
|
+
}
|
|
143
|
+
// Create session abort controller linked to external signals
|
|
144
|
+
this.sessionAbortController = new AbortController();
|
|
145
|
+
const externalSignal = sessionOptions.signal ?? appOptions.signal;
|
|
146
|
+
if (externalSignal) {
|
|
147
|
+
if (externalSignal.aborted) {
|
|
148
|
+
this.sessionAbortController.abort(externalSignal.reason);
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
externalSignal.addEventListener("abort", () => {
|
|
152
|
+
this.sessionAbortController.abort(externalSignal.reason);
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
// Hydrate from snapshot if provided
|
|
157
|
+
if (sessionOptions.snapshot) {
|
|
158
|
+
this.hydrate(sessionOptions.snapshot);
|
|
159
|
+
}
|
|
160
|
+
// Seed initial timeline if provided
|
|
161
|
+
if (sessionOptions.initialTimeline?.length) {
|
|
162
|
+
this._previousOutput = {
|
|
163
|
+
timeline: sessionOptions.initialTimeline,
|
|
164
|
+
system: [],
|
|
165
|
+
ephemeral: [],
|
|
166
|
+
sections: {},
|
|
167
|
+
tools: [],
|
|
168
|
+
metadata: {},
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
// Start recording if enabled via options
|
|
172
|
+
if (sessionOptions.recording) {
|
|
173
|
+
this.startRecording(sessionOptions.recording);
|
|
174
|
+
}
|
|
175
|
+
// Initialize procedures
|
|
176
|
+
this.initProcedures();
|
|
177
|
+
}
|
|
178
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
179
|
+
// Public Properties
|
|
180
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
181
|
+
get status() {
|
|
182
|
+
return this._status;
|
|
183
|
+
}
|
|
184
|
+
get currentTick() {
|
|
185
|
+
return this._tick;
|
|
186
|
+
}
|
|
187
|
+
get isAborted() {
|
|
188
|
+
return this._isAborted;
|
|
189
|
+
}
|
|
190
|
+
get parent() {
|
|
191
|
+
return this._parent;
|
|
192
|
+
}
|
|
193
|
+
get children() {
|
|
194
|
+
return this._children;
|
|
195
|
+
}
|
|
196
|
+
get queuedMessages() {
|
|
197
|
+
return this._queuedMessages;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Observable scheduler state for DevTools.
|
|
201
|
+
*
|
|
202
|
+
* Returns a Signal containing the scheduler's current state,
|
|
203
|
+
* including status, pending reasons, and reconciliation metrics.
|
|
204
|
+
*
|
|
205
|
+
* Returns null if the session hasn't been initialized yet.
|
|
206
|
+
*
|
|
207
|
+
* @example
|
|
208
|
+
* ```typescript
|
|
209
|
+
* // In DevTools
|
|
210
|
+
* effect(() => {
|
|
211
|
+
* const state = session.schedulerState?.();
|
|
212
|
+
* if (state) {
|
|
213
|
+
* console.log(`Status: ${state.status}, reconciles: ${state.reconcileCount}`);
|
|
214
|
+
* }
|
|
215
|
+
* });
|
|
216
|
+
* ```
|
|
217
|
+
*/
|
|
218
|
+
get schedulerState() {
|
|
219
|
+
return this.scheduler?.getState() ?? null;
|
|
220
|
+
}
|
|
221
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
222
|
+
// Queue Procedure
|
|
223
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
224
|
+
queue;
|
|
225
|
+
send;
|
|
226
|
+
render;
|
|
227
|
+
spawn;
|
|
228
|
+
initProcedures() {
|
|
229
|
+
// Queue procedure - queues messages and notifies components
|
|
230
|
+
this.queue = createProcedure({
|
|
231
|
+
name: "session:queue",
|
|
232
|
+
metadata: { operation: "queue" },
|
|
233
|
+
handleFactory: false,
|
|
234
|
+
executionBoundary: false,
|
|
235
|
+
}, async (message) => {
|
|
236
|
+
if (this._status === "closed") {
|
|
237
|
+
throw new Error("Session is closed");
|
|
238
|
+
}
|
|
239
|
+
const messageWithId = ensureMessageId(message);
|
|
240
|
+
this._queuedMessages.push(messageWithId);
|
|
241
|
+
// Publish to messages channel for reactive updates
|
|
242
|
+
this.channel("messages").publish({
|
|
243
|
+
type: "message_queued",
|
|
244
|
+
channel: "messages",
|
|
245
|
+
payload: messageWithId,
|
|
246
|
+
});
|
|
247
|
+
// Notify components via useOnMessage hooks if compiler exists
|
|
248
|
+
if (this.compiler) {
|
|
249
|
+
const executionMessage = {
|
|
250
|
+
id: randomUUID(),
|
|
251
|
+
type: "message",
|
|
252
|
+
content: message,
|
|
253
|
+
timestamp: Date.now(),
|
|
254
|
+
};
|
|
255
|
+
const tickState = {
|
|
256
|
+
tick: this._tick,
|
|
257
|
+
stop: () => { },
|
|
258
|
+
queuedMessages: [],
|
|
259
|
+
};
|
|
260
|
+
await this.compiler.notifyOnMessage(executionMessage, tickState);
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
// Send procedure - queues messages + delegates to render
|
|
264
|
+
this.send = createProcedure({
|
|
265
|
+
name: "session:send",
|
|
266
|
+
metadata: { operation: "send" },
|
|
267
|
+
handleFactory: false,
|
|
268
|
+
executionBoundary: false,
|
|
269
|
+
}, async (input) => {
|
|
270
|
+
if (this._status === "closed") {
|
|
271
|
+
throw new Error("Session is closed");
|
|
272
|
+
}
|
|
273
|
+
const { messages = [], props, metadata, maxTicks, signal } = input;
|
|
274
|
+
// Apply metadata to messages
|
|
275
|
+
const allMessages = messages.map((m) => (metadata ? { ...m, metadata } : m));
|
|
276
|
+
// Update props if provided
|
|
277
|
+
if (props) {
|
|
278
|
+
this._lastProps = props;
|
|
279
|
+
}
|
|
280
|
+
// Queue messages synchronously and notify components
|
|
281
|
+
for (const msg of allMessages) {
|
|
282
|
+
const msgWithId = ensureMessageId(msg);
|
|
283
|
+
this._queuedMessages.push(msgWithId);
|
|
284
|
+
this.channel("messages").publish({
|
|
285
|
+
type: "message_queued",
|
|
286
|
+
channel: "messages",
|
|
287
|
+
payload: msgWithId,
|
|
288
|
+
});
|
|
289
|
+
// Notify components via useOnMessage hooks if compiler exists
|
|
290
|
+
if (this.compiler) {
|
|
291
|
+
const executionMessage = {
|
|
292
|
+
id: randomUUID(),
|
|
293
|
+
type: "message",
|
|
294
|
+
content: msgWithId,
|
|
295
|
+
timestamp: Date.now(),
|
|
296
|
+
};
|
|
297
|
+
const tickState = {
|
|
298
|
+
tick: this._tick,
|
|
299
|
+
stop: () => { },
|
|
300
|
+
queuedMessages: [],
|
|
301
|
+
};
|
|
302
|
+
// Fire and forget - don't await to keep send() fast
|
|
303
|
+
this.compiler.notifyOnMessage(executionMessage, tickState).catch(() => { });
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
// Build execution options from input
|
|
307
|
+
const executionOptions = {};
|
|
308
|
+
if (maxTicks !== undefined)
|
|
309
|
+
executionOptions.maxTicks = maxTicks;
|
|
310
|
+
if (signal)
|
|
311
|
+
executionOptions.signal = signal;
|
|
312
|
+
// If already running, return the existing handle (concurrent send idempotency)
|
|
313
|
+
if (this._status === "running" && this._currentHandle) {
|
|
314
|
+
this.addExecutionSignal(signal);
|
|
315
|
+
return this._currentHandle;
|
|
316
|
+
}
|
|
317
|
+
// If idle and we have something to do, start tick
|
|
318
|
+
if ((allMessages.length > 0 || props) && this._status === "idle") {
|
|
319
|
+
// Use last known props if available, otherwise default to empty props.
|
|
320
|
+
const tickProps = (this._lastProps ?? {});
|
|
321
|
+
return await this.render(tickProps, executionOptions);
|
|
322
|
+
}
|
|
323
|
+
// Nothing to do - create a handle that resolves immediately with empty result
|
|
324
|
+
return this.createEmptyHandle();
|
|
325
|
+
});
|
|
326
|
+
// Render procedure - creates and runs a tick execution
|
|
327
|
+
this.render = createProcedure({
|
|
328
|
+
name: "session:render",
|
|
329
|
+
metadata: { operation: "render" },
|
|
330
|
+
handleFactory: false,
|
|
331
|
+
executionBoundary: false,
|
|
332
|
+
}, (props, options) => {
|
|
333
|
+
if (this._status === "closed") {
|
|
334
|
+
throw new Error("Session is closed");
|
|
335
|
+
}
|
|
336
|
+
// Props is explicitly provided (even if empty object) - always run tick
|
|
337
|
+
// Only skip if props is undefined/null AND no queued messages
|
|
338
|
+
const propsProvided = props !== undefined && props !== null;
|
|
339
|
+
if (!propsProvided && this._queuedMessages.length === 0) {
|
|
340
|
+
return this.createEmptyHandle();
|
|
341
|
+
}
|
|
342
|
+
// Hot-update: if already running, update props and return existing handle
|
|
343
|
+
if (this._status === "running" && this._currentHandle) {
|
|
344
|
+
this._lastProps = props;
|
|
345
|
+
this.addExecutionSignal(options?.signal);
|
|
346
|
+
return this._currentHandle;
|
|
347
|
+
}
|
|
348
|
+
// Create new execution
|
|
349
|
+
this._lastProps = props;
|
|
350
|
+
const handle = this.createSessionHandle(props, options);
|
|
351
|
+
this._currentHandle = handle;
|
|
352
|
+
return handle;
|
|
353
|
+
});
|
|
354
|
+
// Spawn procedure - creates ephemeral child session
|
|
355
|
+
this.spawn = createProcedure({
|
|
356
|
+
name: "session:spawn",
|
|
357
|
+
metadata: { operation: "spawn" },
|
|
358
|
+
handleFactory: false,
|
|
359
|
+
executionBoundary: false,
|
|
360
|
+
}, async (component, input = {}) => {
|
|
361
|
+
if (this._status === "closed") {
|
|
362
|
+
throw new Error("Session is closed");
|
|
363
|
+
}
|
|
364
|
+
if (this._spawnDepth >= SessionImpl.MAX_SPAWN_DEPTH) {
|
|
365
|
+
throw new Error(`Maximum spawn depth (${SessionImpl.MAX_SPAWN_DEPTH}) exceeded`);
|
|
366
|
+
}
|
|
367
|
+
// 1. Resolve to ComponentFunction
|
|
368
|
+
const { Component, mergedProps } = this.resolveSpawnTarget(component, input);
|
|
369
|
+
// 2. Create child SessionImpl (ephemeral — NOT registered in App's registry)
|
|
370
|
+
// Whitelist structural fields only — lifecycle callbacks, session management,
|
|
371
|
+
// signal, and devTools are intentionally excluded. New AppOptions fields
|
|
372
|
+
// must be explicitly added here if children should inherit them.
|
|
373
|
+
const childAppOptions = {
|
|
374
|
+
model: this.appOptions.model,
|
|
375
|
+
tools: this.appOptions.tools,
|
|
376
|
+
mcpServers: this.appOptions.mcpServers,
|
|
377
|
+
maxTicks: this.appOptions.maxTicks,
|
|
378
|
+
inheritDefaults: this.appOptions.inheritDefaults,
|
|
379
|
+
};
|
|
380
|
+
const childOptions = {
|
|
381
|
+
signal: this.executionAbortController?.signal,
|
|
382
|
+
devTools: this.sessionOptions.devTools ?? this.appOptions.devTools,
|
|
383
|
+
};
|
|
384
|
+
const child = new SessionImpl(Component, childAppOptions, childOptions);
|
|
385
|
+
child._parent = this;
|
|
386
|
+
child._spawnDepth = this._spawnDepth + 1;
|
|
387
|
+
this._children.push(child);
|
|
388
|
+
// 3. Delegate to child.send()
|
|
389
|
+
const handle = await child.send({
|
|
390
|
+
...input,
|
|
391
|
+
props: mergedProps,
|
|
392
|
+
});
|
|
393
|
+
// 4. Cleanup on completion
|
|
394
|
+
handle.result
|
|
395
|
+
.finally(() => {
|
|
396
|
+
this._children = this._children.filter((c) => c !== child);
|
|
397
|
+
child.close();
|
|
398
|
+
})
|
|
399
|
+
.catch(() => { });
|
|
400
|
+
return handle;
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
404
|
+
// SessionExecutionHandle Creation
|
|
405
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
406
|
+
/**
|
|
407
|
+
* Create a SessionExecutionHandle with explicit delegation.
|
|
408
|
+
* The handle is AsyncIterable (not PromiseLike — use `.result` for the final value).
|
|
409
|
+
*/
|
|
410
|
+
createSessionHandle(props, options) {
|
|
411
|
+
const session = this;
|
|
412
|
+
const events = new EventEmitter();
|
|
413
|
+
const traceId = randomUUID();
|
|
414
|
+
// Create the result promise
|
|
415
|
+
let resolveResult;
|
|
416
|
+
let rejectResult;
|
|
417
|
+
const resultPromise = new Promise((resolve, reject) => {
|
|
418
|
+
resolveResult = resolve;
|
|
419
|
+
rejectResult = reject;
|
|
420
|
+
});
|
|
421
|
+
// Prevent unhandled rejections when abort happens before awaiting.
|
|
422
|
+
resultPromise.catch(() => { });
|
|
423
|
+
// Store resolvers for external completion
|
|
424
|
+
this._currentResultResolve = resolveResult;
|
|
425
|
+
this._currentResultReject = rejectResult;
|
|
426
|
+
// Track status
|
|
427
|
+
let status = "running";
|
|
428
|
+
// Event queue for async iteration
|
|
429
|
+
const eventQueue = [];
|
|
430
|
+
const eventResolvers = [];
|
|
431
|
+
let _iterationComplete = false;
|
|
432
|
+
this.startExecutionAbort(options?.signal);
|
|
433
|
+
// Create event buffer for dual consumption BEFORE starting execution
|
|
434
|
+
const eventBuffer = new EventBuffer();
|
|
435
|
+
// Forward events to event buffer
|
|
436
|
+
events.on("*", (event) => {
|
|
437
|
+
eventBuffer.push(event);
|
|
438
|
+
});
|
|
439
|
+
const pushEvent = (event) => {
|
|
440
|
+
if (eventResolvers.length > 0) {
|
|
441
|
+
const resolver = eventResolvers.shift();
|
|
442
|
+
resolver({ value: event, done: false });
|
|
443
|
+
}
|
|
444
|
+
else {
|
|
445
|
+
eventQueue.push(event);
|
|
446
|
+
}
|
|
447
|
+
events.emit("event", event);
|
|
448
|
+
events.emit("*", event);
|
|
449
|
+
};
|
|
450
|
+
const completeIteration = () => {
|
|
451
|
+
_iterationComplete = true;
|
|
452
|
+
eventBuffer.close();
|
|
453
|
+
for (const resolver of eventResolvers) {
|
|
454
|
+
resolver({ value: undefined, done: true });
|
|
455
|
+
}
|
|
456
|
+
eventResolvers.length = 0;
|
|
457
|
+
};
|
|
458
|
+
// Start execution asynchronously
|
|
459
|
+
(async () => {
|
|
460
|
+
try {
|
|
461
|
+
const result = await this.runWithContext(() => this.executeTick(props, options));
|
|
462
|
+
status = "completed";
|
|
463
|
+
// Call onComplete callback
|
|
464
|
+
try {
|
|
465
|
+
this.callbacks.onComplete?.(result);
|
|
466
|
+
}
|
|
467
|
+
catch {
|
|
468
|
+
// Callbacks should not throw
|
|
469
|
+
}
|
|
470
|
+
resolveResult(result);
|
|
471
|
+
completeIteration();
|
|
472
|
+
}
|
|
473
|
+
catch (error) {
|
|
474
|
+
status = "error";
|
|
475
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
476
|
+
// Call onError callback
|
|
477
|
+
try {
|
|
478
|
+
this.callbacks.onError?.(err);
|
|
479
|
+
}
|
|
480
|
+
catch {
|
|
481
|
+
// Callbacks should not throw
|
|
482
|
+
}
|
|
483
|
+
eventBuffer.error(err);
|
|
484
|
+
rejectResult(err);
|
|
485
|
+
completeIteration();
|
|
486
|
+
}
|
|
487
|
+
finally {
|
|
488
|
+
this._currentHandle = null;
|
|
489
|
+
this._currentResultResolve = null;
|
|
490
|
+
this._currentResultReject = null;
|
|
491
|
+
this._isAborted = false;
|
|
492
|
+
this.finishExecutionAbort();
|
|
493
|
+
}
|
|
494
|
+
})();
|
|
495
|
+
// Create handle - use .result for final value, not PromiseLike
|
|
496
|
+
const handle = {
|
|
497
|
+
[ExecutionHandleBrand]: true,
|
|
498
|
+
// AsyncIterable delegation - delegates to EventBuffer for dual consumption
|
|
499
|
+
[Symbol.asyncIterator]() {
|
|
500
|
+
return eventBuffer[Symbol.asyncIterator]();
|
|
501
|
+
},
|
|
502
|
+
// ExecutionHandle properties
|
|
503
|
+
get status() {
|
|
504
|
+
return status;
|
|
505
|
+
},
|
|
506
|
+
get traceId() {
|
|
507
|
+
return traceId;
|
|
508
|
+
},
|
|
509
|
+
get events() {
|
|
510
|
+
return eventBuffer;
|
|
511
|
+
},
|
|
512
|
+
get result() {
|
|
513
|
+
return resultPromise;
|
|
514
|
+
},
|
|
515
|
+
abort(reason) {
|
|
516
|
+
if (status === "running") {
|
|
517
|
+
status = "aborted";
|
|
518
|
+
session._isAborted = true;
|
|
519
|
+
session.executionAbortController?.abort(reason ?? "Aborted via handle");
|
|
520
|
+
eventBuffer.close();
|
|
521
|
+
rejectResult(new AbortError(reason ?? "Aborted via handle", "ABORT_SIGNAL"));
|
|
522
|
+
completeIteration();
|
|
523
|
+
}
|
|
524
|
+
},
|
|
525
|
+
// Session-specific properties
|
|
526
|
+
get sessionId() {
|
|
527
|
+
return session.id;
|
|
528
|
+
},
|
|
529
|
+
get currentTick() {
|
|
530
|
+
return session._tick;
|
|
531
|
+
},
|
|
532
|
+
queueMessage(message) {
|
|
533
|
+
session._queuedMessages.push(message);
|
|
534
|
+
session.channel("messages").publish({
|
|
535
|
+
type: "message_queued",
|
|
536
|
+
channel: "messages",
|
|
537
|
+
payload: message,
|
|
538
|
+
});
|
|
539
|
+
},
|
|
540
|
+
submitToolResult(toolUseId, response) {
|
|
541
|
+
session.channel("tool_confirmation").publish({
|
|
542
|
+
type: "response",
|
|
543
|
+
channel: "tool_confirmation",
|
|
544
|
+
id: toolUseId,
|
|
545
|
+
payload: response,
|
|
546
|
+
});
|
|
547
|
+
},
|
|
548
|
+
};
|
|
549
|
+
// Wire up event emission from session to handle
|
|
550
|
+
this.on("event", pushEvent);
|
|
551
|
+
return handle;
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Create an empty handle for when there's nothing to do.
|
|
555
|
+
*/
|
|
556
|
+
createEmptyHandle() {
|
|
557
|
+
const session = this;
|
|
558
|
+
const emptyResult = {
|
|
559
|
+
response: "",
|
|
560
|
+
outputs: {},
|
|
561
|
+
usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
|
|
562
|
+
raw: {
|
|
563
|
+
timeline: [],
|
|
564
|
+
system: [],
|
|
565
|
+
ephemeral: [],
|
|
566
|
+
sections: {},
|
|
567
|
+
tools: [],
|
|
568
|
+
metadata: {},
|
|
569
|
+
},
|
|
570
|
+
};
|
|
571
|
+
// Empty event buffer for empty handle
|
|
572
|
+
const emptyEventBuffer = new EventBuffer();
|
|
573
|
+
emptyEventBuffer.close();
|
|
574
|
+
const handle = {
|
|
575
|
+
[ExecutionHandleBrand]: true,
|
|
576
|
+
[Symbol.asyncIterator]() {
|
|
577
|
+
return emptyEventBuffer[Symbol.asyncIterator]();
|
|
578
|
+
},
|
|
579
|
+
get status() {
|
|
580
|
+
return "completed";
|
|
581
|
+
},
|
|
582
|
+
get traceId() {
|
|
583
|
+
return randomUUID();
|
|
584
|
+
},
|
|
585
|
+
get events() {
|
|
586
|
+
return emptyEventBuffer;
|
|
587
|
+
},
|
|
588
|
+
get result() {
|
|
589
|
+
return Promise.resolve(emptyResult);
|
|
590
|
+
},
|
|
591
|
+
abort() { },
|
|
592
|
+
get sessionId() {
|
|
593
|
+
return session.id;
|
|
594
|
+
},
|
|
595
|
+
get currentTick() {
|
|
596
|
+
return session._tick;
|
|
597
|
+
},
|
|
598
|
+
queueMessage(message) {
|
|
599
|
+
session._queuedMessages.push(message);
|
|
600
|
+
},
|
|
601
|
+
submitToolResult(_toolUseId, _response) { },
|
|
602
|
+
};
|
|
603
|
+
return handle;
|
|
604
|
+
}
|
|
605
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
606
|
+
// Spawn Target Resolution
|
|
607
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
608
|
+
/**
|
|
609
|
+
* Resolve spawn target to a ComponentFunction and merged props.
|
|
610
|
+
*/
|
|
611
|
+
resolveSpawnTarget(component, input) {
|
|
612
|
+
// JSX Element
|
|
613
|
+
if (React.isValidElement(component)) {
|
|
614
|
+
return {
|
|
615
|
+
Component: component.type,
|
|
616
|
+
mergedProps: {
|
|
617
|
+
...component.props,
|
|
618
|
+
...(input.props ?? {}),
|
|
619
|
+
},
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
// Component function
|
|
623
|
+
return {
|
|
624
|
+
Component: component,
|
|
625
|
+
mergedProps: input.props ?? {},
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
629
|
+
// Channels
|
|
630
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
631
|
+
channel(name) {
|
|
632
|
+
let channel = this._channels.get(name);
|
|
633
|
+
if (!channel) {
|
|
634
|
+
channel = new Channel(name);
|
|
635
|
+
this._channels.set(name, channel);
|
|
636
|
+
}
|
|
637
|
+
return channel;
|
|
638
|
+
}
|
|
639
|
+
submitToolResult(toolUseId, response) {
|
|
640
|
+
this.channel("tool_confirmation").publish({
|
|
641
|
+
type: "response",
|
|
642
|
+
channel: "tool_confirmation",
|
|
643
|
+
id: toolUseId,
|
|
644
|
+
payload: response,
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
648
|
+
// Interrupt & Abort
|
|
649
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
650
|
+
interrupt(message, reason) {
|
|
651
|
+
if (this._status === "closed") {
|
|
652
|
+
throw new Error("Session is closed");
|
|
653
|
+
}
|
|
654
|
+
if (message) {
|
|
655
|
+
this._queuedMessages.push(message);
|
|
656
|
+
}
|
|
657
|
+
if (this._status === "running") {
|
|
658
|
+
this._isAborted = true;
|
|
659
|
+
this.executionAbortController?.abort(reason ?? "interrupt");
|
|
660
|
+
// Propagate interrupt to child sessions (spread-copy: interrupt cleanup may mutate array)
|
|
661
|
+
for (const child of [...this._children]) {
|
|
662
|
+
child.interrupt(undefined, reason ?? "Parent interrupted");
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
clearAbort() {
|
|
667
|
+
this._isAborted = false;
|
|
668
|
+
}
|
|
669
|
+
startExecutionAbort(signal) {
|
|
670
|
+
this.executionAbortCleanup.forEach((cleanup) => cleanup());
|
|
671
|
+
this.executionAbortCleanup = [];
|
|
672
|
+
this._isAborted = false;
|
|
673
|
+
this.executionAbortController = new AbortController();
|
|
674
|
+
this.addExecutionSignal(this.sessionAbortController.signal);
|
|
675
|
+
this.addExecutionSignal(signal);
|
|
676
|
+
}
|
|
677
|
+
addExecutionSignal(signal) {
|
|
678
|
+
if (!signal || !this.executionAbortController)
|
|
679
|
+
return;
|
|
680
|
+
if (signal.aborted) {
|
|
681
|
+
this._isAborted = true;
|
|
682
|
+
this.executionAbortController.abort(signal.reason);
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
const handler = () => {
|
|
686
|
+
this._isAborted = true;
|
|
687
|
+
this.executionAbortController?.abort(signal.reason);
|
|
688
|
+
};
|
|
689
|
+
signal.addEventListener("abort", handler);
|
|
690
|
+
this.executionAbortCleanup.push(() => signal.removeEventListener("abort", handler));
|
|
691
|
+
}
|
|
692
|
+
finishExecutionAbort() {
|
|
693
|
+
this.executionAbortCleanup.forEach((cleanup) => cleanup());
|
|
694
|
+
this.executionAbortCleanup = [];
|
|
695
|
+
this.executionAbortController = null;
|
|
696
|
+
}
|
|
697
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
698
|
+
// Events (AsyncIterable)
|
|
699
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
700
|
+
events() {
|
|
701
|
+
const self = this;
|
|
702
|
+
return {
|
|
703
|
+
[Symbol.asyncIterator]() {
|
|
704
|
+
return {
|
|
705
|
+
async next() {
|
|
706
|
+
if (self._eventQueue.length > 0) {
|
|
707
|
+
return { value: self._eventQueue.shift(), done: false };
|
|
708
|
+
}
|
|
709
|
+
if (self._executionComplete) {
|
|
710
|
+
return { value: undefined, done: true };
|
|
711
|
+
}
|
|
712
|
+
return new Promise((resolve) => {
|
|
713
|
+
self._eventResolvers.push(resolve);
|
|
714
|
+
});
|
|
715
|
+
},
|
|
716
|
+
};
|
|
717
|
+
},
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
/**
|
|
721
|
+
* Get merged lifecycle callbacks (session overrides app).
|
|
722
|
+
*/
|
|
723
|
+
get callbacks() {
|
|
724
|
+
return {
|
|
725
|
+
onEvent: this.sessionOptions.onEvent ?? this.appOptions.onEvent,
|
|
726
|
+
onTickStart: this.sessionOptions.onTickStart ?? this.appOptions.onTickStart,
|
|
727
|
+
onTickEnd: this.sessionOptions.onTickEnd ?? this.appOptions.onTickEnd,
|
|
728
|
+
onComplete: this.sessionOptions.onComplete ?? this.appOptions.onComplete,
|
|
729
|
+
onError: this.sessionOptions.onError ?? this.appOptions.onError,
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
emitEvent(event) {
|
|
733
|
+
const executionId = this._currentExecutionId ?? Context.tryGet()?.executionId;
|
|
734
|
+
// Enrich event with executionId and tick if missing
|
|
735
|
+
let enrichedEvent = executionId && !("executionId" in event) ? { ...event, executionId } : event;
|
|
736
|
+
if (!("id" in event)) {
|
|
737
|
+
enrichedEvent = { ...enrichedEvent, id: randomUUID() };
|
|
738
|
+
}
|
|
739
|
+
if (!("timestamp" in event)) {
|
|
740
|
+
enrichedEvent = { ...enrichedEvent, timestamp: new Date().toISOString() };
|
|
741
|
+
}
|
|
742
|
+
// Always set tick if missing - session knows the actual tick
|
|
743
|
+
if (!("tick" in event)) {
|
|
744
|
+
enrichedEvent = { ...enrichedEvent, tick: this._tick || 1 };
|
|
745
|
+
}
|
|
746
|
+
// Assign monotonically increasing sequence number for durable streams
|
|
747
|
+
this._sequence++;
|
|
748
|
+
enrichedEvent = { ...enrichedEvent, sequence: this._sequence };
|
|
749
|
+
// Forward to DevTools (check context for overrides)
|
|
750
|
+
const sessionCtx = getSessionContext();
|
|
751
|
+
forwardToDevTools(enrichedEvent, {
|
|
752
|
+
sessionId: sessionCtx.sessionId ?? this.id,
|
|
753
|
+
rootComponent: sessionCtx.rootComponent ?? (this.Component.name || "Agent"),
|
|
754
|
+
devToolsEnabled: sessionCtx.devToolsEnabled ?? this.sessionOptions.devTools ?? false,
|
|
755
|
+
});
|
|
756
|
+
// Invoke lifecycle callbacks
|
|
757
|
+
const cb = this.callbacks;
|
|
758
|
+
try {
|
|
759
|
+
cb.onEvent?.(enrichedEvent);
|
|
760
|
+
// Call specific callbacks based on event type
|
|
761
|
+
const eventType = enrichedEvent.type;
|
|
762
|
+
if (eventType === "tick_start") {
|
|
763
|
+
cb.onTickStart?.(enrichedEvent.tick, enrichedEvent.executionId);
|
|
764
|
+
}
|
|
765
|
+
else if (eventType === "tick_end") {
|
|
766
|
+
cb.onTickEnd?.(enrichedEvent.tick, enrichedEvent.usage);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
catch {
|
|
770
|
+
// Callbacks should not throw, but don't let them break execution
|
|
771
|
+
}
|
|
772
|
+
// Emit via EventEmitter
|
|
773
|
+
this.emit("event", enrichedEvent);
|
|
774
|
+
// Also queue for AsyncIterable consumers
|
|
775
|
+
if (this._eventResolvers.length > 0) {
|
|
776
|
+
const resolve = this._eventResolvers.shift();
|
|
777
|
+
resolve({ value: enrichedEvent, done: false });
|
|
778
|
+
}
|
|
779
|
+
else {
|
|
780
|
+
this._eventQueue.push(enrichedEvent);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
784
|
+
// Snapshot & Inspect
|
|
785
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
786
|
+
snapshot() {
|
|
787
|
+
return {
|
|
788
|
+
version: "1.0",
|
|
789
|
+
sessionId: this.id,
|
|
790
|
+
tick: this._tick,
|
|
791
|
+
timeline: this._previousOutput?.timeline ?? null,
|
|
792
|
+
componentState: this.serializeFiberTree(), // Serialized fiber tree with hook states
|
|
793
|
+
usage: { ...this._totalUsage },
|
|
794
|
+
timestamp: Date.now(),
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
/**
|
|
798
|
+
* Set the hibernate callback.
|
|
799
|
+
* Called by the App when registering this session.
|
|
800
|
+
* @internal
|
|
801
|
+
*/
|
|
802
|
+
setHibernateCallback(callback) {
|
|
803
|
+
this._hibernateCallback = callback;
|
|
804
|
+
}
|
|
805
|
+
async hibernate() {
|
|
806
|
+
if (this._status === "closed") {
|
|
807
|
+
return null;
|
|
808
|
+
}
|
|
809
|
+
if (this._status === "running") {
|
|
810
|
+
// Cannot hibernate while running - abort first
|
|
811
|
+
this.interrupt(undefined, "Hibernation requested");
|
|
812
|
+
}
|
|
813
|
+
// Delegate to the App's hibernate callback if available
|
|
814
|
+
if (this._hibernateCallback) {
|
|
815
|
+
return this._hibernateCallback();
|
|
816
|
+
}
|
|
817
|
+
// No callback - just return the snapshot without persisting
|
|
818
|
+
// This allows sessions created outside of App management to still hibernate
|
|
819
|
+
const snapshot = this.snapshot();
|
|
820
|
+
this.close();
|
|
821
|
+
return snapshot;
|
|
822
|
+
}
|
|
823
|
+
inspect() {
|
|
824
|
+
// Get fiber summary for component/hook stats
|
|
825
|
+
const fiberSummary = this.getFiberSummary();
|
|
826
|
+
// Get component names from fiber tree
|
|
827
|
+
const componentNames = this.collectComponentNames();
|
|
828
|
+
// Get last tick's tool data from snapshots if available
|
|
829
|
+
const lastSnapshot = this._snapshots.length > 0 ? this._snapshots[this._snapshots.length - 1] : null;
|
|
830
|
+
return {
|
|
831
|
+
id: this.id,
|
|
832
|
+
status: this._status,
|
|
833
|
+
currentTick: this._tick,
|
|
834
|
+
queuedMessages: [...this._queuedMessages],
|
|
835
|
+
currentPhase: this._status === "running" ? "model" : undefined, // Approximate
|
|
836
|
+
isAborted: this._isAborted,
|
|
837
|
+
lastOutput: this._previousOutput,
|
|
838
|
+
lastModelOutput: this._lastModelOutput,
|
|
839
|
+
lastToolCalls: lastSnapshot?.tools.calls ?? [],
|
|
840
|
+
lastToolResults: lastSnapshot?.tools.results.map((r) => ({
|
|
841
|
+
toolUseId: r.toolUseId,
|
|
842
|
+
name: r.name,
|
|
843
|
+
success: r.success,
|
|
844
|
+
})) ?? [],
|
|
845
|
+
totalUsage: { ...this._totalUsage },
|
|
846
|
+
tickCount: this._totalUsage.ticks ?? 0,
|
|
847
|
+
components: {
|
|
848
|
+
count: fiberSummary.componentCount,
|
|
849
|
+
names: componentNames,
|
|
850
|
+
},
|
|
851
|
+
hooks: {
|
|
852
|
+
count: fiberSummary.hookCount,
|
|
853
|
+
// React manages hooks internally, so detailed type info isn't available
|
|
854
|
+
byType: {},
|
|
855
|
+
},
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
/**
|
|
859
|
+
* Collect unique component names from the fiber tree.
|
|
860
|
+
*/
|
|
861
|
+
collectComponentNames() {
|
|
862
|
+
if (!this.compiler)
|
|
863
|
+
return [];
|
|
864
|
+
const tree = this.compiler.serializeFiberTree();
|
|
865
|
+
if (!tree)
|
|
866
|
+
return [];
|
|
867
|
+
const names = new Set();
|
|
868
|
+
const collectNames = (node) => {
|
|
869
|
+
// Skip host primitives (Section, Message, etc.) and fragments
|
|
870
|
+
if (!node.type.startsWith("agentick.") && node.type !== "Fragment") {
|
|
871
|
+
names.add(node.type);
|
|
872
|
+
}
|
|
873
|
+
for (const child of node.children) {
|
|
874
|
+
collectNames(child);
|
|
875
|
+
}
|
|
876
|
+
};
|
|
877
|
+
collectNames(tree);
|
|
878
|
+
return Array.from(names);
|
|
879
|
+
}
|
|
880
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
881
|
+
// Recording (stub - can add back if needed)
|
|
882
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
883
|
+
startRecording(mode) {
|
|
884
|
+
this._recordingMode = mode;
|
|
885
|
+
this._recordingStartedAt = new Date().toISOString();
|
|
886
|
+
this._snapshots = [];
|
|
887
|
+
this._recording = {
|
|
888
|
+
sessionId: this.id,
|
|
889
|
+
startedAt: this._recordingStartedAt,
|
|
890
|
+
config: {
|
|
891
|
+
componentName: this.Component.name || "Anonymous",
|
|
892
|
+
initialProps: {},
|
|
893
|
+
maxTicks: this.appOptions.maxTicks ?? 10,
|
|
894
|
+
mode,
|
|
895
|
+
},
|
|
896
|
+
inputs: [],
|
|
897
|
+
snapshots: this._snapshots,
|
|
898
|
+
summary: {
|
|
899
|
+
tickCount: 0,
|
|
900
|
+
totalDuration: 0,
|
|
901
|
+
totalUsage: { inputTokens: 0, outputTokens: 0, totalTokens: 0, ticks: 0 },
|
|
902
|
+
finalStatus: "running",
|
|
903
|
+
},
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
stopRecording() {
|
|
907
|
+
if (this._recording) {
|
|
908
|
+
this._recording.endedAt = new Date().toISOString();
|
|
909
|
+
this._recording.summary.tickCount = this._snapshots.length;
|
|
910
|
+
this._recording.summary.totalUsage = { ...this._totalUsage };
|
|
911
|
+
this._recording.summary.finalStatus =
|
|
912
|
+
this._status === "idle" ? "completed" : this._status;
|
|
913
|
+
}
|
|
914
|
+
this._recordingMode = null;
|
|
915
|
+
}
|
|
916
|
+
getRecording() {
|
|
917
|
+
if (this._recording) {
|
|
918
|
+
// Update summary with current state
|
|
919
|
+
this._recording.summary.tickCount = this._snapshots.length;
|
|
920
|
+
this._recording.summary.totalUsage = { ...this._totalUsage };
|
|
921
|
+
}
|
|
922
|
+
return this._recording;
|
|
923
|
+
}
|
|
924
|
+
getSnapshotAt(tick) {
|
|
925
|
+
return this._snapshots.find((s) => s.tick === tick) ?? null;
|
|
926
|
+
}
|
|
927
|
+
/**
|
|
928
|
+
* Record a tick snapshot for time-travel debugging.
|
|
929
|
+
*/
|
|
930
|
+
recordSnapshot(tickNumber, tickStartTime, tickData) {
|
|
931
|
+
if (!this._recordingMode)
|
|
932
|
+
return;
|
|
933
|
+
const { formatted, modelId, modelInput, modelStartTime, modelOutput, toolCalls = [], toolResults = [], toolStartTime, shouldContinue = false, stopReason, } = tickData;
|
|
934
|
+
const now = Date.now();
|
|
935
|
+
const fiberSummary = this.getFiberSummary();
|
|
936
|
+
// Extract COM data from formatted input
|
|
937
|
+
const comSections = {};
|
|
938
|
+
if (formatted?.sections) {
|
|
939
|
+
for (const [id, section] of Object.entries(formatted.sections)) {
|
|
940
|
+
comSections[id] = {
|
|
941
|
+
content: typeof section.content === "string" ? section.content : JSON.stringify(section.content),
|
|
942
|
+
priority: section.priority,
|
|
943
|
+
};
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
// Extract tool definitions (ToolDefinition has name/description/input directly)
|
|
947
|
+
const comTools = (formatted?.tools ?? []).map((t) => ({
|
|
948
|
+
name: t.name ?? "unknown",
|
|
949
|
+
description: t.description,
|
|
950
|
+
inputSchema: t.input,
|
|
951
|
+
}));
|
|
952
|
+
const snapshot = {
|
|
953
|
+
sessionId: this.id,
|
|
954
|
+
tick: tickNumber,
|
|
955
|
+
timestamp: new Date().toISOString(),
|
|
956
|
+
duration: now - tickStartTime,
|
|
957
|
+
fiber: {
|
|
958
|
+
tree: this._recordingMode === "full" ? this.serializeFiberTree() : null,
|
|
959
|
+
summary: {
|
|
960
|
+
componentCount: fiberSummary.componentCount,
|
|
961
|
+
hookCount: fiberSummary.hookCount,
|
|
962
|
+
hooksByType: fiberSummary.hooksByType,
|
|
963
|
+
},
|
|
964
|
+
},
|
|
965
|
+
com: {
|
|
966
|
+
sections: comSections,
|
|
967
|
+
timeline: formatted?.timeline ?? [],
|
|
968
|
+
tools: comTools,
|
|
969
|
+
modelId: modelId ?? null,
|
|
970
|
+
metadata: formatted?.metadata ?? {},
|
|
971
|
+
},
|
|
972
|
+
model: {
|
|
973
|
+
input: {
|
|
974
|
+
formatted: modelInput ?? "",
|
|
975
|
+
tokenCount: undefined, // Could be populated if available
|
|
976
|
+
},
|
|
977
|
+
output: {
|
|
978
|
+
content: modelOutput?.message?.content ?? [],
|
|
979
|
+
stopReason: modelOutput?.stopReason ?? "unknown",
|
|
980
|
+
tokenCount: modelOutput?.usage?.outputTokens,
|
|
981
|
+
},
|
|
982
|
+
latency: modelStartTime ? (toolStartTime ?? now) - modelStartTime : 0,
|
|
983
|
+
},
|
|
984
|
+
tools: {
|
|
985
|
+
calls: toolCalls,
|
|
986
|
+
results: toolResults.map((r) => ({
|
|
987
|
+
toolUseId: r.toolUseId,
|
|
988
|
+
name: r.name ?? "unknown",
|
|
989
|
+
success: !r.error,
|
|
990
|
+
content: r.content,
|
|
991
|
+
})),
|
|
992
|
+
totalDuration: toolStartTime ? now - toolStartTime : 0,
|
|
993
|
+
},
|
|
994
|
+
execution: {
|
|
995
|
+
phase: "complete",
|
|
996
|
+
shouldContinue,
|
|
997
|
+
stopReason,
|
|
998
|
+
queuedMessages: [...this._queuedMessages],
|
|
999
|
+
executionId: Context.tryGet()?.executionId,
|
|
1000
|
+
},
|
|
1001
|
+
};
|
|
1002
|
+
this._snapshots.push(snapshot);
|
|
1003
|
+
}
|
|
1004
|
+
serializeFiberTree() {
|
|
1005
|
+
if (!this.compiler)
|
|
1006
|
+
return null;
|
|
1007
|
+
return this.compiler.serializeFiberTree();
|
|
1008
|
+
}
|
|
1009
|
+
getFiberSummary() {
|
|
1010
|
+
if (!this.compiler) {
|
|
1011
|
+
return { componentCount: 0, hookCount: 0, effectCount: 0, depth: 0, hooksByType: {} };
|
|
1012
|
+
}
|
|
1013
|
+
return this.compiler.getFiberSummary();
|
|
1014
|
+
}
|
|
1015
|
+
/**
|
|
1016
|
+
* Broadcast context utilization info via the session:context channel.
|
|
1017
|
+
* Enables real-time context tracking in UI via useContextInfo() hook.
|
|
1018
|
+
*/
|
|
1019
|
+
broadcastContextInfo(executionId, modelId, modelMetadata, tickUsage, tick, cumulativeUsage, timestampStr) {
|
|
1020
|
+
// Get context info (effective = adapter metadata merged with catalog)
|
|
1021
|
+
const modelInfo = getEffectiveModelInfo({
|
|
1022
|
+
model: modelId,
|
|
1023
|
+
provider: modelMetadata?.provider,
|
|
1024
|
+
contextWindow: modelMetadata?.contextWindow,
|
|
1025
|
+
maxOutputTokens: modelMetadata?.maxOutputTokens,
|
|
1026
|
+
}, modelId);
|
|
1027
|
+
const inputTokens = tickUsage?.inputTokens ?? 0;
|
|
1028
|
+
const outputTokens = tickUsage?.outputTokens ?? 0;
|
|
1029
|
+
const totalTokens = tickUsage?.totalTokens ?? inputTokens + outputTokens;
|
|
1030
|
+
// Calculate utilization if we have context window info
|
|
1031
|
+
const utilization = modelInfo?.contextWindow
|
|
1032
|
+
? (getContextUtilization(modelId, inputTokens) ?? undefined)
|
|
1033
|
+
: undefined;
|
|
1034
|
+
const payload = {
|
|
1035
|
+
modelId,
|
|
1036
|
+
modelName: modelInfo?.name,
|
|
1037
|
+
provider: modelInfo?.provider || modelMetadata?.provider,
|
|
1038
|
+
contextWindow: modelInfo?.contextWindow,
|
|
1039
|
+
inputTokens,
|
|
1040
|
+
outputTokens,
|
|
1041
|
+
totalTokens,
|
|
1042
|
+
utilization,
|
|
1043
|
+
maxOutputTokens: modelInfo?.maxOutputTokens,
|
|
1044
|
+
supportsVision: modelInfo?.supportsVision,
|
|
1045
|
+
supportsToolUse: modelInfo?.supportsToolUse,
|
|
1046
|
+
isReasoningModel: modelInfo?.isReasoningModel,
|
|
1047
|
+
tick,
|
|
1048
|
+
cumulativeUsage: {
|
|
1049
|
+
inputTokens: cumulativeUsage.inputTokens,
|
|
1050
|
+
outputTokens: cumulativeUsage.outputTokens,
|
|
1051
|
+
totalTokens: cumulativeUsage.totalTokens,
|
|
1052
|
+
ticks: cumulativeUsage.ticks ?? tick,
|
|
1053
|
+
},
|
|
1054
|
+
timestamp: timestampStr,
|
|
1055
|
+
};
|
|
1056
|
+
// Publish to channel (if channel service is available)
|
|
1057
|
+
const ctx = Context.tryGet();
|
|
1058
|
+
if (ctx?.channels) {
|
|
1059
|
+
try {
|
|
1060
|
+
ctx.channels.publish(ctx, FrameworkChannels.CONTEXT, {
|
|
1061
|
+
type: "context_update",
|
|
1062
|
+
payload,
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
1065
|
+
catch {
|
|
1066
|
+
// Silently ignore if channel publish fails
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
// Also emit as DevTools event for DevTools UI
|
|
1070
|
+
if (devToolsEmitter.hasSubscribers()) {
|
|
1071
|
+
devToolsEmitter.emitEvent({
|
|
1072
|
+
type: "context_update",
|
|
1073
|
+
executionId,
|
|
1074
|
+
sessionId: this.id,
|
|
1075
|
+
sequence: ++this._sequence,
|
|
1076
|
+
timestamp: Date.now(),
|
|
1077
|
+
modelId: payload.modelId,
|
|
1078
|
+
modelName: payload.modelName,
|
|
1079
|
+
provider: payload.provider,
|
|
1080
|
+
contextWindow: payload.contextWindow,
|
|
1081
|
+
inputTokens: payload.inputTokens,
|
|
1082
|
+
outputTokens: payload.outputTokens,
|
|
1083
|
+
totalTokens: payload.totalTokens,
|
|
1084
|
+
utilization: payload.utilization,
|
|
1085
|
+
maxOutputTokens: payload.maxOutputTokens,
|
|
1086
|
+
supportsVision: payload.supportsVision,
|
|
1087
|
+
supportsToolUse: payload.supportsToolUse,
|
|
1088
|
+
isReasoningModel: payload.isReasoningModel,
|
|
1089
|
+
tick: payload.tick,
|
|
1090
|
+
cumulativeUsage: payload.cumulativeUsage,
|
|
1091
|
+
});
|
|
1092
|
+
}
|
|
1093
|
+
// Update compiler's contextInfoStore so JSX components can access via useContextInfo
|
|
1094
|
+
if (this.compiler) {
|
|
1095
|
+
this.compiler.contextInfoStore.update({
|
|
1096
|
+
modelId: payload.modelId,
|
|
1097
|
+
modelName: payload.modelName,
|
|
1098
|
+
provider: payload.provider,
|
|
1099
|
+
contextWindow: payload.contextWindow,
|
|
1100
|
+
inputTokens: payload.inputTokens,
|
|
1101
|
+
outputTokens: payload.outputTokens,
|
|
1102
|
+
totalTokens: payload.totalTokens,
|
|
1103
|
+
utilization: payload.utilization,
|
|
1104
|
+
maxOutputTokens: payload.maxOutputTokens,
|
|
1105
|
+
supportsVision: payload.supportsVision,
|
|
1106
|
+
supportsToolUse: payload.supportsToolUse,
|
|
1107
|
+
isReasoningModel: payload.isReasoningModel,
|
|
1108
|
+
tick: payload.tick,
|
|
1109
|
+
cumulativeUsage: payload.cumulativeUsage,
|
|
1110
|
+
estimatedContextTokens: this._estimatedContextTokens,
|
|
1111
|
+
});
|
|
1112
|
+
}
|
|
1113
|
+
// Emit as stream event for client-side React consumption
|
|
1114
|
+
this.emitEvent({
|
|
1115
|
+
type: "context_update",
|
|
1116
|
+
modelId: payload.modelId,
|
|
1117
|
+
modelName: payload.modelName,
|
|
1118
|
+
provider: payload.provider,
|
|
1119
|
+
contextWindow: payload.contextWindow,
|
|
1120
|
+
inputTokens: payload.inputTokens,
|
|
1121
|
+
outputTokens: payload.outputTokens,
|
|
1122
|
+
totalTokens: payload.totalTokens,
|
|
1123
|
+
utilization: payload.utilization,
|
|
1124
|
+
maxOutputTokens: payload.maxOutputTokens,
|
|
1125
|
+
supportsVision: payload.supportsVision,
|
|
1126
|
+
supportsToolUse: payload.supportsToolUse,
|
|
1127
|
+
isReasoningModel: payload.isReasoningModel,
|
|
1128
|
+
cumulativeUsage: payload.cumulativeUsage,
|
|
1129
|
+
tick,
|
|
1130
|
+
timestamp: timestampStr,
|
|
1131
|
+
});
|
|
1132
|
+
}
|
|
1133
|
+
/**
|
|
1134
|
+
* Emit a fiber_snapshot event to DevTools after each tick.
|
|
1135
|
+
* This enables the Fiber Tree panel in DevTools to show component hierarchy and hook values.
|
|
1136
|
+
*
|
|
1137
|
+
* @param tick - The tick number
|
|
1138
|
+
* @param executionId - The execution ID
|
|
1139
|
+
* @param compiled - Optional compiled structure for token estimation
|
|
1140
|
+
*/
|
|
1141
|
+
emitFiberSnapshot(tick, executionId, compiled) {
|
|
1142
|
+
// Only emit if DevTools has subscribers (avoid serialization overhead)
|
|
1143
|
+
if (!devToolsEmitter.hasSubscribers())
|
|
1144
|
+
return;
|
|
1145
|
+
try {
|
|
1146
|
+
const tree = this.serializeFiberTree();
|
|
1147
|
+
const summary = this.getFiberSummary();
|
|
1148
|
+
// Compute token summary if compiled structure is available
|
|
1149
|
+
let tokenSummary;
|
|
1150
|
+
let compiledPreview;
|
|
1151
|
+
if (compiled) {
|
|
1152
|
+
try {
|
|
1153
|
+
const tokens = computeTokenSummary(compiled);
|
|
1154
|
+
tokenSummary = {
|
|
1155
|
+
system: tokens.system,
|
|
1156
|
+
messages: tokens.messages,
|
|
1157
|
+
tools: tokens.tools,
|
|
1158
|
+
ephemeral: tokens.ephemeral,
|
|
1159
|
+
total: tokens.total,
|
|
1160
|
+
byComponent: Object.fromEntries(tokens.byComponent),
|
|
1161
|
+
};
|
|
1162
|
+
// Get system prompt preview (first 200 chars)
|
|
1163
|
+
let systemPrompt;
|
|
1164
|
+
if (compiled.systemEntries.length > 0) {
|
|
1165
|
+
const firstSystem = compiled.systemEntries[0];
|
|
1166
|
+
if (firstSystem.content.length > 0) {
|
|
1167
|
+
const firstBlock = firstSystem.content[0];
|
|
1168
|
+
if ("text" in firstBlock && typeof firstBlock.text === "string") {
|
|
1169
|
+
systemPrompt = firstBlock.text.slice(0, 200);
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
compiledPreview = {
|
|
1174
|
+
systemPrompt,
|
|
1175
|
+
messageCount: compiled.timelineEntries.length,
|
|
1176
|
+
toolCount: compiled.tools.length,
|
|
1177
|
+
ephemeralCount: compiled.ephemeral.length,
|
|
1178
|
+
};
|
|
1179
|
+
}
|
|
1180
|
+
catch {
|
|
1181
|
+
// Ignore token estimation errors
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
const event = {
|
|
1185
|
+
type: "fiber_snapshot",
|
|
1186
|
+
sessionId: this.id,
|
|
1187
|
+
executionId,
|
|
1188
|
+
sequence: ++this._sequence, // Use session's sequence counter for DevTools events
|
|
1189
|
+
tick,
|
|
1190
|
+
timestamp: Date.now(),
|
|
1191
|
+
tree: tree,
|
|
1192
|
+
summary: summary,
|
|
1193
|
+
tokenSummary,
|
|
1194
|
+
compiledPreview,
|
|
1195
|
+
};
|
|
1196
|
+
devToolsEmitter.emitEvent(event);
|
|
1197
|
+
}
|
|
1198
|
+
catch {
|
|
1199
|
+
// Silently ignore serialization errors - DevTools is optional
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
1203
|
+
// Close
|
|
1204
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
1205
|
+
close() {
|
|
1206
|
+
if (this._status === "closed")
|
|
1207
|
+
return;
|
|
1208
|
+
this._status = "closed";
|
|
1209
|
+
// Close all child sessions
|
|
1210
|
+
for (const child of [...this._children]) {
|
|
1211
|
+
child.close();
|
|
1212
|
+
}
|
|
1213
|
+
this._children = [];
|
|
1214
|
+
this.sessionAbortController.abort("Session closed");
|
|
1215
|
+
this.executionAbortController?.abort("Session closed");
|
|
1216
|
+
// Complete any pending event iterators
|
|
1217
|
+
this._executionComplete = true;
|
|
1218
|
+
for (const resolve of this._eventResolvers) {
|
|
1219
|
+
resolve({ value: undefined, done: true });
|
|
1220
|
+
}
|
|
1221
|
+
this._eventResolvers = [];
|
|
1222
|
+
// Dispose scheduler (cancels pending work and cleans up signal)
|
|
1223
|
+
if (this.scheduler) {
|
|
1224
|
+
this.scheduler.dispose();
|
|
1225
|
+
this.scheduler = null;
|
|
1226
|
+
}
|
|
1227
|
+
// Unmount compiler if it exists
|
|
1228
|
+
if (this.compiler) {
|
|
1229
|
+
this.compiler.unmount().catch(() => { });
|
|
1230
|
+
this.compiler = null;
|
|
1231
|
+
}
|
|
1232
|
+
this.ctx = null;
|
|
1233
|
+
this.structureRenderer = null;
|
|
1234
|
+
this.emit("close", this.id);
|
|
1235
|
+
}
|
|
1236
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
1237
|
+
// Internal: Execution
|
|
1238
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
1239
|
+
async runWithContext(fn) {
|
|
1240
|
+
const current = Context.tryGet();
|
|
1241
|
+
const baseContext = this._capturedContext || current;
|
|
1242
|
+
// Create a channel service that wraps session channels
|
|
1243
|
+
// This allows tools to publish events via Context.get().channels
|
|
1244
|
+
const channelService = {
|
|
1245
|
+
getChannel: (_ctx, channelName) => this.channel(channelName),
|
|
1246
|
+
publish: (_ctx, channelName, event) => {
|
|
1247
|
+
this.channel(channelName).publish({ ...event, channel: channelName });
|
|
1248
|
+
},
|
|
1249
|
+
subscribe: (_ctx, channelName, handler) => {
|
|
1250
|
+
return this.channel(channelName).subscribe(handler);
|
|
1251
|
+
},
|
|
1252
|
+
waitForResponse: (_ctx, channelName, requestId, timeoutMs) => {
|
|
1253
|
+
return this.channel(channelName).waitForResponse(requestId, timeoutMs);
|
|
1254
|
+
},
|
|
1255
|
+
};
|
|
1256
|
+
// Session-specific context fields for DevTools enrichment
|
|
1257
|
+
const sessionContext = {
|
|
1258
|
+
sessionId: this.id,
|
|
1259
|
+
rootComponent: this.Component.name || "Agent",
|
|
1260
|
+
devToolsEnabled: this.sessionOptions.devTools ?? false,
|
|
1261
|
+
channels: channelService,
|
|
1262
|
+
};
|
|
1263
|
+
if (!baseContext) {
|
|
1264
|
+
// No base context - create minimal context with session fields
|
|
1265
|
+
return Context.run(Context.create(sessionContext), fn);
|
|
1266
|
+
}
|
|
1267
|
+
// Merge base, current, and session context
|
|
1268
|
+
const merged = { ...baseContext, ...(current ?? {}), ...sessionContext };
|
|
1269
|
+
return Context.run(merged, fn);
|
|
1270
|
+
}
|
|
1271
|
+
hydrate(snapshot) {
|
|
1272
|
+
this._tick = snapshot.tick;
|
|
1273
|
+
// Hydrate timeline (conversation history)
|
|
1274
|
+
if (snapshot.timeline) {
|
|
1275
|
+
this._previousOutput = {
|
|
1276
|
+
timeline: snapshot.timeline,
|
|
1277
|
+
system: [],
|
|
1278
|
+
ephemeral: [],
|
|
1279
|
+
sections: {},
|
|
1280
|
+
tools: [],
|
|
1281
|
+
metadata: {},
|
|
1282
|
+
};
|
|
1283
|
+
}
|
|
1284
|
+
// Hydrate usage stats
|
|
1285
|
+
if (snapshot.usage) {
|
|
1286
|
+
this._totalUsage = { ...snapshot.usage };
|
|
1287
|
+
}
|
|
1288
|
+
// Hydrate fiber tree (component state)
|
|
1289
|
+
// This will be applied when the compiler is first created
|
|
1290
|
+
if (snapshot.componentState) {
|
|
1291
|
+
this._pendingHydrationData = snapshot.componentState;
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
/**
|
|
1295
|
+
* The core tick execution loop.
|
|
1296
|
+
*
|
|
1297
|
+
* This is where the magic happens:
|
|
1298
|
+
* 1. Setup compilation infrastructure (COM, FiberCompiler)
|
|
1299
|
+
* 2. Loop: compile -> model -> tools -> ingest
|
|
1300
|
+
* 3. Return result
|
|
1301
|
+
*/
|
|
1302
|
+
async executeTick(props, options) {
|
|
1303
|
+
if (this._status === "closed") {
|
|
1304
|
+
throw new Error("Session is closed");
|
|
1305
|
+
}
|
|
1306
|
+
const signal = this.executionAbortController?.signal ?? this.sessionAbortController.signal;
|
|
1307
|
+
if (signal.aborted) {
|
|
1308
|
+
throw new AbortError("Session aborted", signal.reason);
|
|
1309
|
+
}
|
|
1310
|
+
this._status = "running";
|
|
1311
|
+
this._executionComplete = false;
|
|
1312
|
+
this._lastProps = props;
|
|
1313
|
+
// Clear _currentOutput from previous execution to prevent stale data
|
|
1314
|
+
// from being included in useConversationHistory() via state.current
|
|
1315
|
+
this._currentOutput = null;
|
|
1316
|
+
const executionId = Context.tryGet()?.executionId || randomUUID();
|
|
1317
|
+
this._currentExecutionId = executionId;
|
|
1318
|
+
const maxTicks = options?.maxTicks ?? this.sessionOptions.maxTicks ?? this.appOptions.maxTicks ?? 10;
|
|
1319
|
+
const timestamp = () => new Date().toISOString();
|
|
1320
|
+
// Create root JSX element
|
|
1321
|
+
const rootElement = jsx(this.Component, props);
|
|
1322
|
+
// Setup or reuse compilation infrastructure
|
|
1323
|
+
await this.ensureCompilationInfrastructure(rootElement);
|
|
1324
|
+
// Transfer queued messages to COM
|
|
1325
|
+
if (this._queuedMessages.length > 0 && this.ctx) {
|
|
1326
|
+
this.log.debug({ count: this._queuedMessages.length }, "Transferring queued messages to COM");
|
|
1327
|
+
for (const msg of this._queuedMessages) {
|
|
1328
|
+
this.log.debug({ role: msg.role }, "Queuing message to COM");
|
|
1329
|
+
this.ctx.queueMessage({
|
|
1330
|
+
id: randomUUID(),
|
|
1331
|
+
type: "message",
|
|
1332
|
+
content: msg,
|
|
1333
|
+
});
|
|
1334
|
+
}
|
|
1335
|
+
this._queuedMessages = [];
|
|
1336
|
+
this.log.debug({ comQueuedCount: this.ctx.getQueuedMessages().length }, "COM queued messages after transfer");
|
|
1337
|
+
}
|
|
1338
|
+
// Track state for this execution
|
|
1339
|
+
const usage = {
|
|
1340
|
+
inputTokens: 0,
|
|
1341
|
+
outputTokens: 0,
|
|
1342
|
+
totalTokens: 0,
|
|
1343
|
+
ticks: 0,
|
|
1344
|
+
};
|
|
1345
|
+
let stopReason;
|
|
1346
|
+
let output;
|
|
1347
|
+
const outputs = {};
|
|
1348
|
+
const responseChunks = [];
|
|
1349
|
+
const toolExecutor = new ToolExecutor();
|
|
1350
|
+
this.emitEvent({
|
|
1351
|
+
type: "execution_start",
|
|
1352
|
+
executionId,
|
|
1353
|
+
timestamp: timestamp(),
|
|
1354
|
+
});
|
|
1355
|
+
try {
|
|
1356
|
+
// Tick loop
|
|
1357
|
+
let shouldContinue = true;
|
|
1358
|
+
while (shouldContinue && this._tick <= maxTicks) {
|
|
1359
|
+
if (signal.aborted) {
|
|
1360
|
+
throw new AbortError("Execution aborted", signal.reason);
|
|
1361
|
+
}
|
|
1362
|
+
const currentTick = this._tick;
|
|
1363
|
+
const tickStartTime = Date.now();
|
|
1364
|
+
this.emitEvent({
|
|
1365
|
+
type: "tick_start",
|
|
1366
|
+
tick: currentTick,
|
|
1367
|
+
timestamp: timestamp(),
|
|
1368
|
+
});
|
|
1369
|
+
// Phase 1: Compile
|
|
1370
|
+
const compiled = await this.compileTick(rootElement);
|
|
1371
|
+
// Emit compiled context to DevTools
|
|
1372
|
+
// Extract system text from COMTimelineEntry[]
|
|
1373
|
+
const systemText = compiled.formatted?.system
|
|
1374
|
+
?.map((entry) => {
|
|
1375
|
+
const content = entry.message?.content ?? entry.content;
|
|
1376
|
+
if (Array.isArray(content)) {
|
|
1377
|
+
return content
|
|
1378
|
+
.filter((b) => b.type === "text")
|
|
1379
|
+
.map((b) => b.text)
|
|
1380
|
+
.join("\n");
|
|
1381
|
+
}
|
|
1382
|
+
return String(content ?? "");
|
|
1383
|
+
})
|
|
1384
|
+
.filter(Boolean)
|
|
1385
|
+
.join("\n\n");
|
|
1386
|
+
this.emitEvent({
|
|
1387
|
+
type: "compiled",
|
|
1388
|
+
tick: currentTick,
|
|
1389
|
+
timestamp: timestamp(),
|
|
1390
|
+
// Rendered output (existing)
|
|
1391
|
+
system: systemText || undefined,
|
|
1392
|
+
messages: compiled.formatted?.timeline,
|
|
1393
|
+
tools: compiled.tools?.map((t) => ({
|
|
1394
|
+
name: t.metadata?.name ?? t.name,
|
|
1395
|
+
description: t.metadata?.description ?? t.description,
|
|
1396
|
+
})),
|
|
1397
|
+
// Raw compiled structure (before rendering)
|
|
1398
|
+
rawCompiled: compiled.rawCompiled
|
|
1399
|
+
? {
|
|
1400
|
+
sections: Object.fromEntries(compiled.rawCompiled.sections ?? new Map()),
|
|
1401
|
+
timelineEntries: compiled.rawCompiled.timelineEntries,
|
|
1402
|
+
systemEntries: compiled.rawCompiled.systemEntries,
|
|
1403
|
+
tools: compiled.rawCompiled.tools,
|
|
1404
|
+
ephemeral: compiled.rawCompiled.ephemeral,
|
|
1405
|
+
}
|
|
1406
|
+
: undefined,
|
|
1407
|
+
// Formatted COMInput (after rendering)
|
|
1408
|
+
formattedInput: compiled.formatted,
|
|
1409
|
+
});
|
|
1410
|
+
if (compiled.shouldStop) {
|
|
1411
|
+
stopReason = compiled.stopReason;
|
|
1412
|
+
this.emitEvent({
|
|
1413
|
+
type: "tick_end",
|
|
1414
|
+
tick: currentTick,
|
|
1415
|
+
shouldContinue: false,
|
|
1416
|
+
timestamp: timestamp(),
|
|
1417
|
+
});
|
|
1418
|
+
// Emit fiber snapshot to DevTools
|
|
1419
|
+
this.emitFiberSnapshot(currentTick, executionId, compiled.rawCompiled);
|
|
1420
|
+
// Clear queued messages - they were made available during compilation
|
|
1421
|
+
this.ctx?.clearQueuedMessages();
|
|
1422
|
+
break;
|
|
1423
|
+
}
|
|
1424
|
+
// Phase 2: Model
|
|
1425
|
+
const model = this.appOptions.model ?? compiled.model;
|
|
1426
|
+
if (!model) {
|
|
1427
|
+
throw new Error("No model configured. Add a <Model> component or pass model in options.");
|
|
1428
|
+
}
|
|
1429
|
+
if (signal.aborted) {
|
|
1430
|
+
throw new AbortError("Execution aborted", signal.reason);
|
|
1431
|
+
}
|
|
1432
|
+
const modelInput = compiled.modelInput ?? compiled.formatted;
|
|
1433
|
+
const modelStartTime = Date.now();
|
|
1434
|
+
// Emit model request to DevTools
|
|
1435
|
+
// modelInput is the Agentick ModelInput format (after fromEngineState transformation)
|
|
1436
|
+
this.emitEvent({
|
|
1437
|
+
type: "model_request",
|
|
1438
|
+
tick: currentTick,
|
|
1439
|
+
timestamp: timestamp(),
|
|
1440
|
+
modelId: model?.metadata?.id ?? model?.metadata?.model,
|
|
1441
|
+
// ModelInput: Agentick's model input format
|
|
1442
|
+
input: modelInput,
|
|
1443
|
+
// Stage marker for pipeline visualization
|
|
1444
|
+
stage: "model_input",
|
|
1445
|
+
});
|
|
1446
|
+
// Emit provider request to DevTools (Stage 4: what the SDK actually receives)
|
|
1447
|
+
if (model.getProviderInput) {
|
|
1448
|
+
const providerInput = await model.getProviderInput(modelInput);
|
|
1449
|
+
this.emitEvent({
|
|
1450
|
+
type: "provider_request",
|
|
1451
|
+
tick: currentTick,
|
|
1452
|
+
timestamp: timestamp(),
|
|
1453
|
+
modelId: model?.metadata?.id ?? model?.metadata?.model,
|
|
1454
|
+
provider: model?.metadata?.provider,
|
|
1455
|
+
providerInput,
|
|
1456
|
+
});
|
|
1457
|
+
}
|
|
1458
|
+
// Stream model output if supported
|
|
1459
|
+
let modelOutput;
|
|
1460
|
+
if (model.stream) {
|
|
1461
|
+
const streamEvents = [];
|
|
1462
|
+
const streamIterable = await model.stream(modelInput);
|
|
1463
|
+
for await (const event of streamIterable) {
|
|
1464
|
+
if (signal.aborted) {
|
|
1465
|
+
throw new AbortError("Execution aborted", signal.reason);
|
|
1466
|
+
}
|
|
1467
|
+
streamEvents.push(event);
|
|
1468
|
+
this.emitEvent(event);
|
|
1469
|
+
if (event.type === "content_delta" && "delta" in event) {
|
|
1470
|
+
responseChunks.push(event.delta);
|
|
1471
|
+
}
|
|
1472
|
+
if (event.type === "message" && "message" in event) {
|
|
1473
|
+
const messageEvent = event;
|
|
1474
|
+
modelOutput = {
|
|
1475
|
+
messages: [messageEvent.message],
|
|
1476
|
+
message: messageEvent.message,
|
|
1477
|
+
stopReason: messageEvent.stopReason,
|
|
1478
|
+
usage: messageEvent.usage,
|
|
1479
|
+
raw: messageEvent.raw, // Reconstructed provider response from adapter
|
|
1480
|
+
};
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
// Aggregate stream events into final output
|
|
1484
|
+
if (!modelOutput) {
|
|
1485
|
+
if (model.processStream) {
|
|
1486
|
+
// Use model's processStream if available
|
|
1487
|
+
modelOutput = await model.processStream(streamEvents);
|
|
1488
|
+
}
|
|
1489
|
+
else if (responseChunks.length > 0) {
|
|
1490
|
+
// Construct from collected chunks
|
|
1491
|
+
const text = responseChunks.join("");
|
|
1492
|
+
modelOutput = {
|
|
1493
|
+
message: {
|
|
1494
|
+
role: "assistant",
|
|
1495
|
+
content: [{ type: "text", text }],
|
|
1496
|
+
},
|
|
1497
|
+
stopReason: "stop",
|
|
1498
|
+
raw: { streamed: true },
|
|
1499
|
+
};
|
|
1500
|
+
}
|
|
1501
|
+
else {
|
|
1502
|
+
throw new Error("Streaming completed but no response was received");
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
else {
|
|
1507
|
+
// Procedure returns ExecutionHandle by default - access .result for actual return value
|
|
1508
|
+
// Handle both real procedures (with .result) and mock functions (direct value)
|
|
1509
|
+
const generateResult = model.generate(modelInput);
|
|
1510
|
+
modelOutput =
|
|
1511
|
+
generateResult && "result" in generateResult
|
|
1512
|
+
? await generateResult.result
|
|
1513
|
+
: await generateResult;
|
|
1514
|
+
}
|
|
1515
|
+
// Extract text from model output
|
|
1516
|
+
if (modelOutput?.message) {
|
|
1517
|
+
const textContent = modelOutput.message.content?.find((b) => b.type === "text");
|
|
1518
|
+
if (textContent && "text" in textContent) {
|
|
1519
|
+
responseChunks.length = 0;
|
|
1520
|
+
responseChunks.push(textContent.text);
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
// Update usage
|
|
1524
|
+
if (modelOutput?.usage) {
|
|
1525
|
+
usage.inputTokens += modelOutput.usage.inputTokens ?? 0;
|
|
1526
|
+
usage.outputTokens += modelOutput.usage.outputTokens ?? 0;
|
|
1527
|
+
usage.totalTokens += modelOutput.usage.totalTokens ?? 0;
|
|
1528
|
+
}
|
|
1529
|
+
if (signal.aborted) {
|
|
1530
|
+
throw new AbortError("Execution aborted", signal.reason);
|
|
1531
|
+
}
|
|
1532
|
+
// Convert model output to engine format
|
|
1533
|
+
if (!model.toEngineState) {
|
|
1534
|
+
throw new Error("Model missing toEngineState method");
|
|
1535
|
+
}
|
|
1536
|
+
const response = await model.toEngineState(modelOutput);
|
|
1537
|
+
// Emit model response to DevTools with full pipeline visibility
|
|
1538
|
+
this.emitEvent({
|
|
1539
|
+
type: "model_response",
|
|
1540
|
+
tick: currentTick,
|
|
1541
|
+
timestamp: timestamp(),
|
|
1542
|
+
// Provider output (raw from SDK - may be reconstructed for streaming)
|
|
1543
|
+
providerOutput: modelOutput?.raw,
|
|
1544
|
+
// ModelOutput (normalized Agentick format)
|
|
1545
|
+
modelOutput: {
|
|
1546
|
+
model: modelOutput?.model,
|
|
1547
|
+
message: modelOutput?.message,
|
|
1548
|
+
usage: modelOutput?.usage,
|
|
1549
|
+
stopReason: modelOutput?.stopReason,
|
|
1550
|
+
toolCalls: modelOutput?.toolCalls,
|
|
1551
|
+
},
|
|
1552
|
+
// Engine state (how it's ingested into timeline)
|
|
1553
|
+
engineState: {
|
|
1554
|
+
newTimelineEntries: response.newTimelineEntries,
|
|
1555
|
+
toolCalls: response.toolCalls,
|
|
1556
|
+
shouldStop: response.shouldStop,
|
|
1557
|
+
stopReason: response.stopReason,
|
|
1558
|
+
},
|
|
1559
|
+
});
|
|
1560
|
+
// Track last model output for inspection
|
|
1561
|
+
if (modelOutput?.message) {
|
|
1562
|
+
this._lastModelOutput = {
|
|
1563
|
+
content: modelOutput.message.content ?? [],
|
|
1564
|
+
stopReason: modelOutput.stopReason ?? "unknown",
|
|
1565
|
+
};
|
|
1566
|
+
}
|
|
1567
|
+
// Phase 3: Tools
|
|
1568
|
+
let toolResults = [];
|
|
1569
|
+
let toolStartTime;
|
|
1570
|
+
if (response.toolCalls?.length && this.ctx) {
|
|
1571
|
+
toolStartTime = Date.now();
|
|
1572
|
+
for (const call of response.toolCalls) {
|
|
1573
|
+
const toolCallTimestamp = timestamp();
|
|
1574
|
+
this.emitEvent({
|
|
1575
|
+
type: "tool_call",
|
|
1576
|
+
callId: call.id,
|
|
1577
|
+
blockIndex: 0,
|
|
1578
|
+
name: call.name,
|
|
1579
|
+
input: call.input,
|
|
1580
|
+
startedAt: toolCallTimestamp,
|
|
1581
|
+
completedAt: toolCallTimestamp,
|
|
1582
|
+
});
|
|
1583
|
+
}
|
|
1584
|
+
toolResults = await this.executeTools(toolExecutor, response.toolCalls, compiled.tools, outputs, currentTick, timestamp);
|
|
1585
|
+
}
|
|
1586
|
+
// Phase 4: Ingest & Tick End Callbacks
|
|
1587
|
+
const ingestResult = await this.ingestTickResult(response, toolResults);
|
|
1588
|
+
// Build TickResult for useTickEnd/useContinuation callbacks
|
|
1589
|
+
// Extract text from model response
|
|
1590
|
+
let tickText;
|
|
1591
|
+
if (modelOutput?.message?.content) {
|
|
1592
|
+
const textContent = modelOutput.message.content.find((b) => b.type === "text");
|
|
1593
|
+
if (textContent && "text" in textContent) {
|
|
1594
|
+
tickText = textContent.text;
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
// Build TickResult with data and control methods
|
|
1598
|
+
const tickResult = {
|
|
1599
|
+
tick: currentTick,
|
|
1600
|
+
text: tickText,
|
|
1601
|
+
content: modelOutput?.message?.content ?? [],
|
|
1602
|
+
toolCalls: response.toolCalls ?? [],
|
|
1603
|
+
toolResults,
|
|
1604
|
+
stopReason: modelOutput?.stopReason,
|
|
1605
|
+
usage: modelOutput?.usage,
|
|
1606
|
+
timeline: ingestResult.timeline,
|
|
1607
|
+
stop: (reasonOrOptions) => {
|
|
1608
|
+
if (typeof reasonOrOptions === "string") {
|
|
1609
|
+
this.ctx?.requestStop({ reason: reasonOrOptions });
|
|
1610
|
+
}
|
|
1611
|
+
else {
|
|
1612
|
+
this.ctx?.requestStop(reasonOrOptions ?? {});
|
|
1613
|
+
}
|
|
1614
|
+
},
|
|
1615
|
+
continue: (reasonOrOptions) => {
|
|
1616
|
+
if (typeof reasonOrOptions === "string") {
|
|
1617
|
+
this.ctx?.requestContinue({ reason: reasonOrOptions });
|
|
1618
|
+
}
|
|
1619
|
+
else {
|
|
1620
|
+
this.ctx?.requestContinue(reasonOrOptions ?? {});
|
|
1621
|
+
}
|
|
1622
|
+
},
|
|
1623
|
+
};
|
|
1624
|
+
// Run useTickEnd/useContinuation callbacks
|
|
1625
|
+
// These can call tickResult.stop() or tickResult.continue() to influence continuation
|
|
1626
|
+
const tickEndState = {
|
|
1627
|
+
tick: currentTick,
|
|
1628
|
+
previous: this._previousOutput ?? undefined,
|
|
1629
|
+
current: this._currentOutput,
|
|
1630
|
+
queuedMessages: [],
|
|
1631
|
+
stop: () => { }, // No-op at tick end - use tickResult.stop() instead
|
|
1632
|
+
};
|
|
1633
|
+
await this.compiler?.notifyTickEnd(tickEndState, tickResult);
|
|
1634
|
+
// Resolve final tick control using COM's priority-based system
|
|
1635
|
+
// This considers: explicit stop/continue requests from callbacks > default behavior
|
|
1636
|
+
const defaultStatus = ingestResult.shouldContinue ? "continue" : "completed";
|
|
1637
|
+
const tickDecision = this.ctx?._resolveTickControl(defaultStatus, ingestResult.stopReason) ?? { status: defaultStatus };
|
|
1638
|
+
shouldContinue = tickDecision.status === "continue";
|
|
1639
|
+
// Get actual model ID: prefer response model, then metadata.model, then metadata.id
|
|
1640
|
+
const actualModelId = modelOutput?.model || model?.metadata?.model || model?.metadata?.id || "unknown";
|
|
1641
|
+
this.emitEvent({
|
|
1642
|
+
type: "tick_end",
|
|
1643
|
+
tick: currentTick,
|
|
1644
|
+
shouldContinue,
|
|
1645
|
+
usage: modelOutput?.usage,
|
|
1646
|
+
stopReason: modelOutput?.stopReason,
|
|
1647
|
+
model: actualModelId,
|
|
1648
|
+
timestamp: timestamp(),
|
|
1649
|
+
});
|
|
1650
|
+
// Broadcast context utilization via channel
|
|
1651
|
+
this.broadcastContextInfo(executionId, actualModelId, model?.metadata, modelOutput?.usage, currentTick, usage, timestamp());
|
|
1652
|
+
// Emit fiber snapshot to DevTools
|
|
1653
|
+
this.emitFiberSnapshot(currentTick, executionId, compiled.rawCompiled);
|
|
1654
|
+
// Clear queued messages after tick completes - they've been processed
|
|
1655
|
+
this.ctx?.clearQueuedMessages();
|
|
1656
|
+
// Record snapshot if recording is enabled
|
|
1657
|
+
this.recordSnapshot(currentTick, tickStartTime, {
|
|
1658
|
+
formatted: compiled.formatted,
|
|
1659
|
+
modelId: model?.metadata?.id ?? model?.metadata?.model ?? null,
|
|
1660
|
+
modelInput: typeof modelInput === "string" ? modelInput : JSON.stringify(modelInput),
|
|
1661
|
+
modelStartTime,
|
|
1662
|
+
modelOutput,
|
|
1663
|
+
toolCalls: response.toolCalls ?? [],
|
|
1664
|
+
toolResults,
|
|
1665
|
+
toolStartTime,
|
|
1666
|
+
shouldContinue,
|
|
1667
|
+
stopReason,
|
|
1668
|
+
});
|
|
1669
|
+
// Update _previousOutput after each tick so the next tick has access to the timeline
|
|
1670
|
+
// This is critical for <Timeline> to render conversation history on subsequent ticks
|
|
1671
|
+
output = await this.complete();
|
|
1672
|
+
this._previousOutput = output;
|
|
1673
|
+
this.log.debug({ timelineLength: output.timeline?.length ?? 0 }, "Updated _previousOutput after tick");
|
|
1674
|
+
this._tick++;
|
|
1675
|
+
usage.ticks = (usage.ticks ?? 0) + 1;
|
|
1676
|
+
}
|
|
1677
|
+
// Final complete (may be redundant but ensures consistency)
|
|
1678
|
+
output = await this.complete();
|
|
1679
|
+
this._previousOutput = output;
|
|
1680
|
+
// Accumulate usage
|
|
1681
|
+
this._totalUsage.inputTokens += usage.inputTokens;
|
|
1682
|
+
this._totalUsage.outputTokens += usage.outputTokens;
|
|
1683
|
+
this._totalUsage.totalTokens += usage.totalTokens;
|
|
1684
|
+
this._totalUsage.ticks = (this._totalUsage.ticks ?? 0) + (usage.ticks ?? 0);
|
|
1685
|
+
const resultPayload = {
|
|
1686
|
+
response: responseChunks.join(""),
|
|
1687
|
+
outputs,
|
|
1688
|
+
usage,
|
|
1689
|
+
stopReason,
|
|
1690
|
+
};
|
|
1691
|
+
this.emitEvent({
|
|
1692
|
+
type: "result",
|
|
1693
|
+
result: resultPayload,
|
|
1694
|
+
timestamp: timestamp(),
|
|
1695
|
+
});
|
|
1696
|
+
}
|
|
1697
|
+
finally {
|
|
1698
|
+
this._executionComplete = true;
|
|
1699
|
+
// Ensure queued messages are cleared even on error/abort paths
|
|
1700
|
+
// (normal path already clears at tick_end, this is a safety net)
|
|
1701
|
+
this.ctx?.clearQueuedMessages();
|
|
1702
|
+
this.emitEvent({
|
|
1703
|
+
type: "execution_end",
|
|
1704
|
+
executionId,
|
|
1705
|
+
stopReason,
|
|
1706
|
+
aborted: this._isAborted,
|
|
1707
|
+
usage,
|
|
1708
|
+
output: output ?? null,
|
|
1709
|
+
timestamp: timestamp(),
|
|
1710
|
+
});
|
|
1711
|
+
// Publish timeline delta to channel for real-time sync across clients
|
|
1712
|
+
// Only send NEW messages since last publish - O(delta) not O(n)
|
|
1713
|
+
if (output?.timeline) {
|
|
1714
|
+
const allMessages = output.timeline
|
|
1715
|
+
.filter((entry) => entry.message)
|
|
1716
|
+
.map((entry) => entry.message);
|
|
1717
|
+
// Only publish if there are new messages
|
|
1718
|
+
if (allMessages.length > this._lastPublishedTimelineLength) {
|
|
1719
|
+
const newMessages = allMessages.slice(this._lastPublishedTimelineLength);
|
|
1720
|
+
this._lastPublishedTimelineLength = allMessages.length;
|
|
1721
|
+
this.channel("timeline").publish({
|
|
1722
|
+
type: "timeline_delta",
|
|
1723
|
+
channel: "timeline",
|
|
1724
|
+
payload: {
|
|
1725
|
+
messages: newMessages, // Only new messages
|
|
1726
|
+
totalCount: allMessages.length, // For sync verification
|
|
1727
|
+
tick: this._tick,
|
|
1728
|
+
},
|
|
1729
|
+
});
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
// Clear execution ID after emitting execution_end
|
|
1733
|
+
this._currentExecutionId = null;
|
|
1734
|
+
for (const resolve of this._eventResolvers) {
|
|
1735
|
+
resolve({ value: undefined, done: true });
|
|
1736
|
+
}
|
|
1737
|
+
this._eventResolvers = [];
|
|
1738
|
+
this._status = "idle";
|
|
1739
|
+
}
|
|
1740
|
+
return {
|
|
1741
|
+
response: responseChunks.join(""),
|
|
1742
|
+
outputs,
|
|
1743
|
+
usage,
|
|
1744
|
+
stopReason,
|
|
1745
|
+
raw: output,
|
|
1746
|
+
};
|
|
1747
|
+
}
|
|
1748
|
+
/**
|
|
1749
|
+
* Ensure compilation infrastructure exists or reset for new run.
|
|
1750
|
+
*/
|
|
1751
|
+
async ensureCompilationInfrastructure(_rootElement) {
|
|
1752
|
+
if (this.ctx && this.compiler && this.structureRenderer) {
|
|
1753
|
+
// Reuse existing - reset for new run
|
|
1754
|
+
this.ctx.clear();
|
|
1755
|
+
this._tick = 1;
|
|
1756
|
+
return;
|
|
1757
|
+
}
|
|
1758
|
+
// Create new infrastructure
|
|
1759
|
+
this.ctx = new COM({
|
|
1760
|
+
metadata: {},
|
|
1761
|
+
});
|
|
1762
|
+
this.compiler = new FiberCompiler(this.ctx, undefined, {});
|
|
1763
|
+
// Apply pending hydration data if available
|
|
1764
|
+
if (this._pendingHydrationData) {
|
|
1765
|
+
this.compiler.setHydrationData(this._pendingHydrationData);
|
|
1766
|
+
this._pendingHydrationData = null; // Clear after applying
|
|
1767
|
+
}
|
|
1768
|
+
// Create scheduler and wire it to the compiler
|
|
1769
|
+
// This enables the reactive model: state changes between ticks trigger reconciliation
|
|
1770
|
+
this.scheduler = new ReconciliationScheduler(this.compiler, {
|
|
1771
|
+
onReconcile: (event) => {
|
|
1772
|
+
this.emit("reconcile", event);
|
|
1773
|
+
},
|
|
1774
|
+
});
|
|
1775
|
+
// Wire compiler state changes to scheduler
|
|
1776
|
+
this.compiler.setReconcileCallback((reason) => {
|
|
1777
|
+
this.scheduler.schedule(reason ?? "compiler recompile request");
|
|
1778
|
+
});
|
|
1779
|
+
// Wire COM recompile requests to scheduler
|
|
1780
|
+
// This unifies COM state signals with the reactive model
|
|
1781
|
+
this.ctx.setRecompileCallback((reason) => {
|
|
1782
|
+
this.scheduler.schedule(reason ?? "COM recompile request");
|
|
1783
|
+
});
|
|
1784
|
+
// Wire COM spawn delegate to session's spawn Procedure
|
|
1785
|
+
this.ctx.setSpawnCallback((agent, input) => this.spawn(agent, input));
|
|
1786
|
+
this.structureRenderer = new StructureRenderer(this.ctx);
|
|
1787
|
+
this.structureRenderer.setDefaultRenderer(new MarkdownRenderer());
|
|
1788
|
+
// Register tools from appOptions
|
|
1789
|
+
if (this.appOptions.tools) {
|
|
1790
|
+
for (const tool of this.appOptions.tools) {
|
|
1791
|
+
this.ctx.addTool(tool);
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
// Notify compiler that compilation is starting
|
|
1795
|
+
await this.compiler.notifyStart();
|
|
1796
|
+
}
|
|
1797
|
+
/**
|
|
1798
|
+
* Compile a single tick.
|
|
1799
|
+
*/
|
|
1800
|
+
async compileTick(rootElement) {
|
|
1801
|
+
if (!this.ctx || !this.compiler || !this.structureRenderer) {
|
|
1802
|
+
throw new Error("Compilation infrastructure not initialized");
|
|
1803
|
+
}
|
|
1804
|
+
// Clear COM for this tick
|
|
1805
|
+
this.ctx.clear();
|
|
1806
|
+
// NOTE: Previous timeline entries are NOT injected into COM here.
|
|
1807
|
+
// They are merged in at formatInput time. This keeps the architecture declarative:
|
|
1808
|
+
// - JSX compilation produces CompiledStructure with NEW entries
|
|
1809
|
+
// - formatInput merges previousTimeline + new entries
|
|
1810
|
+
// - No imperative accumulation during render
|
|
1811
|
+
// Re-register tools
|
|
1812
|
+
if (this.appOptions.tools) {
|
|
1813
|
+
for (const tool of this.appOptions.tools) {
|
|
1814
|
+
this.ctx.addTool(tool);
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
// Get queued messages for TickState - these are NEW messages for this tick
|
|
1818
|
+
const queuedMessages = this.ctx.getQueuedMessages();
|
|
1819
|
+
this.log.debug({ queuedCount: queuedMessages.length }, "Queued messages available for TickState");
|
|
1820
|
+
// Prepare tick state
|
|
1821
|
+
const tickState = {
|
|
1822
|
+
tick: this._tick,
|
|
1823
|
+
previous: this._previousOutput ?? undefined,
|
|
1824
|
+
current: this._currentOutput,
|
|
1825
|
+
queuedMessages: queuedMessages,
|
|
1826
|
+
stop: (reason) => {
|
|
1827
|
+
tickState.stopReason = reason;
|
|
1828
|
+
},
|
|
1829
|
+
};
|
|
1830
|
+
// Notify compiler of tick start
|
|
1831
|
+
await this.compiler.notifyTickStart(tickState);
|
|
1832
|
+
// Create tick control for useTick() hook
|
|
1833
|
+
const tickControl = {
|
|
1834
|
+
requestTick: () => {
|
|
1835
|
+
// For now, requesting a tick schedules the next iteration
|
|
1836
|
+
// This will be used by the reactive model for external triggers
|
|
1837
|
+
this.emit("tick:request");
|
|
1838
|
+
},
|
|
1839
|
+
cancelTick: () => {
|
|
1840
|
+
this.emit("tick:cancel");
|
|
1841
|
+
},
|
|
1842
|
+
status: this._status,
|
|
1843
|
+
tickCount: this._tick,
|
|
1844
|
+
};
|
|
1845
|
+
// Create channel accessor for useChannel() hook
|
|
1846
|
+
const getChannel = (name) => this.channel(name);
|
|
1847
|
+
// Enter tick mode - scheduler will defer reconciliations until exitTick
|
|
1848
|
+
this.scheduler?.enterTick();
|
|
1849
|
+
let compiled;
|
|
1850
|
+
const wasHydrating = this.compiler.isHydratingNow();
|
|
1851
|
+
try {
|
|
1852
|
+
// Compile until stable
|
|
1853
|
+
// Note: tickControl and getChannel are available for future use
|
|
1854
|
+
// but not currently used by the v2 FiberCompiler
|
|
1855
|
+
void tickControl;
|
|
1856
|
+
void getChannel;
|
|
1857
|
+
const result = await this.compiler.compileUntilStable(rootElement, tickState, {
|
|
1858
|
+
maxIterations: 50,
|
|
1859
|
+
});
|
|
1860
|
+
compiled = result.compiled;
|
|
1861
|
+
// Complete hydration after first successful compile
|
|
1862
|
+
if (wasHydrating) {
|
|
1863
|
+
this.compiler.completeHydration();
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
finally {
|
|
1867
|
+
// Exit tick mode - any pending reconciliations will now flush
|
|
1868
|
+
this.scheduler?.exitTick();
|
|
1869
|
+
}
|
|
1870
|
+
// Apply compiled structure (sections, tools, metadata - but NOT timeline entries)
|
|
1871
|
+
await this.structureRenderer.apply(compiled);
|
|
1872
|
+
// Format input - compiled structure IS the complete projection
|
|
1873
|
+
// JSX components render history as <Message>, so compiled.timelineEntries is complete
|
|
1874
|
+
const formatted = await this.structureRenderer.formatInput(this.ctx.toInput());
|
|
1875
|
+
// Track what we're sending to the model (for combining with response in complete())
|
|
1876
|
+
this._lastSentTimeline = formatted.timeline ?? [];
|
|
1877
|
+
// Track estimated context tokens for contextInfo
|
|
1878
|
+
this._estimatedContextTokens = formatted.totalTokens;
|
|
1879
|
+
// Get model from COM if not in options
|
|
1880
|
+
const model = this.ctx.getModel?.();
|
|
1881
|
+
let modelInput;
|
|
1882
|
+
if (model?.fromEngineState) {
|
|
1883
|
+
modelInput = await model.fromEngineState(formatted);
|
|
1884
|
+
}
|
|
1885
|
+
// Get tools
|
|
1886
|
+
const tools = this.ctx.getTools?.() ?? [];
|
|
1887
|
+
// Check for stop
|
|
1888
|
+
const stopReason = tickState.stopReason;
|
|
1889
|
+
return {
|
|
1890
|
+
formatted,
|
|
1891
|
+
model,
|
|
1892
|
+
modelInput,
|
|
1893
|
+
tools: tools,
|
|
1894
|
+
shouldStop: !!stopReason,
|
|
1895
|
+
stopReason,
|
|
1896
|
+
rawCompiled: compiled,
|
|
1897
|
+
};
|
|
1898
|
+
}
|
|
1899
|
+
/**
|
|
1900
|
+
* Execute tools and emit results.
|
|
1901
|
+
*/
|
|
1902
|
+
async executeTools(executor, toolCalls, configTools, outputs, currentTick, timestamp) {
|
|
1903
|
+
const results = [];
|
|
1904
|
+
const executableTools = configTools.filter((t) => "run" in t);
|
|
1905
|
+
for (const call of toolCalls) {
|
|
1906
|
+
const startedAt = timestamp();
|
|
1907
|
+
// Check if OUTPUT tool
|
|
1908
|
+
const tool = executableTools.find((t) => t.metadata?.name === call.name);
|
|
1909
|
+
const isOutputTool = tool && tool.metadata?.type === "output";
|
|
1910
|
+
if (isOutputTool) {
|
|
1911
|
+
outputs[call.name] = call.input;
|
|
1912
|
+
const completedAt = timestamp();
|
|
1913
|
+
this.emitEvent({
|
|
1914
|
+
type: "tool_result",
|
|
1915
|
+
callId: call.id,
|
|
1916
|
+
name: call.name,
|
|
1917
|
+
result: call.input,
|
|
1918
|
+
isError: false,
|
|
1919
|
+
executedBy: "engine",
|
|
1920
|
+
startedAt,
|
|
1921
|
+
completedAt,
|
|
1922
|
+
});
|
|
1923
|
+
continue;
|
|
1924
|
+
}
|
|
1925
|
+
// Execute tool
|
|
1926
|
+
try {
|
|
1927
|
+
const result = await executor.processToolWithConfirmation(call, this.ctx, executableTools);
|
|
1928
|
+
results.push(result.result);
|
|
1929
|
+
const completedAt = timestamp();
|
|
1930
|
+
this.emitEvent({
|
|
1931
|
+
type: "tool_result",
|
|
1932
|
+
callId: result.result.toolUseId,
|
|
1933
|
+
name: result.result.name,
|
|
1934
|
+
result: result.result,
|
|
1935
|
+
isError: !result.result.success,
|
|
1936
|
+
executedBy: "engine",
|
|
1937
|
+
startedAt,
|
|
1938
|
+
completedAt,
|
|
1939
|
+
});
|
|
1940
|
+
}
|
|
1941
|
+
catch (error) {
|
|
1942
|
+
const errorResult = {
|
|
1943
|
+
id: randomUUID(),
|
|
1944
|
+
toolUseId: call.id,
|
|
1945
|
+
name: call.name,
|
|
1946
|
+
success: false,
|
|
1947
|
+
content: [
|
|
1948
|
+
{
|
|
1949
|
+
type: "text",
|
|
1950
|
+
text: `Tool execution failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
1951
|
+
},
|
|
1952
|
+
],
|
|
1953
|
+
};
|
|
1954
|
+
results.push(errorResult);
|
|
1955
|
+
const completedAt = timestamp();
|
|
1956
|
+
this.emitEvent({
|
|
1957
|
+
type: "tool_result",
|
|
1958
|
+
callId: call.id,
|
|
1959
|
+
name: call.name,
|
|
1960
|
+
result: errorResult,
|
|
1961
|
+
isError: true,
|
|
1962
|
+
executedBy: "engine",
|
|
1963
|
+
startedAt,
|
|
1964
|
+
completedAt,
|
|
1965
|
+
});
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
return results;
|
|
1969
|
+
}
|
|
1970
|
+
/**
|
|
1971
|
+
* Ingest model response and tool results.
|
|
1972
|
+
*/
|
|
1973
|
+
async ingestTickResult(response, toolResults) {
|
|
1974
|
+
if (!this.ctx || !this.compiler) {
|
|
1975
|
+
throw new Error("Compilation infrastructure not initialized");
|
|
1976
|
+
}
|
|
1977
|
+
// Convert queued user messages to timeline entries BEFORE clearing
|
|
1978
|
+
// This preserves the user message in the conversation history
|
|
1979
|
+
const queuedMessages = this.ctx.getQueuedMessages();
|
|
1980
|
+
const existingTimeline = this.ctx.getTimeline();
|
|
1981
|
+
const existingByMessage = new Map();
|
|
1982
|
+
for (const entry of existingTimeline) {
|
|
1983
|
+
if (entry.message) {
|
|
1984
|
+
existingByMessage.set(entry.message, entry);
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
const userEntries = [];
|
|
1988
|
+
const newUserEntries = [];
|
|
1989
|
+
for (const queued of queuedMessages) {
|
|
1990
|
+
if (queued.type !== "user" || !queued.content)
|
|
1991
|
+
continue;
|
|
1992
|
+
const message = queued.content;
|
|
1993
|
+
const existing = existingByMessage.get(message);
|
|
1994
|
+
if (existing) {
|
|
1995
|
+
userEntries.push(existing);
|
|
1996
|
+
continue;
|
|
1997
|
+
}
|
|
1998
|
+
const entry = {
|
|
1999
|
+
kind: "message",
|
|
2000
|
+
message,
|
|
2001
|
+
tags: ["user_input"],
|
|
2002
|
+
};
|
|
2003
|
+
userEntries.push(entry);
|
|
2004
|
+
newUserEntries.push(entry);
|
|
2005
|
+
}
|
|
2006
|
+
// Build tool result entries
|
|
2007
|
+
const toolResultEntries = toolResults.length > 0
|
|
2008
|
+
? [
|
|
2009
|
+
{
|
|
2010
|
+
kind: "message",
|
|
2011
|
+
message: {
|
|
2012
|
+
role: "tool",
|
|
2013
|
+
content: toolResults.map((r) => ({
|
|
2014
|
+
id: r.id,
|
|
2015
|
+
type: "tool_result",
|
|
2016
|
+
toolUseId: r.toolUseId,
|
|
2017
|
+
name: r.name,
|
|
2018
|
+
content: r.content || [],
|
|
2019
|
+
isError: !r.success,
|
|
2020
|
+
})),
|
|
2021
|
+
},
|
|
2022
|
+
tags: ["tool_output"],
|
|
2023
|
+
},
|
|
2024
|
+
]
|
|
2025
|
+
: [];
|
|
2026
|
+
// Build current output - user messages first, then assistant response, then tool results
|
|
2027
|
+
const current = {
|
|
2028
|
+
timeline: [...userEntries, ...(response.newTimelineEntries || []), ...toolResultEntries],
|
|
2029
|
+
toolCalls: response.toolCalls,
|
|
2030
|
+
toolResults,
|
|
2031
|
+
};
|
|
2032
|
+
// Add entries to COM - user entries first, then assistant response
|
|
2033
|
+
for (const entry of newUserEntries) {
|
|
2034
|
+
this.ctx.addTimelineEntry(entry);
|
|
2035
|
+
}
|
|
2036
|
+
if (response.newTimelineEntries) {
|
|
2037
|
+
for (const entry of response.newTimelineEntries) {
|
|
2038
|
+
this.ctx.addTimelineEntry(entry);
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
2041
|
+
if (toolResults.length > 0) {
|
|
2042
|
+
this.ctx.addTimelineEntry({
|
|
2043
|
+
kind: "message",
|
|
2044
|
+
message: {
|
|
2045
|
+
role: "tool",
|
|
2046
|
+
content: toolResults.map((r) => ({
|
|
2047
|
+
type: "tool_result",
|
|
2048
|
+
toolUseId: r.toolUseId,
|
|
2049
|
+
name: r.name,
|
|
2050
|
+
content: r.content || [],
|
|
2051
|
+
isError: !r.success,
|
|
2052
|
+
})),
|
|
2053
|
+
},
|
|
2054
|
+
tags: ["tool_output"],
|
|
2055
|
+
});
|
|
2056
|
+
}
|
|
2057
|
+
this._currentOutput = current;
|
|
2058
|
+
// Resolve tick control
|
|
2059
|
+
const shouldStop = response.shouldStop || false;
|
|
2060
|
+
const stopReason = response.stopReason?.reason;
|
|
2061
|
+
return {
|
|
2062
|
+
shouldContinue: !shouldStop && (response.toolCalls?.length > 0 || false),
|
|
2063
|
+
stopReason,
|
|
2064
|
+
timeline: current.timeline,
|
|
2065
|
+
};
|
|
2066
|
+
}
|
|
2067
|
+
/**
|
|
2068
|
+
* Complete execution and return final state.
|
|
2069
|
+
*
|
|
2070
|
+
* Returns the complete timeline for this execution:
|
|
2071
|
+
* - What was sent to the model (from compiled JSX via _lastSentTimeline)
|
|
2072
|
+
* - What the model responded (from _currentOutput.timeline - only NEW entries)
|
|
2073
|
+
*
|
|
2074
|
+
* This ensures the full conversation history is preserved across executions.
|
|
2075
|
+
*/
|
|
2076
|
+
async complete() {
|
|
2077
|
+
if (!this.ctx || !this.structureRenderer || !this.compiler) {
|
|
2078
|
+
throw new Error("Compilation infrastructure not initialized");
|
|
2079
|
+
}
|
|
2080
|
+
// Build complete timeline:
|
|
2081
|
+
// 1. _lastSentTimeline = what was sent to model (includes history rendered by JSX)
|
|
2082
|
+
// 2. _currentOutput.timeline = new entries from this tick (user input + model response + tool results)
|
|
2083
|
+
//
|
|
2084
|
+
// Only USER messages might overlap (rendered via JSX AND in queuedMessages).
|
|
2085
|
+
// Model responses (assistant/tool) are ALWAYS new and should NEVER be deduplicated.
|
|
2086
|
+
//
|
|
2087
|
+
// NOTE: We use content-based comparison for user messages because <Message {...entry.message}>
|
|
2088
|
+
// creates NEW message objects when rendering history, so reference equality fails.
|
|
2089
|
+
const sentTimeline = this._lastSentTimeline ?? [];
|
|
2090
|
+
const currentTimeline = this._currentOutput?.timeline ?? [];
|
|
2091
|
+
// Create a signature for user messages only
|
|
2092
|
+
const userMessageSignature = (entry) => {
|
|
2093
|
+
if (!entry.message || entry.message.role !== "user")
|
|
2094
|
+
return null;
|
|
2095
|
+
const m = entry.message;
|
|
2096
|
+
if (m.id)
|
|
2097
|
+
return `id:${m.id}`;
|
|
2098
|
+
const contentStr = JSON.stringify(m.content);
|
|
2099
|
+
return `user:${contentStr}`;
|
|
2100
|
+
};
|
|
2101
|
+
// Only track signatures of USER messages that were sent
|
|
2102
|
+
const sentUserSignatures = new Set(sentTimeline.map(userMessageSignature).filter((s) => s !== null));
|
|
2103
|
+
// Filter currentTimeline:
|
|
2104
|
+
// - USER messages: dedupe against sentTimeline (might be rendered via JSX)
|
|
2105
|
+
// - Other roles (assistant, tool): always include (they're new from this tick)
|
|
2106
|
+
const newEntries = currentTimeline.filter((entry) => {
|
|
2107
|
+
if (!entry.message)
|
|
2108
|
+
return true; // Non-message entries always included
|
|
2109
|
+
if (entry.message.role !== "user")
|
|
2110
|
+
return true; // Assistant/tool always included
|
|
2111
|
+
// User message: check for duplicates
|
|
2112
|
+
const sig = userMessageSignature(entry);
|
|
2113
|
+
return sig === null || !sentUserSignatures.has(sig);
|
|
2114
|
+
});
|
|
2115
|
+
const timeline = [...sentTimeline, ...newEntries];
|
|
2116
|
+
const comOutput = this.ctx.toInput();
|
|
2117
|
+
const finalOutput = {
|
|
2118
|
+
...comOutput,
|
|
2119
|
+
timeline,
|
|
2120
|
+
};
|
|
2121
|
+
// Notify compiler of completion
|
|
2122
|
+
try {
|
|
2123
|
+
await this.compiler.notifyComplete(finalOutput);
|
|
2124
|
+
}
|
|
2125
|
+
catch {
|
|
2126
|
+
// Ignore completion errors
|
|
2127
|
+
}
|
|
2128
|
+
return finalOutput;
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
2131
|
+
//# sourceMappingURL=session.js.map
|