@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,1437 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* V2 Hooks Implementation
|
|
3
|
+
*
|
|
4
|
+
* React-inspired hooks for function components in the tick-based agent model.
|
|
5
|
+
* Key difference from React: async-first, effects can be async.
|
|
6
|
+
*
|
|
7
|
+
* Rules of Hooks:
|
|
8
|
+
* 1. Only call hooks at the top level of a function component
|
|
9
|
+
* 2. Only call hooks from function components or custom hooks
|
|
10
|
+
* 3. Call hooks in the same order every render
|
|
11
|
+
*/
|
|
12
|
+
import { HookTag, EffectPhase } from "../compiler/types";
|
|
13
|
+
import { signal as createSignal, computed, createCOMStateSignal, createReadonlyCOMStateSignal, isSignal, isComputed, } from "./signal";
|
|
14
|
+
import { shouldSkipRecompile } from "../compiler/fiber-compiler";
|
|
15
|
+
import { Logger } from "../core/logger";
|
|
16
|
+
const log = Logger.for("hooks");
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// Render Context (Global During Render)
|
|
19
|
+
// ============================================================================
|
|
20
|
+
let renderContext = null;
|
|
21
|
+
/**
|
|
22
|
+
* Get current render context. Throws if called outside render.
|
|
23
|
+
*/
|
|
24
|
+
export function getCurrentContext() {
|
|
25
|
+
if (renderContext === null) {
|
|
26
|
+
throw new Error("Invalid hook call. Hooks can only be called inside a function component.\n" +
|
|
27
|
+
"Possible causes:\n" +
|
|
28
|
+
"1. Calling a hook outside a component\n" +
|
|
29
|
+
"2. Calling hooks conditionally or in a loop\n" +
|
|
30
|
+
"3. Mismatched tentickle package versions");
|
|
31
|
+
}
|
|
32
|
+
return renderContext;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Set render context (called by compiler).
|
|
36
|
+
*/
|
|
37
|
+
export function setRenderContext(ctx) {
|
|
38
|
+
renderContext = ctx;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Get current fiber (for advanced use).
|
|
42
|
+
*/
|
|
43
|
+
export function getCurrentFiber() {
|
|
44
|
+
return renderContext?.fiber ?? null;
|
|
45
|
+
}
|
|
46
|
+
// ============================================================================
|
|
47
|
+
// Context Access Hooks
|
|
48
|
+
// ============================================================================
|
|
49
|
+
/**
|
|
50
|
+
* Get the COM (Context Object Model) for the current render.
|
|
51
|
+
*
|
|
52
|
+
* Use this instead of receiving COM as a component parameter.
|
|
53
|
+
* This makes components have standard React-like signatures: `(props) => JSX.Element`
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```tsx
|
|
57
|
+
* // Before (legacy pattern with magic args):
|
|
58
|
+
* const MyComponent = (props, com, state) => { ... };
|
|
59
|
+
*
|
|
60
|
+
* // After (recommended hook pattern):
|
|
61
|
+
* const MyComponent = (props) => {
|
|
62
|
+
* const com = useCom();
|
|
63
|
+
* const state = useTickState();
|
|
64
|
+
* // ...
|
|
65
|
+
* };
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
export function useCom() {
|
|
69
|
+
return getCurrentContext().com;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Get the TickState for the current render.
|
|
73
|
+
*
|
|
74
|
+
* TickState contains:
|
|
75
|
+
* - `tick`: Current tick number (1-indexed)
|
|
76
|
+
* - `previous`: COMInput from previous tick (conversation history)
|
|
77
|
+
* - `current`: COMOutput from current tick (model response)
|
|
78
|
+
* - `stop(reason)`: Function to stop execution
|
|
79
|
+
* - `queuedMessages`: Messages received during execution
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* ```tsx
|
|
83
|
+
* const MyComponent = (props) => {
|
|
84
|
+
* const state = useTickState();
|
|
85
|
+
* console.log(`Tick ${state.tick}`);
|
|
86
|
+
* // Access previous conversation via state.previous?.timeline
|
|
87
|
+
* };
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
90
|
+
export function useTickState() {
|
|
91
|
+
return getCurrentContext().tickState;
|
|
92
|
+
}
|
|
93
|
+
// ============================================================================
|
|
94
|
+
// Work Scheduling
|
|
95
|
+
// ============================================================================
|
|
96
|
+
/**
|
|
97
|
+
* Schedule work for a fiber using the scheduler from the current render context.
|
|
98
|
+
* This ensures concurrent compilations don't interfere with each other.
|
|
99
|
+
*/
|
|
100
|
+
// function scheduleWork(fiber: FiberNode): void {
|
|
101
|
+
// // During render, we have renderContext available with the correct scheduler
|
|
102
|
+
// const ctx = renderContext;
|
|
103
|
+
// if (ctx?.scheduleWork) {
|
|
104
|
+
// ctx.scheduleWork(fiber);
|
|
105
|
+
// }
|
|
106
|
+
// }
|
|
107
|
+
// ============================================================================
|
|
108
|
+
// Hook State Management
|
|
109
|
+
// ============================================================================
|
|
110
|
+
/**
|
|
111
|
+
* Mount a new hook during initial render.
|
|
112
|
+
* If hydrating, attempts to restore state from hydration data.
|
|
113
|
+
*/
|
|
114
|
+
function mountWorkInProgressHook() {
|
|
115
|
+
const ctx = getCurrentContext();
|
|
116
|
+
// Track hook index for hydration
|
|
117
|
+
const hookIndex = ctx.hookIndex ?? 0;
|
|
118
|
+
ctx.hookIndex = hookIndex + 1;
|
|
119
|
+
const hook = {
|
|
120
|
+
memoizedState: undefined,
|
|
121
|
+
queue: null,
|
|
122
|
+
effect: null,
|
|
123
|
+
next: null,
|
|
124
|
+
tag: HookTag.State,
|
|
125
|
+
};
|
|
126
|
+
// Check for hydration data
|
|
127
|
+
if (ctx.isHydrating && ctx.hydrationData?.hooks) {
|
|
128
|
+
const hydrationHook = ctx.hydrationData.hooks[hookIndex];
|
|
129
|
+
if (hydrationHook) {
|
|
130
|
+
// Restore memoized state from hydration
|
|
131
|
+
hook.memoizedState = hydrationHook.value;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (ctx.workInProgressHook === null) {
|
|
135
|
+
ctx.fiber.memoizedState = hook;
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
ctx.workInProgressHook.next = hook;
|
|
139
|
+
}
|
|
140
|
+
ctx.workInProgressHook = hook;
|
|
141
|
+
return hook;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Check if a hook should skip initialization because it's being hydrated.
|
|
145
|
+
* Returns the hydrated value if available.
|
|
146
|
+
*/
|
|
147
|
+
// function getHydratedValue<T>(hookIndex: number): T | undefined {
|
|
148
|
+
// const ctx = getCurrentContext();
|
|
149
|
+
// if (ctx.isHydrating && ctx.hydrationData?.hooks) {
|
|
150
|
+
// const hydrationHook = ctx.hydrationData.hooks[hookIndex];
|
|
151
|
+
// if (hydrationHook) {
|
|
152
|
+
// return hydrationHook.value as T;
|
|
153
|
+
// }
|
|
154
|
+
// }
|
|
155
|
+
// return undefined;
|
|
156
|
+
// }
|
|
157
|
+
function updateWorkInProgressHook() {
|
|
158
|
+
const ctx = getCurrentContext();
|
|
159
|
+
const current = ctx.currentHook;
|
|
160
|
+
if (current === null) {
|
|
161
|
+
throw new Error("Rendered more hooks than during the previous render. " +
|
|
162
|
+
"Hooks must be called in the same order every render.");
|
|
163
|
+
}
|
|
164
|
+
const newHook = {
|
|
165
|
+
memoizedState: current.memoizedState,
|
|
166
|
+
baseState: current.baseState,
|
|
167
|
+
queue: current.queue,
|
|
168
|
+
effect: current.effect,
|
|
169
|
+
next: null,
|
|
170
|
+
tag: current.tag,
|
|
171
|
+
};
|
|
172
|
+
if (ctx.workInProgressHook === null) {
|
|
173
|
+
ctx.fiber.memoizedState = newHook;
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
ctx.workInProgressHook.next = newHook;
|
|
177
|
+
}
|
|
178
|
+
ctx.workInProgressHook = newHook;
|
|
179
|
+
ctx.currentHook = current.next;
|
|
180
|
+
return newHook;
|
|
181
|
+
}
|
|
182
|
+
function mountOrUpdateHook(tag) {
|
|
183
|
+
const ctx = getCurrentContext();
|
|
184
|
+
const isMount = ctx.currentHook === null && ctx.fiber.alternate === null;
|
|
185
|
+
const hook = isMount ? mountWorkInProgressHook() : updateWorkInProgressHook();
|
|
186
|
+
hook.tag = tag;
|
|
187
|
+
return hook;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Extract signal values for dependency comparison.
|
|
191
|
+
* Signals/computed values are unwrapped to their current value.
|
|
192
|
+
*/
|
|
193
|
+
function unwrapDeps(deps) {
|
|
194
|
+
if (!deps)
|
|
195
|
+
return deps;
|
|
196
|
+
return deps.map((dep) => {
|
|
197
|
+
// If it's a signal or computed, read its current value
|
|
198
|
+
if (isSignal(dep) || isComputed(dep)) {
|
|
199
|
+
return dep();
|
|
200
|
+
}
|
|
201
|
+
return dep;
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
// ============================================================================
|
|
205
|
+
// STATE HOOKS
|
|
206
|
+
// ============================================================================
|
|
207
|
+
/**
|
|
208
|
+
* useState - Local component state.
|
|
209
|
+
*
|
|
210
|
+
* State persists across renders via fiber storage.
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* ```tsx
|
|
214
|
+
* // Old (deprecated):
|
|
215
|
+
* const [count, setCount] = useState(0);
|
|
216
|
+
*
|
|
217
|
+
* // New (recommended):
|
|
218
|
+
* const count = useSignal(0);
|
|
219
|
+
* count.set(10) or count.update(n => n + 1)
|
|
220
|
+
* ```
|
|
221
|
+
*/
|
|
222
|
+
export function useState(initialState) {
|
|
223
|
+
return useReducer((state, action) => typeof action === "function" ? action(state) : action, initialState, typeof initialState === "function" ? initialState : undefined);
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* useReducer - State with reducer pattern.
|
|
227
|
+
*/
|
|
228
|
+
export function useReducer(reducer, initialArg, init) {
|
|
229
|
+
const hook = mountOrUpdateHook(HookTag.Reducer);
|
|
230
|
+
const ctx = getCurrentContext();
|
|
231
|
+
const fiber = ctx.fiber;
|
|
232
|
+
// Capture scheduler at creation time to ensure concurrent compilations don't interfere
|
|
233
|
+
const scheduler = ctx.scheduleWork;
|
|
234
|
+
if (hook.queue === null) {
|
|
235
|
+
// Mount: initialize
|
|
236
|
+
// During hydration, memoizedState was already set from snapshot in mountWorkInProgressHook
|
|
237
|
+
// Only initialize if we don't have a hydrated value
|
|
238
|
+
const hasHydratedValue = ctx.isHydrating && hook.memoizedState !== undefined;
|
|
239
|
+
const initialState = hasHydratedValue
|
|
240
|
+
? hook.memoizedState
|
|
241
|
+
: init
|
|
242
|
+
? init(initialArg)
|
|
243
|
+
: initialArg;
|
|
244
|
+
hook.memoizedState = initialState;
|
|
245
|
+
hook.baseState = initialState;
|
|
246
|
+
const queue = {
|
|
247
|
+
pending: [], // Array instead of circular linked list - safer for concurrent dispatch
|
|
248
|
+
dispatch: null,
|
|
249
|
+
lastRenderedState: initialState,
|
|
250
|
+
};
|
|
251
|
+
hook.queue = queue;
|
|
252
|
+
const dispatch = (action) => {
|
|
253
|
+
dispatchAction(fiber, hook, queue, reducer, action, scheduler);
|
|
254
|
+
};
|
|
255
|
+
queue.dispatch = dispatch;
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
// Update: process pending updates
|
|
259
|
+
const queue = hook.queue;
|
|
260
|
+
let newState = hook.baseState;
|
|
261
|
+
// Process all pending updates from the array
|
|
262
|
+
if (queue.pending.length > 0) {
|
|
263
|
+
for (const update of queue.pending) {
|
|
264
|
+
newState = reducer(newState, update.action);
|
|
265
|
+
}
|
|
266
|
+
// Clear the queue after processing
|
|
267
|
+
queue.pending = [];
|
|
268
|
+
}
|
|
269
|
+
hook.memoizedState = newState;
|
|
270
|
+
queue.lastRenderedState = newState;
|
|
271
|
+
}
|
|
272
|
+
return [hook.memoizedState, hook.queue.dispatch];
|
|
273
|
+
}
|
|
274
|
+
function dispatchAction(fiber, hook, queue, reducer, action, scheduler) {
|
|
275
|
+
const update = { action };
|
|
276
|
+
// Array.push is atomic in JavaScript's single-threaded model,
|
|
277
|
+
// avoiding race conditions that could occur with circular linked list manipulation
|
|
278
|
+
// when multiple async operations dispatch concurrently.
|
|
279
|
+
queue.pending.push(update);
|
|
280
|
+
// Eagerly compute for bailout
|
|
281
|
+
const current = hook.memoizedState;
|
|
282
|
+
const newState = reducer(current, action);
|
|
283
|
+
if (Object.is(current, newState)) {
|
|
284
|
+
return; // Bailout
|
|
285
|
+
}
|
|
286
|
+
// Use the captured scheduler, not the global one
|
|
287
|
+
if (scheduler) {
|
|
288
|
+
scheduler(fiber);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* useSignal - Signal-based state in function components.
|
|
293
|
+
*
|
|
294
|
+
* Provides full signal API (not just [value, setter]).
|
|
295
|
+
* Automatically triggers recompiles when the signal is updated (like useState).
|
|
296
|
+
*
|
|
297
|
+
* @example
|
|
298
|
+
* ```tsx
|
|
299
|
+
* function Counter() {
|
|
300
|
+
* const count = useSignal(0);
|
|
301
|
+
* return <Text>Count: {count()}</Text>;
|
|
302
|
+
* }
|
|
303
|
+
* ```
|
|
304
|
+
*/
|
|
305
|
+
export function useSignal(initialValue) {
|
|
306
|
+
const hook = mountOrUpdateHook(HookTag.Signal);
|
|
307
|
+
const ctx = getCurrentContext();
|
|
308
|
+
const fiber = ctx.fiber;
|
|
309
|
+
// Capture scheduler at creation time to ensure concurrent compilations don't interfere
|
|
310
|
+
const scheduler = ctx.scheduleWork;
|
|
311
|
+
if (hook.memoizedState === undefined) {
|
|
312
|
+
const baseSignal = createSignal(initialValue);
|
|
313
|
+
// Wrap set and update to trigger recompiles (like useState)
|
|
314
|
+
// This makes useSignal behave consistently with useState for triggering renders
|
|
315
|
+
const originalSet = baseSignal.set;
|
|
316
|
+
const originalUpdate = baseSignal.update;
|
|
317
|
+
const wrappedSet = (value) => {
|
|
318
|
+
originalSet(value);
|
|
319
|
+
// Trigger recompile if we have a fiber and we're not in a phase that should skip
|
|
320
|
+
// Use the captured scheduler, not the global one
|
|
321
|
+
if (fiber && !shouldSkipRecompile() && scheduler) {
|
|
322
|
+
scheduler(fiber);
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
const wrappedUpdate = (updater) => {
|
|
326
|
+
originalUpdate(updater);
|
|
327
|
+
// Trigger recompile if we have a fiber and we're not in a phase that should skip
|
|
328
|
+
// Use the captured scheduler, not the global one
|
|
329
|
+
if (fiber && !shouldSkipRecompile() && scheduler) {
|
|
330
|
+
scheduler(fiber);
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
// Create wrapped signal with original signal's functionality
|
|
334
|
+
const wrappedSignal = baseSignal;
|
|
335
|
+
wrappedSignal.set = wrappedSet;
|
|
336
|
+
wrappedSignal.update = wrappedUpdate;
|
|
337
|
+
hook.memoizedState = wrappedSignal;
|
|
338
|
+
}
|
|
339
|
+
return hook.memoizedState;
|
|
340
|
+
}
|
|
341
|
+
// ============================================================================
|
|
342
|
+
// COM STATE HOOKS
|
|
343
|
+
// ============================================================================
|
|
344
|
+
/**
|
|
345
|
+
* useComState - COM-bound shared state.
|
|
346
|
+
*
|
|
347
|
+
* Returns a signal bound to COM state. State is shared across all components
|
|
348
|
+
* and persisted. Changes automatically trigger recompilation.
|
|
349
|
+
*
|
|
350
|
+
* @example
|
|
351
|
+
* ```tsx
|
|
352
|
+
* function Timeline() {
|
|
353
|
+
* const messages = useComState('timeline', []);
|
|
354
|
+
* return <Timeline>{messages().map(...)}</Timeline>;
|
|
355
|
+
* }
|
|
356
|
+
* ```
|
|
357
|
+
*/
|
|
358
|
+
export function useComState(key, initialValue) {
|
|
359
|
+
const hook = mountOrUpdateHook(HookTag.ComState);
|
|
360
|
+
const ctx = getCurrentContext();
|
|
361
|
+
if (hook.memoizedState === undefined) {
|
|
362
|
+
const signal = createCOMStateSignal(ctx.com, key, initialValue);
|
|
363
|
+
hook.memoizedState = signal;
|
|
364
|
+
// Cleanup on unmount
|
|
365
|
+
hook.effect = {
|
|
366
|
+
phase: EffectPhase.Unmount,
|
|
367
|
+
create: () => undefined,
|
|
368
|
+
destroy: () => signal.dispose(),
|
|
369
|
+
deps: null,
|
|
370
|
+
pending: false,
|
|
371
|
+
next: null,
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
// Safe to cast: initialValue is required, so T is never undefined
|
|
375
|
+
return hook.memoizedState;
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* useWatch - Read-only COM state observation.
|
|
379
|
+
* Returns a ReadonlySignal for reactive access to the watched state.
|
|
380
|
+
*
|
|
381
|
+
* @example
|
|
382
|
+
* ```tsx
|
|
383
|
+
* function StatusDisplay() {
|
|
384
|
+
* const status = useWatch('agentStatus', 'idle');
|
|
385
|
+
* return <Text>Status: {status()}</Text>;
|
|
386
|
+
* }
|
|
387
|
+
* ```
|
|
388
|
+
*/
|
|
389
|
+
export function useWatch(key, defaultValue) {
|
|
390
|
+
const hook = mountOrUpdateHook(HookTag.WatchState);
|
|
391
|
+
const ctx = getCurrentContext();
|
|
392
|
+
if (hook.memoizedState === undefined) {
|
|
393
|
+
const signal = createReadonlyCOMStateSignal(ctx.com, key, defaultValue);
|
|
394
|
+
hook.memoizedState = signal;
|
|
395
|
+
hook.effect = {
|
|
396
|
+
phase: EffectPhase.Unmount,
|
|
397
|
+
create: () => undefined,
|
|
398
|
+
destroy: () => signal.dispose(),
|
|
399
|
+
deps: null,
|
|
400
|
+
pending: false,
|
|
401
|
+
next: null,
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
return hook.memoizedState;
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* useInput - Reactive prop access with default value.
|
|
408
|
+
*/
|
|
409
|
+
export function useInput(propKey, defaultValue) {
|
|
410
|
+
const ctx = getCurrentContext();
|
|
411
|
+
const value = ctx.fiber.props[propKey];
|
|
412
|
+
return (value !== undefined ? value : defaultValue);
|
|
413
|
+
}
|
|
414
|
+
// ============================================================================
|
|
415
|
+
// EFFECT HOOKS
|
|
416
|
+
// ============================================================================
|
|
417
|
+
/**
|
|
418
|
+
* useEffect - Side effect after commit.
|
|
419
|
+
*
|
|
420
|
+
* Unlike React, callback CAN be async.
|
|
421
|
+
* Signals/computed values in deps array are automatically unwrapped.
|
|
422
|
+
*
|
|
423
|
+
* During hydration:
|
|
424
|
+
* - Mount effects (deps = []) are skipped (state is restored, not fresh)
|
|
425
|
+
* - Effects with deps still run (deps might have changed since snapshot)
|
|
426
|
+
*
|
|
427
|
+
* @example
|
|
428
|
+
* ```tsx
|
|
429
|
+
* function Logger() {
|
|
430
|
+
* const message = useComState('message', '');
|
|
431
|
+
*
|
|
432
|
+
* useEffect(async () => {
|
|
433
|
+
* await logToServer(message()); // Read signal value
|
|
434
|
+
* return () => console.log('cleanup');
|
|
435
|
+
* }, [message]); // Signal auto-tracked by value
|
|
436
|
+
* }
|
|
437
|
+
* ```
|
|
438
|
+
*/
|
|
439
|
+
export function useEffect(create, deps) {
|
|
440
|
+
const hook = mountOrUpdateHook(HookTag.Effect);
|
|
441
|
+
const ctx = getCurrentContext();
|
|
442
|
+
// Unwrap signals in deps for comparison
|
|
443
|
+
const unwrappedDeps = unwrapDeps(deps);
|
|
444
|
+
// Check if this is a mount effect during hydration
|
|
445
|
+
// Mount effects have empty deps array: useEffect(fn, [])
|
|
446
|
+
const isMountEffect = Array.isArray(unwrappedDeps) && unwrappedDeps.length === 0;
|
|
447
|
+
const isHydratingMount = ctx.isHydrating && isMountEffect && hook.effect === null;
|
|
448
|
+
// Skip mount effects during hydration - state is restored, not fresh
|
|
449
|
+
if (isHydratingMount) {
|
|
450
|
+
// Still create the effect structure but mark as not pending
|
|
451
|
+
hook.effect = {
|
|
452
|
+
phase: EffectPhase.Commit,
|
|
453
|
+
create,
|
|
454
|
+
destroy: null,
|
|
455
|
+
deps: unwrappedDeps ?? null,
|
|
456
|
+
pending: false, // Don't run during hydration
|
|
457
|
+
next: null,
|
|
458
|
+
};
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
const hasDepsChanged = hook.effect === null ||
|
|
462
|
+
unwrappedDeps === undefined ||
|
|
463
|
+
unwrappedDeps === null ||
|
|
464
|
+
!areHookInputsEqual(unwrappedDeps, hook.effect.deps);
|
|
465
|
+
if (hasDepsChanged) {
|
|
466
|
+
hook.effect = {
|
|
467
|
+
phase: EffectPhase.Commit,
|
|
468
|
+
create,
|
|
469
|
+
destroy: hook.effect?.destroy ?? null,
|
|
470
|
+
deps: unwrappedDeps ?? null,
|
|
471
|
+
pending: true,
|
|
472
|
+
next: null,
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* useInit - Component initialization that runs once on mount.
|
|
478
|
+
* Can be async and should be awaited if it returns a Promise.
|
|
479
|
+
* Runs DURING render, blocking until complete.
|
|
480
|
+
*
|
|
481
|
+
* Use for: loading initial data, setting up state before first render
|
|
482
|
+
*
|
|
483
|
+
* @example
|
|
484
|
+
* ```tsx
|
|
485
|
+
* function MyComponent() {
|
|
486
|
+
* const data = useComState('data', []);
|
|
487
|
+
*
|
|
488
|
+
* await useInit(async (com, state) => {
|
|
489
|
+
* const initialData = await loadData();
|
|
490
|
+
* data.set(initialData);
|
|
491
|
+
* });
|
|
492
|
+
*
|
|
493
|
+
* return <Section>{data().map(...)}</Section>;
|
|
494
|
+
* }
|
|
495
|
+
* ```
|
|
496
|
+
*/
|
|
497
|
+
export async function useInit(callback) {
|
|
498
|
+
const ctx = getCurrentContext();
|
|
499
|
+
const hook = mountOrUpdateHook(HookTag.Memo);
|
|
500
|
+
if (hook.memoizedState === undefined) {
|
|
501
|
+
const result = callback(ctx.com, ctx.tickState);
|
|
502
|
+
const promise = result instanceof Promise ? result : Promise.resolve();
|
|
503
|
+
hook.memoizedState = promise;
|
|
504
|
+
await promise;
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
// Already initialized - return cached promise
|
|
508
|
+
(await hook.memoizedState);
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* useOnMount - Run once when component mounts as a side effect.
|
|
512
|
+
* Runs AFTER render (as an effect), does not block rendering.
|
|
513
|
+
* Use for non-critical side effects like logging, analytics.
|
|
514
|
+
*
|
|
515
|
+
* For blocking initialization, use `useInit` instead.
|
|
516
|
+
*
|
|
517
|
+
* @example
|
|
518
|
+
* ```tsx
|
|
519
|
+
* function MyComponent() {
|
|
520
|
+
* useOnMount((com) => {
|
|
521
|
+
* log.info('Component mounted');
|
|
522
|
+
* });
|
|
523
|
+
* return <Text>Hello</Text>;
|
|
524
|
+
* }
|
|
525
|
+
* ```
|
|
526
|
+
*/
|
|
527
|
+
export function useOnMount(callback) {
|
|
528
|
+
const ctx = getCurrentContext();
|
|
529
|
+
useEffect(() => {
|
|
530
|
+
callback(ctx.com);
|
|
531
|
+
}, []);
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* useOnUnmount - Run once when component unmounts.
|
|
535
|
+
*/
|
|
536
|
+
export function useOnUnmount(callback) {
|
|
537
|
+
const ctx = getCurrentContext();
|
|
538
|
+
useEffect(() => {
|
|
539
|
+
return () => callback(ctx.com);
|
|
540
|
+
}, []);
|
|
541
|
+
}
|
|
542
|
+
/**
|
|
543
|
+
* useTickStart - Run at start of each tick, before render.
|
|
544
|
+
*
|
|
545
|
+
* @deprecated Use `useOnTickStart` instead for consistent naming convention.
|
|
546
|
+
*/
|
|
547
|
+
export function useTickStart(callback) {
|
|
548
|
+
const hook = mountOrUpdateHook(HookTag.TickStart);
|
|
549
|
+
const ctx = getCurrentContext();
|
|
550
|
+
// Always pending - runs every tick
|
|
551
|
+
hook.effect = {
|
|
552
|
+
phase: EffectPhase.TickStart,
|
|
553
|
+
create: () => callback(ctx.com, ctx.tickState),
|
|
554
|
+
destroy: null,
|
|
555
|
+
deps: null,
|
|
556
|
+
pending: true,
|
|
557
|
+
next: null,
|
|
558
|
+
};
|
|
559
|
+
hook.memoizedState = callback;
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* useTickEnd - Run at end of each tick, after model execution.
|
|
563
|
+
*
|
|
564
|
+
* @deprecated Use `useOnTickEnd` instead for consistent naming convention.
|
|
565
|
+
*/
|
|
566
|
+
export function useTickEnd(callback) {
|
|
567
|
+
const hook = mountOrUpdateHook(HookTag.TickEnd);
|
|
568
|
+
const ctx = getCurrentContext();
|
|
569
|
+
hook.effect = {
|
|
570
|
+
phase: EffectPhase.TickEnd,
|
|
571
|
+
create: () => callback(ctx.com, ctx.tickState),
|
|
572
|
+
destroy: null,
|
|
573
|
+
deps: null,
|
|
574
|
+
pending: true,
|
|
575
|
+
next: null,
|
|
576
|
+
};
|
|
577
|
+
hook.memoizedState = callback;
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* useAfterCompile - Run after compile, can request recompile.
|
|
581
|
+
*
|
|
582
|
+
* @deprecated Use `useOnAfterCompile` instead for consistent naming convention.
|
|
583
|
+
*/
|
|
584
|
+
export function useAfterCompile(callback) {
|
|
585
|
+
const hook = mountOrUpdateHook(HookTag.AfterCompile);
|
|
586
|
+
const _ctx = getCurrentContext();
|
|
587
|
+
// Store callback and create effect
|
|
588
|
+
hook.memoizedState = callback;
|
|
589
|
+
hook.effect = {
|
|
590
|
+
phase: EffectPhase.AfterCompile,
|
|
591
|
+
create: () => {
|
|
592
|
+
// Will be called by compiler with compiled structure
|
|
593
|
+
return undefined;
|
|
594
|
+
},
|
|
595
|
+
destroy: null,
|
|
596
|
+
deps: null,
|
|
597
|
+
pending: true,
|
|
598
|
+
next: null,
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* useOnMessage - Handle execution messages.
|
|
603
|
+
*
|
|
604
|
+
* Called immediately when messages are sent to the running execution via:
|
|
605
|
+
* - RuntimeSession.sendMessage() - Direct programmatic injection
|
|
606
|
+
* - ExecutionHandle.send() - Via handle reference
|
|
607
|
+
* - Channel events with type='message' - From client
|
|
608
|
+
*
|
|
609
|
+
* Messages are processed immediately when they arrive, not at tick boundaries.
|
|
610
|
+
* Use com.abort() to interrupt execution if needed, or update state for the next tick.
|
|
611
|
+
* Messages are also available in TickState.queuedMessages during render.
|
|
612
|
+
*
|
|
613
|
+
* @example
|
|
614
|
+
* ```tsx
|
|
615
|
+
* function InteractiveAgent() {
|
|
616
|
+
* const feedback = useComState('userFeedback', []);
|
|
617
|
+
*
|
|
618
|
+
* useOnMessage((com, message, state) => {
|
|
619
|
+
* if (message.type === 'stop') {
|
|
620
|
+
* com.abort('User requested stop');
|
|
621
|
+
* } else if (message.type === 'feedback') {
|
|
622
|
+
* feedback.update(f => [...f, message.content]);
|
|
623
|
+
* }
|
|
624
|
+
* });
|
|
625
|
+
*
|
|
626
|
+
* return <Section>{feedback().map(f => <Paragraph>{f}</Paragraph>)}</Section>;
|
|
627
|
+
* }
|
|
628
|
+
* ```
|
|
629
|
+
*/
|
|
630
|
+
export function useOnMessage(callback) {
|
|
631
|
+
const hook = mountOrUpdateHook(HookTag.OnMessage);
|
|
632
|
+
// Store the latest callback in memoizedState
|
|
633
|
+
// This will be retrieved and called by notifyOnMessage in FiberCompiler
|
|
634
|
+
hook.memoizedState = callback;
|
|
635
|
+
// Mark with OnMessage tag for identification during traversal
|
|
636
|
+
hook.effect = {
|
|
637
|
+
phase: EffectPhase.OnMessage,
|
|
638
|
+
create: () => undefined, // Will be called dynamically with message
|
|
639
|
+
destroy: null,
|
|
640
|
+
deps: null,
|
|
641
|
+
pending: false, // Not pending by default - only runs when message arrives
|
|
642
|
+
next: null,
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
// ============================================================================
|
|
646
|
+
// LIFECYCLE HOOKS (useOn* naming convention)
|
|
647
|
+
// ============================================================================
|
|
648
|
+
/**
|
|
649
|
+
* useOnTickStart - Run at start of each tick, before render.
|
|
650
|
+
*
|
|
651
|
+
* This is the canonical hook for tick-start lifecycle.
|
|
652
|
+
* Alias: useTickStart (deprecated, prefer useOnTickStart)
|
|
653
|
+
*
|
|
654
|
+
* @example
|
|
655
|
+
* ```tsx
|
|
656
|
+
* function MyAgent() {
|
|
657
|
+
* useOnTickStart((com, state) => {
|
|
658
|
+
* console.log(`Starting tick ${state.tick}`);
|
|
659
|
+
* });
|
|
660
|
+
* return <System>You are helpful.</System>;
|
|
661
|
+
* }
|
|
662
|
+
* ```
|
|
663
|
+
*/
|
|
664
|
+
export function useOnTickStart(callback) {
|
|
665
|
+
useTickStart(callback);
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* useOnAfterRender - Run after render/reconciliation, before compile.
|
|
669
|
+
*
|
|
670
|
+
* Use this to inspect or modify state after all components have rendered
|
|
671
|
+
* but before the context is compiled and sent to the model.
|
|
672
|
+
*
|
|
673
|
+
* @example
|
|
674
|
+
* ```tsx
|
|
675
|
+
* function MyAgent() {
|
|
676
|
+
* useOnAfterRender((com, state) => {
|
|
677
|
+
* console.log('Components rendered, about to compile');
|
|
678
|
+
* });
|
|
679
|
+
* return <System>You are helpful.</System>;
|
|
680
|
+
* }
|
|
681
|
+
* ```
|
|
682
|
+
*/
|
|
683
|
+
export function useOnAfterRender(callback) {
|
|
684
|
+
const hook = mountOrUpdateHook(HookTag.AfterRender);
|
|
685
|
+
const ctx = getCurrentContext();
|
|
686
|
+
hook.effect = {
|
|
687
|
+
phase: EffectPhase.AfterRender,
|
|
688
|
+
create: () => callback(ctx.com, ctx.tickState),
|
|
689
|
+
destroy: null,
|
|
690
|
+
deps: null,
|
|
691
|
+
pending: true,
|
|
692
|
+
next: null,
|
|
693
|
+
};
|
|
694
|
+
hook.memoizedState = callback;
|
|
695
|
+
}
|
|
696
|
+
/**
|
|
697
|
+
* useOnAfterCompile - Run after compile, before model call.
|
|
698
|
+
*
|
|
699
|
+
* Use this to transform the compiled structure before it's sent to the model.
|
|
700
|
+
* Can request recompile if needed.
|
|
701
|
+
*
|
|
702
|
+
* This is the canonical hook for after-compile lifecycle.
|
|
703
|
+
* Alias: useAfterCompile (deprecated, prefer useOnAfterCompile)
|
|
704
|
+
*
|
|
705
|
+
* @example
|
|
706
|
+
* ```tsx
|
|
707
|
+
* function MyAgent() {
|
|
708
|
+
* useOnAfterCompile((com, compiled, state) => {
|
|
709
|
+
* // Inspect or modify compiled structure
|
|
710
|
+
* if (compiled.tokenCount > 10000) {
|
|
711
|
+
* // Request summarization
|
|
712
|
+
* }
|
|
713
|
+
* });
|
|
714
|
+
* return <System>You are helpful.</System>;
|
|
715
|
+
* }
|
|
716
|
+
* ```
|
|
717
|
+
*/
|
|
718
|
+
export function useOnAfterCompile(callback) {
|
|
719
|
+
useAfterCompile(callback);
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* useOnTickEnd - Run at end of each tick, after model execution.
|
|
723
|
+
*
|
|
724
|
+
* Use this to process model response, decide on continuation, or clean up.
|
|
725
|
+
*
|
|
726
|
+
* This is the canonical hook for tick-end lifecycle.
|
|
727
|
+
* Alias: useTickEnd (deprecated, prefer useOnTickEnd)
|
|
728
|
+
*
|
|
729
|
+
* @example
|
|
730
|
+
* ```tsx
|
|
731
|
+
* function MyAgent() {
|
|
732
|
+
* useOnTickEnd((com, state) => {
|
|
733
|
+
* console.log(`Tick ${state.tick} complete`);
|
|
734
|
+
* if (state.response?.stopReason === 'stop') {
|
|
735
|
+
* com.complete();
|
|
736
|
+
* }
|
|
737
|
+
* });
|
|
738
|
+
* return <System>You are helpful.</System>;
|
|
739
|
+
* }
|
|
740
|
+
* ```
|
|
741
|
+
*/
|
|
742
|
+
export function useOnTickEnd(callback) {
|
|
743
|
+
useTickEnd(callback);
|
|
744
|
+
}
|
|
745
|
+
/**
|
|
746
|
+
* useOnComplete - Run when session completes (all ticks done).
|
|
747
|
+
*
|
|
748
|
+
* This hook runs once when the session finishes execution, whether through:
|
|
749
|
+
* - Natural completion (shouldContinue returns false)
|
|
750
|
+
* - Explicit com.complete() call
|
|
751
|
+
* - Error or abort
|
|
752
|
+
*
|
|
753
|
+
* Use this for cleanup, final logging, or teardown logic.
|
|
754
|
+
*
|
|
755
|
+
* @example
|
|
756
|
+
* ```tsx
|
|
757
|
+
* function MyAgent() {
|
|
758
|
+
* useOnComplete((com, state) => {
|
|
759
|
+
* console.log(`Session complete after ${state.tick} ticks`);
|
|
760
|
+
* // Cleanup resources
|
|
761
|
+
* });
|
|
762
|
+
* return <System>You are helpful.</System>;
|
|
763
|
+
* }
|
|
764
|
+
* ```
|
|
765
|
+
*/
|
|
766
|
+
export function useOnComplete(callback) {
|
|
767
|
+
const hook = mountOrUpdateHook(HookTag.Complete);
|
|
768
|
+
const ctx = getCurrentContext();
|
|
769
|
+
hook.effect = {
|
|
770
|
+
phase: EffectPhase.Complete,
|
|
771
|
+
create: () => callback(ctx.com, ctx.tickState),
|
|
772
|
+
destroy: null,
|
|
773
|
+
deps: null,
|
|
774
|
+
pending: true,
|
|
775
|
+
next: null,
|
|
776
|
+
};
|
|
777
|
+
hook.memoizedState = callback;
|
|
778
|
+
}
|
|
779
|
+
// ============================================================================
|
|
780
|
+
// ASYNC HOOKS
|
|
781
|
+
// ============================================================================
|
|
782
|
+
/**
|
|
783
|
+
* useAsync - Async data fetching.
|
|
784
|
+
*
|
|
785
|
+
* Unlike React (which needs Suspense), we just track loading state.
|
|
786
|
+
* The tick can wait for async work to complete.
|
|
787
|
+
*
|
|
788
|
+
* @example
|
|
789
|
+
* ```tsx
|
|
790
|
+
* function UserProfile({ userId }) {
|
|
791
|
+
* const { data: user, loading, error } = useAsync(
|
|
792
|
+
* () => fetchUser(userId),
|
|
793
|
+
* [userId]
|
|
794
|
+
* );
|
|
795
|
+
*
|
|
796
|
+
* if (loading) return null;
|
|
797
|
+
* if (error) return <Text>Error: {error.message}</Text>;
|
|
798
|
+
*
|
|
799
|
+
* return <Text>User: {user.name}</Text>;
|
|
800
|
+
* }
|
|
801
|
+
* ```
|
|
802
|
+
*/
|
|
803
|
+
export function useAsync(asyncFn, deps) {
|
|
804
|
+
const [state, setState] = useState({
|
|
805
|
+
data: undefined,
|
|
806
|
+
loading: true,
|
|
807
|
+
error: undefined,
|
|
808
|
+
});
|
|
809
|
+
// Track if deps changed
|
|
810
|
+
const prevDeps = useRef(null);
|
|
811
|
+
const depsChanged = prevDeps.current === null || !areHookInputsEqual(deps, prevDeps.current);
|
|
812
|
+
prevDeps.current = deps;
|
|
813
|
+
// Only trigger on deps change
|
|
814
|
+
if (depsChanged && state.loading === false) {
|
|
815
|
+
setState({ data: undefined, loading: true, error: undefined });
|
|
816
|
+
}
|
|
817
|
+
useEffect(() => {
|
|
818
|
+
let cancelled = false;
|
|
819
|
+
asyncFn()
|
|
820
|
+
.then((data) => {
|
|
821
|
+
if (!cancelled) {
|
|
822
|
+
setState({ data, loading: false, error: undefined });
|
|
823
|
+
}
|
|
824
|
+
})
|
|
825
|
+
.catch((error) => {
|
|
826
|
+
if (!cancelled) {
|
|
827
|
+
setState({ data: undefined, loading: false, error });
|
|
828
|
+
}
|
|
829
|
+
});
|
|
830
|
+
return () => {
|
|
831
|
+
cancelled = true;
|
|
832
|
+
};
|
|
833
|
+
}, deps);
|
|
834
|
+
return state;
|
|
835
|
+
}
|
|
836
|
+
// ============================================================================
|
|
837
|
+
// MEMOIZATION HOOKS
|
|
838
|
+
// ============================================================================
|
|
839
|
+
/**
|
|
840
|
+
* useMemo - Memoize expensive computation.
|
|
841
|
+
*/
|
|
842
|
+
export function useMemo(factory, deps) {
|
|
843
|
+
const hook = mountOrUpdateHook(HookTag.Memo);
|
|
844
|
+
const memoState = hook.memoizedState;
|
|
845
|
+
const prevDeps = memoState?.[1];
|
|
846
|
+
if (prevDeps !== undefined && areHookInputsEqual(deps, prevDeps)) {
|
|
847
|
+
return memoState[0];
|
|
848
|
+
}
|
|
849
|
+
const value = factory();
|
|
850
|
+
hook.memoizedState = [value, deps];
|
|
851
|
+
return value;
|
|
852
|
+
}
|
|
853
|
+
/**
|
|
854
|
+
* useComputed - Create a reactive computed signal that persists across renders.
|
|
855
|
+
*
|
|
856
|
+
* Unlike useMemo which returns a plain value, useComputed returns a ComputedSignal
|
|
857
|
+
* that automatically tracks dependencies and updates when they change.
|
|
858
|
+
* The computed signal is disposed and recreated only when deps change.
|
|
859
|
+
*
|
|
860
|
+
* @example
|
|
861
|
+
* ```typescript
|
|
862
|
+
* const timeline = useComState('timeline', []);
|
|
863
|
+
* const recentMessages = useComputed(() => timeline().slice(-10), []);
|
|
864
|
+
*
|
|
865
|
+
* // Read the computed value
|
|
866
|
+
* const messages = recentMessages(); // or recentMessages.value
|
|
867
|
+
* ```
|
|
868
|
+
*/
|
|
869
|
+
export function useComputed(computation, deps = []) {
|
|
870
|
+
const hook = mountOrUpdateHook(HookTag.Memo);
|
|
871
|
+
const memoState = hook.memoizedState;
|
|
872
|
+
const prevDeps = memoState?.[1];
|
|
873
|
+
// If deps haven't changed, return existing computed
|
|
874
|
+
if (prevDeps !== undefined && areHookInputsEqual(deps, prevDeps)) {
|
|
875
|
+
return memoState[0];
|
|
876
|
+
}
|
|
877
|
+
// Deps changed or first render - dispose old computed if it exists
|
|
878
|
+
if (memoState?.[0]) {
|
|
879
|
+
memoState[0].dispose();
|
|
880
|
+
}
|
|
881
|
+
// Create new computed signal
|
|
882
|
+
const computedSignal = computed(computation);
|
|
883
|
+
hook.memoizedState = [computedSignal, deps];
|
|
884
|
+
return computedSignal;
|
|
885
|
+
}
|
|
886
|
+
/**
|
|
887
|
+
* useCallback - Memoize callback function.
|
|
888
|
+
*/
|
|
889
|
+
export function useCallback(callback, deps) {
|
|
890
|
+
return useMemo(() => callback, deps);
|
|
891
|
+
}
|
|
892
|
+
// ============================================================================
|
|
893
|
+
// REF HOOKS
|
|
894
|
+
// ============================================================================
|
|
895
|
+
/**
|
|
896
|
+
* useRef - Mutable ref that persists across renders.
|
|
897
|
+
*/
|
|
898
|
+
export function useRef(initialValue) {
|
|
899
|
+
const hook = mountOrUpdateHook(HookTag.Ref);
|
|
900
|
+
const ctx = getCurrentContext();
|
|
901
|
+
if (hook.memoizedState === undefined) {
|
|
902
|
+
hook.memoizedState = { current: initialValue };
|
|
903
|
+
}
|
|
904
|
+
else if (ctx.isHydrating &&
|
|
905
|
+
hook.memoizedState !== null &&
|
|
906
|
+
typeof hook.memoizedState !== "object") {
|
|
907
|
+
// During hydration, memoizedState is the raw value (not wrapped in { current })
|
|
908
|
+
// because serialization extracts ref.current for JSON serialization
|
|
909
|
+
hook.memoizedState = { current: hook.memoizedState };
|
|
910
|
+
}
|
|
911
|
+
return hook.memoizedState;
|
|
912
|
+
}
|
|
913
|
+
/**
|
|
914
|
+
* useCOMRef - Get component ref from COM.
|
|
915
|
+
*/
|
|
916
|
+
export function useCOMRef(refName) {
|
|
917
|
+
const ctx = getCurrentContext();
|
|
918
|
+
return ctx.com.getRef(refName);
|
|
919
|
+
}
|
|
920
|
+
// ============================================================================
|
|
921
|
+
// UTILITY HOOKS
|
|
922
|
+
// ============================================================================
|
|
923
|
+
/**
|
|
924
|
+
* usePrevious - Track previous value.
|
|
925
|
+
*/
|
|
926
|
+
export function usePrevious(value) {
|
|
927
|
+
const ref = useRef(undefined);
|
|
928
|
+
const previous = ref.current;
|
|
929
|
+
// Update ref during render (not in effect) so it's ready for next render
|
|
930
|
+
ref.current = value;
|
|
931
|
+
return previous;
|
|
932
|
+
}
|
|
933
|
+
/**
|
|
934
|
+
* useToggle - Boolean toggle state.
|
|
935
|
+
*/
|
|
936
|
+
export function useToggle(initial = false) {
|
|
937
|
+
const sig = useSignal(initial);
|
|
938
|
+
const toggle = useCallback(() => sig.set((v) => !v), []);
|
|
939
|
+
return [sig(), toggle];
|
|
940
|
+
}
|
|
941
|
+
/**
|
|
942
|
+
* useCounter - Numeric counter.
|
|
943
|
+
*/
|
|
944
|
+
export function useCounter(initial = 0) {
|
|
945
|
+
const [count, setCount] = useState(initial);
|
|
946
|
+
return {
|
|
947
|
+
count,
|
|
948
|
+
increment: useCallback(() => setCount((c) => c + 1), []),
|
|
949
|
+
decrement: useCallback(() => setCount((c) => c - 1), []),
|
|
950
|
+
set: setCount,
|
|
951
|
+
reset: useCallback(() => setCount(initial), [initial]),
|
|
952
|
+
};
|
|
953
|
+
}
|
|
954
|
+
/**
|
|
955
|
+
* useAbortSignal - Get abort signal for current execution.
|
|
956
|
+
*/
|
|
957
|
+
export function useAbortSignal() {
|
|
958
|
+
const ctx = getCurrentContext();
|
|
959
|
+
return ctx.abortSignal;
|
|
960
|
+
}
|
|
961
|
+
/**
|
|
962
|
+
* useDebugValue - Display value in devtools (no-op in production).
|
|
963
|
+
*/
|
|
964
|
+
export function useDebugValue(value, formatter) {
|
|
965
|
+
if (process.env["NODE_ENV"] === "development") {
|
|
966
|
+
const ctx = getCurrentContext();
|
|
967
|
+
ctx.fiber.debugName = String(formatter ? formatter(value) : value);
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
/**
|
|
971
|
+
* useTick - Control tick execution from within a component.
|
|
972
|
+
*
|
|
973
|
+
* This hook provides components with the ability to:
|
|
974
|
+
* - Request a new tick (e.g., when enough messages have accumulated)
|
|
975
|
+
* - Cancel a pending tick request
|
|
976
|
+
* - Check the current tick status
|
|
977
|
+
* - Get the total tick count
|
|
978
|
+
*
|
|
979
|
+
* @example
|
|
980
|
+
* ```tsx
|
|
981
|
+
* function BatchProcessor() {
|
|
982
|
+
* const { requestTick, tickStatus, tickCount } = useTick();
|
|
983
|
+
* const queue = useComState('queue', []);
|
|
984
|
+
*
|
|
985
|
+
* useEffect(() => {
|
|
986
|
+
* // Request tick when queue has 10 items and we're idle
|
|
987
|
+
* if (queue().length >= 10 && tickStatus === 'idle') {
|
|
988
|
+
* requestTick();
|
|
989
|
+
* }
|
|
990
|
+
* }, [queue().length, tickStatus]);
|
|
991
|
+
*
|
|
992
|
+
* return <Section>Tick {tickCount}: {queue().length} items</Section>;
|
|
993
|
+
* }
|
|
994
|
+
* ```
|
|
995
|
+
*/
|
|
996
|
+
export function useTick() {
|
|
997
|
+
const ctx = getCurrentContext();
|
|
998
|
+
const tickControl = ctx.tickControl;
|
|
999
|
+
const tickState = ctx.tickState;
|
|
1000
|
+
// Default values if tick control not available
|
|
1001
|
+
const defaultResult = {
|
|
1002
|
+
requestTick: () => {
|
|
1003
|
+
if (process.env["NODE_ENV"] === "development") {
|
|
1004
|
+
console.warn("[useTick] requestTick called but tickControl not available. " +
|
|
1005
|
+
"Tick control is only available when running in a Session context.");
|
|
1006
|
+
}
|
|
1007
|
+
},
|
|
1008
|
+
cancelTick: () => {
|
|
1009
|
+
if (process.env["NODE_ENV"] === "development") {
|
|
1010
|
+
console.warn("[useTick] cancelTick called but tickControl not available. " +
|
|
1011
|
+
"Tick control is only available when running in a Session context.");
|
|
1012
|
+
}
|
|
1013
|
+
},
|
|
1014
|
+
tickStatus: "idle",
|
|
1015
|
+
tickCount: tickState?.tick ?? 0,
|
|
1016
|
+
};
|
|
1017
|
+
if (!tickControl) {
|
|
1018
|
+
return defaultResult;
|
|
1019
|
+
}
|
|
1020
|
+
return {
|
|
1021
|
+
requestTick: tickControl.requestTick,
|
|
1022
|
+
cancelTick: tickControl.cancelTick,
|
|
1023
|
+
tickStatus: tickControl.status,
|
|
1024
|
+
tickCount: tickControl.tickCount,
|
|
1025
|
+
};
|
|
1026
|
+
}
|
|
1027
|
+
/**
|
|
1028
|
+
* useChannel - Access a named channel for pub/sub communication.
|
|
1029
|
+
*
|
|
1030
|
+
* Channels enable real-time bidirectional communication between components
|
|
1031
|
+
* and external code (UI, other services, etc.).
|
|
1032
|
+
*
|
|
1033
|
+
* Use cases:
|
|
1034
|
+
* - User input during model execution (e.g., stop button, feedback)
|
|
1035
|
+
* - Progress updates to UI
|
|
1036
|
+
* - Tool confirmation dialogs
|
|
1037
|
+
* - Live data streaming to/from external systems
|
|
1038
|
+
*
|
|
1039
|
+
* @param name - Channel name (e.g., 'progress', 'user-input', 'tool:confirm')
|
|
1040
|
+
* @returns Channel access object with subscribe, publish, and waitForResponse
|
|
1041
|
+
*
|
|
1042
|
+
* @example Basic subscription
|
|
1043
|
+
* ```tsx
|
|
1044
|
+
* function ProgressDisplay() {
|
|
1045
|
+
* const [progress, setProgress] = useState(0);
|
|
1046
|
+
* const { subscribe, available } = useChannel('progress');
|
|
1047
|
+
*
|
|
1048
|
+
* useEffect(() => {
|
|
1049
|
+
* if (!available) return;
|
|
1050
|
+
* return subscribe((event) => {
|
|
1051
|
+
* if (event.type === 'update') {
|
|
1052
|
+
* setProgress(event.payload.percent);
|
|
1053
|
+
* }
|
|
1054
|
+
* });
|
|
1055
|
+
* }, [available]);
|
|
1056
|
+
*
|
|
1057
|
+
* return <Section>Progress: {progress}%</Section>;
|
|
1058
|
+
* }
|
|
1059
|
+
* ```
|
|
1060
|
+
*
|
|
1061
|
+
* @example Request/Response pattern
|
|
1062
|
+
* ```tsx
|
|
1063
|
+
* function ConfirmationHandler() {
|
|
1064
|
+
* const { publish, waitForResponse, available } = useChannel('confirmations');
|
|
1065
|
+
*
|
|
1066
|
+
* const confirmAction = async (action: string) => {
|
|
1067
|
+
* if (!available) throw new Error('Channels not available');
|
|
1068
|
+
*
|
|
1069
|
+
* const requestId = crypto.randomUUID();
|
|
1070
|
+
* publish({ type: 'request', id: requestId, payload: { action } });
|
|
1071
|
+
* const response = await waitForResponse(requestId, 30000);
|
|
1072
|
+
* return response.payload.confirmed;
|
|
1073
|
+
* };
|
|
1074
|
+
*
|
|
1075
|
+
* // ...
|
|
1076
|
+
* }
|
|
1077
|
+
* ```
|
|
1078
|
+
*
|
|
1079
|
+
* @example Publishing events
|
|
1080
|
+
* ```tsx
|
|
1081
|
+
* function StatusReporter() {
|
|
1082
|
+
* const { publish } = useChannel('status');
|
|
1083
|
+
*
|
|
1084
|
+
* useTickEnd(() => {
|
|
1085
|
+
* publish({ type: 'tick-complete', payload: { timestamp: Date.now() } });
|
|
1086
|
+
* });
|
|
1087
|
+
*
|
|
1088
|
+
* return null;
|
|
1089
|
+
* }
|
|
1090
|
+
* ```
|
|
1091
|
+
*/
|
|
1092
|
+
export function useChannel(name) {
|
|
1093
|
+
const ctx = getCurrentContext();
|
|
1094
|
+
// Get the channel from the context (provided by Session)
|
|
1095
|
+
const channel = ctx.getChannel?.(name);
|
|
1096
|
+
// Default no-op implementations for when channels aren't available
|
|
1097
|
+
const unavailableResult = {
|
|
1098
|
+
channel: undefined,
|
|
1099
|
+
subscribe: () => {
|
|
1100
|
+
if (process.env["NODE_ENV"] === "development") {
|
|
1101
|
+
console.warn(`[useChannel] subscribe called on '${name}' but channel service not available. ` +
|
|
1102
|
+
"Channels require ChannelService configuration.");
|
|
1103
|
+
}
|
|
1104
|
+
return () => { }; // No-op unsubscribe
|
|
1105
|
+
},
|
|
1106
|
+
publish: () => {
|
|
1107
|
+
if (process.env["NODE_ENV"] === "development") {
|
|
1108
|
+
console.warn(`[useChannel] publish called on '${name}' but channel service not available. ` +
|
|
1109
|
+
"Channels require ChannelService configuration.");
|
|
1110
|
+
}
|
|
1111
|
+
},
|
|
1112
|
+
waitForResponse: () => {
|
|
1113
|
+
return Promise.reject(new Error(`Channel '${name}' not available. Channels require ChannelService configuration.`));
|
|
1114
|
+
},
|
|
1115
|
+
available: false,
|
|
1116
|
+
};
|
|
1117
|
+
if (!channel) {
|
|
1118
|
+
return unavailableResult;
|
|
1119
|
+
}
|
|
1120
|
+
return {
|
|
1121
|
+
channel,
|
|
1122
|
+
subscribe: (handler) => channel.subscribe(handler),
|
|
1123
|
+
publish: (event) => channel.publish({ ...event, channel: name }),
|
|
1124
|
+
waitForResponse: (requestId, timeoutMs) => channel.waitForResponse(requestId, timeoutMs),
|
|
1125
|
+
available: true,
|
|
1126
|
+
};
|
|
1127
|
+
}
|
|
1128
|
+
/**
|
|
1129
|
+
* useChannelSubscription - Subscribe to channel events with automatic cleanup.
|
|
1130
|
+
*
|
|
1131
|
+
* This is a convenience hook that combines useChannel with useEffect for
|
|
1132
|
+
* the common pattern of subscribing to a channel and cleaning up on unmount.
|
|
1133
|
+
*
|
|
1134
|
+
* @param name - Channel name
|
|
1135
|
+
* @param handler - Event handler function
|
|
1136
|
+
* @param deps - Dependencies array (handler is only updated when deps change)
|
|
1137
|
+
*
|
|
1138
|
+
* @example
|
|
1139
|
+
* ```tsx
|
|
1140
|
+
* function NotificationListener() {
|
|
1141
|
+
* const [notifications, setNotifications] = useState<string[]>([]);
|
|
1142
|
+
*
|
|
1143
|
+
* useChannelSubscription('notifications', (event) => {
|
|
1144
|
+
* if (event.type === 'new') {
|
|
1145
|
+
* setNotifications(n => [...n, event.payload.message]);
|
|
1146
|
+
* }
|
|
1147
|
+
* }, []);
|
|
1148
|
+
*
|
|
1149
|
+
* return <Section>{notifications.map(n => <Text>{n}</Text>)}</Section>;
|
|
1150
|
+
* }
|
|
1151
|
+
* ```
|
|
1152
|
+
*/
|
|
1153
|
+
export function useChannelSubscription(name, handler, deps = []) {
|
|
1154
|
+
const { subscribe, available } = useChannel(name);
|
|
1155
|
+
// Use a ref to store the latest handler to avoid stale closures
|
|
1156
|
+
const handlerRef = useRef(handler);
|
|
1157
|
+
handlerRef.current = handler;
|
|
1158
|
+
useEffect(() => {
|
|
1159
|
+
if (!available)
|
|
1160
|
+
return;
|
|
1161
|
+
// Subscribe with a wrapper that always uses the latest handler
|
|
1162
|
+
return subscribe((event) => handlerRef.current(event));
|
|
1163
|
+
}, [available, ...deps]);
|
|
1164
|
+
}
|
|
1165
|
+
/**
|
|
1166
|
+
* Get the full conversation history across all ticks.
|
|
1167
|
+
*
|
|
1168
|
+
* Combines `state.previous.timeline` (history) with `state.current.timeline`
|
|
1169
|
+
* (current tick) into a single array.
|
|
1170
|
+
*
|
|
1171
|
+
* This is a utility hook that extracts data from TickState - it doesn't use the
|
|
1172
|
+
* fiber system and can be called anywhere you have access to TickState.
|
|
1173
|
+
*
|
|
1174
|
+
* @param state - The TickState from component context
|
|
1175
|
+
* @param options - Optional filtering and limiting
|
|
1176
|
+
* @returns Array of timeline entries
|
|
1177
|
+
*
|
|
1178
|
+
* @example Basic usage
|
|
1179
|
+
* ```tsx
|
|
1180
|
+
* const ChatAgent = ({ message }: Props) => {
|
|
1181
|
+
* const history = useConversationHistory();
|
|
1182
|
+
*
|
|
1183
|
+
* return (
|
|
1184
|
+
* <>
|
|
1185
|
+
* <Model model={claude} />
|
|
1186
|
+
* <System>You are helpful.</System>
|
|
1187
|
+
* {history.map((entry, i) => (
|
|
1188
|
+
* <Message key={i} {...entry.message} />
|
|
1189
|
+
* ))}
|
|
1190
|
+
* <User>{message}</User>
|
|
1191
|
+
* </>
|
|
1192
|
+
* );
|
|
1193
|
+
* };
|
|
1194
|
+
* ```
|
|
1195
|
+
*
|
|
1196
|
+
* @example With options
|
|
1197
|
+
* ```tsx
|
|
1198
|
+
* const history = useConversationHistory({
|
|
1199
|
+
* roles: ['user', 'assistant'], // Exclude tool messages
|
|
1200
|
+
* limit: 10, // Last 10 messages
|
|
1201
|
+
* });
|
|
1202
|
+
* ```
|
|
1203
|
+
*/
|
|
1204
|
+
export function useConversationHistory(options) {
|
|
1205
|
+
const state = useTickState();
|
|
1206
|
+
const com = useCom();
|
|
1207
|
+
// This hook returns PURE DATA for components to render.
|
|
1208
|
+
// Components render this data as <Message> projections.
|
|
1209
|
+
// The compiled output IS what the model sees - JSX is the single source of truth.
|
|
1210
|
+
//
|
|
1211
|
+
// Sources:
|
|
1212
|
+
// - previous: History from previous ticks (the main conversation history)
|
|
1213
|
+
// - current: Model output from last tick (may overlap with previous)
|
|
1214
|
+
// - injected: Entries added via com.injectHistory() during this tick
|
|
1215
|
+
// - pending: Queued messages for THIS tick (new user input)
|
|
1216
|
+
//
|
|
1217
|
+
// Deduplication by message reference prevents double-counting.
|
|
1218
|
+
const seen = new Set();
|
|
1219
|
+
let entries = [];
|
|
1220
|
+
const addEntries = (source, sourceName) => {
|
|
1221
|
+
const count = source?.length ?? 0;
|
|
1222
|
+
log.debug({ source: sourceName, count }, "useConversationHistory adding entries from source");
|
|
1223
|
+
if (!source)
|
|
1224
|
+
return;
|
|
1225
|
+
for (const entry of source) {
|
|
1226
|
+
// Dedupe by message reference for entries with messages
|
|
1227
|
+
if (entry.message) {
|
|
1228
|
+
if (seen.has(entry.message))
|
|
1229
|
+
continue;
|
|
1230
|
+
seen.add(entry.message);
|
|
1231
|
+
}
|
|
1232
|
+
entries.push(entry);
|
|
1233
|
+
}
|
|
1234
|
+
};
|
|
1235
|
+
// Add all sources - this is DATA for components to render
|
|
1236
|
+
addEntries(state.previous?.timeline, "state.previous.timeline");
|
|
1237
|
+
addEntries(state.current?.timeline, "state.current.timeline");
|
|
1238
|
+
addEntries(com.getInjectedHistory(), "com.getInjectedHistory()");
|
|
1239
|
+
// Include pending messages (queued for this tick)
|
|
1240
|
+
const pending = state.queuedMessages || [];
|
|
1241
|
+
for (const msg of pending) {
|
|
1242
|
+
if (msg.type === "user" && msg.content && typeof msg.content === "object" && "role" in msg.content) {
|
|
1243
|
+
const message = msg.content;
|
|
1244
|
+
if (message && !seen.has(message)) {
|
|
1245
|
+
seen.add(message);
|
|
1246
|
+
entries.push({
|
|
1247
|
+
kind: "message",
|
|
1248
|
+
message,
|
|
1249
|
+
tags: ["pending", "user_input"],
|
|
1250
|
+
});
|
|
1251
|
+
log.debug({ role: message.role }, "useConversationHistory added pending message");
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
log.debug({ totalEntries: entries.length }, "useConversationHistory total entries");
|
|
1256
|
+
// Apply role filter if specified
|
|
1257
|
+
if (options?.roles) {
|
|
1258
|
+
const roleSet = new Set(options.roles);
|
|
1259
|
+
entries = entries.filter((entry) => entry.message?.role && roleSet.has(entry.message.role));
|
|
1260
|
+
}
|
|
1261
|
+
// Apply custom filter
|
|
1262
|
+
if (options?.filter) {
|
|
1263
|
+
entries = entries.filter(options.filter);
|
|
1264
|
+
}
|
|
1265
|
+
// Apply limit (from end)
|
|
1266
|
+
if (options?.limit && options.limit > 0) {
|
|
1267
|
+
entries = entries.slice(-options.limit);
|
|
1268
|
+
}
|
|
1269
|
+
return entries;
|
|
1270
|
+
}
|
|
1271
|
+
/**
|
|
1272
|
+
* Get just the messages from conversation history (convenience wrapper).
|
|
1273
|
+
*
|
|
1274
|
+
* @param options - Optional filtering and limiting
|
|
1275
|
+
* @returns Array of messages (excluding entries without messages)
|
|
1276
|
+
*/
|
|
1277
|
+
export function useMessages(options) {
|
|
1278
|
+
return useConversationHistory(options)
|
|
1279
|
+
.filter((entry) => entry.message)
|
|
1280
|
+
.map((entry) => entry.message);
|
|
1281
|
+
}
|
|
1282
|
+
/**
|
|
1283
|
+
* Get message count from conversation history.
|
|
1284
|
+
*
|
|
1285
|
+
* @returns Number of messages in history
|
|
1286
|
+
*/
|
|
1287
|
+
export function useMessageCount() {
|
|
1288
|
+
return useConversationHistory().filter((entry) => entry.message).length;
|
|
1289
|
+
}
|
|
1290
|
+
/**
|
|
1291
|
+
* Get messages queued for the next tick.
|
|
1292
|
+
*
|
|
1293
|
+
* Messages can be queued via:
|
|
1294
|
+
* - `session.queue.exec(msg)` - Queue a message for later processing
|
|
1295
|
+
* - `session.interrupt(msg)` - Interrupt and queue a message
|
|
1296
|
+
* - `RuntimeSession.sendMessage(msg)` - Direct programmatic injection
|
|
1297
|
+
*
|
|
1298
|
+
* Queued messages are available during render and can be used to show
|
|
1299
|
+
* pending messages in the UI or to process them in the current tick.
|
|
1300
|
+
*
|
|
1301
|
+
* @returns Array of queued execution messages
|
|
1302
|
+
*
|
|
1303
|
+
* @example Show pending messages
|
|
1304
|
+
* ```tsx
|
|
1305
|
+
* const ChatWithPending = () => {
|
|
1306
|
+
* const history = useConversationHistory();
|
|
1307
|
+
* const pending = useQueuedMessages();
|
|
1308
|
+
*
|
|
1309
|
+
* return (
|
|
1310
|
+
* <>
|
|
1311
|
+
* {history.map((entry, i) => (
|
|
1312
|
+
* <Message key={i} {...entry.message} />
|
|
1313
|
+
* ))}
|
|
1314
|
+
* {pending.length > 0 && (
|
|
1315
|
+
* <System>Processing {pending.length} pending message(s)...</System>
|
|
1316
|
+
* )}
|
|
1317
|
+
* </>
|
|
1318
|
+
* );
|
|
1319
|
+
* };
|
|
1320
|
+
* ```
|
|
1321
|
+
*
|
|
1322
|
+
* @example Process pending messages
|
|
1323
|
+
* ```tsx
|
|
1324
|
+
* const Agent = () => {
|
|
1325
|
+
* const pending = useQueuedMessages();
|
|
1326
|
+
*
|
|
1327
|
+
* // Access the first pending message if any
|
|
1328
|
+
* const firstPending = pending[0];
|
|
1329
|
+
* if (firstPending?.type === 'interrupt') {
|
|
1330
|
+
* return <System>Handling interrupt: {firstPending.content}</System>;
|
|
1331
|
+
* }
|
|
1332
|
+
*
|
|
1333
|
+
* return <AssistantMessage />;
|
|
1334
|
+
* };
|
|
1335
|
+
* ```
|
|
1336
|
+
*/
|
|
1337
|
+
export function useQueuedMessages() {
|
|
1338
|
+
const state = useTickState();
|
|
1339
|
+
return state.queuedMessages ?? [];
|
|
1340
|
+
}
|
|
1341
|
+
// ============================================================================
|
|
1342
|
+
// History Injection Hooks
|
|
1343
|
+
// ============================================================================
|
|
1344
|
+
/**
|
|
1345
|
+
* Inject historical timeline entries into the conversation.
|
|
1346
|
+
*
|
|
1347
|
+
* Use this to load an existing conversation when the component mounts.
|
|
1348
|
+
* The entries are injected once (on first render) and then available via
|
|
1349
|
+
* `useConversationHistory()` and `<Timeline />`.
|
|
1350
|
+
*
|
|
1351
|
+
* This hook only injects on the first render. On subsequent renders (tick 2+),
|
|
1352
|
+
* the timeline naturally includes all entries via `TickState.previous.timeline`.
|
|
1353
|
+
*
|
|
1354
|
+
* @param entries - Timeline entries to inject, or a function that returns them
|
|
1355
|
+
* @param deps - Dependency array (like useEffect). If omitted, injects once on mount.
|
|
1356
|
+
*
|
|
1357
|
+
* @example Basic usage with static entries
|
|
1358
|
+
* ```tsx
|
|
1359
|
+
* const ChatAgent = ({ conversationHistory }: Props) => {
|
|
1360
|
+
* useInjectHistory(conversationHistory);
|
|
1361
|
+
* return <Timeline />;
|
|
1362
|
+
* };
|
|
1363
|
+
* ```
|
|
1364
|
+
*
|
|
1365
|
+
* @example Async loading in useInit
|
|
1366
|
+
* ```tsx
|
|
1367
|
+
* const ChatAgent = ({ conversationId }: Props) => {
|
|
1368
|
+
* const [loaded, setLoaded] = useState(false);
|
|
1369
|
+
*
|
|
1370
|
+
* await useInit(async () => {
|
|
1371
|
+
* const conversation = await loadConversation(conversationId);
|
|
1372
|
+
* injectHistory(conversation.entries); // Use standalone function
|
|
1373
|
+
* setLoaded(true);
|
|
1374
|
+
* });
|
|
1375
|
+
*
|
|
1376
|
+
* if (!loaded) return null;
|
|
1377
|
+
* return <Timeline />;
|
|
1378
|
+
* };
|
|
1379
|
+
* ```
|
|
1380
|
+
*/
|
|
1381
|
+
export function useInjectHistory(entries, deps) {
|
|
1382
|
+
const com = useCom();
|
|
1383
|
+
const hasInjected = useRef(false);
|
|
1384
|
+
// Resolve entries (can be array or function)
|
|
1385
|
+
const resolvedEntries = typeof entries === "function" ? entries() : entries;
|
|
1386
|
+
// Only inject once (unless deps change)
|
|
1387
|
+
const shouldInject = deps
|
|
1388
|
+
? !hasInjected.current // With deps, use ref to track
|
|
1389
|
+
: !hasInjected.current; // Without deps, also use ref
|
|
1390
|
+
if (shouldInject && resolvedEntries.length > 0) {
|
|
1391
|
+
com.injectHistory(resolvedEntries);
|
|
1392
|
+
hasInjected.current = true;
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
/**
|
|
1396
|
+
* Standalone function to inject history (for use in useInit or async contexts).
|
|
1397
|
+
*
|
|
1398
|
+
* Unlike the hook, this can be called anywhere you have access to COM.
|
|
1399
|
+
*
|
|
1400
|
+
* @param com - The COM instance
|
|
1401
|
+
* @param entries - Timeline entries to inject
|
|
1402
|
+
*
|
|
1403
|
+
* @example
|
|
1404
|
+
* ```tsx
|
|
1405
|
+
* const ChatAgent = ({ conversationId }: Props) => {
|
|
1406
|
+
* const com = useCom();
|
|
1407
|
+
*
|
|
1408
|
+
* await useInit(async () => {
|
|
1409
|
+
* const conversation = await loadConversation(conversationId);
|
|
1410
|
+
* injectHistory(com, conversation.entries);
|
|
1411
|
+
* });
|
|
1412
|
+
*
|
|
1413
|
+
* return <Timeline />;
|
|
1414
|
+
* };
|
|
1415
|
+
* ```
|
|
1416
|
+
*/
|
|
1417
|
+
export function injectHistory(com, entries) {
|
|
1418
|
+
com.injectHistory(entries);
|
|
1419
|
+
}
|
|
1420
|
+
// ============================================================================
|
|
1421
|
+
// Helpers
|
|
1422
|
+
// ============================================================================
|
|
1423
|
+
function areHookInputsEqual(nextDeps, prevDeps) {
|
|
1424
|
+
if (prevDeps === null)
|
|
1425
|
+
return false;
|
|
1426
|
+
if (process.env["NODE_ENV"] === "development" && nextDeps.length !== prevDeps.length) {
|
|
1427
|
+
console.warn("Hook dependency array changed size between renders. " +
|
|
1428
|
+
"The array must remain constant in length.");
|
|
1429
|
+
}
|
|
1430
|
+
for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
|
|
1431
|
+
if (Object.is(nextDeps[i], prevDeps[i]))
|
|
1432
|
+
continue;
|
|
1433
|
+
return false;
|
|
1434
|
+
}
|
|
1435
|
+
return true;
|
|
1436
|
+
}
|
|
1437
|
+
//# sourceMappingURL=hooks.js.map
|