@athenaintel/react 0.20.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +74 -0
- package/dist/assets/use-asset-embed.cjs +8 -0
- package/dist/assets/use-asset-embed.cjs.map +1 -1
- package/dist/assets/use-asset-embed.js +8 -0
- package/dist/assets/use-asset-embed.js.map +1 -1
- package/dist/chat/StatewireApprovalCard.cjs +22 -4
- package/dist/chat/StatewireApprovalCard.cjs.map +1 -1
- package/dist/chat/StatewireApprovalCard.d.ts +14 -0
- package/dist/chat/StatewireApprovalCard.js +23 -5
- package/dist/chat/StatewireApprovalCard.js.map +1 -1
- package/dist/chat/StatewireClientToolBridge.cjs +2 -1
- package/dist/chat/StatewireClientToolBridge.cjs.map +1 -1
- package/dist/chat/StatewireClientToolBridge.js +2 -1
- package/dist/chat/StatewireClientToolBridge.js.map +1 -1
- package/dist/chat/StatewireQueuedMessages.cjs +2 -1
- package/dist/chat/StatewireQueuedMessages.cjs.map +1 -1
- package/dist/chat/StatewireQueuedMessages.js +2 -1
- package/dist/chat/StatewireQueuedMessages.js.map +1 -1
- package/dist/chat/athena-citation-links.cjs +17 -1
- package/dist/chat/athena-citation-links.cjs.map +1 -1
- package/dist/chat/athena-citation-links.js +17 -1
- package/dist/chat/athena-citation-links.js.map +1 -1
- package/dist/chat/statewire-client-tools.cjs +2 -1
- package/dist/chat/statewire-client-tools.cjs.map +1 -1
- package/dist/chat/statewire-client-tools.js +2 -1
- package/dist/chat/statewire-client-tools.js.map +1 -1
- package/dist/chat/super-grouping/SuperGroupingCard.cjs +69 -187
- package/dist/chat/super-grouping/SuperGroupingCard.cjs.map +1 -1
- package/dist/chat/super-grouping/SuperGroupingCard.d.ts +10 -6
- package/dist/chat/super-grouping/SuperGroupingCard.js +73 -191
- package/dist/chat/super-grouping/SuperGroupingCard.js.map +1 -1
- package/dist/chat/super-grouping/ToolTimeline.cjs +131 -0
- package/dist/chat/super-grouping/ToolTimeline.cjs.map +1 -0
- package/dist/chat/super-grouping/ToolTimeline.d.ts +16 -0
- package/dist/chat/super-grouping/ToolTimeline.js +131 -0
- package/dist/chat/super-grouping/ToolTimeline.js.map +1 -0
- package/dist/chat/super-grouping/activity-timeline.cjs +14 -8
- package/dist/chat/super-grouping/activity-timeline.cjs.map +1 -1
- package/dist/chat/super-grouping/activity-timeline.d.ts +3 -0
- package/dist/chat/super-grouping/activity-timeline.js +14 -8
- package/dist/chat/super-grouping/activity-timeline.js.map +1 -1
- package/dist/chat/super-grouping/timeline-step.cjs +65 -0
- package/dist/chat/super-grouping/timeline-step.cjs.map +1 -0
- package/dist/chat/super-grouping/timeline-step.d.ts +17 -0
- package/dist/chat/super-grouping/timeline-step.js +65 -0
- package/dist/chat/super-grouping/timeline-step.js.map +1 -0
- package/dist/chat/tiptap-text.cjs +8 -8
- package/dist/chat/tiptap-text.cjs.map +1 -1
- package/dist/chat/use-athena-run-live.cjs +2 -1
- package/dist/chat/use-athena-run-live.cjs.map +1 -1
- package/dist/chat/use-athena-run-live.js +2 -1
- package/dist/chat/use-athena-run-live.js.map +1 -1
- package/dist/chat/use-statewire-approval-request.cjs +7 -2
- package/dist/chat/use-statewire-approval-request.cjs.map +1 -1
- package/dist/chat/use-statewire-approval-request.d.ts +12 -2
- package/dist/chat/use-statewire-approval-request.js +8 -3
- package/dist/chat/use-statewire-approval-request.js.map +1 -1
- package/dist/chat-ui/composer/tiptap-composer.cjs +24 -65
- package/dist/chat-ui/composer/tiptap-composer.cjs.map +1 -1
- package/dist/chat-ui/composer/tiptap-composer.js +24 -65
- package/dist/chat-ui/composer/tiptap-composer.js.map +1 -1
- package/dist/chat-ui/index.cjs +2 -1
- package/dist/chat-ui/index.cjs.map +1 -1
- package/dist/chat-ui/index.js +2 -1
- package/dist/chat-ui/index.js.map +1 -1
- package/dist/chat-ui/mentions/mention-chip.cjs +49 -4
- package/dist/chat-ui/mentions/mention-chip.cjs.map +1 -1
- package/dist/chat-ui/mentions/mention-chip.d.ts +11 -0
- package/dist/chat-ui/mentions/mention-chip.js +50 -5
- package/dist/chat-ui/mentions/mention-chip.js.map +1 -1
- package/dist/chat-ui/mentions/mention-extension.cjs +18 -6
- package/dist/chat-ui/mentions/mention-extension.cjs.map +1 -1
- package/dist/chat-ui/mentions/mention-extension.d.ts +2 -0
- package/dist/chat-ui/mentions/mention-extension.js +18 -6
- package/dist/chat-ui/mentions/mention-extension.js.map +1 -1
- package/dist/chat-ui/mentions/suggestions/components/mention-suggestion-list.cjs +1 -1
- package/dist/chat-ui/mentions/suggestions/components/mention-suggestion-list.cjs.map +1 -1
- package/dist/chat-ui/mentions/suggestions/components/mention-suggestion-list.js +1 -1
- package/dist/chat-ui/mentions/suggestions/components/mention-suggestion-list.js.map +1 -1
- package/dist/chat-ui/mentions/suggestions/components/menu-item.cjs +9 -6
- package/dist/chat-ui/mentions/suggestions/components/menu-item.cjs.map +1 -1
- package/dist/chat-ui/mentions/suggestions/components/menu-item.d.ts +2 -2
- package/dist/chat-ui/mentions/suggestions/components/menu-item.js +10 -7
- package/dist/chat-ui/mentions/suggestions/components/menu-item.js.map +1 -1
- package/dist/chat-ui/mentions/suggestions/components/menu-list.cjs +2 -2
- package/dist/chat-ui/mentions/suggestions/components/menu-list.cjs.map +1 -1
- package/dist/chat-ui/mentions/suggestions/components/menu-list.d.ts +2 -2
- package/dist/chat-ui/mentions/suggestions/components/menu-list.js +2 -2
- package/dist/chat-ui/mentions/suggestions/components/menu-list.js.map +1 -1
- package/dist/chat-ui/mentions/suggestions/data/api-client.cjs +2 -1
- package/dist/chat-ui/mentions/suggestions/data/api-client.cjs.map +1 -1
- package/dist/chat-ui/mentions/suggestions/data/api-client.d.ts +3 -2
- package/dist/chat-ui/mentions/suggestions/data/api-client.js +2 -1
- package/dist/chat-ui/mentions/suggestions/data/api-client.js.map +1 -1
- package/dist/chat-ui/messages/message-components.cjs +6 -29
- package/dist/chat-ui/messages/message-components.cjs.map +1 -1
- package/dist/chat-ui/messages/message-components.d.ts +1 -13
- package/dist/chat-ui/messages/message-components.js +8 -31
- package/dist/chat-ui/messages/message-components.js.map +1 -1
- package/dist/chat-ui/messages/runtime-message.cjs +3 -2
- package/dist/chat-ui/messages/runtime-message.cjs.map +1 -1
- package/dist/chat-ui/messages/runtime-message.js +3 -2
- package/dist/chat-ui/messages/runtime-message.js.map +1 -1
- package/dist/chat-ui/messages/statewire-continue-run.cjs +23 -8
- package/dist/chat-ui/messages/statewire-continue-run.cjs.map +1 -1
- package/dist/chat-ui/messages/statewire-continue-run.d.ts +14 -4
- package/dist/chat-ui/messages/statewire-continue-run.js +23 -8
- package/dist/chat-ui/messages/statewire-continue-run.js.map +1 -1
- package/dist/chat-ui/messages/user-edit-composer.cjs +2 -1
- package/dist/chat-ui/messages/user-edit-composer.cjs.map +1 -1
- package/dist/chat-ui/messages/user-edit-composer.js +2 -1
- package/dist/chat-ui/messages/user-edit-composer.js.map +1 -1
- package/dist/chat-ui/rich-text/chat-message-body.cjs +76 -0
- package/dist/chat-ui/rich-text/chat-message-body.cjs.map +1 -0
- package/dist/chat-ui/rich-text/chat-message-body.d.ts +5 -0
- package/dist/chat-ui/rich-text/chat-message-body.js +76 -0
- package/dist/chat-ui/rich-text/chat-message-body.js.map +1 -0
- package/dist/chat-ui/rich-text/chat-node-views.cjs +51 -0
- package/dist/chat-ui/rich-text/chat-node-views.cjs.map +1 -0
- package/dist/chat-ui/rich-text/chat-node-views.d.ts +13 -0
- package/dist/chat-ui/rich-text/chat-node-views.js +51 -0
- package/dist/chat-ui/rich-text/chat-node-views.js.map +1 -0
- package/dist/chat-ui/rich-text/chat-text-schema.cjs +70 -0
- package/dist/chat-ui/rich-text/chat-text-schema.cjs.map +1 -0
- package/dist/chat-ui/rich-text/chat-text-schema.d.ts +7 -0
- package/dist/chat-ui/rich-text/chat-text-schema.js +70 -0
- package/dist/chat-ui/rich-text/chat-text-schema.js.map +1 -0
- package/dist/chat-ui/rich-text/dom-output-spec.cjs +83 -0
- package/dist/chat-ui/rich-text/dom-output-spec.cjs.map +1 -0
- package/dist/chat-ui/rich-text/dom-output-spec.d.ts +11 -0
- package/dist/chat-ui/rich-text/dom-output-spec.js +83 -0
- package/dist/chat-ui/rich-text/dom-output-spec.js.map +1 -0
- package/dist/chat-ui/statewire.cjs +2 -1
- package/dist/chat-ui/statewire.cjs.map +1 -1
- package/dist/chat-ui/statewire.js +2 -1
- package/dist/chat-ui/statewire.js.map +1 -1
- package/dist/chat-ui/thread/statewire-history-edge.cjs +2 -1
- package/dist/chat-ui/thread/statewire-history-edge.cjs.map +1 -1
- package/dist/chat-ui/thread/statewire-history-edge.js +2 -1
- package/dist/chat-ui/thread/statewire-history-edge.js.map +1 -1
- package/dist/index.cjs +2 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/node_modules/qrcode-generator/dist/qrcode.cjs +32 -0
- package/dist/node_modules/qrcode-generator/dist/qrcode.cjs.map +1 -0
- package/dist/node_modules/qrcode-generator/dist/qrcode.js +31 -0
- package/dist/node_modules/qrcode-generator/dist/qrcode.js.map +1 -0
- package/dist/node_modules/zod/v4/classic/errors.cjs +44 -0
- package/dist/node_modules/zod/v4/classic/errors.cjs.map +1 -0
- package/dist/node_modules/zod/v4/classic/errors.js +44 -0
- package/dist/node_modules/zod/v4/classic/errors.js.map +1 -0
- package/dist/node_modules/zod/v4/classic/iso.cjs +43 -0
- package/dist/node_modules/zod/v4/classic/iso.cjs.map +1 -0
- package/dist/node_modules/zod/v4/classic/iso.js +43 -0
- package/dist/node_modules/zod/v4/classic/iso.js.map +1 -0
- package/dist/node_modules/zod/v4/classic/parse.cjs +29 -0
- package/dist/node_modules/zod/v4/classic/parse.cjs.map +1 -0
- package/dist/node_modules/zod/v4/classic/parse.js +29 -0
- package/dist/node_modules/zod/v4/classic/parse.js.map +1 -0
- package/dist/node_modules/zod/v4/classic/schemas.cjs +871 -0
- package/dist/node_modules/zod/v4/classic/schemas.cjs.map +1 -0
- package/dist/node_modules/zod/v4/classic/schemas.js +871 -0
- package/dist/node_modules/zod/v4/classic/schemas.js.map +1 -0
- package/dist/node_modules/zod/v4/core/api.cjs +570 -0
- package/dist/node_modules/zod/v4/core/api.cjs.map +1 -0
- package/dist/node_modules/zod/v4/core/api.js +570 -0
- package/dist/node_modules/zod/v4/core/api.js.map +1 -0
- package/dist/node_modules/zod/v4/core/checks.cjs +411 -0
- package/dist/node_modules/zod/v4/core/checks.cjs.map +1 -0
- package/dist/node_modules/zod/v4/core/checks.js +411 -0
- package/dist/node_modules/zod/v4/core/checks.js.map +1 -0
- package/dist/node_modules/zod/v4/core/core.cjs +77 -0
- package/dist/node_modules/zod/v4/core/core.cjs.map +1 -0
- package/dist/node_modules/zod/v4/core/core.js +77 -0
- package/dist/node_modules/zod/v4/core/core.js.map +1 -0
- package/dist/node_modules/zod/v4/core/doc.cjs +38 -0
- package/dist/node_modules/zod/v4/core/doc.cjs.map +1 -0
- package/dist/node_modules/zod/v4/core/doc.js +38 -0
- package/dist/node_modules/zod/v4/core/doc.js.map +1 -0
- package/dist/node_modules/zod/v4/core/errors.cjs +76 -0
- package/dist/node_modules/zod/v4/core/errors.cjs.map +1 -0
- package/dist/node_modules/zod/v4/core/errors.js +76 -0
- package/dist/node_modules/zod/v4/core/errors.js.map +1 -0
- package/dist/node_modules/zod/v4/core/json-schema-processors.cjs +312 -0
- package/dist/node_modules/zod/v4/core/json-schema-processors.cjs.map +1 -0
- package/dist/node_modules/zod/v4/core/json-schema-processors.js +312 -0
- package/dist/node_modules/zod/v4/core/json-schema-processors.js.map +1 -0
- package/dist/node_modules/zod/v4/core/parse.cjs +96 -0
- package/dist/node_modules/zod/v4/core/parse.cjs.map +1 -0
- package/dist/node_modules/zod/v4/core/parse.js +96 -0
- package/dist/node_modules/zod/v4/core/parse.js.map +1 -0
- package/dist/node_modules/zod/v4/core/regexes.cjs +86 -0
- package/dist/node_modules/zod/v4/core/regexes.cjs.map +1 -0
- package/dist/node_modules/zod/v4/core/regexes.js +86 -0
- package/dist/node_modules/zod/v4/core/regexes.js.map +1 -0
- package/dist/node_modules/zod/v4/core/registries.cjs +52 -0
- package/dist/node_modules/zod/v4/core/registries.cjs.map +1 -0
- package/dist/node_modules/zod/v4/core/registries.js +52 -0
- package/dist/node_modules/zod/v4/core/registries.js.map +1 -0
- package/dist/node_modules/zod/v4/core/schemas.cjs +1412 -0
- package/dist/node_modules/zod/v4/core/schemas.cjs.map +1 -0
- package/dist/node_modules/zod/v4/core/schemas.js +1413 -0
- package/dist/node_modules/zod/v4/core/schemas.js.map +1 -0
- package/dist/node_modules/zod/v4/core/to-json-schema.cjs +370 -0
- package/dist/node_modules/zod/v4/core/to-json-schema.cjs.map +1 -0
- package/dist/node_modules/zod/v4/core/to-json-schema.js +370 -0
- package/dist/node_modules/zod/v4/core/to-json-schema.js.map +1 -0
- package/dist/node_modules/zod/v4/core/util.cjs +444 -0
- package/dist/node_modules/zod/v4/core/util.cjs.map +1 -0
- package/dist/node_modules/zod/v4/core/util.js +444 -0
- package/dist/node_modules/zod/v4/core/util.js.map +1 -0
- package/dist/node_modules/zod/v4/core/versions.cjs +9 -0
- package/dist/node_modules/zod/v4/core/versions.cjs.map +1 -0
- package/dist/node_modules/zod/v4/core/versions.js +9 -0
- package/dist/node_modules/zod/v4/core/versions.js.map +1 -0
- package/dist/packages/agent-runtime/src/client-tool-bridge.cjs +7 -1
- package/dist/packages/agent-runtime/src/client-tool-bridge.cjs.map +1 -1
- package/dist/packages/agent-runtime/src/client-tool-bridge.js +7 -1
- package/dist/packages/agent-runtime/src/client-tool-bridge.js.map +1 -1
- package/dist/packages/agent-runtime/src/conversation-list.cjs +4 -0
- package/dist/packages/agent-runtime/src/conversation-list.cjs.map +1 -1
- package/dist/packages/agent-runtime/src/conversation-list.js +4 -0
- package/dist/packages/agent-runtime/src/conversation-list.js.map +1 -1
- package/dist/packages/agent-runtime/src/edit-command.cjs.map +1 -1
- package/dist/packages/agent-runtime/src/edit-command.js.map +1 -1
- package/dist/packages/agent-runtime/src/generated/runconfig-contract.cjs +2 -0
- package/dist/packages/agent-runtime/src/generated/runconfig-contract.cjs.map +1 -1
- package/dist/packages/agent-runtime/src/generated/runconfig-contract.js +2 -0
- package/dist/packages/agent-runtime/src/generated/runconfig-contract.js.map +1 -1
- package/dist/packages/agent-runtime/src/interrupts.cjs +31 -7
- package/dist/packages/agent-runtime/src/interrupts.cjs.map +1 -1
- package/dist/packages/agent-runtime/src/interrupts.js +31 -7
- package/dist/packages/agent-runtime/src/interrupts.js.map +1 -1
- package/dist/packages/agent-runtime/src/messages.cjs +35 -5
- package/dist/packages/agent-runtime/src/messages.cjs.map +1 -1
- package/dist/packages/agent-runtime/src/messages.js +35 -5
- package/dist/packages/agent-runtime/src/messages.js.map +1 -1
- package/dist/packages/agent-runtime/src/protocol.cjs +4 -6
- package/dist/packages/agent-runtime/src/protocol.cjs.map +1 -1
- package/dist/packages/agent-runtime/src/protocol.js +4 -6
- package/dist/packages/agent-runtime/src/protocol.js.map +1 -1
- package/dist/packages/agent-runtime/src/quota-safe-local-storage-session.cjs +296 -14
- package/dist/packages/agent-runtime/src/quota-safe-local-storage-session.cjs.map +1 -1
- package/dist/packages/agent-runtime/src/quota-safe-local-storage-session.js +293 -11
- package/dist/packages/agent-runtime/src/quota-safe-local-storage-session.js.map +1 -1
- package/dist/packages/agent-runtime/src/screen-point.cjs +68 -0
- package/dist/packages/agent-runtime/src/screen-point.cjs.map +1 -0
- package/dist/packages/agent-runtime/src/screen-point.js +68 -0
- package/dist/packages/agent-runtime/src/screen-point.js.map +1 -0
- package/dist/packages/agent-runtime/src/statewire-transport.cjs +6 -1
- package/dist/packages/agent-runtime/src/statewire-transport.cjs.map +1 -1
- package/dist/packages/agent-runtime/src/statewire-transport.js +6 -1
- package/dist/packages/agent-runtime/src/statewire-transport.js.map +1 -1
- package/dist/packages/agent-runtime/src/thread-handoff.cjs +85 -0
- package/dist/packages/agent-runtime/src/thread-handoff.cjs.map +1 -0
- package/dist/packages/agent-runtime/src/thread-handoff.js +85 -0
- package/dist/packages/agent-runtime/src/thread-handoff.js.map +1 -0
- package/dist/packages/agent-runtime/src/thread-snapshot-cache.cjs +1 -1
- package/dist/packages/agent-runtime/src/thread-snapshot-cache.cjs.map +1 -1
- package/dist/packages/agent-runtime/src/thread-snapshot-cache.js +1 -1
- package/dist/packages/agent-runtime/src/thread-snapshot-cache.js.map +1 -1
- package/dist/packages/agent-runtime/src/tool-metadata.cjs +0 -8
- package/dist/packages/agent-runtime/src/tool-metadata.cjs.map +1 -1
- package/dist/packages/agent-runtime/src/tool-metadata.js +0 -8
- package/dist/packages/agent-runtime/src/tool-metadata.js.map +1 -1
- package/dist/packages/agent-runtime/src/use-deep-agent-config.cjs +14 -0
- package/dist/packages/agent-runtime/src/use-deep-agent-config.cjs.map +1 -0
- package/dist/packages/agent-runtime/src/use-deep-agent-config.js +14 -0
- package/dist/packages/agent-runtime/src/use-deep-agent-config.js.map +1 -0
- package/dist/packages/agent-runtime/src/use-deep-agent-edit-submit.cjs +21 -5
- package/dist/packages/agent-runtime/src/use-deep-agent-edit-submit.cjs.map +1 -1
- package/dist/packages/agent-runtime/src/use-deep-agent-edit-submit.js +21 -5
- package/dist/packages/agent-runtime/src/use-deep-agent-edit-submit.js.map +1 -1
- package/dist/packages/agent-runtime/src/use-deep-agent-runtime.cjs +13 -3
- package/dist/packages/agent-runtime/src/use-deep-agent-runtime.cjs.map +1 -1
- package/dist/packages/agent-runtime/src/use-deep-agent-runtime.js +14 -4
- package/dist/packages/agent-runtime/src/use-deep-agent-runtime.js.map +1 -1
- package/dist/packages/agent-runtime/src/use-deep-agent-thread.cjs +35 -15
- package/dist/packages/agent-runtime/src/use-deep-agent-thread.cjs.map +1 -1
- package/dist/packages/agent-runtime/src/use-deep-agent-thread.js +35 -15
- package/dist/packages/agent-runtime/src/use-deep-agent-thread.js.map +1 -1
- package/dist/packages/agent-runtime/src/voice-presentation.cjs.map +1 -1
- package/dist/packages/agent-runtime/src/voice-presentation.js.map +1 -1
- package/dist/packages/react/package.json.cjs +1 -1
- package/dist/packages/react/package.json.js +1 -1
- package/dist/provider/AthenaProvider.cjs +2 -1
- package/dist/provider/AthenaProvider.cjs.map +1 -1
- package/dist/provider/AthenaProvider.js +2 -1
- package/dist/provider/AthenaProvider.js.map +1 -1
- package/dist/runtime/statewire-lifecycle.cjs +2 -1
- package/dist/runtime/statewire-lifecycle.cjs.map +1 -1
- package/dist/runtime/statewire-lifecycle.js +2 -1
- package/dist/runtime/statewire-lifecycle.js.map +1 -1
- package/dist/runtime/useAthenaStatewireRuntime.cjs +2 -1
- package/dist/runtime/useAthenaStatewireRuntime.cjs.map +1 -1
- package/dist/runtime/useAthenaStatewireRuntime.js +2 -1
- package/dist/runtime/useAthenaStatewireRuntime.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/threads/conversation-list-fetcher.cjs +4 -0
- package/dist/threads/conversation-list-fetcher.cjs.map +1 -1
- package/dist/threads/conversation-list-fetcher.js +4 -0
- package/dist/threads/conversation-list-fetcher.js.map +1 -1
- package/dist/threads/statewire-thread-list.cjs +2 -1
- package/dist/threads/statewire-thread-list.cjs.map +1 -1
- package/dist/threads/statewire-thread-list.js +2 -1
- package/dist/threads/statewire-thread-list.js.map +1 -1
- package/dist/threads/use-rest-conversation-history.cjs +2 -1
- package/dist/threads/use-rest-conversation-history.cjs.map +1 -1
- package/dist/threads/use-rest-conversation-history.js +2 -1
- package/dist/threads/use-rest-conversation-history.js.map +1 -1
- package/dist/threads/use-statewire-conversation-history.cjs +2 -1
- package/dist/threads/use-statewire-conversation-history.cjs.map +1 -1
- package/dist/threads/use-statewire-conversation-history.d.ts +1 -0
- package/dist/threads/use-statewire-conversation-history.js +2 -1
- package/dist/threads/use-statewire-conversation-history.js.map +1 -1
- package/dist/tools/index.cjs +2 -1
- package/dist/tools/index.cjs.map +1 -1
- package/dist/tools/index.js +2 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/misc-tool-uis.cjs +2 -1
- package/dist/tools/misc-tool-uis.cjs.map +1 -1
- package/dist/tools/misc-tool-uis.js +2 -1
- package/dist/tools/misc-tool-uis.js.map +1 -1
- package/dist/tools/primitives.cjs +2 -1
- package/dist/tools/primitives.cjs.map +1 -1
- package/dist/tools/primitives.js +2 -1
- package/dist/tools/primitives.js.map +1 -1
- package/dist/tools/tool-fallback.cjs +2 -1
- package/dist/tools/tool-fallback.cjs.map +1 -1
- package/dist/tools/tool-fallback.js +2 -1
- package/dist/tools/tool-fallback.js.map +1 -1
- package/dist/tools/voice-update-tool-ui.cjs +2 -1
- package/dist/tools/voice-update-tool-ui.cjs.map +1 -1
- package/dist/tools/voice-update-tool-ui.js +2 -1
- package/dist/tools/voice-update-tool-ui.js.map +1 -1
- package/dist/voice/index.cjs +6 -0
- package/dist/voice/index.cjs.map +1 -0
- package/dist/voice/index.d.ts +2 -0
- package/dist/voice/index.js +6 -0
- package/dist/voice/index.js.map +1 -0
- package/dist/voice/use-athena-voice-session.cjs +315 -0
- package/dist/voice/use-athena-voice-session.cjs.map +1 -0
- package/dist/voice/use-athena-voice-session.d.ts +145 -0
- package/dist/voice/use-athena-voice-session.js +315 -0
- package/dist/voice/use-athena-voice-session.js.map +1 -0
- package/package.json +17 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"statewire-transport.js","sources":["../../../../../agent-runtime/src/statewire-transport.ts"],"sourcesContent":["import { type Statewire, type StatewireClient, StatewireHttp, StatewireWebsocket } from 'statewire';\nimport type { DeepAgentStreamValues } from './types';\n\nexport type StatewireTransportOptions = StatewireClient.HttpOptions &\n StatewireClient.WebsocketOptions;\n\n/**\n * Build the deep-agent statewire transport: WebSocket by default, SSE when the\n * host app's toggle asks for it. The toggle source is host-specific (env var,\n * extension flag, config store), so the caller resolves it to a boolean.\n *\n * `TState` defaults to the deep-agent wire shape: the thread mounts that make\n * up nearly every caller get a typed state without naming it, and the other\n * mounts (the conversation index) name theirs.\n */\nexport const statewireTransport = <\n TState = DeepAgentStreamValues,\n TCommands extends Statewire.Commands = Statewire.Commands,\n>(\n options: StatewireTransportOptions,\n { sse = false }: { sse?: boolean } = {}\n) =>\n sse\n ? StatewireHttp<TState, TCommands>({ protocol:
|
|
1
|
+
{"version":3,"file":"statewire-transport.js","sources":["../../../../../agent-runtime/src/statewire-transport.ts"],"sourcesContent":["import { type Statewire, type StatewireClient, StatewireHttp, StatewireWebsocket } from 'statewire';\nimport type { DeepAgentStreamValues } from './types';\n\nexport type StatewireTransportOptions = StatewireClient.HttpOptions &\n StatewireClient.WebsocketOptions;\n\n/**\n * The application protocol the deep-agent transport stamps on every command.\n * The thread client must declare the same protocol: statewire's core applies\n * its optimistic folds only to commands of its own protocol, so a mismatch\n * leaves a send invisible until the server's frame lands.\n */\nexport const DEEP_AGENT_STATEWIRE_PROTOCOL = 'athena';\n\n/**\n * Build the deep-agent statewire transport: WebSocket by default, SSE when the\n * host app's toggle asks for it. The toggle source is host-specific (env var,\n * extension flag, config store), so the caller resolves it to a boolean.\n *\n * `TState` defaults to the deep-agent wire shape: the thread mounts that make\n * up nearly every caller get a typed state without naming it, and the other\n * mounts (the conversation index) name theirs.\n */\nexport const statewireTransport = <\n TState = DeepAgentStreamValues,\n TCommands extends Statewire.Commands = Statewire.Commands,\n>(\n options: StatewireTransportOptions,\n { sse = false }: { sse?: boolean } = {}\n) =>\n sse\n ? StatewireHttp<TState, TCommands>({ protocol: DEEP_AGENT_STATEWIRE_PROTOCOL, ...options })\n : StatewireWebsocket<TState, TCommands>({\n protocol: DEEP_AGENT_STATEWIRE_PROTOCOL,\n ...options,\n });\n"],"names":[],"mappings":";AAYO,MAAM,gCAAgC;AAWtC,MAAM,qBAAqB,CAIhC,SACA,EAAE,MAAM,MAAA,IAA6B,CAAA,MAErC,MACI,cAAiC,EAAE,UAAU,+BAA+B,GAAG,QAAA,CAAS,IACxF,mBAAsC;AAAA,EACpC,UAAU;AAAA,EACV,GAAG;AACL,CAAC;"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
require("../../../node_modules/qrcode-generator/dist/qrcode.cjs");
|
|
4
|
+
const schemas = require("../../../node_modules/zod/v4/classic/schemas.cjs");
|
|
5
|
+
const HANDOFF_DESTINATIONS = ["chrome", "mobile", "word", "powerpoint", "excel"];
|
|
6
|
+
const OFFICE_HANDOFF_DESTINATIONS = ["word", "powerpoint", "excel"];
|
|
7
|
+
["mobile", ...OFFICE_HANDOFF_DESTINATIONS];
|
|
8
|
+
const handoffEnvironmentSchema = schemas.enum(["production", "staging", "localhost"]);
|
|
9
|
+
const handoffStateSchema = schemas.enum(["issued", "launched", "claimed", "acknowledged", "failed"]);
|
|
10
|
+
const handoffReadinessStateSchema = schemas.enum([
|
|
11
|
+
"unsupported",
|
|
12
|
+
"unknown",
|
|
13
|
+
"not_registered",
|
|
14
|
+
"registered",
|
|
15
|
+
"stale"
|
|
16
|
+
]);
|
|
17
|
+
const handoffDestinationSchema = schemas.enum(HANDOFF_DESTINATIONS);
|
|
18
|
+
const destinationReadinessSchema = schemas.object({
|
|
19
|
+
destination: handoffDestinationSchema,
|
|
20
|
+
state: handoffReadinessStateSchema,
|
|
21
|
+
last_seen_at: schemas.string().nullable().optional(),
|
|
22
|
+
label: schemas.string().nullable().optional(),
|
|
23
|
+
marketplace_url: schemas.string().nullable().optional()
|
|
24
|
+
});
|
|
25
|
+
schemas.object({
|
|
26
|
+
environment: handoffEnvironmentSchema,
|
|
27
|
+
destinations: schemas.array(destinationReadinessSchema)
|
|
28
|
+
});
|
|
29
|
+
const threadHandoffViewSchema = schemas.object({
|
|
30
|
+
handoff_id: schemas.string(),
|
|
31
|
+
thread_id: schemas.string(),
|
|
32
|
+
session_asset_id: schemas.string().nullable(),
|
|
33
|
+
source: schemas.string(),
|
|
34
|
+
destination: handoffDestinationSchema,
|
|
35
|
+
environment: handoffEnvironmentSchema,
|
|
36
|
+
state: handoffStateSchema,
|
|
37
|
+
issued_at: schemas.string(),
|
|
38
|
+
expires_at: schemas.string(),
|
|
39
|
+
claimed_at: schemas.string().nullable(),
|
|
40
|
+
acknowledged_at: schemas.string().nullable(),
|
|
41
|
+
failure_reason: schemas.string().nullable(),
|
|
42
|
+
push_delivered: schemas.boolean().nullable()
|
|
43
|
+
});
|
|
44
|
+
const mobileHandoffLaunchSchema = schemas.object({
|
|
45
|
+
kind: schemas.literal("mobile"),
|
|
46
|
+
push_attempted: schemas.boolean(),
|
|
47
|
+
push_delivered: schemas.boolean(),
|
|
48
|
+
device_count: schemas.number(),
|
|
49
|
+
/** Universal/App Link (or browser URL) carrying only the opaque handoff id. */
|
|
50
|
+
link_url: schemas.string(),
|
|
51
|
+
/** Custom-scheme fallback for browsers that do not honour app links. */
|
|
52
|
+
scheme_url: schemas.string()
|
|
53
|
+
});
|
|
54
|
+
const officeHandoffLaunchSchema = schemas.object({
|
|
55
|
+
kind: schemas.literal("office"),
|
|
56
|
+
host: schemas.enum(OFFICE_HANDOFF_DESTINATIONS),
|
|
57
|
+
/** `ms-word:nft|u|<template>` — opens a blank document from the template. */
|
|
58
|
+
desktop_url: schemas.string(),
|
|
59
|
+
template_url: schemas.string(),
|
|
60
|
+
/** Office on the web: blank document, Athena opened from the ribbon. */
|
|
61
|
+
web_url: schemas.string(),
|
|
62
|
+
marketplace_url: schemas.string().nullable()
|
|
63
|
+
});
|
|
64
|
+
const chromeHandoffLaunchSchema = schemas.object({ kind: schemas.literal("chrome") });
|
|
65
|
+
const handoffLaunchSchema = schemas.discriminatedUnion("kind", [
|
|
66
|
+
mobileHandoffLaunchSchema,
|
|
67
|
+
officeHandoffLaunchSchema,
|
|
68
|
+
chromeHandoffLaunchSchema
|
|
69
|
+
]);
|
|
70
|
+
schemas.object({
|
|
71
|
+
handoff: threadHandoffViewSchema,
|
|
72
|
+
launch: handoffLaunchSchema
|
|
73
|
+
});
|
|
74
|
+
exports.HANDOFF_DESTINATIONS = HANDOFF_DESTINATIONS;
|
|
75
|
+
exports.OFFICE_HANDOFF_DESTINATIONS = OFFICE_HANDOFF_DESTINATIONS;
|
|
76
|
+
exports.chromeHandoffLaunchSchema = chromeHandoffLaunchSchema;
|
|
77
|
+
exports.destinationReadinessSchema = destinationReadinessSchema;
|
|
78
|
+
exports.handoffEnvironmentSchema = handoffEnvironmentSchema;
|
|
79
|
+
exports.handoffLaunchSchema = handoffLaunchSchema;
|
|
80
|
+
exports.handoffReadinessStateSchema = handoffReadinessStateSchema;
|
|
81
|
+
exports.handoffStateSchema = handoffStateSchema;
|
|
82
|
+
exports.mobileHandoffLaunchSchema = mobileHandoffLaunchSchema;
|
|
83
|
+
exports.officeHandoffLaunchSchema = officeHandoffLaunchSchema;
|
|
84
|
+
exports.threadHandoffViewSchema = threadHandoffViewSchema;
|
|
85
|
+
//# sourceMappingURL=thread-handoff.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"thread-handoff.cjs","sources":["../../../../../agent-runtime/src/thread-handoff.ts"],"sourcesContent":["/**\n * Cross-surface \"Continue in…\" thread handoff — the client half of agora's\n * `/api/handoffs` protocol shared by Olympus (Spaces) and Athena in Chrome.\n *\n * A handoff never carries conversation content. The server binds an opaque\n * `handoff_id` to the durable thread, and the destination surface (mobile app,\n * Office add-in) claims that id and acknowledges once it owns the thread. The\n * source surface closes its own copilot only after that acknowledgement.\n */\nimport qrcode from 'qrcode-generator';\nimport { z } from 'zod';\n\nexport const HANDOFF_DESTINATIONS = ['chrome', 'mobile', 'word', 'powerpoint', 'excel'] as const;\nexport type HandoffDestination = (typeof HANDOFF_DESTINATIONS)[number];\n\nexport const OFFICE_HANDOFF_DESTINATIONS = ['word', 'powerpoint', 'excel'] as const;\nexport type OfficeHandoffDestination = (typeof OFFICE_HANDOFF_DESTINATIONS)[number];\n\n/** Destinations agora brokers; Chrome is launched page-side by the extension. */\nexport const SERVER_HANDOFF_DESTINATIONS = ['mobile', ...OFFICE_HANDOFF_DESTINATIONS] as const;\nexport type ServerHandoffDestination = (typeof SERVER_HANDOFF_DESTINATIONS)[number];\n\nexport type HandoffSource = 'spaces' | 'chrome' | 'mobile' | 'word' | 'powerpoint' | 'excel';\n\nexport const handoffEnvironmentSchema = z.enum(['production', 'staging', 'localhost']);\nexport type HandoffEnvironment = z.infer<typeof handoffEnvironmentSchema>;\n\nexport const handoffStateSchema = z.enum(['issued', 'launched', 'claimed', 'acknowledged', 'failed']);\nexport type HandoffState = z.infer<typeof handoffStateSchema>;\n\nexport const handoffReadinessStateSchema = z.enum([\n 'unsupported',\n 'unknown',\n 'not_registered',\n 'registered',\n 'stale',\n]);\nexport type HandoffReadinessState = z.infer<typeof handoffReadinessStateSchema>;\n\nconst handoffDestinationSchema = z.enum(HANDOFF_DESTINATIONS);\n\nexport const destinationReadinessSchema = z.object({\n destination: handoffDestinationSchema,\n state: handoffReadinessStateSchema,\n last_seen_at: z.string().nullable().optional(),\n label: z.string().nullable().optional(),\n marketplace_url: z.string().nullable().optional(),\n});\nexport type DestinationReadiness = z.infer<typeof destinationReadinessSchema>;\n\nexport const handoffReadinessResponseSchema = z.object({\n environment: handoffEnvironmentSchema,\n destinations: z.array(destinationReadinessSchema),\n});\nexport type HandoffReadinessResponse = z.infer<typeof handoffReadinessResponseSchema>;\n\nexport const threadHandoffViewSchema = z.object({\n handoff_id: z.string(),\n thread_id: z.string(),\n session_asset_id: z.string().nullable(),\n source: z.string(),\n destination: handoffDestinationSchema,\n environment: handoffEnvironmentSchema,\n state: handoffStateSchema,\n issued_at: z.string(),\n expires_at: z.string(),\n claimed_at: z.string().nullable(),\n acknowledged_at: z.string().nullable(),\n failure_reason: z.string().nullable(),\n push_delivered: z.boolean().nullable(),\n});\nexport type ThreadHandoffView = z.infer<typeof threadHandoffViewSchema>;\n\nexport const mobileHandoffLaunchSchema = z.object({\n kind: z.literal('mobile'),\n push_attempted: z.boolean(),\n push_delivered: z.boolean(),\n device_count: z.number(),\n /** Universal/App Link (or browser URL) carrying only the opaque handoff id. */\n link_url: z.string(),\n /** Custom-scheme fallback for browsers that do not honour app links. */\n scheme_url: z.string(),\n});\nexport type MobileHandoffLaunch = z.infer<typeof mobileHandoffLaunchSchema>;\n\nexport const officeHandoffLaunchSchema = z.object({\n kind: z.literal('office'),\n host: z.enum(OFFICE_HANDOFF_DESTINATIONS),\n /** `ms-word:nft|u|<template>` — opens a blank document from the template. */\n desktop_url: z.string(),\n template_url: z.string(),\n /** Office on the web: blank document, Athena opened from the ribbon. */\n web_url: z.string(),\n marketplace_url: z.string().nullable(),\n});\nexport type OfficeHandoffLaunch = z.infer<typeof officeHandoffLaunchSchema>;\n\nexport const chromeHandoffLaunchSchema = z.object({ kind: z.literal('chrome') });\n\nexport const handoffLaunchSchema = z.discriminatedUnion('kind', [\n mobileHandoffLaunchSchema,\n officeHandoffLaunchSchema,\n chromeHandoffLaunchSchema,\n]);\nexport type HandoffLaunch = z.infer<typeof handoffLaunchSchema>;\n\nexport const createThreadHandoffResponseSchema = z.object({\n handoff: threadHandoffViewSchema,\n launch: handoffLaunchSchema,\n});\nexport type CreateThreadHandoffResponse = z.infer<typeof createThreadHandoffResponseSchema>;\n\nexport interface CreateThreadHandoffInput {\n threadId: string;\n destination: ServerHandoffDestination;\n source: HandoffSource;\n}\n\n/**\n * Surface-specific HTTP adapter. Olympus binds it to `apiClient`; the\n * extension binds it to its authenticated fetch. Both must resolve (not\n * reject) with parsed responses so the flow can attribute failures.\n */\nexport interface ThreadHandoffTransport {\n getReadiness: (options: { signal?: AbortSignal }) => Promise<HandoffReadinessResponse>;\n createHandoff: (input: CreateThreadHandoffInput) => Promise<CreateThreadHandoffResponse>;\n getHandoff: (options: { handoffId: string; signal?: AbortSignal }) => Promise<ThreadHandoffView>;\n}\n\nexport const HANDOFF_DESTINATION_LABELS: Record<HandoffDestination, string> = {\n chrome: 'Google Chrome',\n mobile: 'Athena Mobile',\n word: 'Microsoft Word',\n powerpoint: 'Microsoft PowerPoint',\n excel: 'Microsoft Excel',\n};\n\nexport const isOfficeHandoffDestination = (\n destination: HandoffDestination\n): destination is OfficeHandoffDestination =>\n (OFFICE_HANDOFF_DESTINATIONS as readonly string[]).includes(destination);\n\nexport const isServerHandoffDestination = (\n destination: HandoffDestination\n): destination is ServerHandoffDestination => destination !== 'chrome';\n\n/** Index a readiness response by destination; missing rows read as `unknown`. */\nexport const readinessByDestination = (\n response: HandoffReadinessResponse | null | undefined\n): Record<HandoffDestination, DestinationReadiness> => {\n const result = {} as Record<HandoffDestination, DestinationReadiness>;\n for (const destination of HANDOFF_DESTINATIONS) {\n result[destination] = { destination, state: 'unknown' };\n }\n for (const row of response?.destinations ?? []) {\n result[row.destination] = row;\n }\n return result;\n};\n\n/**\n * How a mobile handoff should be presented before the phone answers.\n *\n * - `app`: a fresh push token exists — the phone gets a notification and the\n * QR is only offered as a secondary path.\n * - `qr`: no usable device is known — lead with the QR / link.\n */\nexport type MobileLaunchMode = 'app' | 'qr';\n\nexport const selectMobileLaunchMode = (readiness: DestinationReadiness | null | undefined): MobileLaunchMode =>\n readiness?.state === 'registered' ? 'app' : 'qr';\n\n/**\n * What a \"Continue in…\" row should say for a destination given its readiness.\n * Shared so Spaces and the extension render identical copy.\n */\nexport interface HandoffDestinationPresentation {\n destination: HandoffDestination;\n label: string;\n /** Secondary line under the label; `null` when the row needs no caveat. */\n detail: string | null;\n /**\n * `install` rows do not create a handoff — they point at install help;\n * `unavailable` rows are disabled.\n */\n action: 'handoff' | 'install' | 'unavailable';\n}\n\nexport const presentHandoffDestination = ({\n destination,\n readiness,\n}: {\n destination: HandoffDestination;\n readiness: DestinationReadiness | null | undefined;\n}): HandoffDestinationPresentation => {\n const label = HANDOFF_DESTINATION_LABELS[destination];\n const state = readiness?.state ?? 'unknown';\n if (destination === 'chrome') {\n return { destination, label, detail: null, action: 'handoff' };\n }\n if (state === 'unsupported') {\n return { destination, label, detail: 'Not available in this environment', action: 'unavailable' };\n }\n if (destination === 'mobile') {\n switch (state) {\n case 'registered':\n return {\n destination,\n label,\n detail: readiness?.label ? `Notify ${readiness.label}` : 'Notify your phone',\n action: 'handoff',\n };\n case 'stale':\n return {\n destination,\n label,\n detail: readiness?.label\n ? `${readiness.label} hasn't checked in recently — scan a QR code`\n : 'Scan a QR code',\n action: 'handoff',\n };\n default:\n return { destination, label, detail: 'Scan a QR code', action: 'handoff' };\n }\n }\n switch (state) {\n case 'registered':\n return { destination, label, detail: 'Opens a blank document', action: 'handoff' };\n case 'stale':\n return {\n destination,\n label,\n detail: 'Opens a blank document — add-in last seen a while ago',\n action: 'handoff',\n };\n case 'not_registered':\n // Presence is only recorded once the pane has run, so \"not detected\" can\n // be a false negative; keep the handoff available unless a store exists.\n return {\n destination,\n label,\n detail: 'Athena add-in not detected',\n action: readiness?.marketplace_url ? 'install' : 'handoff',\n };\n default:\n return { destination, label, detail: 'Opens a blank document', action: 'handoff' };\n }\n};\n\n/**\n * QR symbol for a handoff link as a single SVG path in module units, so any\n * surface can render `<svg viewBox=\"0 0 {size} {size}\"><path d={path} /></svg>`\n * without a canvas or an image request.\n */\nexport const buildHandoffQrSvg = ({ text }: { text: string }): { size: number; path: string } => {\n const symbol = qrcode(0, 'M');\n symbol.addData(text);\n symbol.make();\n const size: number = symbol.getModuleCount();\n const segments: string[] = [];\n for (let row = 0; row < size; row += 1) {\n let runStart = -1;\n for (let col = 0; col <= size; col += 1) {\n const dark: boolean = col < size && symbol.isDark(row, col);\n if (dark && runStart === -1) {\n runStart = col;\n } else if (!dark && runStart !== -1) {\n segments.push(`M${runStart} ${row}h${col - runStart}v1h-${col - runStart}z`);\n runStart = -1;\n }\n }\n }\n return { size, path: segments.join('') };\n};\n\nexport type HandoffOutcome =\n | { status: 'acknowledged'; handoff: ThreadHandoffView }\n | { status: 'failed'; handoff: ThreadHandoffView | null; reason: string }\n | { status: 'timeout'; handoff: ThreadHandoffView | null }\n | { status: 'cancelled'; handoff: ThreadHandoffView | null };\n\nexport const isTerminalHandoffState = (state: HandoffState): boolean =>\n state === 'acknowledged' || state === 'failed';\n\nexport interface WaitForHandoffOutcomeOptions {\n transport: Pick<ThreadHandoffTransport, 'getHandoff'>;\n handoffId: string;\n /** Wall-clock budget; mobile pushes typically land in seconds, Office launches in tens of seconds. */\n timeoutMs: number;\n pollIntervalMs?: number;\n signal?: AbortSignal;\n onUpdate?: (handoff: ThreadHandoffView) => void;\n /** Injected for tests. */\n now?: () => number;\n sleep?: (ms: number, signal?: AbortSignal) => Promise<void>;\n}\n\nconst defaultSleep = (ms: number, signal?: AbortSignal): Promise<void> =>\n new Promise((resolve) => {\n if (signal?.aborted) {\n resolve();\n return;\n }\n const timer = setTimeout(() => {\n signal?.removeEventListener('abort', onAbort);\n resolve();\n }, ms);\n function onAbort() {\n clearTimeout(timer);\n resolve();\n }\n signal?.addEventListener('abort', onAbort, { once: true });\n });\n\n/**\n * Poll one handoff until the destination acknowledges, the server marks it\n * failed, the budget elapses, or the caller aborts. Each request is bound to\n * the remaining budget so a stalled response cannot outlive the deadline.\n * Retryable transport errors are retried within the budget; permanent ones\n * (auth, wrong deployment, malformed payload) fail immediately with their\n * actionable message. The last known view is reported.\n */\nexport async function waitForHandoffOutcome({\n transport,\n handoffId,\n timeoutMs,\n pollIntervalMs = 1500,\n signal,\n onUpdate,\n now = () => Date.now(),\n sleep = defaultSleep,\n}: WaitForHandoffOutcomeOptions): Promise<HandoffOutcome> {\n const deadline = now() + timeoutMs;\n let lastKnown: ThreadHandoffView | null = null;\n while (true) {\n if (signal?.aborted) {\n return { status: 'cancelled', handoff: lastKnown };\n }\n const budgetMs: number = deadline - now();\n if (budgetMs <= 0) {\n return { status: 'timeout', handoff: lastKnown };\n }\n const poll = new AbortController();\n const abortPoll = () => poll.abort();\n const pollDeadline = setTimeout(abortPoll, budgetMs);\n signal?.addEventListener('abort', abortPoll, { once: true });\n try {\n const view = await transport.getHandoff({ handoffId, signal: poll.signal });\n lastKnown = view;\n onUpdate?.(view);\n if (view.state === 'acknowledged') {\n return { status: 'acknowledged', handoff: view };\n }\n if (view.state === 'failed') {\n return {\n status: 'failed',\n handoff: view,\n reason: view.failure_reason ?? 'The destination could not open this conversation.',\n };\n }\n if (now() >= Date.parse(view.expires_at)) {\n return { status: 'timeout', handoff: view };\n }\n } catch (error) {\n if (signal?.aborted) {\n return { status: 'cancelled', handoff: lastKnown };\n }\n if (poll.signal.aborted) {\n return { status: 'timeout', handoff: lastKnown };\n }\n switch (classifyHandoffTransportError(error)) {\n case 'gone':\n return {\n status: 'failed',\n handoff: lastKnown,\n reason: 'This handoff expired before the destination opened it.',\n };\n case 'permanent':\n return { status: 'failed', handoff: lastKnown, reason: describeHandoffTransportError(error) };\n case 'retryable':\n break;\n }\n } finally {\n clearTimeout(pollDeadline);\n signal?.removeEventListener('abort', abortPoll);\n }\n const remainingMs: number = deadline - now();\n if (remainingMs <= 0) {\n return { status: 'timeout', handoff: lastKnown };\n }\n await sleep(Math.min(pollIntervalMs, remainingMs), signal);\n if (now() >= deadline) {\n return { status: 'timeout', handoff: lastKnown };\n }\n }\n}\n\n/** Error raised by a transport when the server no longer knows the handoff (404). */\nexport class HandoffGoneError extends Error {\n constructor(handoffId: string) {\n super(`Handoff ${handoffId} is no longer available`);\n this.name = 'HandoffGoneError';\n }\n}\n\nexport const isHandoffGoneError = (error: unknown): error is HandoffGoneError =>\n error instanceof HandoffGoneError ||\n (error instanceof Error && error.name === 'HandoffGoneError');\n\n/**\n * Transport failure that polling the same handoff can never recover from:\n * the session is signed out, the token belongs to another deployment, the\n * server rejected the request, or the payload did not match the schema.\n */\nexport class HandoffPermanentError extends Error {\n constructor(message: string, options?: { cause?: unknown }) {\n super(message, options);\n this.name = 'HandoffPermanentError';\n }\n}\n\nexport const isHandoffPermanentError = (error: unknown): error is HandoffPermanentError =>\n error instanceof HandoffPermanentError ||\n (error instanceof Error && error.name === 'HandoffPermanentError');\n\nexport type HandoffTransportErrorKind = 'gone' | 'permanent' | 'retryable';\n\n/**\n * Shared classification for both transports: only network failures and\n * retryable server statuses keep the poll alive. Schema mismatches surface as\n * `ZodError` from either adapter and are permanent.\n */\nexport const classifyHandoffTransportError = (error: unknown): HandoffTransportErrorKind => {\n if (isHandoffGoneError(error)) return 'gone';\n if (isHandoffPermanentError(error) || error instanceof z.ZodError) return 'permanent';\n return 'retryable';\n};\n\n/** 5xx, timeouts and rate limits may clear on the next poll; any other 4xx will not. */\nexport const isRetryableHandoffHttpStatus = (status: number): boolean =>\n status >= 500 || status === 408 || status === 429;\n\n/**\n * Map a non-2xx status to the transport error the poll loop understands.\n * `detail` is the server's actionable message when it sent one.\n */\nexport const handoffHttpError = ({\n handoffId,\n status,\n detail,\n}: {\n handoffId: string;\n status: number;\n detail: string | null;\n}): Error => {\n if (status === 404) return new HandoffGoneError(handoffId);\n const message: string = detail ?? `The handoff request failed (${status}).`;\n return isRetryableHandoffHttpStatus(status) ? new Error(message) : new HandoffPermanentError(message);\n};\n\nexport const describeHandoffTransportError = (error: unknown): string => {\n if (error instanceof z.ZodError) {\n return 'The server sent an unexpected response. Refresh and try again.';\n }\n if (error instanceof Error && error.message) return error.message;\n return 'Lost track of the handoff. Try again.';\n};\n\n/** `?handoff=<id>` — the query parameter mobile-web and QR links carry. */\nexport const HANDOFF_URL_PARAM = 'handoff';\n\n/**\n * Read a handoff id from a URL. Ids are server-issued, URL-safe tokens; anything\n * else is rejected so a tampered link never reaches the claim path.\n */\nexport const parseHandoffIdParam = (value: string | null | undefined): string | null => {\n if (!value) {\n return null;\n }\n return /^[A-Za-z0-9_-]{8,128}$/.test(value) ? value : null;\n};\n\n/** Athena Mobile's custom URL scheme (`mobile-app-react-native/app.json` → `scheme`). */\nexport const MOBILE_APP_SCHEME = 'athenavisionapplication';\n\n/**\n * Custom-scheme launch for a handoff — what a phone's browser opens when the\n * Universal/App Link landed on mobile web instead of in the app. Mirrors\n * agora's `_mobile_scheme_url`; carries only the opaque handoff id.\n */\nexport const buildMobileHandoffSchemeUrl = ({ handoffId }: { handoffId: string }): string =>\n `${MOBILE_APP_SCHEME}://chat?${HANDOFF_URL_PARAM}=${encodeURIComponent(handoffId)}`;\n"],"names":["z.enum","z.object","z.string","z.array","z.boolean","z.literal","z.number","z.discriminatedUnion"],"mappings":";;;;AAYO,MAAM,uBAAuB,CAAC,UAAU,UAAU,QAAQ,cAAc,OAAO;AAG/E,MAAM,8BAA8B,CAAC,QAAQ,cAAc,OAAO;AAI9B,CAAC,UAAU,GAAG,2BAA2B;AAK7E,MAAM,2BAA2BA,QAAAA,KAAO,CAAC,cAAc,WAAW,WAAW,CAAC;AAG9E,MAAM,qBAAqBA,QAAAA,KAAO,CAAC,UAAU,YAAY,WAAW,gBAAgB,QAAQ,CAAC;AAG7F,MAAM,8BAA8BA,QAAAA,KAAO;AAAA,EAChD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAGD,MAAM,2BAA2BA,QAAAA,KAAO,oBAAoB;AAErD,MAAM,6BAA6BC,QAAAA,OAAS;AAAA,EACjD,aAAa;AAAA,EACb,OAAO;AAAA,EACP,cAAcC,QAAAA,OAAE,EAAS,SAAA,EAAW,SAAA;AAAA,EACpC,OAAOA,QAAAA,OAAE,EAAS,SAAA,EAAW,SAAA;AAAA,EAC7B,iBAAiBA,QAAAA,OAAE,EAAS,SAAA,EAAW,SAAA;AACzC,CAAC;AAG6CD,QAAAA,OAAS;AAAA,EACrD,aAAa;AAAA,EACb,cAAcE,QAAAA,MAAQ,0BAA0B;AAClD,CAAC;AAGM,MAAM,0BAA0BF,QAAAA,OAAS;AAAA,EAC9C,YAAYC,QAAAA,OAAE;AAAA,EACd,WAAWA,QAAAA,OAAE;AAAA,EACb,kBAAkBA,QAAAA,OAAE,EAAS,SAAA;AAAA,EAC7B,QAAQA,QAAAA,OAAE;AAAA,EACV,aAAa;AAAA,EACb,aAAa;AAAA,EACb,OAAO;AAAA,EACP,WAAWA,QAAAA,OAAE;AAAA,EACb,YAAYA,QAAAA,OAAE;AAAA,EACd,YAAYA,QAAAA,OAAE,EAAS,SAAA;AAAA,EACvB,iBAAiBA,QAAAA,OAAE,EAAS,SAAA;AAAA,EAC5B,gBAAgBA,QAAAA,OAAE,EAAS,SAAA;AAAA,EAC3B,gBAAgBE,QAAAA,QAAE,EAAU,SAAA;AAC9B,CAAC;AAGM,MAAM,4BAA4BH,QAAAA,OAAS;AAAA,EAChD,MAAMI,QAAAA,QAAU,QAAQ;AAAA,EACxB,gBAAgBD,QAAAA,QAAE;AAAA,EAClB,gBAAgBA,QAAAA,QAAE;AAAA,EAClB,cAAcE,QAAAA,OAAE;AAAA;AAAA,EAEhB,UAAUJ,QAAAA,OAAE;AAAA;AAAA,EAEZ,YAAYA,QAAAA,OAAE;AAChB,CAAC;AAGM,MAAM,4BAA4BD,QAAAA,OAAS;AAAA,EAChD,MAAMI,QAAAA,QAAU,QAAQ;AAAA,EACxB,MAAML,QAAAA,KAAO,2BAA2B;AAAA;AAAA,EAExC,aAAaE,QAAAA,OAAE;AAAA,EACf,cAAcA,QAAAA,OAAE;AAAA;AAAA,EAEhB,SAASA,QAAAA,OAAE;AAAA,EACX,iBAAiBA,QAAAA,OAAE,EAAS,SAAA;AAC9B,CAAC;AAGM,MAAM,4BAA4BD,QAAAA,OAAS,EAAE,MAAMI,QAAAA,QAAU,QAAQ,EAAA,CAAG;AAExE,MAAM,sBAAsBE,QAAAA,mBAAqB,QAAQ;AAAA,EAC9D;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAGgDN,QAAAA,OAAS;AAAA,EACxD,SAAS;AAAA,EACT,QAAQ;AACV,CAAC;;;;;;;;;;;;"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import "../../../node_modules/qrcode-generator/dist/qrcode.js";
|
|
2
|
+
import { enum as _enum, object, string, boolean, literal, discriminatedUnion, array, number } from "../../../node_modules/zod/v4/classic/schemas.js";
|
|
3
|
+
const HANDOFF_DESTINATIONS = ["chrome", "mobile", "word", "powerpoint", "excel"];
|
|
4
|
+
const OFFICE_HANDOFF_DESTINATIONS = ["word", "powerpoint", "excel"];
|
|
5
|
+
["mobile", ...OFFICE_HANDOFF_DESTINATIONS];
|
|
6
|
+
const handoffEnvironmentSchema = _enum(["production", "staging", "localhost"]);
|
|
7
|
+
const handoffStateSchema = _enum(["issued", "launched", "claimed", "acknowledged", "failed"]);
|
|
8
|
+
const handoffReadinessStateSchema = _enum([
|
|
9
|
+
"unsupported",
|
|
10
|
+
"unknown",
|
|
11
|
+
"not_registered",
|
|
12
|
+
"registered",
|
|
13
|
+
"stale"
|
|
14
|
+
]);
|
|
15
|
+
const handoffDestinationSchema = _enum(HANDOFF_DESTINATIONS);
|
|
16
|
+
const destinationReadinessSchema = object({
|
|
17
|
+
destination: handoffDestinationSchema,
|
|
18
|
+
state: handoffReadinessStateSchema,
|
|
19
|
+
last_seen_at: string().nullable().optional(),
|
|
20
|
+
label: string().nullable().optional(),
|
|
21
|
+
marketplace_url: string().nullable().optional()
|
|
22
|
+
});
|
|
23
|
+
object({
|
|
24
|
+
environment: handoffEnvironmentSchema,
|
|
25
|
+
destinations: array(destinationReadinessSchema)
|
|
26
|
+
});
|
|
27
|
+
const threadHandoffViewSchema = object({
|
|
28
|
+
handoff_id: string(),
|
|
29
|
+
thread_id: string(),
|
|
30
|
+
session_asset_id: string().nullable(),
|
|
31
|
+
source: string(),
|
|
32
|
+
destination: handoffDestinationSchema,
|
|
33
|
+
environment: handoffEnvironmentSchema,
|
|
34
|
+
state: handoffStateSchema,
|
|
35
|
+
issued_at: string(),
|
|
36
|
+
expires_at: string(),
|
|
37
|
+
claimed_at: string().nullable(),
|
|
38
|
+
acknowledged_at: string().nullable(),
|
|
39
|
+
failure_reason: string().nullable(),
|
|
40
|
+
push_delivered: boolean().nullable()
|
|
41
|
+
});
|
|
42
|
+
const mobileHandoffLaunchSchema = object({
|
|
43
|
+
kind: literal("mobile"),
|
|
44
|
+
push_attempted: boolean(),
|
|
45
|
+
push_delivered: boolean(),
|
|
46
|
+
device_count: number(),
|
|
47
|
+
/** Universal/App Link (or browser URL) carrying only the opaque handoff id. */
|
|
48
|
+
link_url: string(),
|
|
49
|
+
/** Custom-scheme fallback for browsers that do not honour app links. */
|
|
50
|
+
scheme_url: string()
|
|
51
|
+
});
|
|
52
|
+
const officeHandoffLaunchSchema = object({
|
|
53
|
+
kind: literal("office"),
|
|
54
|
+
host: _enum(OFFICE_HANDOFF_DESTINATIONS),
|
|
55
|
+
/** `ms-word:nft|u|<template>` — opens a blank document from the template. */
|
|
56
|
+
desktop_url: string(),
|
|
57
|
+
template_url: string(),
|
|
58
|
+
/** Office on the web: blank document, Athena opened from the ribbon. */
|
|
59
|
+
web_url: string(),
|
|
60
|
+
marketplace_url: string().nullable()
|
|
61
|
+
});
|
|
62
|
+
const chromeHandoffLaunchSchema = object({ kind: literal("chrome") });
|
|
63
|
+
const handoffLaunchSchema = discriminatedUnion("kind", [
|
|
64
|
+
mobileHandoffLaunchSchema,
|
|
65
|
+
officeHandoffLaunchSchema,
|
|
66
|
+
chromeHandoffLaunchSchema
|
|
67
|
+
]);
|
|
68
|
+
object({
|
|
69
|
+
handoff: threadHandoffViewSchema,
|
|
70
|
+
launch: handoffLaunchSchema
|
|
71
|
+
});
|
|
72
|
+
export {
|
|
73
|
+
HANDOFF_DESTINATIONS,
|
|
74
|
+
OFFICE_HANDOFF_DESTINATIONS,
|
|
75
|
+
chromeHandoffLaunchSchema,
|
|
76
|
+
destinationReadinessSchema,
|
|
77
|
+
handoffEnvironmentSchema,
|
|
78
|
+
handoffLaunchSchema,
|
|
79
|
+
handoffReadinessStateSchema,
|
|
80
|
+
handoffStateSchema,
|
|
81
|
+
mobileHandoffLaunchSchema,
|
|
82
|
+
officeHandoffLaunchSchema,
|
|
83
|
+
threadHandoffViewSchema
|
|
84
|
+
};
|
|
85
|
+
//# sourceMappingURL=thread-handoff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"thread-handoff.js","sources":["../../../../../agent-runtime/src/thread-handoff.ts"],"sourcesContent":["/**\n * Cross-surface \"Continue in…\" thread handoff — the client half of agora's\n * `/api/handoffs` protocol shared by Olympus (Spaces) and Athena in Chrome.\n *\n * A handoff never carries conversation content. The server binds an opaque\n * `handoff_id` to the durable thread, and the destination surface (mobile app,\n * Office add-in) claims that id and acknowledges once it owns the thread. The\n * source surface closes its own copilot only after that acknowledgement.\n */\nimport qrcode from 'qrcode-generator';\nimport { z } from 'zod';\n\nexport const HANDOFF_DESTINATIONS = ['chrome', 'mobile', 'word', 'powerpoint', 'excel'] as const;\nexport type HandoffDestination = (typeof HANDOFF_DESTINATIONS)[number];\n\nexport const OFFICE_HANDOFF_DESTINATIONS = ['word', 'powerpoint', 'excel'] as const;\nexport type OfficeHandoffDestination = (typeof OFFICE_HANDOFF_DESTINATIONS)[number];\n\n/** Destinations agora brokers; Chrome is launched page-side by the extension. */\nexport const SERVER_HANDOFF_DESTINATIONS = ['mobile', ...OFFICE_HANDOFF_DESTINATIONS] as const;\nexport type ServerHandoffDestination = (typeof SERVER_HANDOFF_DESTINATIONS)[number];\n\nexport type HandoffSource = 'spaces' | 'chrome' | 'mobile' | 'word' | 'powerpoint' | 'excel';\n\nexport const handoffEnvironmentSchema = z.enum(['production', 'staging', 'localhost']);\nexport type HandoffEnvironment = z.infer<typeof handoffEnvironmentSchema>;\n\nexport const handoffStateSchema = z.enum(['issued', 'launched', 'claimed', 'acknowledged', 'failed']);\nexport type HandoffState = z.infer<typeof handoffStateSchema>;\n\nexport const handoffReadinessStateSchema = z.enum([\n 'unsupported',\n 'unknown',\n 'not_registered',\n 'registered',\n 'stale',\n]);\nexport type HandoffReadinessState = z.infer<typeof handoffReadinessStateSchema>;\n\nconst handoffDestinationSchema = z.enum(HANDOFF_DESTINATIONS);\n\nexport const destinationReadinessSchema = z.object({\n destination: handoffDestinationSchema,\n state: handoffReadinessStateSchema,\n last_seen_at: z.string().nullable().optional(),\n label: z.string().nullable().optional(),\n marketplace_url: z.string().nullable().optional(),\n});\nexport type DestinationReadiness = z.infer<typeof destinationReadinessSchema>;\n\nexport const handoffReadinessResponseSchema = z.object({\n environment: handoffEnvironmentSchema,\n destinations: z.array(destinationReadinessSchema),\n});\nexport type HandoffReadinessResponse = z.infer<typeof handoffReadinessResponseSchema>;\n\nexport const threadHandoffViewSchema = z.object({\n handoff_id: z.string(),\n thread_id: z.string(),\n session_asset_id: z.string().nullable(),\n source: z.string(),\n destination: handoffDestinationSchema,\n environment: handoffEnvironmentSchema,\n state: handoffStateSchema,\n issued_at: z.string(),\n expires_at: z.string(),\n claimed_at: z.string().nullable(),\n acknowledged_at: z.string().nullable(),\n failure_reason: z.string().nullable(),\n push_delivered: z.boolean().nullable(),\n});\nexport type ThreadHandoffView = z.infer<typeof threadHandoffViewSchema>;\n\nexport const mobileHandoffLaunchSchema = z.object({\n kind: z.literal('mobile'),\n push_attempted: z.boolean(),\n push_delivered: z.boolean(),\n device_count: z.number(),\n /** Universal/App Link (or browser URL) carrying only the opaque handoff id. */\n link_url: z.string(),\n /** Custom-scheme fallback for browsers that do not honour app links. */\n scheme_url: z.string(),\n});\nexport type MobileHandoffLaunch = z.infer<typeof mobileHandoffLaunchSchema>;\n\nexport const officeHandoffLaunchSchema = z.object({\n kind: z.literal('office'),\n host: z.enum(OFFICE_HANDOFF_DESTINATIONS),\n /** `ms-word:nft|u|<template>` — opens a blank document from the template. */\n desktop_url: z.string(),\n template_url: z.string(),\n /** Office on the web: blank document, Athena opened from the ribbon. */\n web_url: z.string(),\n marketplace_url: z.string().nullable(),\n});\nexport type OfficeHandoffLaunch = z.infer<typeof officeHandoffLaunchSchema>;\n\nexport const chromeHandoffLaunchSchema = z.object({ kind: z.literal('chrome') });\n\nexport const handoffLaunchSchema = z.discriminatedUnion('kind', [\n mobileHandoffLaunchSchema,\n officeHandoffLaunchSchema,\n chromeHandoffLaunchSchema,\n]);\nexport type HandoffLaunch = z.infer<typeof handoffLaunchSchema>;\n\nexport const createThreadHandoffResponseSchema = z.object({\n handoff: threadHandoffViewSchema,\n launch: handoffLaunchSchema,\n});\nexport type CreateThreadHandoffResponse = z.infer<typeof createThreadHandoffResponseSchema>;\n\nexport interface CreateThreadHandoffInput {\n threadId: string;\n destination: ServerHandoffDestination;\n source: HandoffSource;\n}\n\n/**\n * Surface-specific HTTP adapter. Olympus binds it to `apiClient`; the\n * extension binds it to its authenticated fetch. Both must resolve (not\n * reject) with parsed responses so the flow can attribute failures.\n */\nexport interface ThreadHandoffTransport {\n getReadiness: (options: { signal?: AbortSignal }) => Promise<HandoffReadinessResponse>;\n createHandoff: (input: CreateThreadHandoffInput) => Promise<CreateThreadHandoffResponse>;\n getHandoff: (options: { handoffId: string; signal?: AbortSignal }) => Promise<ThreadHandoffView>;\n}\n\nexport const HANDOFF_DESTINATION_LABELS: Record<HandoffDestination, string> = {\n chrome: 'Google Chrome',\n mobile: 'Athena Mobile',\n word: 'Microsoft Word',\n powerpoint: 'Microsoft PowerPoint',\n excel: 'Microsoft Excel',\n};\n\nexport const isOfficeHandoffDestination = (\n destination: HandoffDestination\n): destination is OfficeHandoffDestination =>\n (OFFICE_HANDOFF_DESTINATIONS as readonly string[]).includes(destination);\n\nexport const isServerHandoffDestination = (\n destination: HandoffDestination\n): destination is ServerHandoffDestination => destination !== 'chrome';\n\n/** Index a readiness response by destination; missing rows read as `unknown`. */\nexport const readinessByDestination = (\n response: HandoffReadinessResponse | null | undefined\n): Record<HandoffDestination, DestinationReadiness> => {\n const result = {} as Record<HandoffDestination, DestinationReadiness>;\n for (const destination of HANDOFF_DESTINATIONS) {\n result[destination] = { destination, state: 'unknown' };\n }\n for (const row of response?.destinations ?? []) {\n result[row.destination] = row;\n }\n return result;\n};\n\n/**\n * How a mobile handoff should be presented before the phone answers.\n *\n * - `app`: a fresh push token exists — the phone gets a notification and the\n * QR is only offered as a secondary path.\n * - `qr`: no usable device is known — lead with the QR / link.\n */\nexport type MobileLaunchMode = 'app' | 'qr';\n\nexport const selectMobileLaunchMode = (readiness: DestinationReadiness | null | undefined): MobileLaunchMode =>\n readiness?.state === 'registered' ? 'app' : 'qr';\n\n/**\n * What a \"Continue in…\" row should say for a destination given its readiness.\n * Shared so Spaces and the extension render identical copy.\n */\nexport interface HandoffDestinationPresentation {\n destination: HandoffDestination;\n label: string;\n /** Secondary line under the label; `null` when the row needs no caveat. */\n detail: string | null;\n /**\n * `install` rows do not create a handoff — they point at install help;\n * `unavailable` rows are disabled.\n */\n action: 'handoff' | 'install' | 'unavailable';\n}\n\nexport const presentHandoffDestination = ({\n destination,\n readiness,\n}: {\n destination: HandoffDestination;\n readiness: DestinationReadiness | null | undefined;\n}): HandoffDestinationPresentation => {\n const label = HANDOFF_DESTINATION_LABELS[destination];\n const state = readiness?.state ?? 'unknown';\n if (destination === 'chrome') {\n return { destination, label, detail: null, action: 'handoff' };\n }\n if (state === 'unsupported') {\n return { destination, label, detail: 'Not available in this environment', action: 'unavailable' };\n }\n if (destination === 'mobile') {\n switch (state) {\n case 'registered':\n return {\n destination,\n label,\n detail: readiness?.label ? `Notify ${readiness.label}` : 'Notify your phone',\n action: 'handoff',\n };\n case 'stale':\n return {\n destination,\n label,\n detail: readiness?.label\n ? `${readiness.label} hasn't checked in recently — scan a QR code`\n : 'Scan a QR code',\n action: 'handoff',\n };\n default:\n return { destination, label, detail: 'Scan a QR code', action: 'handoff' };\n }\n }\n switch (state) {\n case 'registered':\n return { destination, label, detail: 'Opens a blank document', action: 'handoff' };\n case 'stale':\n return {\n destination,\n label,\n detail: 'Opens a blank document — add-in last seen a while ago',\n action: 'handoff',\n };\n case 'not_registered':\n // Presence is only recorded once the pane has run, so \"not detected\" can\n // be a false negative; keep the handoff available unless a store exists.\n return {\n destination,\n label,\n detail: 'Athena add-in not detected',\n action: readiness?.marketplace_url ? 'install' : 'handoff',\n };\n default:\n return { destination, label, detail: 'Opens a blank document', action: 'handoff' };\n }\n};\n\n/**\n * QR symbol for a handoff link as a single SVG path in module units, so any\n * surface can render `<svg viewBox=\"0 0 {size} {size}\"><path d={path} /></svg>`\n * without a canvas or an image request.\n */\nexport const buildHandoffQrSvg = ({ text }: { text: string }): { size: number; path: string } => {\n const symbol = qrcode(0, 'M');\n symbol.addData(text);\n symbol.make();\n const size: number = symbol.getModuleCount();\n const segments: string[] = [];\n for (let row = 0; row < size; row += 1) {\n let runStart = -1;\n for (let col = 0; col <= size; col += 1) {\n const dark: boolean = col < size && symbol.isDark(row, col);\n if (dark && runStart === -1) {\n runStart = col;\n } else if (!dark && runStart !== -1) {\n segments.push(`M${runStart} ${row}h${col - runStart}v1h-${col - runStart}z`);\n runStart = -1;\n }\n }\n }\n return { size, path: segments.join('') };\n};\n\nexport type HandoffOutcome =\n | { status: 'acknowledged'; handoff: ThreadHandoffView }\n | { status: 'failed'; handoff: ThreadHandoffView | null; reason: string }\n | { status: 'timeout'; handoff: ThreadHandoffView | null }\n | { status: 'cancelled'; handoff: ThreadHandoffView | null };\n\nexport const isTerminalHandoffState = (state: HandoffState): boolean =>\n state === 'acknowledged' || state === 'failed';\n\nexport interface WaitForHandoffOutcomeOptions {\n transport: Pick<ThreadHandoffTransport, 'getHandoff'>;\n handoffId: string;\n /** Wall-clock budget; mobile pushes typically land in seconds, Office launches in tens of seconds. */\n timeoutMs: number;\n pollIntervalMs?: number;\n signal?: AbortSignal;\n onUpdate?: (handoff: ThreadHandoffView) => void;\n /** Injected for tests. */\n now?: () => number;\n sleep?: (ms: number, signal?: AbortSignal) => Promise<void>;\n}\n\nconst defaultSleep = (ms: number, signal?: AbortSignal): Promise<void> =>\n new Promise((resolve) => {\n if (signal?.aborted) {\n resolve();\n return;\n }\n const timer = setTimeout(() => {\n signal?.removeEventListener('abort', onAbort);\n resolve();\n }, ms);\n function onAbort() {\n clearTimeout(timer);\n resolve();\n }\n signal?.addEventListener('abort', onAbort, { once: true });\n });\n\n/**\n * Poll one handoff until the destination acknowledges, the server marks it\n * failed, the budget elapses, or the caller aborts. Each request is bound to\n * the remaining budget so a stalled response cannot outlive the deadline.\n * Retryable transport errors are retried within the budget; permanent ones\n * (auth, wrong deployment, malformed payload) fail immediately with their\n * actionable message. The last known view is reported.\n */\nexport async function waitForHandoffOutcome({\n transport,\n handoffId,\n timeoutMs,\n pollIntervalMs = 1500,\n signal,\n onUpdate,\n now = () => Date.now(),\n sleep = defaultSleep,\n}: WaitForHandoffOutcomeOptions): Promise<HandoffOutcome> {\n const deadline = now() + timeoutMs;\n let lastKnown: ThreadHandoffView | null = null;\n while (true) {\n if (signal?.aborted) {\n return { status: 'cancelled', handoff: lastKnown };\n }\n const budgetMs: number = deadline - now();\n if (budgetMs <= 0) {\n return { status: 'timeout', handoff: lastKnown };\n }\n const poll = new AbortController();\n const abortPoll = () => poll.abort();\n const pollDeadline = setTimeout(abortPoll, budgetMs);\n signal?.addEventListener('abort', abortPoll, { once: true });\n try {\n const view = await transport.getHandoff({ handoffId, signal: poll.signal });\n lastKnown = view;\n onUpdate?.(view);\n if (view.state === 'acknowledged') {\n return { status: 'acknowledged', handoff: view };\n }\n if (view.state === 'failed') {\n return {\n status: 'failed',\n handoff: view,\n reason: view.failure_reason ?? 'The destination could not open this conversation.',\n };\n }\n if (now() >= Date.parse(view.expires_at)) {\n return { status: 'timeout', handoff: view };\n }\n } catch (error) {\n if (signal?.aborted) {\n return { status: 'cancelled', handoff: lastKnown };\n }\n if (poll.signal.aborted) {\n return { status: 'timeout', handoff: lastKnown };\n }\n switch (classifyHandoffTransportError(error)) {\n case 'gone':\n return {\n status: 'failed',\n handoff: lastKnown,\n reason: 'This handoff expired before the destination opened it.',\n };\n case 'permanent':\n return { status: 'failed', handoff: lastKnown, reason: describeHandoffTransportError(error) };\n case 'retryable':\n break;\n }\n } finally {\n clearTimeout(pollDeadline);\n signal?.removeEventListener('abort', abortPoll);\n }\n const remainingMs: number = deadline - now();\n if (remainingMs <= 0) {\n return { status: 'timeout', handoff: lastKnown };\n }\n await sleep(Math.min(pollIntervalMs, remainingMs), signal);\n if (now() >= deadline) {\n return { status: 'timeout', handoff: lastKnown };\n }\n }\n}\n\n/** Error raised by a transport when the server no longer knows the handoff (404). */\nexport class HandoffGoneError extends Error {\n constructor(handoffId: string) {\n super(`Handoff ${handoffId} is no longer available`);\n this.name = 'HandoffGoneError';\n }\n}\n\nexport const isHandoffGoneError = (error: unknown): error is HandoffGoneError =>\n error instanceof HandoffGoneError ||\n (error instanceof Error && error.name === 'HandoffGoneError');\n\n/**\n * Transport failure that polling the same handoff can never recover from:\n * the session is signed out, the token belongs to another deployment, the\n * server rejected the request, or the payload did not match the schema.\n */\nexport class HandoffPermanentError extends Error {\n constructor(message: string, options?: { cause?: unknown }) {\n super(message, options);\n this.name = 'HandoffPermanentError';\n }\n}\n\nexport const isHandoffPermanentError = (error: unknown): error is HandoffPermanentError =>\n error instanceof HandoffPermanentError ||\n (error instanceof Error && error.name === 'HandoffPermanentError');\n\nexport type HandoffTransportErrorKind = 'gone' | 'permanent' | 'retryable';\n\n/**\n * Shared classification for both transports: only network failures and\n * retryable server statuses keep the poll alive. Schema mismatches surface as\n * `ZodError` from either adapter and are permanent.\n */\nexport const classifyHandoffTransportError = (error: unknown): HandoffTransportErrorKind => {\n if (isHandoffGoneError(error)) return 'gone';\n if (isHandoffPermanentError(error) || error instanceof z.ZodError) return 'permanent';\n return 'retryable';\n};\n\n/** 5xx, timeouts and rate limits may clear on the next poll; any other 4xx will not. */\nexport const isRetryableHandoffHttpStatus = (status: number): boolean =>\n status >= 500 || status === 408 || status === 429;\n\n/**\n * Map a non-2xx status to the transport error the poll loop understands.\n * `detail` is the server's actionable message when it sent one.\n */\nexport const handoffHttpError = ({\n handoffId,\n status,\n detail,\n}: {\n handoffId: string;\n status: number;\n detail: string | null;\n}): Error => {\n if (status === 404) return new HandoffGoneError(handoffId);\n const message: string = detail ?? `The handoff request failed (${status}).`;\n return isRetryableHandoffHttpStatus(status) ? new Error(message) : new HandoffPermanentError(message);\n};\n\nexport const describeHandoffTransportError = (error: unknown): string => {\n if (error instanceof z.ZodError) {\n return 'The server sent an unexpected response. Refresh and try again.';\n }\n if (error instanceof Error && error.message) return error.message;\n return 'Lost track of the handoff. Try again.';\n};\n\n/** `?handoff=<id>` — the query parameter mobile-web and QR links carry. */\nexport const HANDOFF_URL_PARAM = 'handoff';\n\n/**\n * Read a handoff id from a URL. Ids are server-issued, URL-safe tokens; anything\n * else is rejected so a tampered link never reaches the claim path.\n */\nexport const parseHandoffIdParam = (value: string | null | undefined): string | null => {\n if (!value) {\n return null;\n }\n return /^[A-Za-z0-9_-]{8,128}$/.test(value) ? value : null;\n};\n\n/** Athena Mobile's custom URL scheme (`mobile-app-react-native/app.json` → `scheme`). */\nexport const MOBILE_APP_SCHEME = 'athenavisionapplication';\n\n/**\n * Custom-scheme launch for a handoff — what a phone's browser opens when the\n * Universal/App Link landed on mobile web instead of in the app. Mirrors\n * agora's `_mobile_scheme_url`; carries only the opaque handoff id.\n */\nexport const buildMobileHandoffSchemeUrl = ({ handoffId }: { handoffId: string }): string =>\n `${MOBILE_APP_SCHEME}://chat?${HANDOFF_URL_PARAM}=${encodeURIComponent(handoffId)}`;\n"],"names":["z.enum","z.object","z.string","z.array","z.boolean","z.literal","z.number","z.discriminatedUnion"],"mappings":";;AAYO,MAAM,uBAAuB,CAAC,UAAU,UAAU,QAAQ,cAAc,OAAO;AAG/E,MAAM,8BAA8B,CAAC,QAAQ,cAAc,OAAO;AAI9B,CAAC,UAAU,GAAG,2BAA2B;AAK7E,MAAM,2BAA2BA,MAAO,CAAC,cAAc,WAAW,WAAW,CAAC;AAG9E,MAAM,qBAAqBA,MAAO,CAAC,UAAU,YAAY,WAAW,gBAAgB,QAAQ,CAAC;AAG7F,MAAM,8BAA8BA,MAAO;AAAA,EAChD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAGD,MAAM,2BAA2BA,MAAO,oBAAoB;AAErD,MAAM,6BAA6BC,OAAS;AAAA,EACjD,aAAa;AAAA,EACb,OAAO;AAAA,EACP,cAAcC,OAAE,EAAS,SAAA,EAAW,SAAA;AAAA,EACpC,OAAOA,OAAE,EAAS,SAAA,EAAW,SAAA;AAAA,EAC7B,iBAAiBA,OAAE,EAAS,SAAA,EAAW,SAAA;AACzC,CAAC;AAG6CD,OAAS;AAAA,EACrD,aAAa;AAAA,EACb,cAAcE,MAAQ,0BAA0B;AAClD,CAAC;AAGM,MAAM,0BAA0BF,OAAS;AAAA,EAC9C,YAAYC,OAAE;AAAA,EACd,WAAWA,OAAE;AAAA,EACb,kBAAkBA,OAAE,EAAS,SAAA;AAAA,EAC7B,QAAQA,OAAE;AAAA,EACV,aAAa;AAAA,EACb,aAAa;AAAA,EACb,OAAO;AAAA,EACP,WAAWA,OAAE;AAAA,EACb,YAAYA,OAAE;AAAA,EACd,YAAYA,OAAE,EAAS,SAAA;AAAA,EACvB,iBAAiBA,OAAE,EAAS,SAAA;AAAA,EAC5B,gBAAgBA,OAAE,EAAS,SAAA;AAAA,EAC3B,gBAAgBE,QAAE,EAAU,SAAA;AAC9B,CAAC;AAGM,MAAM,4BAA4BH,OAAS;AAAA,EAChD,MAAMI,QAAU,QAAQ;AAAA,EACxB,gBAAgBD,QAAE;AAAA,EAClB,gBAAgBA,QAAE;AAAA,EAClB,cAAcE,OAAE;AAAA;AAAA,EAEhB,UAAUJ,OAAE;AAAA;AAAA,EAEZ,YAAYA,OAAE;AAChB,CAAC;AAGM,MAAM,4BAA4BD,OAAS;AAAA,EAChD,MAAMI,QAAU,QAAQ;AAAA,EACxB,MAAML,MAAO,2BAA2B;AAAA;AAAA,EAExC,aAAaE,OAAE;AAAA,EACf,cAAcA,OAAE;AAAA;AAAA,EAEhB,SAASA,OAAE;AAAA,EACX,iBAAiBA,OAAE,EAAS,SAAA;AAC9B,CAAC;AAGM,MAAM,4BAA4BD,OAAS,EAAE,MAAMI,QAAU,QAAQ,EAAA,CAAG;AAExE,MAAM,sBAAsBE,mBAAqB,QAAQ;AAAA,EAC9D;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAGgDN,OAAS;AAAA,EACxD,SAAS;AAAA,EACT,QAAQ;AACV,CAAC;"}
|
|
@@ -67,7 +67,7 @@ function toThreadSnapshotSeed(state) {
|
|
|
67
67
|
const {
|
|
68
68
|
__interrupt__: _interrupt,
|
|
69
69
|
ptc_subagents: _ptcSubagents,
|
|
70
|
-
|
|
70
|
+
outOfBandRunActive: _outOfBandRunActive,
|
|
71
71
|
...rest
|
|
72
72
|
} = state;
|
|
73
73
|
return { ...rest, runs: seedRunsLane(state.runs) };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"thread-snapshot-cache.cjs","sources":["../../../../../agent-runtime/src/thread-snapshot-cache.ts"],"sourcesContent":["import type { DeepAgentStreamValues } from './types';\n\n/**\n * Per-tab LRU of the last wire state seen per thread, so a switch back to a\n * recently-visited thread can mount with that snapshot instead of an empty\n * \"loading\" state (see `UseDeepAgentThreadOptions.snapshotCache`). Entries are\n * references into states the wire already produced — remembering is O(1) and\n * copies nothing; the bound is `maxThreads` retained states.\n */\nexport interface DeepAgentThreadSnapshotCache {\n /** Record the latest hydrated wire state for a thread. */\n remember(threadId: string, state: DeepAgentStreamValues): void;\n /** The freshest remembered state for a thread, or `undefined` when none is\n * held or the entry aged out. */\n take(threadId: string): DeepAgentStreamValues | undefined;\n /**\n * Drop a single thread's entry. Called when the server refuses\n * authorization for the thread — a refused thread must not warm-paint on\n * its next mount.\n */\n forget(threadId: string): void;\n /**\n * Drop every entry. Called when the acting identity changes — a snapshot\n * captured under one account must never seed a mount under another.\n */\n clear(): void;\n /**\n * Whether a fresh entry is held for the thread, without consuming or\n * touching it. Hosts read it at selection time to decide whether the switch\n * can paint from a seed (no loading treatment) or must cover the attach\n * wait. Optional so hand-rolled caches keep type-checking; the built-in\n * cache implements it.\n */\n peek?(threadId: string): boolean;\n}\n\n/** A write observed on the cache — the durable-mirror seam (see `onChange`). */\nexport type DeepAgentThreadSnapshotCacheChange =\n | { type: 'remember'; threadId: string; state: DeepAgentStreamValues; at: number }\n | { type: 'forget'; threadId: string }\n | { type: 'clear' };\n\nexport interface DeepAgentThreadSnapshotCacheOptions {\n /** Threads retained; the least-recently written entry evicts beyond this. */\n maxThreads?: number;\n /**\n * Entries older than this are dropped at read time. The seed's value is\n * \"the thread you just left\" — a snapshot from long ago is more likely to\n * flash content the server has since rewritten than to save a paint.\n */\n maxAgeMs?: number;\n /**\n * Observer of every write (`remember`, `forget`, `clear`). Hosts mirror\n * entries into durable storage from here so a reload can seed too; the\n * observer is never invoked for `restore`, which re-admits mirrored entries.\n * Exceptions are swallowed — a mirror fault must not break the wire path.\n */\n onChange?: (change: DeepAgentThreadSnapshotCacheChange) => void;\n /** Injectable clock (tests). */\n now?: () => number;\n}\n\n/** The built-in cache: the host contract plus the durable-mirror re-admission. */\nexport interface CreatedDeepAgentThreadSnapshotCache extends DeepAgentThreadSnapshotCache {\n peek(threadId: string): boolean;\n /**\n * Re-admit an entry captured earlier — a mirrored snapshot after a reload —\n * under its ORIGINAL capture time, so the age bound still applies. Never\n * displaces a fresher in-memory entry and never notifies `onChange`.\n */\n restore(threadId: string, state: DeepAgentStreamValues, at: number): void;\n}\n\nconst DEFAULT_MAX_THREADS = 30;\nconst DEFAULT_MAX_AGE_MS = 60 * 60 * 1000;\n\nexport function createDeepAgentThreadSnapshotCache(\n options: DeepAgentThreadSnapshotCacheOptions = {}\n): CreatedDeepAgentThreadSnapshotCache {\n const maxThreads = options.maxThreads ?? DEFAULT_MAX_THREADS;\n const maxAgeMs = options.maxAgeMs ?? DEFAULT_MAX_AGE_MS;\n const now = options.now ?? (() => Date.now());\n const notify = (change: DeepAgentThreadSnapshotCacheChange): void => {\n try {\n options.onChange?.(change);\n } catch {\n // The mirror is best-effort; the in-memory cache stays authoritative.\n }\n };\n // Map iteration order is insertion order; delete-then-set on every write\n // keeps the first key the least-recently written.\n const entries = new Map<string, { state: DeepAgentStreamValues; at: number }>();\n const evict = (): void => {\n while (entries.size > maxThreads) {\n const oldest = entries.keys().next().value;\n if (oldest === undefined) break;\n entries.delete(oldest);\n }\n };\n const fresh = (threadId: string): { state: DeepAgentStreamValues; at: number } | undefined => {\n const entry = entries.get(threadId);\n if (!entry) return undefined;\n if (now() - entry.at > maxAgeMs) {\n entries.delete(threadId);\n return undefined;\n }\n return entry;\n };\n return {\n remember(threadId, state) {\n const at = now();\n entries.delete(threadId);\n entries.set(threadId, { state, at });\n evict();\n notify({ type: 'remember', threadId, state, at });\n },\n take(threadId) {\n return fresh(threadId)?.state;\n },\n peek(threadId) {\n return fresh(threadId) !== undefined;\n },\n restore(threadId, state, at) {\n if (now() - at > maxAgeMs) return;\n const existing = entries.get(threadId);\n if (existing && existing.at >= at) return;\n entries.delete(threadId);\n entries.set(threadId, { state, at });\n evict();\n },\n forget(threadId) {\n entries.delete(threadId);\n notify({ type: 'forget', threadId });\n },\n clear() {\n entries.clear();\n notify({ type: 'clear' });\n },\n };\n}\n\n/**\n * Shape a remembered wire state for use as a mount's `initialState`.\n *\n * Messages/todos and the other app-owned keys seed as captured. The run lane\n * seeds by what the user left: a thread whose run was `running` when the\n * snapshot was taken seeds a running entry (see {@link seedRunsLane}) so the\n * switch back reads Running with a Stop control on its first frame — the same\n * frame `runs: []` would render as \"ready\" with a Send button over a thread the\n * server is still mid-run on. Every other live-lifecycle lane normalizes to\n * resting: a seeded input request could collect a decision the server may have\n * already resolved, so an `input-required` entry seeds as no run at all.\n * `__interrupt__` is dropped for the same reason, and so are `ptc_subagents`\n * (the live in-eval dispatch lane — a seeded \"running\" dispatch card under a\n * resting thread would render active with no eval behind it) and\n * `legacyRunActive` (the host's out-of-band-run-in-progress flag — seeding a\n * finished run's `true` would hide the composer until the attach corrected\n * it). Durable thread properties (`runConfig`, `sessionPersisted`, …) seed as\n * captured; the attach snapshot then replaces the whole seed.\n */\nexport function toThreadSnapshotSeed(state: DeepAgentStreamValues): DeepAgentStreamValues {\n const {\n __interrupt__: _interrupt,\n ptc_subagents: _ptcSubagents,\n legacyRunActive: _legacyRunActive,\n ...rest\n } = state;\n return { ...rest, runs: seedRunsLane(state.runs) };\n}\n\n/**\n * The run entries a seed carries.\n *\n * Only a `running` entry survives, and only its replicated identity and its\n * durable lanes: `runId` (the target of a Stop sent during the attach\n * round-trip — the live run's id if it is still running, and a refused stop\n * if it finished meanwhile), `epoch` (rides that Stop so a replayed stop cannot\n * end a later leg) and the `queue`/`steerQueue` entries (server lanes the tray\n * renders). The in-flight records rest until the snapshot: `dispatching` and\n * `nextDispatch` are batches the materializer would re-apply onto messages\n * that already contain them, `stopping` is a rewind the host may have\n * finished, `inputRequests` is a parked decision, and `error` is a failure the\n * next user run clears. An entry in any other status — `stopped`, `error`,\n * `input-required` — seeds as no run.\n */\nfunction seedRunsLane(\n runs: DeepAgentStreamValues['runs']\n): NonNullable<DeepAgentStreamValues['runs']> {\n const live = runs?.[0];\n if (live === undefined || live.status !== 'running') return [];\n return [\n {\n runId: live.runId,\n status: 'running',\n queue: live.queue,\n steerQueue: live.steerQueue,\n ...(live.epoch !== undefined && { epoch: live.epoch }),\n },\n ];\n}\n"],"names":[],"mappings":";;AAyEA,MAAM,sBAAsB;AAC5B,MAAM,qBAAqB,KAAK,KAAK;AAE9B,SAAS,mCACd,UAA+C,IACV;AACrC,QAAM,aAAa,QAAQ,cAAc;AACzC,QAAM,WAAW,QAAQ,YAAY;AACrC,QAAM,MAAM,QAAQ,QAAQ,MAAM,KAAK,IAAA;AACvC,QAAM,SAAS,CAAC,WAAqD;;AACnE,QAAI;AACF,oBAAQ,aAAR,iCAAmB;AAAA,IACrB,QAAQ;AAAA,IAER;AAAA,EACF;AAGA,QAAM,8BAAc,IAAA;AACpB,QAAM,QAAQ,MAAY;AACxB,WAAO,QAAQ,OAAO,YAAY;AAChC,YAAM,SAAS,QAAQ,KAAA,EAAO,OAAO;AACrC,UAAI,WAAW,OAAW;AAC1B,cAAQ,OAAO,MAAM;AAAA,IACvB;AAAA,EACF;AACA,QAAM,QAAQ,CAAC,aAA+E;AAC5F,UAAM,QAAQ,QAAQ,IAAI,QAAQ;AAClC,QAAI,CAAC,MAAO,QAAO;AACnB,QAAI,IAAA,IAAQ,MAAM,KAAK,UAAU;AAC/B,cAAQ,OAAO,QAAQ;AACvB,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AACA,SAAO;AAAA,IACL,SAAS,UAAU,OAAO;AACxB,YAAM,KAAK,IAAA;AACX,cAAQ,OAAO,QAAQ;AACvB,cAAQ,IAAI,UAAU,EAAE,OAAO,IAAI;AACnC,YAAA;AACA,aAAO,EAAE,MAAM,YAAY,UAAU,OAAO,IAAI;AAAA,IAClD;AAAA,IACA,KAAK,UAAU;;AACb,cAAO,WAAM,QAAQ,MAAd,mBAAiB;AAAA,IAC1B;AAAA,IACA,KAAK,UAAU;AACb,aAAO,MAAM,QAAQ,MAAM;AAAA,IAC7B;AAAA,IACA,QAAQ,UAAU,OAAO,IAAI;AAC3B,UAAI,IAAA,IAAQ,KAAK,SAAU;AAC3B,YAAM,WAAW,QAAQ,IAAI,QAAQ;AACrC,UAAI,YAAY,SAAS,MAAM,GAAI;AACnC,cAAQ,OAAO,QAAQ;AACvB,cAAQ,IAAI,UAAU,EAAE,OAAO,IAAI;AACnC,YAAA;AAAA,IACF;AAAA,IACA,OAAO,UAAU;AACf,cAAQ,OAAO,QAAQ;AACvB,aAAO,EAAE,MAAM,UAAU,SAAA,CAAU;AAAA,IACrC;AAAA,IACA,QAAQ;AACN,cAAQ,MAAA;AACR,aAAO,EAAE,MAAM,SAAS;AAAA,IAC1B;AAAA,EAAA;AAEJ;AAqBO,SAAS,qBAAqB,OAAqD;AACxF,QAAM;AAAA,IACJ,eAAe;AAAA,IACf,eAAe;AAAA,IACf,iBAAiB;AAAA,IACjB,GAAG;AAAA,EAAA,IACD;AACJ,SAAO,EAAE,GAAG,MAAM,MAAM,aAAa,MAAM,IAAI,EAAA;AACjD;AAiBA,SAAS,aACP,MAC4C;AAC5C,QAAM,OAAO,6BAAO;AACpB,MAAI,SAAS,UAAa,KAAK,WAAW,kBAAkB,CAAA;AAC5D,SAAO;AAAA,IACL;AAAA,MACE,OAAO,KAAK;AAAA,MACZ,QAAQ;AAAA,MACR,OAAO,KAAK;AAAA,MACZ,YAAY,KAAK;AAAA,MACjB,GAAI,KAAK,UAAU,UAAa,EAAE,OAAO,KAAK,MAAA;AAAA,IAAM;AAAA,EACtD;AAEJ;;;"}
|
|
1
|
+
{"version":3,"file":"thread-snapshot-cache.cjs","sources":["../../../../../agent-runtime/src/thread-snapshot-cache.ts"],"sourcesContent":["import type { DeepAgentStreamValues } from './types';\n\n/**\n * Per-tab LRU of the last wire state seen per thread, so a switch back to a\n * recently-visited thread can mount with that snapshot instead of an empty\n * \"loading\" state (see `UseDeepAgentThreadOptions.snapshotCache`). Entries are\n * references into states the wire already produced — remembering is O(1) and\n * copies nothing; the bound is `maxThreads` retained states.\n */\nexport interface DeepAgentThreadSnapshotCache {\n /** Record the latest hydrated wire state for a thread. */\n remember(threadId: string, state: DeepAgentStreamValues): void;\n /** The freshest remembered state for a thread, or `undefined` when none is\n * held or the entry aged out. */\n take(threadId: string): DeepAgentStreamValues | undefined;\n /**\n * Drop a single thread's entry. Called when the server refuses\n * authorization for the thread — a refused thread must not warm-paint on\n * its next mount.\n */\n forget(threadId: string): void;\n /**\n * Drop every entry. Called when the acting identity changes — a snapshot\n * captured under one account must never seed a mount under another.\n */\n clear(): void;\n /**\n * Whether a fresh entry is held for the thread, without consuming or\n * touching it. Hosts read it at selection time to decide whether the switch\n * can paint from a seed (no loading treatment) or must cover the attach\n * wait. Optional so hand-rolled caches keep type-checking; the built-in\n * cache implements it.\n */\n peek?(threadId: string): boolean;\n}\n\n/** A write observed on the cache — the durable-mirror seam (see `onChange`). */\nexport type DeepAgentThreadSnapshotCacheChange =\n | { type: 'remember'; threadId: string; state: DeepAgentStreamValues; at: number }\n | { type: 'forget'; threadId: string }\n | { type: 'clear' };\n\nexport interface DeepAgentThreadSnapshotCacheOptions {\n /** Threads retained; the least-recently written entry evicts beyond this. */\n maxThreads?: number;\n /**\n * Entries older than this are dropped at read time. The seed's value is\n * \"the thread you just left\" — a snapshot from long ago is more likely to\n * flash content the server has since rewritten than to save a paint.\n */\n maxAgeMs?: number;\n /**\n * Observer of every write (`remember`, `forget`, `clear`). Hosts mirror\n * entries into durable storage from here so a reload can seed too; the\n * observer is never invoked for `restore`, which re-admits mirrored entries.\n * Exceptions are swallowed — a mirror fault must not break the wire path.\n */\n onChange?: (change: DeepAgentThreadSnapshotCacheChange) => void;\n /** Injectable clock (tests). */\n now?: () => number;\n}\n\n/** The built-in cache: the host contract plus the durable-mirror re-admission. */\nexport interface CreatedDeepAgentThreadSnapshotCache extends DeepAgentThreadSnapshotCache {\n peek(threadId: string): boolean;\n /**\n * Re-admit an entry captured earlier — a mirrored snapshot after a reload —\n * under its ORIGINAL capture time, so the age bound still applies. Never\n * displaces a fresher in-memory entry and never notifies `onChange`.\n */\n restore(threadId: string, state: DeepAgentStreamValues, at: number): void;\n}\n\nconst DEFAULT_MAX_THREADS = 30;\nconst DEFAULT_MAX_AGE_MS = 60 * 60 * 1000;\n\nexport function createDeepAgentThreadSnapshotCache(\n options: DeepAgentThreadSnapshotCacheOptions = {}\n): CreatedDeepAgentThreadSnapshotCache {\n const maxThreads = options.maxThreads ?? DEFAULT_MAX_THREADS;\n const maxAgeMs = options.maxAgeMs ?? DEFAULT_MAX_AGE_MS;\n const now = options.now ?? (() => Date.now());\n const notify = (change: DeepAgentThreadSnapshotCacheChange): void => {\n try {\n options.onChange?.(change);\n } catch {\n // The mirror is best-effort; the in-memory cache stays authoritative.\n }\n };\n // Map iteration order is insertion order; delete-then-set on every write\n // keeps the first key the least-recently written.\n const entries = new Map<string, { state: DeepAgentStreamValues; at: number }>();\n const evict = (): void => {\n while (entries.size > maxThreads) {\n const oldest = entries.keys().next().value;\n if (oldest === undefined) break;\n entries.delete(oldest);\n }\n };\n const fresh = (threadId: string): { state: DeepAgentStreamValues; at: number } | undefined => {\n const entry = entries.get(threadId);\n if (!entry) return undefined;\n if (now() - entry.at > maxAgeMs) {\n entries.delete(threadId);\n return undefined;\n }\n return entry;\n };\n return {\n remember(threadId, state) {\n const at = now();\n entries.delete(threadId);\n entries.set(threadId, { state, at });\n evict();\n notify({ type: 'remember', threadId, state, at });\n },\n take(threadId) {\n return fresh(threadId)?.state;\n },\n peek(threadId) {\n return fresh(threadId) !== undefined;\n },\n restore(threadId, state, at) {\n if (now() - at > maxAgeMs) return;\n const existing = entries.get(threadId);\n if (existing && existing.at >= at) return;\n entries.delete(threadId);\n entries.set(threadId, { state, at });\n evict();\n },\n forget(threadId) {\n entries.delete(threadId);\n notify({ type: 'forget', threadId });\n },\n clear() {\n entries.clear();\n notify({ type: 'clear' });\n },\n };\n}\n\n/**\n * Shape a remembered wire state for use as a mount's `initialState`.\n *\n * Messages/todos and the other app-owned keys seed as captured. The run lane\n * seeds by what the user left: a thread whose run was `running` when the\n * snapshot was taken seeds a running entry (see {@link seedRunsLane}) so the\n * switch back reads Running with a Stop control on its first frame — the same\n * frame `runs: []` would render as \"ready\" with a Send button over a thread the\n * server is still mid-run on. Every other live-lifecycle lane normalizes to\n * resting: a seeded input request could collect a decision the server may have\n * already resolved, so an `input-required` entry seeds as no run at all.\n * `__interrupt__` is dropped for the same reason, and so are `ptc_subagents`\n * (the live in-eval dispatch lane — a seeded \"running\" dispatch card under a\n * resting thread would render active with no eval behind it) and\n * `outOfBandRunActive` (the host's out-of-band-run-in-progress flag — seeding a\n * finished run's `true` would hide the composer until the attach corrected\n * it). Durable thread properties (`runConfig`, `sessionPersisted`, …) seed as\n * captured; the attach snapshot then replaces the whole seed.\n */\nexport function toThreadSnapshotSeed(state: DeepAgentStreamValues): DeepAgentStreamValues {\n const {\n __interrupt__: _interrupt,\n ptc_subagents: _ptcSubagents,\n outOfBandRunActive: _outOfBandRunActive,\n ...rest\n } = state;\n return { ...rest, runs: seedRunsLane(state.runs) };\n}\n\n/**\n * The run entries a seed carries.\n *\n * Only a `running` entry survives, and only its replicated identity and its\n * durable lanes: `runId` (the target of a Stop sent during the attach\n * round-trip — the live run's id if it is still running, and a refused stop\n * if it finished meanwhile), `epoch` (rides that Stop so a replayed stop cannot\n * end a later leg) and the `queue`/`steerQueue` entries (server lanes the tray\n * renders). The in-flight records rest until the snapshot: `dispatching` and\n * `nextDispatch` are batches the materializer would re-apply onto messages\n * that already contain them, `stopping` is a rewind the host may have\n * finished, `inputRequests` is a parked decision, and `error` is a failure the\n * next user run clears. An entry in any other status — `stopped`, `error`,\n * `input-required` — seeds as no run.\n */\nfunction seedRunsLane(\n runs: DeepAgentStreamValues['runs']\n): NonNullable<DeepAgentStreamValues['runs']> {\n const live = runs?.[0];\n if (live === undefined || live.status !== 'running') return [];\n return [\n {\n runId: live.runId,\n status: 'running',\n queue: live.queue,\n steerQueue: live.steerQueue,\n ...(live.epoch !== undefined && { epoch: live.epoch }),\n },\n ];\n}\n"],"names":[],"mappings":";;AAyEA,MAAM,sBAAsB;AAC5B,MAAM,qBAAqB,KAAK,KAAK;AAE9B,SAAS,mCACd,UAA+C,IACV;AACrC,QAAM,aAAa,QAAQ,cAAc;AACzC,QAAM,WAAW,QAAQ,YAAY;AACrC,QAAM,MAAM,QAAQ,QAAQ,MAAM,KAAK,IAAA;AACvC,QAAM,SAAS,CAAC,WAAqD;;AACnE,QAAI;AACF,oBAAQ,aAAR,iCAAmB;AAAA,IACrB,QAAQ;AAAA,IAER;AAAA,EACF;AAGA,QAAM,8BAAc,IAAA;AACpB,QAAM,QAAQ,MAAY;AACxB,WAAO,QAAQ,OAAO,YAAY;AAChC,YAAM,SAAS,QAAQ,KAAA,EAAO,OAAO;AACrC,UAAI,WAAW,OAAW;AAC1B,cAAQ,OAAO,MAAM;AAAA,IACvB;AAAA,EACF;AACA,QAAM,QAAQ,CAAC,aAA+E;AAC5F,UAAM,QAAQ,QAAQ,IAAI,QAAQ;AAClC,QAAI,CAAC,MAAO,QAAO;AACnB,QAAI,IAAA,IAAQ,MAAM,KAAK,UAAU;AAC/B,cAAQ,OAAO,QAAQ;AACvB,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AACA,SAAO;AAAA,IACL,SAAS,UAAU,OAAO;AACxB,YAAM,KAAK,IAAA;AACX,cAAQ,OAAO,QAAQ;AACvB,cAAQ,IAAI,UAAU,EAAE,OAAO,IAAI;AACnC,YAAA;AACA,aAAO,EAAE,MAAM,YAAY,UAAU,OAAO,IAAI;AAAA,IAClD;AAAA,IACA,KAAK,UAAU;;AACb,cAAO,WAAM,QAAQ,MAAd,mBAAiB;AAAA,IAC1B;AAAA,IACA,KAAK,UAAU;AACb,aAAO,MAAM,QAAQ,MAAM;AAAA,IAC7B;AAAA,IACA,QAAQ,UAAU,OAAO,IAAI;AAC3B,UAAI,IAAA,IAAQ,KAAK,SAAU;AAC3B,YAAM,WAAW,QAAQ,IAAI,QAAQ;AACrC,UAAI,YAAY,SAAS,MAAM,GAAI;AACnC,cAAQ,OAAO,QAAQ;AACvB,cAAQ,IAAI,UAAU,EAAE,OAAO,IAAI;AACnC,YAAA;AAAA,IACF;AAAA,IACA,OAAO,UAAU;AACf,cAAQ,OAAO,QAAQ;AACvB,aAAO,EAAE,MAAM,UAAU,SAAA,CAAU;AAAA,IACrC;AAAA,IACA,QAAQ;AACN,cAAQ,MAAA;AACR,aAAO,EAAE,MAAM,SAAS;AAAA,IAC1B;AAAA,EAAA;AAEJ;AAqBO,SAAS,qBAAqB,OAAqD;AACxF,QAAM;AAAA,IACJ,eAAe;AAAA,IACf,eAAe;AAAA,IACf,oBAAoB;AAAA,IACpB,GAAG;AAAA,EAAA,IACD;AACJ,SAAO,EAAE,GAAG,MAAM,MAAM,aAAa,MAAM,IAAI,EAAA;AACjD;AAiBA,SAAS,aACP,MAC4C;AAC5C,QAAM,OAAO,6BAAO;AACpB,MAAI,SAAS,UAAa,KAAK,WAAW,kBAAkB,CAAA;AAC5D,SAAO;AAAA,IACL;AAAA,MACE,OAAO,KAAK;AAAA,MACZ,QAAQ;AAAA,MACR,OAAO,KAAK;AAAA,MACZ,YAAY,KAAK;AAAA,MACjB,GAAI,KAAK,UAAU,UAAa,EAAE,OAAO,KAAK,MAAA;AAAA,IAAM;AAAA,EACtD;AAEJ;;;"}
|
|
@@ -65,7 +65,7 @@ function toThreadSnapshotSeed(state) {
|
|
|
65
65
|
const {
|
|
66
66
|
__interrupt__: _interrupt,
|
|
67
67
|
ptc_subagents: _ptcSubagents,
|
|
68
|
-
|
|
68
|
+
outOfBandRunActive: _outOfBandRunActive,
|
|
69
69
|
...rest
|
|
70
70
|
} = state;
|
|
71
71
|
return { ...rest, runs: seedRunsLane(state.runs) };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"thread-snapshot-cache.js","sources":["../../../../../agent-runtime/src/thread-snapshot-cache.ts"],"sourcesContent":["import type { DeepAgentStreamValues } from './types';\n\n/**\n * Per-tab LRU of the last wire state seen per thread, so a switch back to a\n * recently-visited thread can mount with that snapshot instead of an empty\n * \"loading\" state (see `UseDeepAgentThreadOptions.snapshotCache`). Entries are\n * references into states the wire already produced — remembering is O(1) and\n * copies nothing; the bound is `maxThreads` retained states.\n */\nexport interface DeepAgentThreadSnapshotCache {\n /** Record the latest hydrated wire state for a thread. */\n remember(threadId: string, state: DeepAgentStreamValues): void;\n /** The freshest remembered state for a thread, or `undefined` when none is\n * held or the entry aged out. */\n take(threadId: string): DeepAgentStreamValues | undefined;\n /**\n * Drop a single thread's entry. Called when the server refuses\n * authorization for the thread — a refused thread must not warm-paint on\n * its next mount.\n */\n forget(threadId: string): void;\n /**\n * Drop every entry. Called when the acting identity changes — a snapshot\n * captured under one account must never seed a mount under another.\n */\n clear(): void;\n /**\n * Whether a fresh entry is held for the thread, without consuming or\n * touching it. Hosts read it at selection time to decide whether the switch\n * can paint from a seed (no loading treatment) or must cover the attach\n * wait. Optional so hand-rolled caches keep type-checking; the built-in\n * cache implements it.\n */\n peek?(threadId: string): boolean;\n}\n\n/** A write observed on the cache — the durable-mirror seam (see `onChange`). */\nexport type DeepAgentThreadSnapshotCacheChange =\n | { type: 'remember'; threadId: string; state: DeepAgentStreamValues; at: number }\n | { type: 'forget'; threadId: string }\n | { type: 'clear' };\n\nexport interface DeepAgentThreadSnapshotCacheOptions {\n /** Threads retained; the least-recently written entry evicts beyond this. */\n maxThreads?: number;\n /**\n * Entries older than this are dropped at read time. The seed's value is\n * \"the thread you just left\" — a snapshot from long ago is more likely to\n * flash content the server has since rewritten than to save a paint.\n */\n maxAgeMs?: number;\n /**\n * Observer of every write (`remember`, `forget`, `clear`). Hosts mirror\n * entries into durable storage from here so a reload can seed too; the\n * observer is never invoked for `restore`, which re-admits mirrored entries.\n * Exceptions are swallowed — a mirror fault must not break the wire path.\n */\n onChange?: (change: DeepAgentThreadSnapshotCacheChange) => void;\n /** Injectable clock (tests). */\n now?: () => number;\n}\n\n/** The built-in cache: the host contract plus the durable-mirror re-admission. */\nexport interface CreatedDeepAgentThreadSnapshotCache extends DeepAgentThreadSnapshotCache {\n peek(threadId: string): boolean;\n /**\n * Re-admit an entry captured earlier — a mirrored snapshot after a reload —\n * under its ORIGINAL capture time, so the age bound still applies. Never\n * displaces a fresher in-memory entry and never notifies `onChange`.\n */\n restore(threadId: string, state: DeepAgentStreamValues, at: number): void;\n}\n\nconst DEFAULT_MAX_THREADS = 30;\nconst DEFAULT_MAX_AGE_MS = 60 * 60 * 1000;\n\nexport function createDeepAgentThreadSnapshotCache(\n options: DeepAgentThreadSnapshotCacheOptions = {}\n): CreatedDeepAgentThreadSnapshotCache {\n const maxThreads = options.maxThreads ?? DEFAULT_MAX_THREADS;\n const maxAgeMs = options.maxAgeMs ?? DEFAULT_MAX_AGE_MS;\n const now = options.now ?? (() => Date.now());\n const notify = (change: DeepAgentThreadSnapshotCacheChange): void => {\n try {\n options.onChange?.(change);\n } catch {\n // The mirror is best-effort; the in-memory cache stays authoritative.\n }\n };\n // Map iteration order is insertion order; delete-then-set on every write\n // keeps the first key the least-recently written.\n const entries = new Map<string, { state: DeepAgentStreamValues; at: number }>();\n const evict = (): void => {\n while (entries.size > maxThreads) {\n const oldest = entries.keys().next().value;\n if (oldest === undefined) break;\n entries.delete(oldest);\n }\n };\n const fresh = (threadId: string): { state: DeepAgentStreamValues; at: number } | undefined => {\n const entry = entries.get(threadId);\n if (!entry) return undefined;\n if (now() - entry.at > maxAgeMs) {\n entries.delete(threadId);\n return undefined;\n }\n return entry;\n };\n return {\n remember(threadId, state) {\n const at = now();\n entries.delete(threadId);\n entries.set(threadId, { state, at });\n evict();\n notify({ type: 'remember', threadId, state, at });\n },\n take(threadId) {\n return fresh(threadId)?.state;\n },\n peek(threadId) {\n return fresh(threadId) !== undefined;\n },\n restore(threadId, state, at) {\n if (now() - at > maxAgeMs) return;\n const existing = entries.get(threadId);\n if (existing && existing.at >= at) return;\n entries.delete(threadId);\n entries.set(threadId, { state, at });\n evict();\n },\n forget(threadId) {\n entries.delete(threadId);\n notify({ type: 'forget', threadId });\n },\n clear() {\n entries.clear();\n notify({ type: 'clear' });\n },\n };\n}\n\n/**\n * Shape a remembered wire state for use as a mount's `initialState`.\n *\n * Messages/todos and the other app-owned keys seed as captured. The run lane\n * seeds by what the user left: a thread whose run was `running` when the\n * snapshot was taken seeds a running entry (see {@link seedRunsLane}) so the\n * switch back reads Running with a Stop control on its first frame — the same\n * frame `runs: []` would render as \"ready\" with a Send button over a thread the\n * server is still mid-run on. Every other live-lifecycle lane normalizes to\n * resting: a seeded input request could collect a decision the server may have\n * already resolved, so an `input-required` entry seeds as no run at all.\n * `__interrupt__` is dropped for the same reason, and so are `ptc_subagents`\n * (the live in-eval dispatch lane — a seeded \"running\" dispatch card under a\n * resting thread would render active with no eval behind it) and\n * `legacyRunActive` (the host's out-of-band-run-in-progress flag — seeding a\n * finished run's `true` would hide the composer until the attach corrected\n * it). Durable thread properties (`runConfig`, `sessionPersisted`, …) seed as\n * captured; the attach snapshot then replaces the whole seed.\n */\nexport function toThreadSnapshotSeed(state: DeepAgentStreamValues): DeepAgentStreamValues {\n const {\n __interrupt__: _interrupt,\n ptc_subagents: _ptcSubagents,\n legacyRunActive: _legacyRunActive,\n ...rest\n } = state;\n return { ...rest, runs: seedRunsLane(state.runs) };\n}\n\n/**\n * The run entries a seed carries.\n *\n * Only a `running` entry survives, and only its replicated identity and its\n * durable lanes: `runId` (the target of a Stop sent during the attach\n * round-trip — the live run's id if it is still running, and a refused stop\n * if it finished meanwhile), `epoch` (rides that Stop so a replayed stop cannot\n * end a later leg) and the `queue`/`steerQueue` entries (server lanes the tray\n * renders). The in-flight records rest until the snapshot: `dispatching` and\n * `nextDispatch` are batches the materializer would re-apply onto messages\n * that already contain them, `stopping` is a rewind the host may have\n * finished, `inputRequests` is a parked decision, and `error` is a failure the\n * next user run clears. An entry in any other status — `stopped`, `error`,\n * `input-required` — seeds as no run.\n */\nfunction seedRunsLane(\n runs: DeepAgentStreamValues['runs']\n): NonNullable<DeepAgentStreamValues['runs']> {\n const live = runs?.[0];\n if (live === undefined || live.status !== 'running') return [];\n return [\n {\n runId: live.runId,\n status: 'running',\n queue: live.queue,\n steerQueue: live.steerQueue,\n ...(live.epoch !== undefined && { epoch: live.epoch }),\n },\n ];\n}\n"],"names":[],"mappings":"AAyEA,MAAM,sBAAsB;AAC5B,MAAM,qBAAqB,KAAK,KAAK;AAE9B,SAAS,mCACd,UAA+C,IACV;AACrC,QAAM,aAAa,QAAQ,cAAc;AACzC,QAAM,WAAW,QAAQ,YAAY;AACrC,QAAM,MAAM,QAAQ,QAAQ,MAAM,KAAK,IAAA;AACvC,QAAM,SAAS,CAAC,WAAqD;AATvE;AAUI,QAAI;AACF,oBAAQ,aAAR,iCAAmB;AAAA,IACrB,QAAQ;AAAA,IAER;AAAA,EACF;AAGA,QAAM,8BAAc,IAAA;AACpB,QAAM,QAAQ,MAAY;AACxB,WAAO,QAAQ,OAAO,YAAY;AAChC,YAAM,SAAS,QAAQ,KAAA,EAAO,OAAO;AACrC,UAAI,WAAW,OAAW;AAC1B,cAAQ,OAAO,MAAM;AAAA,IACvB;AAAA,EACF;AACA,QAAM,QAAQ,CAAC,aAA+E;AAC5F,UAAM,QAAQ,QAAQ,IAAI,QAAQ;AAClC,QAAI,CAAC,MAAO,QAAO;AACnB,QAAI,IAAA,IAAQ,MAAM,KAAK,UAAU;AAC/B,cAAQ,OAAO,QAAQ;AACvB,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AACA,SAAO;AAAA,IACL,SAAS,UAAU,OAAO;AACxB,YAAM,KAAK,IAAA;AACX,cAAQ,OAAO,QAAQ;AACvB,cAAQ,IAAI,UAAU,EAAE,OAAO,IAAI;AACnC,YAAA;AACA,aAAO,EAAE,MAAM,YAAY,UAAU,OAAO,IAAI;AAAA,IAClD;AAAA,IACA,KAAK,UAAU;AA3CnB;AA4CM,cAAO,WAAM,QAAQ,MAAd,mBAAiB;AAAA,IAC1B;AAAA,IACA,KAAK,UAAU;AACb,aAAO,MAAM,QAAQ,MAAM;AAAA,IAC7B;AAAA,IACA,QAAQ,UAAU,OAAO,IAAI;AAC3B,UAAI,IAAA,IAAQ,KAAK,SAAU;AAC3B,YAAM,WAAW,QAAQ,IAAI,QAAQ;AACrC,UAAI,YAAY,SAAS,MAAM,GAAI;AACnC,cAAQ,OAAO,QAAQ;AACvB,cAAQ,IAAI,UAAU,EAAE,OAAO,IAAI;AACnC,YAAA;AAAA,IACF;AAAA,IACA,OAAO,UAAU;AACf,cAAQ,OAAO,QAAQ;AACvB,aAAO,EAAE,MAAM,UAAU,SAAA,CAAU;AAAA,IACrC;AAAA,IACA,QAAQ;AACN,cAAQ,MAAA;AACR,aAAO,EAAE,MAAM,SAAS;AAAA,IAC1B;AAAA,EAAA;AAEJ;AAqBO,SAAS,qBAAqB,OAAqD;AACxF,QAAM;AAAA,IACJ,eAAe;AAAA,IACf,eAAe;AAAA,IACf,iBAAiB;AAAA,IACjB,GAAG;AAAA,EAAA,IACD;AACJ,SAAO,EAAE,GAAG,MAAM,MAAM,aAAa,MAAM,IAAI,EAAA;AACjD;AAiBA,SAAS,aACP,MAC4C;AAC5C,QAAM,OAAO,6BAAO;AACpB,MAAI,SAAS,UAAa,KAAK,WAAW,kBAAkB,CAAA;AAC5D,SAAO;AAAA,IACL;AAAA,MACE,OAAO,KAAK;AAAA,MACZ,QAAQ;AAAA,MACR,OAAO,KAAK;AAAA,MACZ,YAAY,KAAK;AAAA,MACjB,GAAI,KAAK,UAAU,UAAa,EAAE,OAAO,KAAK,MAAA;AAAA,IAAM;AAAA,EACtD;AAEJ;"}
|
|
1
|
+
{"version":3,"file":"thread-snapshot-cache.js","sources":["../../../../../agent-runtime/src/thread-snapshot-cache.ts"],"sourcesContent":["import type { DeepAgentStreamValues } from './types';\n\n/**\n * Per-tab LRU of the last wire state seen per thread, so a switch back to a\n * recently-visited thread can mount with that snapshot instead of an empty\n * \"loading\" state (see `UseDeepAgentThreadOptions.snapshotCache`). Entries are\n * references into states the wire already produced — remembering is O(1) and\n * copies nothing; the bound is `maxThreads` retained states.\n */\nexport interface DeepAgentThreadSnapshotCache {\n /** Record the latest hydrated wire state for a thread. */\n remember(threadId: string, state: DeepAgentStreamValues): void;\n /** The freshest remembered state for a thread, or `undefined` when none is\n * held or the entry aged out. */\n take(threadId: string): DeepAgentStreamValues | undefined;\n /**\n * Drop a single thread's entry. Called when the server refuses\n * authorization for the thread — a refused thread must not warm-paint on\n * its next mount.\n */\n forget(threadId: string): void;\n /**\n * Drop every entry. Called when the acting identity changes — a snapshot\n * captured under one account must never seed a mount under another.\n */\n clear(): void;\n /**\n * Whether a fresh entry is held for the thread, without consuming or\n * touching it. Hosts read it at selection time to decide whether the switch\n * can paint from a seed (no loading treatment) or must cover the attach\n * wait. Optional so hand-rolled caches keep type-checking; the built-in\n * cache implements it.\n */\n peek?(threadId: string): boolean;\n}\n\n/** A write observed on the cache — the durable-mirror seam (see `onChange`). */\nexport type DeepAgentThreadSnapshotCacheChange =\n | { type: 'remember'; threadId: string; state: DeepAgentStreamValues; at: number }\n | { type: 'forget'; threadId: string }\n | { type: 'clear' };\n\nexport interface DeepAgentThreadSnapshotCacheOptions {\n /** Threads retained; the least-recently written entry evicts beyond this. */\n maxThreads?: number;\n /**\n * Entries older than this are dropped at read time. The seed's value is\n * \"the thread you just left\" — a snapshot from long ago is more likely to\n * flash content the server has since rewritten than to save a paint.\n */\n maxAgeMs?: number;\n /**\n * Observer of every write (`remember`, `forget`, `clear`). Hosts mirror\n * entries into durable storage from here so a reload can seed too; the\n * observer is never invoked for `restore`, which re-admits mirrored entries.\n * Exceptions are swallowed — a mirror fault must not break the wire path.\n */\n onChange?: (change: DeepAgentThreadSnapshotCacheChange) => void;\n /** Injectable clock (tests). */\n now?: () => number;\n}\n\n/** The built-in cache: the host contract plus the durable-mirror re-admission. */\nexport interface CreatedDeepAgentThreadSnapshotCache extends DeepAgentThreadSnapshotCache {\n peek(threadId: string): boolean;\n /**\n * Re-admit an entry captured earlier — a mirrored snapshot after a reload —\n * under its ORIGINAL capture time, so the age bound still applies. Never\n * displaces a fresher in-memory entry and never notifies `onChange`.\n */\n restore(threadId: string, state: DeepAgentStreamValues, at: number): void;\n}\n\nconst DEFAULT_MAX_THREADS = 30;\nconst DEFAULT_MAX_AGE_MS = 60 * 60 * 1000;\n\nexport function createDeepAgentThreadSnapshotCache(\n options: DeepAgentThreadSnapshotCacheOptions = {}\n): CreatedDeepAgentThreadSnapshotCache {\n const maxThreads = options.maxThreads ?? DEFAULT_MAX_THREADS;\n const maxAgeMs = options.maxAgeMs ?? DEFAULT_MAX_AGE_MS;\n const now = options.now ?? (() => Date.now());\n const notify = (change: DeepAgentThreadSnapshotCacheChange): void => {\n try {\n options.onChange?.(change);\n } catch {\n // The mirror is best-effort; the in-memory cache stays authoritative.\n }\n };\n // Map iteration order is insertion order; delete-then-set on every write\n // keeps the first key the least-recently written.\n const entries = new Map<string, { state: DeepAgentStreamValues; at: number }>();\n const evict = (): void => {\n while (entries.size > maxThreads) {\n const oldest = entries.keys().next().value;\n if (oldest === undefined) break;\n entries.delete(oldest);\n }\n };\n const fresh = (threadId: string): { state: DeepAgentStreamValues; at: number } | undefined => {\n const entry = entries.get(threadId);\n if (!entry) return undefined;\n if (now() - entry.at > maxAgeMs) {\n entries.delete(threadId);\n return undefined;\n }\n return entry;\n };\n return {\n remember(threadId, state) {\n const at = now();\n entries.delete(threadId);\n entries.set(threadId, { state, at });\n evict();\n notify({ type: 'remember', threadId, state, at });\n },\n take(threadId) {\n return fresh(threadId)?.state;\n },\n peek(threadId) {\n return fresh(threadId) !== undefined;\n },\n restore(threadId, state, at) {\n if (now() - at > maxAgeMs) return;\n const existing = entries.get(threadId);\n if (existing && existing.at >= at) return;\n entries.delete(threadId);\n entries.set(threadId, { state, at });\n evict();\n },\n forget(threadId) {\n entries.delete(threadId);\n notify({ type: 'forget', threadId });\n },\n clear() {\n entries.clear();\n notify({ type: 'clear' });\n },\n };\n}\n\n/**\n * Shape a remembered wire state for use as a mount's `initialState`.\n *\n * Messages/todos and the other app-owned keys seed as captured. The run lane\n * seeds by what the user left: a thread whose run was `running` when the\n * snapshot was taken seeds a running entry (see {@link seedRunsLane}) so the\n * switch back reads Running with a Stop control on its first frame — the same\n * frame `runs: []` would render as \"ready\" with a Send button over a thread the\n * server is still mid-run on. Every other live-lifecycle lane normalizes to\n * resting: a seeded input request could collect a decision the server may have\n * already resolved, so an `input-required` entry seeds as no run at all.\n * `__interrupt__` is dropped for the same reason, and so are `ptc_subagents`\n * (the live in-eval dispatch lane — a seeded \"running\" dispatch card under a\n * resting thread would render active with no eval behind it) and\n * `outOfBandRunActive` (the host's out-of-band-run-in-progress flag — seeding a\n * finished run's `true` would hide the composer until the attach corrected\n * it). Durable thread properties (`runConfig`, `sessionPersisted`, …) seed as\n * captured; the attach snapshot then replaces the whole seed.\n */\nexport function toThreadSnapshotSeed(state: DeepAgentStreamValues): DeepAgentStreamValues {\n const {\n __interrupt__: _interrupt,\n ptc_subagents: _ptcSubagents,\n outOfBandRunActive: _outOfBandRunActive,\n ...rest\n } = state;\n return { ...rest, runs: seedRunsLane(state.runs) };\n}\n\n/**\n * The run entries a seed carries.\n *\n * Only a `running` entry survives, and only its replicated identity and its\n * durable lanes: `runId` (the target of a Stop sent during the attach\n * round-trip — the live run's id if it is still running, and a refused stop\n * if it finished meanwhile), `epoch` (rides that Stop so a replayed stop cannot\n * end a later leg) and the `queue`/`steerQueue` entries (server lanes the tray\n * renders). The in-flight records rest until the snapshot: `dispatching` and\n * `nextDispatch` are batches the materializer would re-apply onto messages\n * that already contain them, `stopping` is a rewind the host may have\n * finished, `inputRequests` is a parked decision, and `error` is a failure the\n * next user run clears. An entry in any other status — `stopped`, `error`,\n * `input-required` — seeds as no run.\n */\nfunction seedRunsLane(\n runs: DeepAgentStreamValues['runs']\n): NonNullable<DeepAgentStreamValues['runs']> {\n const live = runs?.[0];\n if (live === undefined || live.status !== 'running') return [];\n return [\n {\n runId: live.runId,\n status: 'running',\n queue: live.queue,\n steerQueue: live.steerQueue,\n ...(live.epoch !== undefined && { epoch: live.epoch }),\n },\n ];\n}\n"],"names":[],"mappings":"AAyEA,MAAM,sBAAsB;AAC5B,MAAM,qBAAqB,KAAK,KAAK;AAE9B,SAAS,mCACd,UAA+C,IACV;AACrC,QAAM,aAAa,QAAQ,cAAc;AACzC,QAAM,WAAW,QAAQ,YAAY;AACrC,QAAM,MAAM,QAAQ,QAAQ,MAAM,KAAK,IAAA;AACvC,QAAM,SAAS,CAAC,WAAqD;AATvE;AAUI,QAAI;AACF,oBAAQ,aAAR,iCAAmB;AAAA,IACrB,QAAQ;AAAA,IAER;AAAA,EACF;AAGA,QAAM,8BAAc,IAAA;AACpB,QAAM,QAAQ,MAAY;AACxB,WAAO,QAAQ,OAAO,YAAY;AAChC,YAAM,SAAS,QAAQ,KAAA,EAAO,OAAO;AACrC,UAAI,WAAW,OAAW;AAC1B,cAAQ,OAAO,MAAM;AAAA,IACvB;AAAA,EACF;AACA,QAAM,QAAQ,CAAC,aAA+E;AAC5F,UAAM,QAAQ,QAAQ,IAAI,QAAQ;AAClC,QAAI,CAAC,MAAO,QAAO;AACnB,QAAI,IAAA,IAAQ,MAAM,KAAK,UAAU;AAC/B,cAAQ,OAAO,QAAQ;AACvB,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AACA,SAAO;AAAA,IACL,SAAS,UAAU,OAAO;AACxB,YAAM,KAAK,IAAA;AACX,cAAQ,OAAO,QAAQ;AACvB,cAAQ,IAAI,UAAU,EAAE,OAAO,IAAI;AACnC,YAAA;AACA,aAAO,EAAE,MAAM,YAAY,UAAU,OAAO,IAAI;AAAA,IAClD;AAAA,IACA,KAAK,UAAU;AA3CnB;AA4CM,cAAO,WAAM,QAAQ,MAAd,mBAAiB;AAAA,IAC1B;AAAA,IACA,KAAK,UAAU;AACb,aAAO,MAAM,QAAQ,MAAM;AAAA,IAC7B;AAAA,IACA,QAAQ,UAAU,OAAO,IAAI;AAC3B,UAAI,IAAA,IAAQ,KAAK,SAAU;AAC3B,YAAM,WAAW,QAAQ,IAAI,QAAQ;AACrC,UAAI,YAAY,SAAS,MAAM,GAAI;AACnC,cAAQ,OAAO,QAAQ;AACvB,cAAQ,IAAI,UAAU,EAAE,OAAO,IAAI;AACnC,YAAA;AAAA,IACF;AAAA,IACA,OAAO,UAAU;AACf,cAAQ,OAAO,QAAQ;AACvB,aAAO,EAAE,MAAM,UAAU,SAAA,CAAU;AAAA,IACrC;AAAA,IACA,QAAQ;AACN,cAAQ,MAAA;AACR,aAAO,EAAE,MAAM,SAAS;AAAA,IAC1B;AAAA,EAAA;AAEJ;AAqBO,SAAS,qBAAqB,OAAqD;AACxF,QAAM;AAAA,IACJ,eAAe;AAAA,IACf,eAAe;AAAA,IACf,oBAAoB;AAAA,IACpB,GAAG;AAAA,EAAA,IACD;AACJ,SAAO,EAAE,GAAG,MAAM,MAAM,aAAa,MAAM,IAAI,EAAA;AACjD;AAiBA,SAAS,aACP,MAC4C;AAC5C,QAAM,OAAO,6BAAO;AACpB,MAAI,SAAS,UAAa,KAAK,WAAW,kBAAkB,CAAA;AAC5D,SAAO;AAAA,IACL;AAAA,MACE,OAAO,KAAK;AAAA,MACZ,QAAQ;AAAA,MACR,OAAO,KAAK;AAAA,MACZ,YAAY,KAAK;AAAA,MACjB,GAAI,KAAK,UAAU,UAAa,EAAE,OAAO,KAAK,MAAA;AAAA,IAAM;AAAA,EACtD;AAEJ;"}
|
|
@@ -105,14 +105,6 @@ const TOOL_DISPLAY_META = {
|
|
|
105
105
|
emoji: "🔍",
|
|
106
106
|
describe: describeQuery
|
|
107
107
|
},
|
|
108
|
-
semantic_search: {
|
|
109
|
-
displayName: "Semantic Search",
|
|
110
|
-
activeLabel: "Searching your sources",
|
|
111
|
-
category: "Web",
|
|
112
|
-
icon: "search",
|
|
113
|
-
emoji: "🔍",
|
|
114
|
-
describe: describeQuery
|
|
115
|
-
},
|
|
116
108
|
deep_research: {
|
|
117
109
|
displayName: "Deep Research",
|
|
118
110
|
activeLabel: "Researching",
|