@athenaintel/react 0.42.1 → 0.43.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 +46 -7
- package/dist/agent-runtime/src/conversation-list-groups.d.ts +74 -0
- package/dist/agent-runtime/src/index.d.ts +3 -2
- package/dist/agent-runtime/src/messages.cjs +38 -7
- package/dist/agent-runtime/src/messages.cjs.map +1 -1
- package/dist/agent-runtime/src/messages.d.ts +26 -8
- package/dist/agent-runtime/src/messages.js +38 -7
- package/dist/agent-runtime/src/messages.js.map +1 -1
- package/dist/agent-runtime/src/quota-safe-local-storage-session.cjs +10 -0
- package/dist/agent-runtime/src/quota-safe-local-storage-session.cjs.map +1 -1
- package/dist/agent-runtime/src/quota-safe-local-storage-session.d.ts +7 -0
- package/dist/agent-runtime/src/quota-safe-local-storage-session.js +10 -0
- package/dist/agent-runtime/src/quota-safe-local-storage-session.js.map +1 -1
- package/dist/agent-runtime/src/session-lane-scope.cjs +25 -0
- package/dist/agent-runtime/src/session-lane-scope.cjs.map +1 -0
- package/dist/agent-runtime/src/session-lane-scope.d.ts +59 -0
- package/dist/agent-runtime/src/session-lane-scope.js +25 -0
- package/dist/agent-runtime/src/session-lane-scope.js.map +1 -0
- package/dist/agent-runtime/src/subagent-transcripts.cjs +2 -2
- package/dist/agent-runtime/src/subagent-transcripts.cjs.map +1 -1
- package/dist/agent-runtime/src/subagent-transcripts.d.ts +2 -2
- package/dist/agent-runtime/src/subagent-transcripts.js +3 -3
- package/dist/agent-runtime/src/subagent-transcripts.js.map +1 -1
- package/dist/agent-runtime/src/tool-metadata.cjs +0 -8
- package/dist/agent-runtime/src/tool-metadata.cjs.map +1 -1
- package/dist/agent-runtime/src/tool-metadata.js +0 -8
- package/dist/agent-runtime/src/tool-metadata.js.map +1 -1
- package/dist/agent-runtime/src/tool-names.cjs +4 -0
- package/dist/agent-runtime/src/tool-names.cjs.map +1 -1
- package/dist/agent-runtime/src/tool-names.d.ts +7 -0
- package/dist/agent-runtime/src/tool-names.js +4 -0
- package/dist/agent-runtime/src/tool-names.js.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-runtime.cjs +18 -1
- package/dist/agent-runtime/src/use-deep-agent-runtime.cjs.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-runtime.d.ts +19 -10
- package/dist/agent-runtime/src/use-deep-agent-runtime.js +19 -2
- package/dist/agent-runtime/src/use-deep-agent-runtime.js.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-thread.cjs +28 -10
- package/dist/agent-runtime/src/use-deep-agent-thread.cjs.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-thread.d.ts +35 -3
- package/dist/agent-runtime/src/use-deep-agent-thread.js +28 -10
- package/dist/agent-runtime/src/use-deep-agent-thread.js.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-voice.cjs +50 -15
- package/dist/agent-runtime/src/use-deep-agent-voice.cjs.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-voice.d.ts +8 -0
- package/dist/agent-runtime/src/use-deep-agent-voice.js +50 -15
- package/dist/agent-runtime/src/use-deep-agent-voice.js.map +1 -1
- package/dist/agent-runtime/src/voice-presentation.cjs +2 -0
- package/dist/agent-runtime/src/voice-presentation.cjs.map +1 -1
- package/dist/agent-runtime/src/voice-presentation.js +2 -0
- package/dist/agent-runtime/src/voice-presentation.js.map +1 -1
- package/dist/catalog/runtime-catalog.js +1 -1
- package/dist/chat/AthenaChat.cjs +125 -103
- package/dist/chat/AthenaChat.cjs.map +1 -1
- package/dist/chat/AthenaChat.d.ts +9 -0
- package/dist/chat/AthenaChat.js +126 -104
- package/dist/chat/AthenaChat.js.map +1 -1
- package/dist/chat/chat-voice-controls.cjs +128 -0
- package/dist/chat/chat-voice-controls.cjs.map +1 -0
- package/dist/chat/chat-voice-controls.d.ts +19 -0
- package/dist/chat/chat-voice-controls.js +128 -0
- package/dist/chat/chat-voice-controls.js.map +1 -0
- package/dist/chat/super-grouping/SuperGroupingCard.cjs +8 -2
- package/dist/chat/super-grouping/SuperGroupingCard.cjs.map +1 -1
- package/dist/chat/super-grouping/SuperGroupingCard.d.ts +6 -1
- package/dist/chat/super-grouping/SuperGroupingCard.js +8 -2
- package/dist/chat/super-grouping/SuperGroupingCard.js.map +1 -1
- package/dist/chat/super-grouping/ToolTimeline.cjs +27 -0
- package/dist/chat/super-grouping/ToolTimeline.cjs.map +1 -1
- package/dist/chat/super-grouping/ToolTimeline.d.ts +5 -1
- package/dist/chat/super-grouping/ToolTimeline.js +27 -0
- package/dist/chat/super-grouping/ToolTimeline.js.map +1 -1
- package/dist/chat/super-grouping/index.d.ts +1 -0
- package/dist/chat/super-grouping/timeline-step.cjs +61 -2
- package/dist/chat/super-grouping/timeline-step.cjs.map +1 -1
- package/dist/chat/super-grouping/timeline-step.d.ts +13 -1
- package/dist/chat/super-grouping/timeline-step.js +61 -2
- package/dist/chat/super-grouping/timeline-step.js.map +1 -1
- package/dist/chat/use-statewire-approval-request.js +1 -1
- package/dist/chat-ui/composer/tiptap-composer.cjs +42 -22
- package/dist/chat-ui/composer/tiptap-composer.cjs.map +1 -1
- package/dist/chat-ui/composer/tiptap-composer.d.ts +1 -0
- package/dist/chat-ui/composer/tiptap-composer.js +43 -23
- package/dist/chat-ui/composer/tiptap-composer.js.map +1 -1
- package/dist/chat-ui/mentions/mention-chip.cjs +144 -35
- package/dist/chat-ui/mentions/mention-chip.cjs.map +1 -1
- package/dist/chat-ui/mentions/mention-chip.d.ts +17 -1
- package/dist/chat-ui/mentions/mention-chip.js +146 -37
- package/dist/chat-ui/mentions/mention-chip.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 -1
- 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/mentions/use-account-workspace-id.cjs +29 -51
- package/dist/chat-ui/mentions/use-account-workspace-id.cjs.map +1 -1
- package/dist/chat-ui/mentions/use-account-workspace-id.d.ts +2 -2
- package/dist/chat-ui/mentions/use-account-workspace-id.js +30 -52
- package/dist/chat-ui/mentions/use-account-workspace-id.js.map +1 -1
- package/dist/chat-ui/mentions/use-mention-asset.cjs +140 -0
- package/dist/chat-ui/mentions/use-mention-asset.cjs.map +1 -0
- package/dist/chat-ui/mentions/use-mention-asset.d.ts +16 -0
- package/dist/chat-ui/mentions/use-mention-asset.js +140 -0
- package/dist/chat-ui/mentions/use-mention-asset.js.map +1 -0
- package/dist/chat-ui/rich-text/chat-link.cjs +5 -4
- package/dist/chat-ui/rich-text/chat-link.cjs.map +1 -1
- package/dist/chat-ui/rich-text/chat-link.d.ts +2 -3
- package/dist/chat-ui/rich-text/chat-link.js +6 -5
- package/dist/chat-ui/rich-text/chat-link.js.map +1 -1
- package/dist/chat-ui/rich-text/chat-node-views.cjs +1 -0
- package/dist/chat-ui/rich-text/chat-node-views.cjs.map +1 -1
- package/dist/chat-ui/rich-text/chat-node-views.js +1 -0
- package/dist/chat-ui/rich-text/chat-node-views.js.map +1 -1
- package/dist/chat-ui/thread/composer-send-or-stop.cjs +2 -1
- package/dist/chat-ui/thread/composer-send-or-stop.cjs.map +1 -1
- package/dist/chat-ui/thread/composer-send-or-stop.d.ts +2 -0
- package/dist/chat-ui/thread/composer-send-or-stop.js +2 -1
- package/dist/chat-ui/thread/composer-send-or-stop.js.map +1 -1
- package/dist/diagnostics/athena-diagnostics.cjs.map +1 -1
- package/dist/diagnostics/athena-diagnostics.d.ts +1 -1
- package/dist/diagnostics/athena-diagnostics.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/react/package.json.cjs +1 -1
- package/dist/react/package.json.js +1 -1
- package/dist/references/src/tool-option-schemas.d.ts +0 -34
- package/dist/runtime/statewire-session-key.cjs.map +1 -1
- package/dist/runtime/statewire-session-key.d.ts +8 -5
- package/dist/runtime/statewire-session-key.js.map +1 -1
- package/dist/runtime/useAthenaStatewireRuntime.cjs +5 -2
- package/dist/runtime/useAthenaStatewireRuntime.cjs.map +1 -1
- package/dist/runtime/useAthenaStatewireRuntime.js +5 -2
- package/dist/runtime/useAthenaStatewireRuntime.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/threads/statewire-thread-list.cjs +6 -2
- package/dist/threads/statewire-thread-list.cjs.map +1 -1
- package/dist/threads/statewire-thread-list.js +6 -2
- package/dist/threads/statewire-thread-list.js.map +1 -1
- package/dist/tools/tool-summary.cjs +0 -2
- package/dist/tools/tool-summary.cjs.map +1 -1
- package/dist/tools/tool-summary.js +0 -2
- package/dist/tools/tool-summary.js.map +1 -1
- package/dist/voice/use-chat-voice-enabled.cjs +43 -0
- package/dist/voice/use-chat-voice-enabled.cjs.map +1 -0
- package/dist/voice/use-chat-voice-enabled.d.ts +9 -0
- package/dist/voice/use-chat-voice-enabled.js +43 -0
- package/dist/voice/use-chat-voice-enabled.js.map +1 -0
- package/package.json +4 -8
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-names.cjs","sources":["../../../../agent-runtime/src/tool-names.ts"],"sourcesContent":["/**\n * Names of the deepagents built-in tools as they appear on the wire, plus the\n * shared arg shapes / display derivations keyed on those names. One home so\n * tool cards, activity panels, and transcript renderers on every surface agree\n * on which call is which.\n */\nimport type { LangChainToolCall } from \"@assistant-ui/react-langgraph\";\n\n/** The built-in deepagents delegation tool; a `task` call spawns a subagent. */\nexport const TASK_TOOL_NAME = \"task\";\n/** The QuickJS code-interpreter tool (langchain_quickjs middleware). */\nexport const EVAL_TOOL_NAME = \"eval\";\n/** The built-in deepagents sandbox shell tool. */\nexport const EXECUTE_TOOL_NAME = \"execute\";\n/**\n * The Athena studio code executors: sandboxed Python whose SDK calls the\n * backend records as PTC sub-calls, exactly like `execute` / `eval`.\n */\nexport const RUN_PYTHON_CODE_TOOL_NAME = \"run_python_code\";\nexport const EXECUTE_PRESENTATION_CODE_TOOL_NAME = \"execute_presentation_code\";\nexport const EXECUTE_SPREADSHEET_CODE_TOOL_NAME = \"execute_spreadsheet_code\";\nexport const EXECUTE_WORD_DOCUMENT_CODE_TOOL_NAME = \"execute_word_document_code\";\n/** Every tool whose ToolMessage artifact may carry promoted PTC sub-calls. */\nexport const PTC_PARENT_TOOL_NAMES: ReadonlySet<string> = new Set<string>([\n EXECUTE_TOOL_NAME,\n EVAL_TOOL_NAME,\n RUN_PYTHON_CODE_TOOL_NAME,\n EXECUTE_PRESENTATION_CODE_TOOL_NAME,\n EXECUTE_SPREADSHEET_CODE_TOOL_NAME,\n EXECUTE_WORD_DOCUMENT_CODE_TOOL_NAME,\n]);\n/** The built-in deepagents filesystem write/edit tools. */\nexport const WRITE_FILE_TOOL_NAME = \"write_file\";\nexport const EDIT_FILE_TOOL_NAME = \"edit_file\";\n\n/**\n * The deepagents async-subagent tools (ADR 0006 coding workers ride these):\n * `start_async_task` launches a remote task, `check_async_task` polls it,\n * `update_async_task` sends a follow-up, `cancel_async_task` interrupts it.\n */\nexport const START_ASYNC_TASK_TOOL_NAME = \"start_async_task\";\nexport const CHECK_ASYNC_TASK_TOOL_NAME = \"check_async_task\";\nexport const UPDATE_ASYNC_TASK_TOOL_NAME = \"update_async_task\";\nexport const CANCEL_ASYNC_TASK_TOOL_NAME = \"cancel_async_task\";\nexport const LIST_ASYNC_TASKS_TOOL_NAME = \"list_async_tasks\";\nexport const ASYNC_TASK_TOOL_NAMES = [\n START_ASYNC_TASK_TOOL_NAME,\n CHECK_ASYNC_TASK_TOOL_NAME,\n UPDATE_ASYNC_TASK_TOOL_NAME,\n CANCEL_ASYNC_TASK_TOOL_NAME,\n LIST_ASYNC_TASKS_TOOL_NAME,\n] as const;\n\n/**\n * Args shape shared by `write_file` / `edit_file` (single source of truth for\n * both the tool card and the changed-files panel). `write_file` carries the\n * whole `content`; `edit_file` carries the `old_string` → `new_string`\n * replacement instead (deepagents `WriteFileSchema` / `EditFileSchema`).\n */\nexport type FileToolArgs = {\n file_path?: string;\n path?: string;\n content?: string;\n old_string?: string;\n new_string?: string;\n replace_all?: boolean;\n /** Cross-asset targeting: the sandbox-backed asset the tool acted on. */\n sandbox_asset_id?: string | null;\n};\n\n/**\n * Human-salient one-liner for a nested tool call — the argument a person would\n * glance at (`execute` → the command, file tools → the path, searches → the\n * query). Empty string when nothing salient exists; capped so the ticker line\n * can't blow out the card.\n */\nexport function salientToolCallDetail(toolCall: LangChainToolCall): string {\n const args = (toolCall.args ?? {}) as Record<string, unknown>;\n const firstString = (...keys: string[]): string => {\n for (const key of keys) {\n const value = args[key];\n if (typeof value === \"string\" && value) return value;\n }\n return \"\";\n };\n let detail: string;\n switch (toolCall.name) {\n case EXECUTE_TOOL_NAME:\n detail = firstString(\"command\");\n break;\n case WRITE_FILE_TOOL_NAME:\n case EDIT_FILE_TOOL_NAME:\n detail = firstString(\"file_path\", \"path\");\n break;\n case \"read_file\":\n case \"ls\":\n detail = firstString(\"file_path\", \"path\");\n break;\n case \"write_todos\": {\n const todos = args.todos;\n detail = Array.isArray(todos)\n ? `${todos.length} step${todos.length === 1 ? \"\" : \"s\"}`\n : \"\";\n break;\n }\n case TASK_TOOL_NAME:\n case START_ASYNC_TASK_TOOL_NAME:\n detail = firstString(\"description\", \"subagent_type\");\n break;\n case UPDATE_ASYNC_TASK_TOOL_NAME:\n detail = firstString(\"message\", \"task_id\");\n break;\n case CHECK_ASYNC_TASK_TOOL_NAME:\n case CANCEL_ASYNC_TASK_TOOL_NAME:\n detail = firstString(\"task_id\");\n break;\n default:\n detail = firstString(\n \"query\",\n \"pattern\",\n \"command\",\n \"path\",\n \"file_path\",\n \"url\",\n \"question\",\n );\n }\n const clean = detail.replace(/\\s+/g, \" \").trim();\n return clean.length > 80 ? `${clean.slice(0, 79)}…` : clean;\n}\n"],"names":[],"mappings":";;AASO,MAAM,iBAAiB;AAIvB,MAAM,oBAAoB;AAmB1B,MAAM,uBAAuB;AAC7B,MAAM,sBAAsB;AAO5B,MAAM,6BAA6B;AACnC,MAAM,6BAA6B;AACnC,MAAM,8BAA8B;AACpC,MAAM,8BAA8B;AAiCpC,SAAS,sBAAsB,UAAqC;AACzE,QAAM,OAAQ,SAAS,QAAQ,CAAA;AAC/B,QAAM,cAAc,IAAI,SAA2B;AACjD,eAAW,OAAO,MAAM;AACtB,YAAM,QAAQ,KAAK,GAAG;AACtB,UAAI,OAAO,UAAU,YAAY,MAAO,QAAO;AAAA,IACjD;AACA,WAAO;AAAA,EACT;AACA,MAAI;AACJ,UAAQ,SAAS,MAAA;AAAA,IACf,KAAK;AACH,eAAS,YAAY,SAAS;AAC9B;AAAA,IACF,KAAK;AAAA,IACL,KAAK;AACH,eAAS,YAAY,aAAa,MAAM;AACxC;AAAA,IACF,KAAK;AAAA,IACL,KAAK;AACH,eAAS,YAAY,aAAa,MAAM;AACxC;AAAA,IACF,KAAK,eAAe;AAClB,YAAM,QAAQ,KAAK;AACnB,eAAS,MAAM,QAAQ,KAAK,IACxB,GAAG,MAAM,MAAM,QAAQ,MAAM,WAAW,IAAI,KAAK,GAAG,KACpD;AACJ;AAAA,IACF;AAAA,IACA,KAAK;AAAA,IACL,KAAK;AACH,eAAS,YAAY,eAAe,eAAe;AACnD;AAAA,IACF,KAAK;AACH,eAAS,YAAY,WAAW,SAAS;AACzC;AAAA,IACF,KAAK;AAAA,IACL,KAAK;AACH,eAAS,YAAY,SAAS;AAC9B;AAAA,IACF;AACE,eAAS;AAAA,QACP;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,EACF;AAEJ,QAAM,QAAQ,OAAO,QAAQ,QAAQ,GAAG,EAAE,KAAA;AAC1C,SAAO,MAAM,SAAS,KAAK,GAAG,MAAM,MAAM,GAAG,EAAE,CAAC,MAAM;AACxD
|
|
1
|
+
{"version":3,"file":"tool-names.cjs","sources":["../../../../agent-runtime/src/tool-names.ts"],"sourcesContent":["/**\n * Names of the deepagents built-in tools as they appear on the wire, plus the\n * shared arg shapes / display derivations keyed on those names. One home so\n * tool cards, activity panels, and transcript renderers on every surface agree\n * on which call is which.\n */\nimport type { LangChainToolCall } from \"@assistant-ui/react-langgraph\";\n\n/** The built-in deepagents delegation tool; a `task` call spawns a subagent. */\nexport const TASK_TOOL_NAME = \"task\";\n/** The QuickJS code-interpreter tool (langchain_quickjs middleware). */\nexport const EVAL_TOOL_NAME = \"eval\";\n/** The built-in deepagents sandbox shell tool. */\nexport const EXECUTE_TOOL_NAME = \"execute\";\n/**\n * The Athena studio code executors: sandboxed Python whose SDK calls the\n * backend records as PTC sub-calls, exactly like `execute` / `eval`.\n */\nexport const RUN_PYTHON_CODE_TOOL_NAME = \"run_python_code\";\nexport const EXECUTE_PRESENTATION_CODE_TOOL_NAME = \"execute_presentation_code\";\nexport const EXECUTE_SPREADSHEET_CODE_TOOL_NAME = \"execute_spreadsheet_code\";\nexport const EXECUTE_WORD_DOCUMENT_CODE_TOOL_NAME = \"execute_word_document_code\";\n/** Every tool whose ToolMessage artifact may carry promoted PTC sub-calls. */\nexport const PTC_PARENT_TOOL_NAMES: ReadonlySet<string> = new Set<string>([\n EXECUTE_TOOL_NAME,\n EVAL_TOOL_NAME,\n RUN_PYTHON_CODE_TOOL_NAME,\n EXECUTE_PRESENTATION_CODE_TOOL_NAME,\n EXECUTE_SPREADSHEET_CODE_TOOL_NAME,\n EXECUTE_WORD_DOCUMENT_CODE_TOOL_NAME,\n]);\n/** The built-in deepagents filesystem write/edit tools. */\nexport const WRITE_FILE_TOOL_NAME = \"write_file\";\nexport const EDIT_FILE_TOOL_NAME = \"edit_file\";\n\n/**\n * Tools whose calls render nothing in chat: no card, no status line. The call\n * and its result stay in the stored transcript and in exports; only the\n * conversation view leaves them out (see `convertDeepAgentMessage`).\n */\nexport const SUBMIT_FEEDBACK_TOOL_NAME = \"submit_feedback\";\nexport const QUIET_TOOL_NAMES: ReadonlySet<string> = new Set([SUBMIT_FEEDBACK_TOOL_NAME]);\n\n/**\n * The deepagents async-subagent tools (ADR 0006 coding workers ride these):\n * `start_async_task` launches a remote task, `check_async_task` polls it,\n * `update_async_task` sends a follow-up, `cancel_async_task` interrupts it.\n */\nexport const START_ASYNC_TASK_TOOL_NAME = \"start_async_task\";\nexport const CHECK_ASYNC_TASK_TOOL_NAME = \"check_async_task\";\nexport const UPDATE_ASYNC_TASK_TOOL_NAME = \"update_async_task\";\nexport const CANCEL_ASYNC_TASK_TOOL_NAME = \"cancel_async_task\";\nexport const LIST_ASYNC_TASKS_TOOL_NAME = \"list_async_tasks\";\nexport const ASYNC_TASK_TOOL_NAMES = [\n START_ASYNC_TASK_TOOL_NAME,\n CHECK_ASYNC_TASK_TOOL_NAME,\n UPDATE_ASYNC_TASK_TOOL_NAME,\n CANCEL_ASYNC_TASK_TOOL_NAME,\n LIST_ASYNC_TASKS_TOOL_NAME,\n] as const;\n\n/**\n * Args shape shared by `write_file` / `edit_file` (single source of truth for\n * both the tool card and the changed-files panel). `write_file` carries the\n * whole `content`; `edit_file` carries the `old_string` → `new_string`\n * replacement instead (deepagents `WriteFileSchema` / `EditFileSchema`).\n */\nexport type FileToolArgs = {\n file_path?: string;\n path?: string;\n content?: string;\n old_string?: string;\n new_string?: string;\n replace_all?: boolean;\n /** Cross-asset targeting: the sandbox-backed asset the tool acted on. */\n sandbox_asset_id?: string | null;\n};\n\n/**\n * Human-salient one-liner for a nested tool call — the argument a person would\n * glance at (`execute` → the command, file tools → the path, searches → the\n * query). Empty string when nothing salient exists; capped so the ticker line\n * can't blow out the card.\n */\nexport function salientToolCallDetail(toolCall: LangChainToolCall): string {\n const args = (toolCall.args ?? {}) as Record<string, unknown>;\n const firstString = (...keys: string[]): string => {\n for (const key of keys) {\n const value = args[key];\n if (typeof value === \"string\" && value) return value;\n }\n return \"\";\n };\n let detail: string;\n switch (toolCall.name) {\n case EXECUTE_TOOL_NAME:\n detail = firstString(\"command\");\n break;\n case WRITE_FILE_TOOL_NAME:\n case EDIT_FILE_TOOL_NAME:\n detail = firstString(\"file_path\", \"path\");\n break;\n case \"read_file\":\n case \"ls\":\n detail = firstString(\"file_path\", \"path\");\n break;\n case \"write_todos\": {\n const todos = args.todos;\n detail = Array.isArray(todos)\n ? `${todos.length} step${todos.length === 1 ? \"\" : \"s\"}`\n : \"\";\n break;\n }\n case TASK_TOOL_NAME:\n case START_ASYNC_TASK_TOOL_NAME:\n detail = firstString(\"description\", \"subagent_type\");\n break;\n case UPDATE_ASYNC_TASK_TOOL_NAME:\n detail = firstString(\"message\", \"task_id\");\n break;\n case CHECK_ASYNC_TASK_TOOL_NAME:\n case CANCEL_ASYNC_TASK_TOOL_NAME:\n detail = firstString(\"task_id\");\n break;\n default:\n detail = firstString(\n \"query\",\n \"pattern\",\n \"command\",\n \"path\",\n \"file_path\",\n \"url\",\n \"question\",\n );\n }\n const clean = detail.replace(/\\s+/g, \" \").trim();\n return clean.length > 80 ? `${clean.slice(0, 79)}…` : clean;\n}\n"],"names":[],"mappings":";;AASO,MAAM,iBAAiB;AAIvB,MAAM,oBAAoB;AAmB1B,MAAM,uBAAuB;AAC7B,MAAM,sBAAsB;AAO5B,MAAM,4BAA4B;AAClC,MAAM,mBAAwC,oBAAI,IAAI,CAAC,yBAAyB,CAAC;AAOjF,MAAM,6BAA6B;AACnC,MAAM,6BAA6B;AACnC,MAAM,8BAA8B;AACpC,MAAM,8BAA8B;AAiCpC,SAAS,sBAAsB,UAAqC;AACzE,QAAM,OAAQ,SAAS,QAAQ,CAAA;AAC/B,QAAM,cAAc,IAAI,SAA2B;AACjD,eAAW,OAAO,MAAM;AACtB,YAAM,QAAQ,KAAK,GAAG;AACtB,UAAI,OAAO,UAAU,YAAY,MAAO,QAAO;AAAA,IACjD;AACA,WAAO;AAAA,EACT;AACA,MAAI;AACJ,UAAQ,SAAS,MAAA;AAAA,IACf,KAAK;AACH,eAAS,YAAY,SAAS;AAC9B;AAAA,IACF,KAAK;AAAA,IACL,KAAK;AACH,eAAS,YAAY,aAAa,MAAM;AACxC;AAAA,IACF,KAAK;AAAA,IACL,KAAK;AACH,eAAS,YAAY,aAAa,MAAM;AACxC;AAAA,IACF,KAAK,eAAe;AAClB,YAAM,QAAQ,KAAK;AACnB,eAAS,MAAM,QAAQ,KAAK,IACxB,GAAG,MAAM,MAAM,QAAQ,MAAM,WAAW,IAAI,KAAK,GAAG,KACpD;AACJ;AAAA,IACF;AAAA,IACA,KAAK;AAAA,IACL,KAAK;AACH,eAAS,YAAY,eAAe,eAAe;AACnD;AAAA,IACF,KAAK;AACH,eAAS,YAAY,WAAW,SAAS;AACzC;AAAA,IACF,KAAK;AAAA,IACL,KAAK;AACH,eAAS,YAAY,SAAS;AAC9B;AAAA,IACF;AACE,eAAS;AAAA,QACP;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,EACF;AAEJ,QAAM,QAAQ,OAAO,QAAQ,QAAQ,GAAG,EAAE,KAAA;AAC1C,SAAO,MAAM,SAAS,KAAK,GAAG,MAAM,MAAM,GAAG,EAAE,CAAC,MAAM;AACxD;;;;;;;;;;;;"}
|
|
@@ -24,6 +24,13 @@ export declare const PTC_PARENT_TOOL_NAMES: ReadonlySet<string>;
|
|
|
24
24
|
/** The built-in deepagents filesystem write/edit tools. */
|
|
25
25
|
export declare const WRITE_FILE_TOOL_NAME = "write_file";
|
|
26
26
|
export declare const EDIT_FILE_TOOL_NAME = "edit_file";
|
|
27
|
+
/**
|
|
28
|
+
* Tools whose calls render nothing in chat: no card, no status line. The call
|
|
29
|
+
* and its result stay in the stored transcript and in exports; only the
|
|
30
|
+
* conversation view leaves them out (see `convertDeepAgentMessage`).
|
|
31
|
+
*/
|
|
32
|
+
export declare const SUBMIT_FEEDBACK_TOOL_NAME = "submit_feedback";
|
|
33
|
+
export declare const QUIET_TOOL_NAMES: ReadonlySet<string>;
|
|
27
34
|
/**
|
|
28
35
|
* The deepagents async-subagent tools (ADR 0006 coding workers ride these):
|
|
29
36
|
* `start_async_task` launches a remote task, `check_async_task` polls it,
|
|
@@ -2,6 +2,8 @@ const TASK_TOOL_NAME = "task";
|
|
|
2
2
|
const EXECUTE_TOOL_NAME = "execute";
|
|
3
3
|
const WRITE_FILE_TOOL_NAME = "write_file";
|
|
4
4
|
const EDIT_FILE_TOOL_NAME = "edit_file";
|
|
5
|
+
const SUBMIT_FEEDBACK_TOOL_NAME = "submit_feedback";
|
|
6
|
+
const QUIET_TOOL_NAMES = /* @__PURE__ */ new Set([SUBMIT_FEEDBACK_TOOL_NAME]);
|
|
5
7
|
const START_ASYNC_TASK_TOOL_NAME = "start_async_task";
|
|
6
8
|
const CHECK_ASYNC_TASK_TOOL_NAME = "check_async_task";
|
|
7
9
|
const UPDATE_ASYNC_TASK_TOOL_NAME = "update_async_task";
|
|
@@ -63,7 +65,9 @@ export {
|
|
|
63
65
|
CHECK_ASYNC_TASK_TOOL_NAME,
|
|
64
66
|
EDIT_FILE_TOOL_NAME,
|
|
65
67
|
EXECUTE_TOOL_NAME,
|
|
68
|
+
QUIET_TOOL_NAMES,
|
|
66
69
|
START_ASYNC_TASK_TOOL_NAME,
|
|
70
|
+
SUBMIT_FEEDBACK_TOOL_NAME,
|
|
67
71
|
TASK_TOOL_NAME,
|
|
68
72
|
UPDATE_ASYNC_TASK_TOOL_NAME,
|
|
69
73
|
WRITE_FILE_TOOL_NAME,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-names.js","sources":["../../../../agent-runtime/src/tool-names.ts"],"sourcesContent":["/**\n * Names of the deepagents built-in tools as they appear on the wire, plus the\n * shared arg shapes / display derivations keyed on those names. One home so\n * tool cards, activity panels, and transcript renderers on every surface agree\n * on which call is which.\n */\nimport type { LangChainToolCall } from \"@assistant-ui/react-langgraph\";\n\n/** The built-in deepagents delegation tool; a `task` call spawns a subagent. */\nexport const TASK_TOOL_NAME = \"task\";\n/** The QuickJS code-interpreter tool (langchain_quickjs middleware). */\nexport const EVAL_TOOL_NAME = \"eval\";\n/** The built-in deepagents sandbox shell tool. */\nexport const EXECUTE_TOOL_NAME = \"execute\";\n/**\n * The Athena studio code executors: sandboxed Python whose SDK calls the\n * backend records as PTC sub-calls, exactly like `execute` / `eval`.\n */\nexport const RUN_PYTHON_CODE_TOOL_NAME = \"run_python_code\";\nexport const EXECUTE_PRESENTATION_CODE_TOOL_NAME = \"execute_presentation_code\";\nexport const EXECUTE_SPREADSHEET_CODE_TOOL_NAME = \"execute_spreadsheet_code\";\nexport const EXECUTE_WORD_DOCUMENT_CODE_TOOL_NAME = \"execute_word_document_code\";\n/** Every tool whose ToolMessage artifact may carry promoted PTC sub-calls. */\nexport const PTC_PARENT_TOOL_NAMES: ReadonlySet<string> = new Set<string>([\n EXECUTE_TOOL_NAME,\n EVAL_TOOL_NAME,\n RUN_PYTHON_CODE_TOOL_NAME,\n EXECUTE_PRESENTATION_CODE_TOOL_NAME,\n EXECUTE_SPREADSHEET_CODE_TOOL_NAME,\n EXECUTE_WORD_DOCUMENT_CODE_TOOL_NAME,\n]);\n/** The built-in deepagents filesystem write/edit tools. */\nexport const WRITE_FILE_TOOL_NAME = \"write_file\";\nexport const EDIT_FILE_TOOL_NAME = \"edit_file\";\n\n/**\n * The deepagents async-subagent tools (ADR 0006 coding workers ride these):\n * `start_async_task` launches a remote task, `check_async_task` polls it,\n * `update_async_task` sends a follow-up, `cancel_async_task` interrupts it.\n */\nexport const START_ASYNC_TASK_TOOL_NAME = \"start_async_task\";\nexport const CHECK_ASYNC_TASK_TOOL_NAME = \"check_async_task\";\nexport const UPDATE_ASYNC_TASK_TOOL_NAME = \"update_async_task\";\nexport const CANCEL_ASYNC_TASK_TOOL_NAME = \"cancel_async_task\";\nexport const LIST_ASYNC_TASKS_TOOL_NAME = \"list_async_tasks\";\nexport const ASYNC_TASK_TOOL_NAMES = [\n START_ASYNC_TASK_TOOL_NAME,\n CHECK_ASYNC_TASK_TOOL_NAME,\n UPDATE_ASYNC_TASK_TOOL_NAME,\n CANCEL_ASYNC_TASK_TOOL_NAME,\n LIST_ASYNC_TASKS_TOOL_NAME,\n] as const;\n\n/**\n * Args shape shared by `write_file` / `edit_file` (single source of truth for\n * both the tool card and the changed-files panel). `write_file` carries the\n * whole `content`; `edit_file` carries the `old_string` → `new_string`\n * replacement instead (deepagents `WriteFileSchema` / `EditFileSchema`).\n */\nexport type FileToolArgs = {\n file_path?: string;\n path?: string;\n content?: string;\n old_string?: string;\n new_string?: string;\n replace_all?: boolean;\n /** Cross-asset targeting: the sandbox-backed asset the tool acted on. */\n sandbox_asset_id?: string | null;\n};\n\n/**\n * Human-salient one-liner for a nested tool call — the argument a person would\n * glance at (`execute` → the command, file tools → the path, searches → the\n * query). Empty string when nothing salient exists; capped so the ticker line\n * can't blow out the card.\n */\nexport function salientToolCallDetail(toolCall: LangChainToolCall): string {\n const args = (toolCall.args ?? {}) as Record<string, unknown>;\n const firstString = (...keys: string[]): string => {\n for (const key of keys) {\n const value = args[key];\n if (typeof value === \"string\" && value) return value;\n }\n return \"\";\n };\n let detail: string;\n switch (toolCall.name) {\n case EXECUTE_TOOL_NAME:\n detail = firstString(\"command\");\n break;\n case WRITE_FILE_TOOL_NAME:\n case EDIT_FILE_TOOL_NAME:\n detail = firstString(\"file_path\", \"path\");\n break;\n case \"read_file\":\n case \"ls\":\n detail = firstString(\"file_path\", \"path\");\n break;\n case \"write_todos\": {\n const todos = args.todos;\n detail = Array.isArray(todos)\n ? `${todos.length} step${todos.length === 1 ? \"\" : \"s\"}`\n : \"\";\n break;\n }\n case TASK_TOOL_NAME:\n case START_ASYNC_TASK_TOOL_NAME:\n detail = firstString(\"description\", \"subagent_type\");\n break;\n case UPDATE_ASYNC_TASK_TOOL_NAME:\n detail = firstString(\"message\", \"task_id\");\n break;\n case CHECK_ASYNC_TASK_TOOL_NAME:\n case CANCEL_ASYNC_TASK_TOOL_NAME:\n detail = firstString(\"task_id\");\n break;\n default:\n detail = firstString(\n \"query\",\n \"pattern\",\n \"command\",\n \"path\",\n \"file_path\",\n \"url\",\n \"question\",\n );\n }\n const clean = detail.replace(/\\s+/g, \" \").trim();\n return clean.length > 80 ? `${clean.slice(0, 79)}…` : clean;\n}\n"],"names":[],"mappings":"AASO,MAAM,iBAAiB;AAIvB,MAAM,oBAAoB;AAmB1B,MAAM,uBAAuB;AAC7B,MAAM,sBAAsB;AAO5B,MAAM,6BAA6B;AACnC,MAAM,6BAA6B;AACnC,MAAM,8BAA8B;AACpC,MAAM,8BAA8B;AAiCpC,SAAS,sBAAsB,UAAqC;AACzE,QAAM,OAAQ,SAAS,QAAQ,CAAA;AAC/B,QAAM,cAAc,IAAI,SAA2B;AACjD,eAAW,OAAO,MAAM;AACtB,YAAM,QAAQ,KAAK,GAAG;AACtB,UAAI,OAAO,UAAU,YAAY,MAAO,QAAO;AAAA,IACjD;AACA,WAAO;AAAA,EACT;AACA,MAAI;AACJ,UAAQ,SAAS,MAAA;AAAA,IACf,KAAK;AACH,eAAS,YAAY,SAAS;AAC9B;AAAA,IACF,KAAK;AAAA,IACL,KAAK;AACH,eAAS,YAAY,aAAa,MAAM;AACxC;AAAA,IACF,KAAK;AAAA,IACL,KAAK;AACH,eAAS,YAAY,aAAa,MAAM;AACxC;AAAA,IACF,KAAK,eAAe;AAClB,YAAM,QAAQ,KAAK;AACnB,eAAS,MAAM,QAAQ,KAAK,IACxB,GAAG,MAAM,MAAM,QAAQ,MAAM,WAAW,IAAI,KAAK,GAAG,KACpD;AACJ;AAAA,IACF;AAAA,IACA,KAAK;AAAA,IACL,KAAK;AACH,eAAS,YAAY,eAAe,eAAe;AACnD;AAAA,IACF,KAAK;AACH,eAAS,YAAY,WAAW,SAAS;AACzC;AAAA,IACF,KAAK;AAAA,IACL,KAAK;AACH,eAAS,YAAY,SAAS;AAC9B;AAAA,IACF;AACE,eAAS;AAAA,QACP;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,EACF;AAEJ,QAAM,QAAQ,OAAO,QAAQ,QAAQ,GAAG,EAAE,KAAA;AAC1C,SAAO,MAAM,SAAS,KAAK,GAAG,MAAM,MAAM,GAAG,EAAE,CAAC,MAAM;AACxD;"}
|
|
1
|
+
{"version":3,"file":"tool-names.js","sources":["../../../../agent-runtime/src/tool-names.ts"],"sourcesContent":["/**\n * Names of the deepagents built-in tools as they appear on the wire, plus the\n * shared arg shapes / display derivations keyed on those names. One home so\n * tool cards, activity panels, and transcript renderers on every surface agree\n * on which call is which.\n */\nimport type { LangChainToolCall } from \"@assistant-ui/react-langgraph\";\n\n/** The built-in deepagents delegation tool; a `task` call spawns a subagent. */\nexport const TASK_TOOL_NAME = \"task\";\n/** The QuickJS code-interpreter tool (langchain_quickjs middleware). */\nexport const EVAL_TOOL_NAME = \"eval\";\n/** The built-in deepagents sandbox shell tool. */\nexport const EXECUTE_TOOL_NAME = \"execute\";\n/**\n * The Athena studio code executors: sandboxed Python whose SDK calls the\n * backend records as PTC sub-calls, exactly like `execute` / `eval`.\n */\nexport const RUN_PYTHON_CODE_TOOL_NAME = \"run_python_code\";\nexport const EXECUTE_PRESENTATION_CODE_TOOL_NAME = \"execute_presentation_code\";\nexport const EXECUTE_SPREADSHEET_CODE_TOOL_NAME = \"execute_spreadsheet_code\";\nexport const EXECUTE_WORD_DOCUMENT_CODE_TOOL_NAME = \"execute_word_document_code\";\n/** Every tool whose ToolMessage artifact may carry promoted PTC sub-calls. */\nexport const PTC_PARENT_TOOL_NAMES: ReadonlySet<string> = new Set<string>([\n EXECUTE_TOOL_NAME,\n EVAL_TOOL_NAME,\n RUN_PYTHON_CODE_TOOL_NAME,\n EXECUTE_PRESENTATION_CODE_TOOL_NAME,\n EXECUTE_SPREADSHEET_CODE_TOOL_NAME,\n EXECUTE_WORD_DOCUMENT_CODE_TOOL_NAME,\n]);\n/** The built-in deepagents filesystem write/edit tools. */\nexport const WRITE_FILE_TOOL_NAME = \"write_file\";\nexport const EDIT_FILE_TOOL_NAME = \"edit_file\";\n\n/**\n * Tools whose calls render nothing in chat: no card, no status line. The call\n * and its result stay in the stored transcript and in exports; only the\n * conversation view leaves them out (see `convertDeepAgentMessage`).\n */\nexport const SUBMIT_FEEDBACK_TOOL_NAME = \"submit_feedback\";\nexport const QUIET_TOOL_NAMES: ReadonlySet<string> = new Set([SUBMIT_FEEDBACK_TOOL_NAME]);\n\n/**\n * The deepagents async-subagent tools (ADR 0006 coding workers ride these):\n * `start_async_task` launches a remote task, `check_async_task` polls it,\n * `update_async_task` sends a follow-up, `cancel_async_task` interrupts it.\n */\nexport const START_ASYNC_TASK_TOOL_NAME = \"start_async_task\";\nexport const CHECK_ASYNC_TASK_TOOL_NAME = \"check_async_task\";\nexport const UPDATE_ASYNC_TASK_TOOL_NAME = \"update_async_task\";\nexport const CANCEL_ASYNC_TASK_TOOL_NAME = \"cancel_async_task\";\nexport const LIST_ASYNC_TASKS_TOOL_NAME = \"list_async_tasks\";\nexport const ASYNC_TASK_TOOL_NAMES = [\n START_ASYNC_TASK_TOOL_NAME,\n CHECK_ASYNC_TASK_TOOL_NAME,\n UPDATE_ASYNC_TASK_TOOL_NAME,\n CANCEL_ASYNC_TASK_TOOL_NAME,\n LIST_ASYNC_TASKS_TOOL_NAME,\n] as const;\n\n/**\n * Args shape shared by `write_file` / `edit_file` (single source of truth for\n * both the tool card and the changed-files panel). `write_file` carries the\n * whole `content`; `edit_file` carries the `old_string` → `new_string`\n * replacement instead (deepagents `WriteFileSchema` / `EditFileSchema`).\n */\nexport type FileToolArgs = {\n file_path?: string;\n path?: string;\n content?: string;\n old_string?: string;\n new_string?: string;\n replace_all?: boolean;\n /** Cross-asset targeting: the sandbox-backed asset the tool acted on. */\n sandbox_asset_id?: string | null;\n};\n\n/**\n * Human-salient one-liner for a nested tool call — the argument a person would\n * glance at (`execute` → the command, file tools → the path, searches → the\n * query). Empty string when nothing salient exists; capped so the ticker line\n * can't blow out the card.\n */\nexport function salientToolCallDetail(toolCall: LangChainToolCall): string {\n const args = (toolCall.args ?? {}) as Record<string, unknown>;\n const firstString = (...keys: string[]): string => {\n for (const key of keys) {\n const value = args[key];\n if (typeof value === \"string\" && value) return value;\n }\n return \"\";\n };\n let detail: string;\n switch (toolCall.name) {\n case EXECUTE_TOOL_NAME:\n detail = firstString(\"command\");\n break;\n case WRITE_FILE_TOOL_NAME:\n case EDIT_FILE_TOOL_NAME:\n detail = firstString(\"file_path\", \"path\");\n break;\n case \"read_file\":\n case \"ls\":\n detail = firstString(\"file_path\", \"path\");\n break;\n case \"write_todos\": {\n const todos = args.todos;\n detail = Array.isArray(todos)\n ? `${todos.length} step${todos.length === 1 ? \"\" : \"s\"}`\n : \"\";\n break;\n }\n case TASK_TOOL_NAME:\n case START_ASYNC_TASK_TOOL_NAME:\n detail = firstString(\"description\", \"subagent_type\");\n break;\n case UPDATE_ASYNC_TASK_TOOL_NAME:\n detail = firstString(\"message\", \"task_id\");\n break;\n case CHECK_ASYNC_TASK_TOOL_NAME:\n case CANCEL_ASYNC_TASK_TOOL_NAME:\n detail = firstString(\"task_id\");\n break;\n default:\n detail = firstString(\n \"query\",\n \"pattern\",\n \"command\",\n \"path\",\n \"file_path\",\n \"url\",\n \"question\",\n );\n }\n const clean = detail.replace(/\\s+/g, \" \").trim();\n return clean.length > 80 ? `${clean.slice(0, 79)}…` : clean;\n}\n"],"names":[],"mappings":"AASO,MAAM,iBAAiB;AAIvB,MAAM,oBAAoB;AAmB1B,MAAM,uBAAuB;AAC7B,MAAM,sBAAsB;AAO5B,MAAM,4BAA4B;AAClC,MAAM,mBAAwC,oBAAI,IAAI,CAAC,yBAAyB,CAAC;AAOjF,MAAM,6BAA6B;AACnC,MAAM,6BAA6B;AACnC,MAAM,8BAA8B;AACpC,MAAM,8BAA8B;AAiCpC,SAAS,sBAAsB,UAAqC;AACzE,QAAM,OAAQ,SAAS,QAAQ,CAAA;AAC/B,QAAM,cAAc,IAAI,SAA2B;AACjD,eAAW,OAAO,MAAM;AACtB,YAAM,QAAQ,KAAK,GAAG;AACtB,UAAI,OAAO,UAAU,YAAY,MAAO,QAAO;AAAA,IACjD;AACA,WAAO;AAAA,EACT;AACA,MAAI;AACJ,UAAQ,SAAS,MAAA;AAAA,IACf,KAAK;AACH,eAAS,YAAY,SAAS;AAC9B;AAAA,IACF,KAAK;AAAA,IACL,KAAK;AACH,eAAS,YAAY,aAAa,MAAM;AACxC;AAAA,IACF,KAAK;AAAA,IACL,KAAK;AACH,eAAS,YAAY,aAAa,MAAM;AACxC;AAAA,IACF,KAAK,eAAe;AAClB,YAAM,QAAQ,KAAK;AACnB,eAAS,MAAM,QAAQ,KAAK,IACxB,GAAG,MAAM,MAAM,QAAQ,MAAM,WAAW,IAAI,KAAK,GAAG,KACpD;AACJ;AAAA,IACF;AAAA,IACA,KAAK;AAAA,IACL,KAAK;AACH,eAAS,YAAY,eAAe,eAAe;AACnD;AAAA,IACF,KAAK;AACH,eAAS,YAAY,WAAW,SAAS;AACzC;AAAA,IACF,KAAK;AAAA,IACL,KAAK;AACH,eAAS,YAAY,SAAS;AAC9B;AAAA,IACF;AACE,eAAS;AAAA,QACP;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,EACF;AAEJ,QAAM,QAAQ,OAAO,QAAQ,QAAQ,GAAG,EAAE,KAAA;AAC1C,SAAO,MAAM,SAAS,KAAK,GAAG,MAAM,MAAM,GAAG,EAAE,CAAC,MAAM;AACxD;"}
|
|
@@ -5,6 +5,7 @@ const errors = require("./errors.cjs");
|
|
|
5
5
|
const modelContext = require("./model-context.cjs");
|
|
6
6
|
const quotaSafeLocalStorageSession = require("./quota-safe-local-storage-session.cjs");
|
|
7
7
|
const sendRecovery = require("./send-recovery.cjs");
|
|
8
|
+
const sessionLaneScope = require("./session-lane-scope.cjs");
|
|
8
9
|
const threadExtras = require("./thread-extras.cjs");
|
|
9
10
|
const useDeepAgentConfig = require("./use-deep-agent-config.cjs");
|
|
10
11
|
const useDeepAgentThread = require("./use-deep-agent-thread.cjs");
|
|
@@ -77,7 +78,22 @@ function useDeepAgentRuntime(options) {
|
|
|
77
78
|
failureStampsRef.current = session === false || session.kind === "pending" ? void 0 : session.kind === "local-storage" ? canPersistLocalSession ? () => quotaSafeLocalStorageSession.readPersistedFailedSendStamps(session.key(threadOptions.threadId)) : void 0 : () => quotaSafeLocalStorageSession.readFailedSendStampsFromRecord(loadSessionRecord(session.store));
|
|
78
79
|
const sessionPending = session !== false && session.kind === "pending";
|
|
79
80
|
const attachKey = sessionPending ? pendingSessionAttachKey(threadOptions.attachKey) : threadOptions.attachKey;
|
|
80
|
-
const
|
|
81
|
+
const laneKey = session !== false && session.kind === "local-storage" ? session.key : null;
|
|
82
|
+
const [laneEpochState, setLaneEpochState] = react.useState(
|
|
83
|
+
() => sessionLaneScope.nextSessionLaneEpoch({
|
|
84
|
+
previous: sessionLaneScope.INITIAL_SESSION_LANE_EPOCH,
|
|
85
|
+
threadId: threadOptions.threadId,
|
|
86
|
+
key: laneKey
|
|
87
|
+
})
|
|
88
|
+
);
|
|
89
|
+
const nextLaneEpochState = sessionLaneScope.nextSessionLaneEpoch({
|
|
90
|
+
previous: laneEpochState,
|
|
91
|
+
threadId: threadOptions.threadId,
|
|
92
|
+
key: laneKey
|
|
93
|
+
});
|
|
94
|
+
if (nextLaneEpochState !== laneEpochState) setLaneEpochState(nextLaneEpochState);
|
|
95
|
+
const laneEpoch = nextLaneEpochState.epoch;
|
|
96
|
+
const attachId = useDeepAgentThread.deepAgentAttachId({ threadId: threadOptions.threadId, attachKey, laneEpoch });
|
|
81
97
|
const sendRecovery$1 = react.useMemo(
|
|
82
98
|
() => sendRecovery.createDeepAgentSendRecovery({
|
|
83
99
|
lane: () => {
|
|
@@ -99,6 +115,7 @@ function useDeepAgentRuntime(options) {
|
|
|
99
115
|
const thread = useDeepAgentThread.useDeepAgentThread({
|
|
100
116
|
...threadOptions,
|
|
101
117
|
...attachKey !== void 0 && { attachKey },
|
|
118
|
+
laneEpoch,
|
|
102
119
|
// Parked without a message (a host suspension, when one applies, wins):
|
|
103
120
|
// nothing dials and nothing journals until the lane is known. What paints
|
|
104
121
|
// the first frame is the real attach's to report, not the park's.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-deep-agent-runtime.cjs","sources":["../../../../agent-runtime/src/use-deep-agent-runtime.ts"],"sourcesContent":["import { useEffect, useMemo, useRef, useState } from 'react';\nimport { isStatewireChannelTeardown } from './errors';\nimport { type DeepAgentModelContextHost, registerDeepAgentRunConfig } from './model-context';\nimport {\n isStorageQuotaError,\n QuotaSafeLocalStorageSession,\n readFailedSendStampsFromRecord,\n readPersistedFailedSendStamps,\n type SessionStorageDegradation,\n} from './quota-safe-local-storage-session';\nimport { createDeepAgentSendRecovery } from './send-recovery';\nimport type { StatewireTransportOptions } from './statewire-transport';\nimport { readStatewireFailedSendLane } from './thread-extras';\nimport { isConfigSyncUnavailableRejection } from './use-deep-agent-config';\nimport { useDeepAgentThread, type UseDeepAgentThreadOptions } from './use-deep-agent-thread';\n\n/** The thread element `useDeepAgentThread` produces — what the host's `useAui`\n * consumes (`useAui({ thread })`). */\nexport type DeepAgentThreadElement = ReturnType<typeof useDeepAgentThread>;\n\n/**\n * Durable command-lane persistence for the thread — where the lane identity and\n * the commands still awaiting a durable verdict live, so a reload resumes the\n * lane instead of minting a new client.\n *\n * - `local-storage`: browser hosts. Backed by {@link QuotaSafeLocalStorageSession}\n * (the upstream `LocalStorageSession` contract with Athena keys, legacy\n * metadata, and retention). A storage failure after mounting — the origin\n * out of quota after statewire's own sweep, a lockdown-mode write refusal —\n * degrades the session to memory-only persistence and is reported through\n * `onStorageDegraded` (default: one console warning); it never stops the\n * connection. Guarded by a shared availability probe: SSR, storage-blocked\n * iframes, and locked-down browsers keep the session process-local, exactly\n * as before the seam existed, while an origin that is merely full mounts the\n * session and lets it degrade. `key` scopes the stored lane — a thread id is\n * only meaningful against the backend (and, where relevant, principal) that\n * minted it, so include the sync-host URL.\n * - `session-store`: React Native hosts pass a hydrated library store backed\n * by AsyncStorage. Its synchronous `load()` is\n * also the send-recovery freshness gate's view of the persisted failed lane,\n * so a store that persists `failed` gets the same 24 h staleness rule as the\n * browser hosts.\n * - `pending`: the principal the lane is scoped by is not known yet (the\n * host's signed-in user is still loading). See {@link DeepAgentPendingSession}.\n * - `false`: process-local session (a reload mints a new client).\n *\n * A `local-storage` `key` must name the same lane for the whole life of the\n * thread's mount. The statewire session that mounts on a lane keeps the client\n * id, lane clocks and journal it loaded there; moved to another key, it carries\n * them along, and the record it left keeps the same client id with a stale\n * clock and whatever it had journaled. Every later mount that loads that record\n * resends its journal and stamps early commands from its clock, at or below the\n * watermark the server keeps for the id, so the server absorbs each one as a\n * replay and answers `expired`. A host whose principal arrives after mount\n * passes `pending` until it does, never a stand-in key.\n */\nexport type DeepAgentSessionPersistence =\n | {\n kind: 'local-storage';\n key: (threadId: string) => string;\n onStorageDegraded?: (degradation: SessionStorageDegradation) => void;\n }\n | { kind: 'session-store'; store: NonNullable<StatewireTransportOptions['sessionStore']> }\n | DeepAgentPendingSession\n | false;\n\n/**\n * The durable lane's principal is not known yet. A lane belongs to one\n * principal and a mounted session cannot move to another lane (see\n * {@link DeepAgentSessionPersistence}), so nothing is mounted, nothing is\n * journaled and the transport stays parked — no dial — until the host passes\n * the real persistence. The park is an attach identity of its own: the attach\n * that follows makes every per-attach decision afresh (the remembered-snapshot\n * seed, the pooled hello, which the park leaves in the pool) and dials the real\n * lane once, as the thread's first attach. The park carries no connection\n * message, so it reads like a never-started chat rather than a paused one; a\n * host suspension, when one applies, wins.\n */\nexport type DeepAgentPendingSession = { kind: 'pending' };\n\n/**\n * The attach key a pending-lane park runs under. It differs from every key the\n * host passes, so the real attach after it is a new attach identity.\n */\nexport function pendingSessionAttachKey(attachKey: string | number | undefined): string {\n return `session-pending:${attachKey ?? ''}`;\n}\n\n/** Structural view of the browser storage global — this package compiles\n * without the DOM lib (React Native honesty; see `globals.d.ts`). */\ntype StorageGlobals = {\n localStorage?: {\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n };\n navigator?: { locks?: unknown };\n};\n\n/**\n * Whether a localStorage-backed session can persist here. A write probe, not a\n * `typeof` check: storage-blocked iframes (SDK embeds under third-party-storage\n * blocking) throw from the `localStorage` accessor itself, and lockdown-mode\n * origins throw from `setItem` — each of those must mean \"stay process-local\",\n * never a crash at mount. An origin that is merely out of quota still reads\n * and takes locks, so it mounts the session: {@link QuotaSafeLocalStorageSession}\n * degrades that to memory-only persistence and recovers when room frees,\n * where staying process-local would give up cross-tab sharing for the whole\n * page load over a condition that clears.\n *\n * The Web Locks API is probed too: the session storage serializes the shared\n * record across tabs through `navigator.locks` and refuses to mount without it\n * (the same precondition as the vendor `LocalStorageSession`), so an origin\n * with localStorage but no Web Locks — older WebViews, Safari before 15.4 —\n * must also stay process-local instead of throwing at mount.\n */\nexport function canPersistDeepAgentSession(): boolean {\n try {\n const globals = globalThis as StorageGlobals;\n if (!globals.navigator?.locks) return false;\n const storage = globals.localStorage;\n if (!storage) return false;\n const probeKey = '__athena_statewire_session_probe__';\n try {\n storage.setItem(probeKey, probeKey);\n storage.removeItem(probeKey);\n } catch (error) {\n return isStorageQuotaError(error);\n }\n return true;\n } catch {\n return false;\n }\n}\n\n/** A host session store's current record, or `null` when the read throws —\n * the recovery gate must never take the runtime down over a storage fault. */\nfunction loadSessionRecord(\n store: NonNullable<StatewireTransportOptions['sessionStore']>\n): ReturnType<NonNullable<StatewireTransportOptions['sessionStore']>['load']> | null {\n try {\n return store.load();\n } catch {\n return null;\n }\n}\n\n/**\n * The shared tail of every surface's transport error policy: channel teardown\n * is lifecycle noise (logged at debug), as is `config_sync_unavailable` — only\n * `config/*` writes emit it, and the host's config sync already handles it\n * through its own command promise (regionally-pinned workspaces run on\n * local-only config); everything else is a real transport error. Hosts with their own error chain\n * (paywall, telemetry, send-failure stores) end it with this so the fallback\n * logging stays uniform per surface.\n */\nexport function logUnhandledStatewireError(surface: string, error: Error): void {\n if (isStatewireChannelTeardown(error)) {\n console.debug(`[${surface}] statewire channel teardown:`, error);\n return;\n }\n if (isConfigSyncUnavailableRejection(error)) {\n console.debug(`[${surface}] config sync unavailable (handled by config sync):`, error);\n return;\n }\n console.error(`[${surface}] statewire transport error:`, error);\n}\n\n/**\n * The imperative thread-state slice of an assistant-ui client — structural so\n * web and native `useAui` results both satisfy it. The send-recovery layer\n * reads the statewire thread extras through it (the sanctioned imperative\n * pattern: `aui.thread().getState()` inside callbacks/effects, never render).\n */\nexport interface DeepAgentThreadStateClientHost {\n thread: () => { getState: () => { extras?: unknown } };\n subscribe: (listener: () => void) => () => void;\n}\n\n/** Deep-agent runConfig registration on the ambient model context (see\n * {@link registerDeepAgentRunConfig}). */\nexport interface DeepAgentRunConfigRegistration {\n /**\n * Builds the `runConfig` that drives the run. The core pulls it when the\n * model context notifies (mount and each key change); the server reads that\n * synced value at command admission. The per-render ref makes each pull use\n * the latest closure but does not trigger a pull itself.\n */\n get: () => Record<string, unknown>;\n /**\n * Re-registers the provider and notifies model-context sync when this value\n * changes (reference equality). Include getter inputs that can change while\n * mounted; they reach the server only after a key change triggers a pull.\n */\n key?: unknown;\n}\n\nexport interface UseDeepAgentRuntimeOptions<\n TClient extends DeepAgentModelContextHost & DeepAgentThreadStateClientHost,\n> extends Omit<UseDeepAgentThreadOptions, 'storage' | 'sessionStore'> {\n /** Host surface tag for the shared fallback error logging (e.g.\n * `'AthenaDeepAgent'`, `'AthenaSDK'`, `'ChromeStatewire'`). */\n surface: string;\n /** Durable command-lane persistence; defaults to process-local. */\n session?: DeepAgentSessionPersistence;\n /**\n * Builds the host's assistant-ui client from the thread element — pass the\n * platform `useAui` (web hosts from `@assistant-ui/react`, React Native\n * hosts from `@assistant-ui/react-native`; this package imports neither).\n * Called unconditionally on every render, so it MUST be (or call) a hook\n * following the rules of hooks — define it at module scope with a `use*`\n * name: `const useHostClient = (thread: DeepAgentThreadElement) =>\n * useAui({ thread })`.\n */\n useClient: (thread: DeepAgentThreadElement) => TClient;\n /** Registers the deep-agent runConfig on the client's model context. Hosts\n * that register elsewhere (e.g. a bridge component mounted under the\n * provider) omit it. */\n runConfig?: DeepAgentRunConfigRegistration;\n}\n\n/**\n * The configurable deep-agent statewire runtime — the one hook every Athena\n * chat surface (Olympus, `@athenaintel/react` SDK, Chrome extension, Office\n * add-in, mobile) mounts its assistant-ui client through.\n *\n * Composes the pieces every surface previously wired by hand:\n * `useDeepAgentThread` (attach lifecycle, auth parking, lane projection,\n * connection/running observers), durable session persistence with the shared\n * availability guard, transport-level send recovery (failed sends the server\n * never judged auto-retry under their original message id — `send-recovery.ts`),\n * the platform client via `useClient`, and the\n * runConfig model-context registration on provider notifications. Everything\n * host-specific — auth headers, run-config building, error policy, adapters,\n * diagnostics recorders — stays an option, so per-surface behavior is\n * configuration, not a fork.\n */\nexport function useDeepAgentRuntime<\n TClient extends DeepAgentModelContextHost & DeepAgentThreadStateClientHost,\n>(options: UseDeepAgentRuntimeOptions<TClient>): TClient {\n const {\n surface,\n session = false,\n useClient,\n runConfig,\n onError,\n onStateChange,\n onCommandChange,\n ...threadOptions\n } = options;\n\n // Probed once per mount, like every browser host did individually: an origin\n // that cannot persist stays process-local for the mount's lifetime.\n const [canPersistLocalSession] = useState(canPersistDeepAgentSession);\n\n // Per-render refs so the transport never remounts on a new closure identity\n // and always reads the latest policy.\n const onErrorRef = useRef(onError);\n onErrorRef.current = onError;\n const surfaceRef = useRef(surface);\n surfaceRef.current = surface;\n const onStateChangeRef = useRef(onStateChange);\n onStateChangeRef.current = onStateChange;\n const onCommandChangeRef = useRef(onCommandChange);\n onCommandChangeRef.current = onCommandChange;\n const diagnosticsRef = useRef(threadOptions.diagnostics);\n diagnosticsRef.current = threadOptions.diagnostics;\n const clientRef = useRef<TClient | null>(null);\n\n // Where the persisted session record lives, for the recovery freshness gate\n // (read per sweep — undefined means \"no persistence, everything is fresh\").\n const failureStampsRef = useRef<(() => ReadonlyMap<string, number> | null) | undefined>(\n undefined\n );\n failureStampsRef.current =\n session === false || session.kind === 'pending'\n ? undefined\n : session.kind === 'local-storage'\n ? canPersistLocalSession\n ? () => readPersistedFailedSendStamps(session.key(threadOptions.threadId))\n : undefined\n : () => readFailedSendStampsFromRecord(loadSessionRecord(session.store));\n\n // A pending lane parks the thread under an attach identity of its own (see\n // `DeepAgentPendingSession`), so the attach once the lane is known is new.\n const sessionPending = session !== false && session.kind === 'pending';\n const attachKey = sessionPending\n ? pendingSessionAttachKey(threadOptions.attachKey)\n : threadOptions.attachKey;\n\n // Send recovery, keyed by attach identity exactly like the thread's session\n // (a switch or re-attach gets fresh hydration gating and retry budgets). The\n // controller re-dispatches failed sends the server never judged; see\n // `send-recovery.ts` for the contract.\n const attachId =\n attachKey === undefined ? threadOptions.threadId : `${threadOptions.threadId}:${attachKey}`;\n // biome-ignore lint/correctness/useExhaustiveDependencies: keyed by attach identity on purpose; every other input is read through a per-render ref.\n const sendRecovery = useMemo(\n () =>\n createDeepAgentSendRecovery({\n lane: () => {\n const client = clientRef.current;\n if (!client) return null;\n return readStatewireFailedSendLane(client.thread().getState().extras);\n },\n failureStamps: () => failureStampsRef.current?.() ?? null,\n diagnostics: () => diagnosticsRef.current,\n }),\n [attachId]\n );\n useEffect(() => () => sendRecovery.dispose(), [sendRecovery]);\n const sendRecoveryRef = useRef(sendRecovery);\n sendRecoveryRef.current = sendRecovery;\n\n const thread = useDeepAgentThread({\n ...threadOptions,\n ...(attachKey !== undefined && { attachKey }),\n // Parked without a message (a host suspension, when one applies, wins):\n // nothing dials and nothing journals until the lane is known. What paints\n // the first frame is the real attach's to report, not the park's.\n ...(sessionPending && {\n suspended: threadOptions.suspended || {},\n onAttachSource: undefined,\n }),\n // A pooled dial for the thread is the real attach's to take: a park that\n // only waits for the lane neither takes nor discards it. A host suspension\n // still discards it, as it does without a pending lane.\n ...(sessionPending && !threadOptions.suspended && { prewarmPool: undefined }),\n ...(session !== false &&\n session.kind === 'local-storage' &&\n canPersistLocalSession && {\n storage: {\n session: QuotaSafeLocalStorageSession({\n threadId: threadOptions.threadId,\n key: session.key,\n ...(session.onStorageDegraded === undefined\n ? {}\n : { onStorageDegraded: session.onStorageDegraded }),\n }),\n },\n }),\n ...(session !== false && session.kind === 'session-store' && { sessionStore: session.store }),\n onStateChange: (state, ctx) => {\n sendRecoveryRef.current.observeStateChange(state);\n onStateChangeRef.current?.(state, ctx);\n },\n onCommandChange: (update, ctx) => {\n sendRecoveryRef.current.observeCommand(update);\n onCommandChangeRef.current?.(update, ctx);\n },\n onError: (error) => {\n const handler = onErrorRef.current;\n if (handler) {\n handler(error);\n return;\n }\n logUnhandledStatewireError(surfaceRef.current, error);\n },\n });\n\n const client = useClient(thread);\n clientRef.current = client;\n\n // A locked shared journal can publish failed history after onCommandChange,\n // with no canonical state change to wake recovery. Observe the client that\n // owns the visible failed lane; the controller guards in-flight retries.\n useEffect(() => {\n const unsubscribe = client.subscribe(() => sendRecovery.observeFailedLaneChange());\n sendRecovery.observeFailedLaneChange();\n return unsubscribe;\n }, [client, sendRecovery]);\n\n // Commands travel bare, so the runConfig that drives the run rides the\n // ambient model context: `contextSync: 'command-send'` mirrors it to the\n // server, which reads `config.runConfig` at command admission. The core pulls\n // on model-context notifications; the per-render ref ensures each pull uses\n // the latest closure but does not trigger a pull itself.\n const getRunConfigRef = useRef(runConfig?.get);\n getRunConfigRef.current = runConfig?.get;\n const hasRunConfig = runConfig !== undefined;\n const runConfigKey = runConfig?.key;\n useEffect(() => {\n if (!hasRunConfig) return undefined;\n return registerDeepAgentRunConfig(client, () => getRunConfigRef.current?.() ?? {});\n }, [client, hasRunConfig, runConfigKey]);\n\n return client;\n}\n\n"],"names":["isStorageQuotaError","isStatewireChannelTeardown","isConfigSyncUnavailableRejection","useState","useRef","readPersistedFailedSendStamps","readFailedSendStampsFromRecord","sendRecovery","useMemo","createDeepAgentSendRecovery","client","readStatewireFailedSendLane","useEffect","useDeepAgentThread","QuotaSafeLocalStorageSession","registerDeepAgentRunConfig"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"use-deep-agent-runtime.cjs","sources":["../../../../agent-runtime/src/use-deep-agent-runtime.ts"],"sourcesContent":["import { useEffect, useMemo, useRef, useState } from 'react';\nimport { isStatewireChannelTeardown } from './errors';\nimport { type DeepAgentModelContextHost, registerDeepAgentRunConfig } from './model-context';\nimport {\n isStorageQuotaError,\n QuotaSafeLocalStorageSession,\n readFailedSendStampsFromRecord,\n readPersistedFailedSendStamps,\n type SessionStorageDegradation,\n} from './quota-safe-local-storage-session';\nimport { createDeepAgentSendRecovery } from './send-recovery';\nimport { INITIAL_SESSION_LANE_EPOCH, nextSessionLaneEpoch } from './session-lane-scope';\nimport type { StatewireTransportOptions } from './statewire-transport';\nimport { readStatewireFailedSendLane } from './thread-extras';\nimport { isConfigSyncUnavailableRejection } from './use-deep-agent-config';\nimport {\n deepAgentAttachId,\n useDeepAgentThread,\n type UseDeepAgentThreadOptions,\n} from './use-deep-agent-thread';\n\n/** The thread element `useDeepAgentThread` produces — what the host's `useAui`\n * consumes (`useAui({ thread })`). */\nexport type DeepAgentThreadElement = ReturnType<typeof useDeepAgentThread>;\n\n/**\n * Durable command-lane persistence for the thread — where the lane identity and\n * the commands still awaiting a durable verdict live, so a reload resumes the\n * lane instead of minting a new client.\n *\n * - `local-storage`: browser hosts. Backed by {@link QuotaSafeLocalStorageSession}\n * (the upstream `LocalStorageSession` contract with Athena keys, legacy\n * metadata, and retention). A storage failure after mounting — the origin\n * out of quota after statewire's own sweep, a lockdown-mode write refusal —\n * degrades the session to memory-only persistence and is reported through\n * `onStorageDegraded` (default: one console warning); it never stops the\n * connection. Guarded by a shared availability probe: SSR, storage-blocked\n * iframes, and locked-down browsers keep the session process-local, exactly\n * as before the seam existed, while an origin that is merely full mounts the\n * session and lets it degrade. `key` scopes the stored lane — a thread id is\n * only meaningful against the backend (and, where relevant, principal) that\n * minted it, so include the sync-host URL.\n * - `session-store`: React Native hosts pass a hydrated library store backed\n * by AsyncStorage. Its synchronous `load()` is\n * also the send-recovery freshness gate's view of the persisted failed lane,\n * so a store that persists `failed` gets the same 24 h staleness rule as the\n * browser hosts.\n * - `pending`: the principal the lane is scoped by is not known yet (the\n * host's signed-in user is still loading). See {@link DeepAgentPendingSession}.\n * - `false`: process-local session (a reload mints a new client).\n *\n * A `local-storage` `key` names one principal's lanes (and one backend's). The\n * statewire session that mounts on a lane keeps the client id, lane clocks and\n * journal it loaded there, and everything in that journal is sent under\n * whatever credential the transport holds at its next attach. So a mounted\n * session never moves to another key: when the key starts naming another\n * principal's lanes for the same thread (another account signed in under a\n * mounted chat), the runtime starts a new lane epoch\n * (`UseDeepAgentThreadOptions['laneEpoch']`). The whole statewire thread\n * remounts on the new principal's lane and attaches afresh, as that lane's own\n * client, with nothing remembered or pooled from before. The previous\n * principal's record stays where it is, with its client id and whatever it had\n * journaled and not yet delivered: that principal's next mount on this browser\n * resends it as them, and the retention sweep retires it otherwise. It is never\n * sent as anyone else. A key for the same principal (a token refresh, a fresh\n * `key` function) and a thread switch keep the lane as it is. A host whose\n * principal arrives after mount passes `pending` until it does, never a\n * stand-in key: moving off a stand-in would be a principal change, a second\n * attach.\n */\nexport type DeepAgentSessionPersistence =\n | {\n kind: 'local-storage';\n key: (threadId: string) => string;\n onStorageDegraded?: (degradation: SessionStorageDegradation) => void;\n }\n | { kind: 'session-store'; store: NonNullable<StatewireTransportOptions['sessionStore']> }\n | DeepAgentPendingSession\n | false;\n\n/**\n * The durable lane's principal is not known yet. A lane belongs to one\n * principal and a mounted session cannot move to another lane (see\n * {@link DeepAgentSessionPersistence}), so nothing is mounted, nothing is\n * journaled and the transport stays parked — no dial — until the host passes\n * the real persistence. The park is an attach identity of its own: the attach\n * that follows makes every per-attach decision afresh (the remembered-snapshot\n * seed, the pooled hello, which the park leaves in the pool) and dials the real\n * lane once, as the thread's first attach. The park carries no connection\n * message, so it reads like a never-started chat rather than a paused one; a\n * host suspension, when one applies, wins.\n */\nexport type DeepAgentPendingSession = { kind: 'pending' };\n\n/**\n * The attach key a pending-lane park runs under. It differs from every key the\n * host passes, so the real attach after it is a new attach identity.\n */\nexport function pendingSessionAttachKey(attachKey: string | number | undefined): string {\n return `session-pending:${attachKey ?? ''}`;\n}\n\n/** Structural view of the browser storage global — this package compiles\n * without the DOM lib (React Native honesty; see `globals.d.ts`). */\ntype StorageGlobals = {\n localStorage?: {\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n };\n navigator?: { locks?: unknown };\n};\n\n/**\n * Whether a localStorage-backed session can persist here. A write probe, not a\n * `typeof` check: storage-blocked iframes (SDK embeds under third-party-storage\n * blocking) throw from the `localStorage` accessor itself, and lockdown-mode\n * origins throw from `setItem` — each of those must mean \"stay process-local\",\n * never a crash at mount. An origin that is merely out of quota still reads\n * and takes locks, so it mounts the session: {@link QuotaSafeLocalStorageSession}\n * degrades that to memory-only persistence and recovers when room frees,\n * where staying process-local would give up cross-tab sharing for the whole\n * page load over a condition that clears.\n *\n * The Web Locks API is probed too: the session storage serializes the shared\n * record across tabs through `navigator.locks` and refuses to mount without it\n * (the same precondition as the vendor `LocalStorageSession`), so an origin\n * with localStorage but no Web Locks — older WebViews, Safari before 15.4 —\n * must also stay process-local instead of throwing at mount.\n */\nexport function canPersistDeepAgentSession(): boolean {\n try {\n const globals = globalThis as StorageGlobals;\n if (!globals.navigator?.locks) return false;\n const storage = globals.localStorage;\n if (!storage) return false;\n const probeKey = '__athena_statewire_session_probe__';\n try {\n storage.setItem(probeKey, probeKey);\n storage.removeItem(probeKey);\n } catch (error) {\n return isStorageQuotaError(error);\n }\n return true;\n } catch {\n return false;\n }\n}\n\n/** A host session store's current record, or `null` when the read throws —\n * the recovery gate must never take the runtime down over a storage fault. */\nfunction loadSessionRecord(\n store: NonNullable<StatewireTransportOptions['sessionStore']>\n): ReturnType<NonNullable<StatewireTransportOptions['sessionStore']>['load']> | null {\n try {\n return store.load();\n } catch {\n return null;\n }\n}\n\n/**\n * The shared tail of every surface's transport error policy: channel teardown\n * is lifecycle noise (logged at debug), as is `config_sync_unavailable` — only\n * `config/*` writes emit it, and the host's config sync already handles it\n * through its own command promise (regionally-pinned workspaces run on\n * local-only config); everything else is a real transport error. Hosts with their own error chain\n * (paywall, telemetry, send-failure stores) end it with this so the fallback\n * logging stays uniform per surface.\n */\nexport function logUnhandledStatewireError(surface: string, error: Error): void {\n if (isStatewireChannelTeardown(error)) {\n console.debug(`[${surface}] statewire channel teardown:`, error);\n return;\n }\n if (isConfigSyncUnavailableRejection(error)) {\n console.debug(`[${surface}] config sync unavailable (handled by config sync):`, error);\n return;\n }\n console.error(`[${surface}] statewire transport error:`, error);\n}\n\n/**\n * The imperative thread-state slice of an assistant-ui client — structural so\n * web and native `useAui` results both satisfy it. The send-recovery layer\n * reads the statewire thread extras through it (the sanctioned imperative\n * pattern: `aui.thread().getState()` inside callbacks/effects, never render).\n */\nexport interface DeepAgentThreadStateClientHost {\n thread: () => { getState: () => { extras?: unknown } };\n subscribe: (listener: () => void) => () => void;\n}\n\n/** Deep-agent runConfig registration on the ambient model context (see\n * {@link registerDeepAgentRunConfig}). */\nexport interface DeepAgentRunConfigRegistration {\n /**\n * Builds the `runConfig` that drives the run. The core pulls it when the\n * model context notifies (mount and each key change); the server reads that\n * synced value at command admission. The per-render ref makes each pull use\n * the latest closure but does not trigger a pull itself.\n */\n get: () => Record<string, unknown>;\n /**\n * Re-registers the provider and notifies model-context sync when this value\n * changes (reference equality). Include getter inputs that can change while\n * mounted; they reach the server only after a key change triggers a pull.\n */\n key?: unknown;\n}\n\nexport interface UseDeepAgentRuntimeOptions<\n TClient extends DeepAgentModelContextHost & DeepAgentThreadStateClientHost,\n> extends Omit<UseDeepAgentThreadOptions, 'storage' | 'sessionStore' | 'laneEpoch'> {\n /** Host surface tag for the shared fallback error logging (e.g.\n * `'AthenaDeepAgent'`, `'AthenaSDK'`, `'ChromeStatewire'`). */\n surface: string;\n /** Durable command-lane persistence; defaults to process-local. */\n session?: DeepAgentSessionPersistence;\n /**\n * Builds the host's assistant-ui client from the thread element — pass the\n * platform `useAui` (web hosts from `@assistant-ui/react`, React Native\n * hosts from `@assistant-ui/react-native`; this package imports neither).\n * Called unconditionally on every render, so it MUST be (or call) a hook\n * following the rules of hooks — define it at module scope with a `use*`\n * name: `const useHostClient = (thread: DeepAgentThreadElement) =>\n * useAui({ thread })`.\n */\n useClient: (thread: DeepAgentThreadElement) => TClient;\n /** Registers the deep-agent runConfig on the client's model context. Hosts\n * that register elsewhere (e.g. a bridge component mounted under the\n * provider) omit it. */\n runConfig?: DeepAgentRunConfigRegistration;\n}\n\n/**\n * The configurable deep-agent statewire runtime — the one hook every Athena\n * chat surface (Olympus, `@athenaintel/react` SDK, Chrome extension, Office\n * add-in, mobile) mounts its assistant-ui client through.\n *\n * Composes the pieces every surface previously wired by hand:\n * `useDeepAgentThread` (attach lifecycle, auth parking, lane projection,\n * connection/running observers), durable session persistence with the shared\n * availability guard, transport-level send recovery (failed sends the server\n * never judged auto-retry under their original message id — `send-recovery.ts`),\n * the platform client via `useClient`, and the\n * runConfig model-context registration on provider notifications. Everything\n * host-specific — auth headers, run-config building, error policy, adapters,\n * diagnostics recorders — stays an option, so per-surface behavior is\n * configuration, not a fork.\n */\nexport function useDeepAgentRuntime<\n TClient extends DeepAgentModelContextHost & DeepAgentThreadStateClientHost,\n>(options: UseDeepAgentRuntimeOptions<TClient>): TClient {\n const {\n surface,\n session = false,\n useClient,\n runConfig,\n onError,\n onStateChange,\n onCommandChange,\n ...threadOptions\n } = options;\n\n // Probed once per mount, like every browser host did individually: an origin\n // that cannot persist stays process-local for the mount's lifetime.\n const [canPersistLocalSession] = useState(canPersistDeepAgentSession);\n\n // Per-render refs so the transport never remounts on a new closure identity\n // and always reads the latest policy.\n const onErrorRef = useRef(onError);\n onErrorRef.current = onError;\n const surfaceRef = useRef(surface);\n surfaceRef.current = surface;\n const onStateChangeRef = useRef(onStateChange);\n onStateChangeRef.current = onStateChange;\n const onCommandChangeRef = useRef(onCommandChange);\n onCommandChangeRef.current = onCommandChange;\n const diagnosticsRef = useRef(threadOptions.diagnostics);\n diagnosticsRef.current = threadOptions.diagnostics;\n const clientRef = useRef<TClient | null>(null);\n\n // Where the persisted session record lives, for the recovery freshness gate\n // (read per sweep — undefined means \"no persistence, everything is fresh\").\n const failureStampsRef = useRef<(() => ReadonlyMap<string, number> | null) | undefined>(\n undefined\n );\n failureStampsRef.current =\n session === false || session.kind === 'pending'\n ? undefined\n : session.kind === 'local-storage'\n ? canPersistLocalSession\n ? () => readPersistedFailedSendStamps(session.key(threadOptions.threadId))\n : undefined\n : () => readFailedSendStampsFromRecord(loadSessionRecord(session.store));\n\n // A pending lane parks the thread under an attach identity of its own (see\n // `DeepAgentPendingSession`), so the attach once the lane is known is new.\n const sessionPending = session !== false && session.kind === 'pending';\n const attachKey = sessionPending\n ? pendingSessionAttachKey(threadOptions.attachKey)\n : threadOptions.attachKey;\n\n // The lane's scope, as the host's `key` names it: whose lanes this mount\n // runs on. A key that starts naming another principal's lanes starts a new\n // lane epoch, which remounts the whole thread on the new lane instead of\n // moving the mounted session there (see `DeepAgentSessionPersistence`).\n // Tracked whether or not this origin can persist: a process-local session\n // holds the previous principal's journal in memory just the same. A pending\n // lane keeps the last known scope, so a sign-out and a sign-in as someone\n // else with a gap between them is still a change.\n const laneKey = session !== false && session.kind === 'local-storage' ? session.key : null;\n const [laneEpochState, setLaneEpochState] = useState(() =>\n nextSessionLaneEpoch({\n previous: INITIAL_SESSION_LANE_EPOCH,\n threadId: threadOptions.threadId,\n key: laneKey,\n })\n );\n const nextLaneEpochState = nextSessionLaneEpoch({\n previous: laneEpochState,\n threadId: threadOptions.threadId,\n key: laneKey,\n });\n // React's derived-state update: re-renders at once with the new epoch, and\n // this render already uses it, so no render pairs the new key with the\n // mounted thread.\n if (nextLaneEpochState !== laneEpochState) setLaneEpochState(nextLaneEpochState);\n const laneEpoch = nextLaneEpochState.epoch;\n\n // Send recovery, keyed by attach identity exactly like the thread's session\n // (a switch, re-attach or new lane epoch gets fresh hydration gating and\n // retry budgets, so it never re-dispatches a previous principal's failed\n // sends). The controller re-dispatches failed sends the server never judged;\n // see `send-recovery.ts` for the contract.\n const attachId = deepAgentAttachId({ threadId: threadOptions.threadId, attachKey, laneEpoch });\n // biome-ignore lint/correctness/useExhaustiveDependencies: keyed by attach identity on purpose; every other input is read through a per-render ref.\n const sendRecovery = useMemo(\n () =>\n createDeepAgentSendRecovery({\n lane: () => {\n const client = clientRef.current;\n if (!client) return null;\n return readStatewireFailedSendLane(client.thread().getState().extras);\n },\n failureStamps: () => failureStampsRef.current?.() ?? null,\n diagnostics: () => diagnosticsRef.current,\n }),\n [attachId]\n );\n useEffect(() => () => sendRecovery.dispose(), [sendRecovery]);\n const sendRecoveryRef = useRef(sendRecovery);\n sendRecoveryRef.current = sendRecovery;\n\n const thread = useDeepAgentThread({\n ...threadOptions,\n ...(attachKey !== undefined && { attachKey }),\n laneEpoch,\n // Parked without a message (a host suspension, when one applies, wins):\n // nothing dials and nothing journals until the lane is known. What paints\n // the first frame is the real attach's to report, not the park's.\n ...(sessionPending && {\n suspended: threadOptions.suspended || {},\n onAttachSource: undefined,\n }),\n // A pooled dial for the thread is the real attach's to take: a park that\n // only waits for the lane neither takes nor discards it. A host suspension\n // still discards it, as it does without a pending lane.\n ...(sessionPending && !threadOptions.suspended && { prewarmPool: undefined }),\n ...(session !== false &&\n session.kind === 'local-storage' &&\n canPersistLocalSession && {\n storage: {\n session: QuotaSafeLocalStorageSession({\n threadId: threadOptions.threadId,\n key: session.key,\n ...(session.onStorageDegraded === undefined\n ? {}\n : { onStorageDegraded: session.onStorageDegraded }),\n }),\n },\n }),\n ...(session !== false && session.kind === 'session-store' && { sessionStore: session.store }),\n onStateChange: (state, ctx) => {\n sendRecoveryRef.current.observeStateChange(state);\n onStateChangeRef.current?.(state, ctx);\n },\n onCommandChange: (update, ctx) => {\n sendRecoveryRef.current.observeCommand(update);\n onCommandChangeRef.current?.(update, ctx);\n },\n onError: (error) => {\n const handler = onErrorRef.current;\n if (handler) {\n handler(error);\n return;\n }\n logUnhandledStatewireError(surfaceRef.current, error);\n },\n });\n\n const client = useClient(thread);\n clientRef.current = client;\n\n // A locked shared journal can publish failed history after onCommandChange,\n // with no canonical state change to wake recovery. Observe the client that\n // owns the visible failed lane; the controller guards in-flight retries.\n useEffect(() => {\n const unsubscribe = client.subscribe(() => sendRecovery.observeFailedLaneChange());\n sendRecovery.observeFailedLaneChange();\n return unsubscribe;\n }, [client, sendRecovery]);\n\n // Commands travel bare, so the runConfig that drives the run rides the\n // ambient model context: `contextSync: 'command-send'` mirrors it to the\n // server, which reads `config.runConfig` at command admission. The core pulls\n // on model-context notifications; the per-render ref ensures each pull uses\n // the latest closure but does not trigger a pull itself.\n const getRunConfigRef = useRef(runConfig?.get);\n getRunConfigRef.current = runConfig?.get;\n const hasRunConfig = runConfig !== undefined;\n const runConfigKey = runConfig?.key;\n useEffect(() => {\n if (!hasRunConfig) return undefined;\n return registerDeepAgentRunConfig(client, () => getRunConfigRef.current?.() ?? {});\n }, [client, hasRunConfig, runConfigKey]);\n\n return client;\n}\n\n"],"names":["isStorageQuotaError","isStatewireChannelTeardown","isConfigSyncUnavailableRejection","useState","useRef","readPersistedFailedSendStamps","readFailedSendStampsFromRecord","nextSessionLaneEpoch","INITIAL_SESSION_LANE_EPOCH","deepAgentAttachId","sendRecovery","useMemo","createDeepAgentSendRecovery","client","readStatewireFailedSendLane","useEffect","useDeepAgentThread","QuotaSafeLocalStorageSession","registerDeepAgentRunConfig"],"mappings":"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"}
|
|
@@ -31,15 +31,24 @@ export type DeepAgentThreadElement = ReturnType<typeof useDeepAgentThread>;
|
|
|
31
31
|
* host's signed-in user is still loading). See {@link DeepAgentPendingSession}.
|
|
32
32
|
* - `false`: process-local session (a reload mints a new client).
|
|
33
33
|
*
|
|
34
|
-
* A `local-storage` `key`
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
34
|
+
* A `local-storage` `key` names one principal's lanes (and one backend's). The
|
|
35
|
+
* statewire session that mounts on a lane keeps the client id, lane clocks and
|
|
36
|
+
* journal it loaded there, and everything in that journal is sent under
|
|
37
|
+
* whatever credential the transport holds at its next attach. So a mounted
|
|
38
|
+
* session never moves to another key: when the key starts naming another
|
|
39
|
+
* principal's lanes for the same thread (another account signed in under a
|
|
40
|
+
* mounted chat), the runtime starts a new lane epoch
|
|
41
|
+
* (`UseDeepAgentThreadOptions['laneEpoch']`). The whole statewire thread
|
|
42
|
+
* remounts on the new principal's lane and attaches afresh, as that lane's own
|
|
43
|
+
* client, with nothing remembered or pooled from before. The previous
|
|
44
|
+
* principal's record stays where it is, with its client id and whatever it had
|
|
45
|
+
* journaled and not yet delivered: that principal's next mount on this browser
|
|
46
|
+
* resends it as them, and the retention sweep retires it otherwise. It is never
|
|
47
|
+
* sent as anyone else. A key for the same principal (a token refresh, a fresh
|
|
48
|
+
* `key` function) and a thread switch keep the lane as it is. A host whose
|
|
49
|
+
* principal arrives after mount passes `pending` until it does, never a
|
|
50
|
+
* stand-in key: moving off a stand-in would be a principal change, a second
|
|
51
|
+
* attach.
|
|
43
52
|
*/
|
|
44
53
|
export type DeepAgentSessionPersistence = {
|
|
45
54
|
kind: 'local-storage';
|
|
@@ -128,7 +137,7 @@ export interface DeepAgentRunConfigRegistration {
|
|
|
128
137
|
*/
|
|
129
138
|
key?: unknown;
|
|
130
139
|
}
|
|
131
|
-
export interface UseDeepAgentRuntimeOptions<TClient extends DeepAgentModelContextHost & DeepAgentThreadStateClientHost> extends Omit<UseDeepAgentThreadOptions, 'storage' | 'sessionStore'> {
|
|
140
|
+
export interface UseDeepAgentRuntimeOptions<TClient extends DeepAgentModelContextHost & DeepAgentThreadStateClientHost> extends Omit<UseDeepAgentThreadOptions, 'storage' | 'sessionStore' | 'laneEpoch'> {
|
|
132
141
|
/** Host surface tag for the shared fallback error logging (e.g.
|
|
133
142
|
* `'AthenaDeepAgent'`, `'AthenaSDK'`, `'ChromeStatewire'`). */
|
|
134
143
|
surface: string;
|
|
@@ -3,9 +3,10 @@ import { isStatewireChannelTeardown } from "./errors.js";
|
|
|
3
3
|
import { registerDeepAgentRunConfig } from "./model-context.js";
|
|
4
4
|
import { readPersistedFailedSendStamps, readFailedSendStampsFromRecord, QuotaSafeLocalStorageSession, isStorageQuotaError } from "./quota-safe-local-storage-session.js";
|
|
5
5
|
import { createDeepAgentSendRecovery } from "./send-recovery.js";
|
|
6
|
+
import { nextSessionLaneEpoch, INITIAL_SESSION_LANE_EPOCH } from "./session-lane-scope.js";
|
|
6
7
|
import { readStatewireFailedSendLane } from "./thread-extras.js";
|
|
7
8
|
import { isConfigSyncUnavailableRejection } from "./use-deep-agent-config.js";
|
|
8
|
-
import { useDeepAgentThread } from "./use-deep-agent-thread.js";
|
|
9
|
+
import { deepAgentAttachId, useDeepAgentThread } from "./use-deep-agent-thread.js";
|
|
9
10
|
function pendingSessionAttachKey(attachKey) {
|
|
10
11
|
return `session-pending:${attachKey ?? ""}`;
|
|
11
12
|
}
|
|
@@ -75,7 +76,22 @@ function useDeepAgentRuntime(options) {
|
|
|
75
76
|
failureStampsRef.current = session === false || session.kind === "pending" ? void 0 : session.kind === "local-storage" ? canPersistLocalSession ? () => readPersistedFailedSendStamps(session.key(threadOptions.threadId)) : void 0 : () => readFailedSendStampsFromRecord(loadSessionRecord(session.store));
|
|
76
77
|
const sessionPending = session !== false && session.kind === "pending";
|
|
77
78
|
const attachKey = sessionPending ? pendingSessionAttachKey(threadOptions.attachKey) : threadOptions.attachKey;
|
|
78
|
-
const
|
|
79
|
+
const laneKey = session !== false && session.kind === "local-storage" ? session.key : null;
|
|
80
|
+
const [laneEpochState, setLaneEpochState] = useState(
|
|
81
|
+
() => nextSessionLaneEpoch({
|
|
82
|
+
previous: INITIAL_SESSION_LANE_EPOCH,
|
|
83
|
+
threadId: threadOptions.threadId,
|
|
84
|
+
key: laneKey
|
|
85
|
+
})
|
|
86
|
+
);
|
|
87
|
+
const nextLaneEpochState = nextSessionLaneEpoch({
|
|
88
|
+
previous: laneEpochState,
|
|
89
|
+
threadId: threadOptions.threadId,
|
|
90
|
+
key: laneKey
|
|
91
|
+
});
|
|
92
|
+
if (nextLaneEpochState !== laneEpochState) setLaneEpochState(nextLaneEpochState);
|
|
93
|
+
const laneEpoch = nextLaneEpochState.epoch;
|
|
94
|
+
const attachId = deepAgentAttachId({ threadId: threadOptions.threadId, attachKey, laneEpoch });
|
|
79
95
|
const sendRecovery = useMemo(
|
|
80
96
|
() => createDeepAgentSendRecovery({
|
|
81
97
|
lane: () => {
|
|
@@ -97,6 +113,7 @@ function useDeepAgentRuntime(options) {
|
|
|
97
113
|
const thread = useDeepAgentThread({
|
|
98
114
|
...threadOptions,
|
|
99
115
|
...attachKey !== void 0 && { attachKey },
|
|
116
|
+
laneEpoch,
|
|
100
117
|
// Parked without a message (a host suspension, when one applies, wins):
|
|
101
118
|
// nothing dials and nothing journals until the lane is known. What paints
|
|
102
119
|
// the first frame is the real attach's to report, not the park's.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-deep-agent-runtime.js","sources":["../../../../agent-runtime/src/use-deep-agent-runtime.ts"],"sourcesContent":["import { useEffect, useMemo, useRef, useState } from 'react';\nimport { isStatewireChannelTeardown } from './errors';\nimport { type DeepAgentModelContextHost, registerDeepAgentRunConfig } from './model-context';\nimport {\n isStorageQuotaError,\n QuotaSafeLocalStorageSession,\n readFailedSendStampsFromRecord,\n readPersistedFailedSendStamps,\n type SessionStorageDegradation,\n} from './quota-safe-local-storage-session';\nimport { createDeepAgentSendRecovery } from './send-recovery';\nimport type { StatewireTransportOptions } from './statewire-transport';\nimport { readStatewireFailedSendLane } from './thread-extras';\nimport { isConfigSyncUnavailableRejection } from './use-deep-agent-config';\nimport { useDeepAgentThread, type UseDeepAgentThreadOptions } from './use-deep-agent-thread';\n\n/** The thread element `useDeepAgentThread` produces — what the host's `useAui`\n * consumes (`useAui({ thread })`). */\nexport type DeepAgentThreadElement = ReturnType<typeof useDeepAgentThread>;\n\n/**\n * Durable command-lane persistence for the thread — where the lane identity and\n * the commands still awaiting a durable verdict live, so a reload resumes the\n * lane instead of minting a new client.\n *\n * - `local-storage`: browser hosts. Backed by {@link QuotaSafeLocalStorageSession}\n * (the upstream `LocalStorageSession` contract with Athena keys, legacy\n * metadata, and retention). A storage failure after mounting — the origin\n * out of quota after statewire's own sweep, a lockdown-mode write refusal —\n * degrades the session to memory-only persistence and is reported through\n * `onStorageDegraded` (default: one console warning); it never stops the\n * connection. Guarded by a shared availability probe: SSR, storage-blocked\n * iframes, and locked-down browsers keep the session process-local, exactly\n * as before the seam existed, while an origin that is merely full mounts the\n * session and lets it degrade. `key` scopes the stored lane — a thread id is\n * only meaningful against the backend (and, where relevant, principal) that\n * minted it, so include the sync-host URL.\n * - `session-store`: React Native hosts pass a hydrated library store backed\n * by AsyncStorage. Its synchronous `load()` is\n * also the send-recovery freshness gate's view of the persisted failed lane,\n * so a store that persists `failed` gets the same 24 h staleness rule as the\n * browser hosts.\n * - `pending`: the principal the lane is scoped by is not known yet (the\n * host's signed-in user is still loading). See {@link DeepAgentPendingSession}.\n * - `false`: process-local session (a reload mints a new client).\n *\n * A `local-storage` `key` must name the same lane for the whole life of the\n * thread's mount. The statewire session that mounts on a lane keeps the client\n * id, lane clocks and journal it loaded there; moved to another key, it carries\n * them along, and the record it left keeps the same client id with a stale\n * clock and whatever it had journaled. Every later mount that loads that record\n * resends its journal and stamps early commands from its clock, at or below the\n * watermark the server keeps for the id, so the server absorbs each one as a\n * replay and answers `expired`. A host whose principal arrives after mount\n * passes `pending` until it does, never a stand-in key.\n */\nexport type DeepAgentSessionPersistence =\n | {\n kind: 'local-storage';\n key: (threadId: string) => string;\n onStorageDegraded?: (degradation: SessionStorageDegradation) => void;\n }\n | { kind: 'session-store'; store: NonNullable<StatewireTransportOptions['sessionStore']> }\n | DeepAgentPendingSession\n | false;\n\n/**\n * The durable lane's principal is not known yet. A lane belongs to one\n * principal and a mounted session cannot move to another lane (see\n * {@link DeepAgentSessionPersistence}), so nothing is mounted, nothing is\n * journaled and the transport stays parked — no dial — until the host passes\n * the real persistence. The park is an attach identity of its own: the attach\n * that follows makes every per-attach decision afresh (the remembered-snapshot\n * seed, the pooled hello, which the park leaves in the pool) and dials the real\n * lane once, as the thread's first attach. The park carries no connection\n * message, so it reads like a never-started chat rather than a paused one; a\n * host suspension, when one applies, wins.\n */\nexport type DeepAgentPendingSession = { kind: 'pending' };\n\n/**\n * The attach key a pending-lane park runs under. It differs from every key the\n * host passes, so the real attach after it is a new attach identity.\n */\nexport function pendingSessionAttachKey(attachKey: string | number | undefined): string {\n return `session-pending:${attachKey ?? ''}`;\n}\n\n/** Structural view of the browser storage global — this package compiles\n * without the DOM lib (React Native honesty; see `globals.d.ts`). */\ntype StorageGlobals = {\n localStorage?: {\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n };\n navigator?: { locks?: unknown };\n};\n\n/**\n * Whether a localStorage-backed session can persist here. A write probe, not a\n * `typeof` check: storage-blocked iframes (SDK embeds under third-party-storage\n * blocking) throw from the `localStorage` accessor itself, and lockdown-mode\n * origins throw from `setItem` — each of those must mean \"stay process-local\",\n * never a crash at mount. An origin that is merely out of quota still reads\n * and takes locks, so it mounts the session: {@link QuotaSafeLocalStorageSession}\n * degrades that to memory-only persistence and recovers when room frees,\n * where staying process-local would give up cross-tab sharing for the whole\n * page load over a condition that clears.\n *\n * The Web Locks API is probed too: the session storage serializes the shared\n * record across tabs through `navigator.locks` and refuses to mount without it\n * (the same precondition as the vendor `LocalStorageSession`), so an origin\n * with localStorage but no Web Locks — older WebViews, Safari before 15.4 —\n * must also stay process-local instead of throwing at mount.\n */\nexport function canPersistDeepAgentSession(): boolean {\n try {\n const globals = globalThis as StorageGlobals;\n if (!globals.navigator?.locks) return false;\n const storage = globals.localStorage;\n if (!storage) return false;\n const probeKey = '__athena_statewire_session_probe__';\n try {\n storage.setItem(probeKey, probeKey);\n storage.removeItem(probeKey);\n } catch (error) {\n return isStorageQuotaError(error);\n }\n return true;\n } catch {\n return false;\n }\n}\n\n/** A host session store's current record, or `null` when the read throws —\n * the recovery gate must never take the runtime down over a storage fault. */\nfunction loadSessionRecord(\n store: NonNullable<StatewireTransportOptions['sessionStore']>\n): ReturnType<NonNullable<StatewireTransportOptions['sessionStore']>['load']> | null {\n try {\n return store.load();\n } catch {\n return null;\n }\n}\n\n/**\n * The shared tail of every surface's transport error policy: channel teardown\n * is lifecycle noise (logged at debug), as is `config_sync_unavailable` — only\n * `config/*` writes emit it, and the host's config sync already handles it\n * through its own command promise (regionally-pinned workspaces run on\n * local-only config); everything else is a real transport error. Hosts with their own error chain\n * (paywall, telemetry, send-failure stores) end it with this so the fallback\n * logging stays uniform per surface.\n */\nexport function logUnhandledStatewireError(surface: string, error: Error): void {\n if (isStatewireChannelTeardown(error)) {\n console.debug(`[${surface}] statewire channel teardown:`, error);\n return;\n }\n if (isConfigSyncUnavailableRejection(error)) {\n console.debug(`[${surface}] config sync unavailable (handled by config sync):`, error);\n return;\n }\n console.error(`[${surface}] statewire transport error:`, error);\n}\n\n/**\n * The imperative thread-state slice of an assistant-ui client — structural so\n * web and native `useAui` results both satisfy it. The send-recovery layer\n * reads the statewire thread extras through it (the sanctioned imperative\n * pattern: `aui.thread().getState()` inside callbacks/effects, never render).\n */\nexport interface DeepAgentThreadStateClientHost {\n thread: () => { getState: () => { extras?: unknown } };\n subscribe: (listener: () => void) => () => void;\n}\n\n/** Deep-agent runConfig registration on the ambient model context (see\n * {@link registerDeepAgentRunConfig}). */\nexport interface DeepAgentRunConfigRegistration {\n /**\n * Builds the `runConfig` that drives the run. The core pulls it when the\n * model context notifies (mount and each key change); the server reads that\n * synced value at command admission. The per-render ref makes each pull use\n * the latest closure but does not trigger a pull itself.\n */\n get: () => Record<string, unknown>;\n /**\n * Re-registers the provider and notifies model-context sync when this value\n * changes (reference equality). Include getter inputs that can change while\n * mounted; they reach the server only after a key change triggers a pull.\n */\n key?: unknown;\n}\n\nexport interface UseDeepAgentRuntimeOptions<\n TClient extends DeepAgentModelContextHost & DeepAgentThreadStateClientHost,\n> extends Omit<UseDeepAgentThreadOptions, 'storage' | 'sessionStore'> {\n /** Host surface tag for the shared fallback error logging (e.g.\n * `'AthenaDeepAgent'`, `'AthenaSDK'`, `'ChromeStatewire'`). */\n surface: string;\n /** Durable command-lane persistence; defaults to process-local. */\n session?: DeepAgentSessionPersistence;\n /**\n * Builds the host's assistant-ui client from the thread element — pass the\n * platform `useAui` (web hosts from `@assistant-ui/react`, React Native\n * hosts from `@assistant-ui/react-native`; this package imports neither).\n * Called unconditionally on every render, so it MUST be (or call) a hook\n * following the rules of hooks — define it at module scope with a `use*`\n * name: `const useHostClient = (thread: DeepAgentThreadElement) =>\n * useAui({ thread })`.\n */\n useClient: (thread: DeepAgentThreadElement) => TClient;\n /** Registers the deep-agent runConfig on the client's model context. Hosts\n * that register elsewhere (e.g. a bridge component mounted under the\n * provider) omit it. */\n runConfig?: DeepAgentRunConfigRegistration;\n}\n\n/**\n * The configurable deep-agent statewire runtime — the one hook every Athena\n * chat surface (Olympus, `@athenaintel/react` SDK, Chrome extension, Office\n * add-in, mobile) mounts its assistant-ui client through.\n *\n * Composes the pieces every surface previously wired by hand:\n * `useDeepAgentThread` (attach lifecycle, auth parking, lane projection,\n * connection/running observers), durable session persistence with the shared\n * availability guard, transport-level send recovery (failed sends the server\n * never judged auto-retry under their original message id — `send-recovery.ts`),\n * the platform client via `useClient`, and the\n * runConfig model-context registration on provider notifications. Everything\n * host-specific — auth headers, run-config building, error policy, adapters,\n * diagnostics recorders — stays an option, so per-surface behavior is\n * configuration, not a fork.\n */\nexport function useDeepAgentRuntime<\n TClient extends DeepAgentModelContextHost & DeepAgentThreadStateClientHost,\n>(options: UseDeepAgentRuntimeOptions<TClient>): TClient {\n const {\n surface,\n session = false,\n useClient,\n runConfig,\n onError,\n onStateChange,\n onCommandChange,\n ...threadOptions\n } = options;\n\n // Probed once per mount, like every browser host did individually: an origin\n // that cannot persist stays process-local for the mount's lifetime.\n const [canPersistLocalSession] = useState(canPersistDeepAgentSession);\n\n // Per-render refs so the transport never remounts on a new closure identity\n // and always reads the latest policy.\n const onErrorRef = useRef(onError);\n onErrorRef.current = onError;\n const surfaceRef = useRef(surface);\n surfaceRef.current = surface;\n const onStateChangeRef = useRef(onStateChange);\n onStateChangeRef.current = onStateChange;\n const onCommandChangeRef = useRef(onCommandChange);\n onCommandChangeRef.current = onCommandChange;\n const diagnosticsRef = useRef(threadOptions.diagnostics);\n diagnosticsRef.current = threadOptions.diagnostics;\n const clientRef = useRef<TClient | null>(null);\n\n // Where the persisted session record lives, for the recovery freshness gate\n // (read per sweep — undefined means \"no persistence, everything is fresh\").\n const failureStampsRef = useRef<(() => ReadonlyMap<string, number> | null) | undefined>(\n undefined\n );\n failureStampsRef.current =\n session === false || session.kind === 'pending'\n ? undefined\n : session.kind === 'local-storage'\n ? canPersistLocalSession\n ? () => readPersistedFailedSendStamps(session.key(threadOptions.threadId))\n : undefined\n : () => readFailedSendStampsFromRecord(loadSessionRecord(session.store));\n\n // A pending lane parks the thread under an attach identity of its own (see\n // `DeepAgentPendingSession`), so the attach once the lane is known is new.\n const sessionPending = session !== false && session.kind === 'pending';\n const attachKey = sessionPending\n ? pendingSessionAttachKey(threadOptions.attachKey)\n : threadOptions.attachKey;\n\n // Send recovery, keyed by attach identity exactly like the thread's session\n // (a switch or re-attach gets fresh hydration gating and retry budgets). The\n // controller re-dispatches failed sends the server never judged; see\n // `send-recovery.ts` for the contract.\n const attachId =\n attachKey === undefined ? threadOptions.threadId : `${threadOptions.threadId}:${attachKey}`;\n // biome-ignore lint/correctness/useExhaustiveDependencies: keyed by attach identity on purpose; every other input is read through a per-render ref.\n const sendRecovery = useMemo(\n () =>\n createDeepAgentSendRecovery({\n lane: () => {\n const client = clientRef.current;\n if (!client) return null;\n return readStatewireFailedSendLane(client.thread().getState().extras);\n },\n failureStamps: () => failureStampsRef.current?.() ?? null,\n diagnostics: () => diagnosticsRef.current,\n }),\n [attachId]\n );\n useEffect(() => () => sendRecovery.dispose(), [sendRecovery]);\n const sendRecoveryRef = useRef(sendRecovery);\n sendRecoveryRef.current = sendRecovery;\n\n const thread = useDeepAgentThread({\n ...threadOptions,\n ...(attachKey !== undefined && { attachKey }),\n // Parked without a message (a host suspension, when one applies, wins):\n // nothing dials and nothing journals until the lane is known. What paints\n // the first frame is the real attach's to report, not the park's.\n ...(sessionPending && {\n suspended: threadOptions.suspended || {},\n onAttachSource: undefined,\n }),\n // A pooled dial for the thread is the real attach's to take: a park that\n // only waits for the lane neither takes nor discards it. A host suspension\n // still discards it, as it does without a pending lane.\n ...(sessionPending && !threadOptions.suspended && { prewarmPool: undefined }),\n ...(session !== false &&\n session.kind === 'local-storage' &&\n canPersistLocalSession && {\n storage: {\n session: QuotaSafeLocalStorageSession({\n threadId: threadOptions.threadId,\n key: session.key,\n ...(session.onStorageDegraded === undefined\n ? {}\n : { onStorageDegraded: session.onStorageDegraded }),\n }),\n },\n }),\n ...(session !== false && session.kind === 'session-store' && { sessionStore: session.store }),\n onStateChange: (state, ctx) => {\n sendRecoveryRef.current.observeStateChange(state);\n onStateChangeRef.current?.(state, ctx);\n },\n onCommandChange: (update, ctx) => {\n sendRecoveryRef.current.observeCommand(update);\n onCommandChangeRef.current?.(update, ctx);\n },\n onError: (error) => {\n const handler = onErrorRef.current;\n if (handler) {\n handler(error);\n return;\n }\n logUnhandledStatewireError(surfaceRef.current, error);\n },\n });\n\n const client = useClient(thread);\n clientRef.current = client;\n\n // A locked shared journal can publish failed history after onCommandChange,\n // with no canonical state change to wake recovery. Observe the client that\n // owns the visible failed lane; the controller guards in-flight retries.\n useEffect(() => {\n const unsubscribe = client.subscribe(() => sendRecovery.observeFailedLaneChange());\n sendRecovery.observeFailedLaneChange();\n return unsubscribe;\n }, [client, sendRecovery]);\n\n // Commands travel bare, so the runConfig that drives the run rides the\n // ambient model context: `contextSync: 'command-send'` mirrors it to the\n // server, which reads `config.runConfig` at command admission. The core pulls\n // on model-context notifications; the per-render ref ensures each pull uses\n // the latest closure but does not trigger a pull itself.\n const getRunConfigRef = useRef(runConfig?.get);\n getRunConfigRef.current = runConfig?.get;\n const hasRunConfig = runConfig !== undefined;\n const runConfigKey = runConfig?.key;\n useEffect(() => {\n if (!hasRunConfig) return undefined;\n return registerDeepAgentRunConfig(client, () => getRunConfigRef.current?.() ?? {});\n }, [client, hasRunConfig, runConfigKey]);\n\n return client;\n}\n\n"],"names":["client"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"use-deep-agent-runtime.js","sources":["../../../../agent-runtime/src/use-deep-agent-runtime.ts"],"sourcesContent":["import { useEffect, useMemo, useRef, useState } from 'react';\nimport { isStatewireChannelTeardown } from './errors';\nimport { type DeepAgentModelContextHost, registerDeepAgentRunConfig } from './model-context';\nimport {\n isStorageQuotaError,\n QuotaSafeLocalStorageSession,\n readFailedSendStampsFromRecord,\n readPersistedFailedSendStamps,\n type SessionStorageDegradation,\n} from './quota-safe-local-storage-session';\nimport { createDeepAgentSendRecovery } from './send-recovery';\nimport { INITIAL_SESSION_LANE_EPOCH, nextSessionLaneEpoch } from './session-lane-scope';\nimport type { StatewireTransportOptions } from './statewire-transport';\nimport { readStatewireFailedSendLane } from './thread-extras';\nimport { isConfigSyncUnavailableRejection } from './use-deep-agent-config';\nimport {\n deepAgentAttachId,\n useDeepAgentThread,\n type UseDeepAgentThreadOptions,\n} from './use-deep-agent-thread';\n\n/** The thread element `useDeepAgentThread` produces — what the host's `useAui`\n * consumes (`useAui({ thread })`). */\nexport type DeepAgentThreadElement = ReturnType<typeof useDeepAgentThread>;\n\n/**\n * Durable command-lane persistence for the thread — where the lane identity and\n * the commands still awaiting a durable verdict live, so a reload resumes the\n * lane instead of minting a new client.\n *\n * - `local-storage`: browser hosts. Backed by {@link QuotaSafeLocalStorageSession}\n * (the upstream `LocalStorageSession` contract with Athena keys, legacy\n * metadata, and retention). A storage failure after mounting — the origin\n * out of quota after statewire's own sweep, a lockdown-mode write refusal —\n * degrades the session to memory-only persistence and is reported through\n * `onStorageDegraded` (default: one console warning); it never stops the\n * connection. Guarded by a shared availability probe: SSR, storage-blocked\n * iframes, and locked-down browsers keep the session process-local, exactly\n * as before the seam existed, while an origin that is merely full mounts the\n * session and lets it degrade. `key` scopes the stored lane — a thread id is\n * only meaningful against the backend (and, where relevant, principal) that\n * minted it, so include the sync-host URL.\n * - `session-store`: React Native hosts pass a hydrated library store backed\n * by AsyncStorage. Its synchronous `load()` is\n * also the send-recovery freshness gate's view of the persisted failed lane,\n * so a store that persists `failed` gets the same 24 h staleness rule as the\n * browser hosts.\n * - `pending`: the principal the lane is scoped by is not known yet (the\n * host's signed-in user is still loading). See {@link DeepAgentPendingSession}.\n * - `false`: process-local session (a reload mints a new client).\n *\n * A `local-storage` `key` names one principal's lanes (and one backend's). The\n * statewire session that mounts on a lane keeps the client id, lane clocks and\n * journal it loaded there, and everything in that journal is sent under\n * whatever credential the transport holds at its next attach. So a mounted\n * session never moves to another key: when the key starts naming another\n * principal's lanes for the same thread (another account signed in under a\n * mounted chat), the runtime starts a new lane epoch\n * (`UseDeepAgentThreadOptions['laneEpoch']`). The whole statewire thread\n * remounts on the new principal's lane and attaches afresh, as that lane's own\n * client, with nothing remembered or pooled from before. The previous\n * principal's record stays where it is, with its client id and whatever it had\n * journaled and not yet delivered: that principal's next mount on this browser\n * resends it as them, and the retention sweep retires it otherwise. It is never\n * sent as anyone else. A key for the same principal (a token refresh, a fresh\n * `key` function) and a thread switch keep the lane as it is. A host whose\n * principal arrives after mount passes `pending` until it does, never a\n * stand-in key: moving off a stand-in would be a principal change, a second\n * attach.\n */\nexport type DeepAgentSessionPersistence =\n | {\n kind: 'local-storage';\n key: (threadId: string) => string;\n onStorageDegraded?: (degradation: SessionStorageDegradation) => void;\n }\n | { kind: 'session-store'; store: NonNullable<StatewireTransportOptions['sessionStore']> }\n | DeepAgentPendingSession\n | false;\n\n/**\n * The durable lane's principal is not known yet. A lane belongs to one\n * principal and a mounted session cannot move to another lane (see\n * {@link DeepAgentSessionPersistence}), so nothing is mounted, nothing is\n * journaled and the transport stays parked — no dial — until the host passes\n * the real persistence. The park is an attach identity of its own: the attach\n * that follows makes every per-attach decision afresh (the remembered-snapshot\n * seed, the pooled hello, which the park leaves in the pool) and dials the real\n * lane once, as the thread's first attach. The park carries no connection\n * message, so it reads like a never-started chat rather than a paused one; a\n * host suspension, when one applies, wins.\n */\nexport type DeepAgentPendingSession = { kind: 'pending' };\n\n/**\n * The attach key a pending-lane park runs under. It differs from every key the\n * host passes, so the real attach after it is a new attach identity.\n */\nexport function pendingSessionAttachKey(attachKey: string | number | undefined): string {\n return `session-pending:${attachKey ?? ''}`;\n}\n\n/** Structural view of the browser storage global — this package compiles\n * without the DOM lib (React Native honesty; see `globals.d.ts`). */\ntype StorageGlobals = {\n localStorage?: {\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n };\n navigator?: { locks?: unknown };\n};\n\n/**\n * Whether a localStorage-backed session can persist here. A write probe, not a\n * `typeof` check: storage-blocked iframes (SDK embeds under third-party-storage\n * blocking) throw from the `localStorage` accessor itself, and lockdown-mode\n * origins throw from `setItem` — each of those must mean \"stay process-local\",\n * never a crash at mount. An origin that is merely out of quota still reads\n * and takes locks, so it mounts the session: {@link QuotaSafeLocalStorageSession}\n * degrades that to memory-only persistence and recovers when room frees,\n * where staying process-local would give up cross-tab sharing for the whole\n * page load over a condition that clears.\n *\n * The Web Locks API is probed too: the session storage serializes the shared\n * record across tabs through `navigator.locks` and refuses to mount without it\n * (the same precondition as the vendor `LocalStorageSession`), so an origin\n * with localStorage but no Web Locks — older WebViews, Safari before 15.4 —\n * must also stay process-local instead of throwing at mount.\n */\nexport function canPersistDeepAgentSession(): boolean {\n try {\n const globals = globalThis as StorageGlobals;\n if (!globals.navigator?.locks) return false;\n const storage = globals.localStorage;\n if (!storage) return false;\n const probeKey = '__athena_statewire_session_probe__';\n try {\n storage.setItem(probeKey, probeKey);\n storage.removeItem(probeKey);\n } catch (error) {\n return isStorageQuotaError(error);\n }\n return true;\n } catch {\n return false;\n }\n}\n\n/** A host session store's current record, or `null` when the read throws —\n * the recovery gate must never take the runtime down over a storage fault. */\nfunction loadSessionRecord(\n store: NonNullable<StatewireTransportOptions['sessionStore']>\n): ReturnType<NonNullable<StatewireTransportOptions['sessionStore']>['load']> | null {\n try {\n return store.load();\n } catch {\n return null;\n }\n}\n\n/**\n * The shared tail of every surface's transport error policy: channel teardown\n * is lifecycle noise (logged at debug), as is `config_sync_unavailable` — only\n * `config/*` writes emit it, and the host's config sync already handles it\n * through its own command promise (regionally-pinned workspaces run on\n * local-only config); everything else is a real transport error. Hosts with their own error chain\n * (paywall, telemetry, send-failure stores) end it with this so the fallback\n * logging stays uniform per surface.\n */\nexport function logUnhandledStatewireError(surface: string, error: Error): void {\n if (isStatewireChannelTeardown(error)) {\n console.debug(`[${surface}] statewire channel teardown:`, error);\n return;\n }\n if (isConfigSyncUnavailableRejection(error)) {\n console.debug(`[${surface}] config sync unavailable (handled by config sync):`, error);\n return;\n }\n console.error(`[${surface}] statewire transport error:`, error);\n}\n\n/**\n * The imperative thread-state slice of an assistant-ui client — structural so\n * web and native `useAui` results both satisfy it. The send-recovery layer\n * reads the statewire thread extras through it (the sanctioned imperative\n * pattern: `aui.thread().getState()` inside callbacks/effects, never render).\n */\nexport interface DeepAgentThreadStateClientHost {\n thread: () => { getState: () => { extras?: unknown } };\n subscribe: (listener: () => void) => () => void;\n}\n\n/** Deep-agent runConfig registration on the ambient model context (see\n * {@link registerDeepAgentRunConfig}). */\nexport interface DeepAgentRunConfigRegistration {\n /**\n * Builds the `runConfig` that drives the run. The core pulls it when the\n * model context notifies (mount and each key change); the server reads that\n * synced value at command admission. The per-render ref makes each pull use\n * the latest closure but does not trigger a pull itself.\n */\n get: () => Record<string, unknown>;\n /**\n * Re-registers the provider and notifies model-context sync when this value\n * changes (reference equality). Include getter inputs that can change while\n * mounted; they reach the server only after a key change triggers a pull.\n */\n key?: unknown;\n}\n\nexport interface UseDeepAgentRuntimeOptions<\n TClient extends DeepAgentModelContextHost & DeepAgentThreadStateClientHost,\n> extends Omit<UseDeepAgentThreadOptions, 'storage' | 'sessionStore' | 'laneEpoch'> {\n /** Host surface tag for the shared fallback error logging (e.g.\n * `'AthenaDeepAgent'`, `'AthenaSDK'`, `'ChromeStatewire'`). */\n surface: string;\n /** Durable command-lane persistence; defaults to process-local. */\n session?: DeepAgentSessionPersistence;\n /**\n * Builds the host's assistant-ui client from the thread element — pass the\n * platform `useAui` (web hosts from `@assistant-ui/react`, React Native\n * hosts from `@assistant-ui/react-native`; this package imports neither).\n * Called unconditionally on every render, so it MUST be (or call) a hook\n * following the rules of hooks — define it at module scope with a `use*`\n * name: `const useHostClient = (thread: DeepAgentThreadElement) =>\n * useAui({ thread })`.\n */\n useClient: (thread: DeepAgentThreadElement) => TClient;\n /** Registers the deep-agent runConfig on the client's model context. Hosts\n * that register elsewhere (e.g. a bridge component mounted under the\n * provider) omit it. */\n runConfig?: DeepAgentRunConfigRegistration;\n}\n\n/**\n * The configurable deep-agent statewire runtime — the one hook every Athena\n * chat surface (Olympus, `@athenaintel/react` SDK, Chrome extension, Office\n * add-in, mobile) mounts its assistant-ui client through.\n *\n * Composes the pieces every surface previously wired by hand:\n * `useDeepAgentThread` (attach lifecycle, auth parking, lane projection,\n * connection/running observers), durable session persistence with the shared\n * availability guard, transport-level send recovery (failed sends the server\n * never judged auto-retry under their original message id — `send-recovery.ts`),\n * the platform client via `useClient`, and the\n * runConfig model-context registration on provider notifications. Everything\n * host-specific — auth headers, run-config building, error policy, adapters,\n * diagnostics recorders — stays an option, so per-surface behavior is\n * configuration, not a fork.\n */\nexport function useDeepAgentRuntime<\n TClient extends DeepAgentModelContextHost & DeepAgentThreadStateClientHost,\n>(options: UseDeepAgentRuntimeOptions<TClient>): TClient {\n const {\n surface,\n session = false,\n useClient,\n runConfig,\n onError,\n onStateChange,\n onCommandChange,\n ...threadOptions\n } = options;\n\n // Probed once per mount, like every browser host did individually: an origin\n // that cannot persist stays process-local for the mount's lifetime.\n const [canPersistLocalSession] = useState(canPersistDeepAgentSession);\n\n // Per-render refs so the transport never remounts on a new closure identity\n // and always reads the latest policy.\n const onErrorRef = useRef(onError);\n onErrorRef.current = onError;\n const surfaceRef = useRef(surface);\n surfaceRef.current = surface;\n const onStateChangeRef = useRef(onStateChange);\n onStateChangeRef.current = onStateChange;\n const onCommandChangeRef = useRef(onCommandChange);\n onCommandChangeRef.current = onCommandChange;\n const diagnosticsRef = useRef(threadOptions.diagnostics);\n diagnosticsRef.current = threadOptions.diagnostics;\n const clientRef = useRef<TClient | null>(null);\n\n // Where the persisted session record lives, for the recovery freshness gate\n // (read per sweep — undefined means \"no persistence, everything is fresh\").\n const failureStampsRef = useRef<(() => ReadonlyMap<string, number> | null) | undefined>(\n undefined\n );\n failureStampsRef.current =\n session === false || session.kind === 'pending'\n ? undefined\n : session.kind === 'local-storage'\n ? canPersistLocalSession\n ? () => readPersistedFailedSendStamps(session.key(threadOptions.threadId))\n : undefined\n : () => readFailedSendStampsFromRecord(loadSessionRecord(session.store));\n\n // A pending lane parks the thread under an attach identity of its own (see\n // `DeepAgentPendingSession`), so the attach once the lane is known is new.\n const sessionPending = session !== false && session.kind === 'pending';\n const attachKey = sessionPending\n ? pendingSessionAttachKey(threadOptions.attachKey)\n : threadOptions.attachKey;\n\n // The lane's scope, as the host's `key` names it: whose lanes this mount\n // runs on. A key that starts naming another principal's lanes starts a new\n // lane epoch, which remounts the whole thread on the new lane instead of\n // moving the mounted session there (see `DeepAgentSessionPersistence`).\n // Tracked whether or not this origin can persist: a process-local session\n // holds the previous principal's journal in memory just the same. A pending\n // lane keeps the last known scope, so a sign-out and a sign-in as someone\n // else with a gap between them is still a change.\n const laneKey = session !== false && session.kind === 'local-storage' ? session.key : null;\n const [laneEpochState, setLaneEpochState] = useState(() =>\n nextSessionLaneEpoch({\n previous: INITIAL_SESSION_LANE_EPOCH,\n threadId: threadOptions.threadId,\n key: laneKey,\n })\n );\n const nextLaneEpochState = nextSessionLaneEpoch({\n previous: laneEpochState,\n threadId: threadOptions.threadId,\n key: laneKey,\n });\n // React's derived-state update: re-renders at once with the new epoch, and\n // this render already uses it, so no render pairs the new key with the\n // mounted thread.\n if (nextLaneEpochState !== laneEpochState) setLaneEpochState(nextLaneEpochState);\n const laneEpoch = nextLaneEpochState.epoch;\n\n // Send recovery, keyed by attach identity exactly like the thread's session\n // (a switch, re-attach or new lane epoch gets fresh hydration gating and\n // retry budgets, so it never re-dispatches a previous principal's failed\n // sends). The controller re-dispatches failed sends the server never judged;\n // see `send-recovery.ts` for the contract.\n const attachId = deepAgentAttachId({ threadId: threadOptions.threadId, attachKey, laneEpoch });\n // biome-ignore lint/correctness/useExhaustiveDependencies: keyed by attach identity on purpose; every other input is read through a per-render ref.\n const sendRecovery = useMemo(\n () =>\n createDeepAgentSendRecovery({\n lane: () => {\n const client = clientRef.current;\n if (!client) return null;\n return readStatewireFailedSendLane(client.thread().getState().extras);\n },\n failureStamps: () => failureStampsRef.current?.() ?? null,\n diagnostics: () => diagnosticsRef.current,\n }),\n [attachId]\n );\n useEffect(() => () => sendRecovery.dispose(), [sendRecovery]);\n const sendRecoveryRef = useRef(sendRecovery);\n sendRecoveryRef.current = sendRecovery;\n\n const thread = useDeepAgentThread({\n ...threadOptions,\n ...(attachKey !== undefined && { attachKey }),\n laneEpoch,\n // Parked without a message (a host suspension, when one applies, wins):\n // nothing dials and nothing journals until the lane is known. What paints\n // the first frame is the real attach's to report, not the park's.\n ...(sessionPending && {\n suspended: threadOptions.suspended || {},\n onAttachSource: undefined,\n }),\n // A pooled dial for the thread is the real attach's to take: a park that\n // only waits for the lane neither takes nor discards it. A host suspension\n // still discards it, as it does without a pending lane.\n ...(sessionPending && !threadOptions.suspended && { prewarmPool: undefined }),\n ...(session !== false &&\n session.kind === 'local-storage' &&\n canPersistLocalSession && {\n storage: {\n session: QuotaSafeLocalStorageSession({\n threadId: threadOptions.threadId,\n key: session.key,\n ...(session.onStorageDegraded === undefined\n ? {}\n : { onStorageDegraded: session.onStorageDegraded }),\n }),\n },\n }),\n ...(session !== false && session.kind === 'session-store' && { sessionStore: session.store }),\n onStateChange: (state, ctx) => {\n sendRecoveryRef.current.observeStateChange(state);\n onStateChangeRef.current?.(state, ctx);\n },\n onCommandChange: (update, ctx) => {\n sendRecoveryRef.current.observeCommand(update);\n onCommandChangeRef.current?.(update, ctx);\n },\n onError: (error) => {\n const handler = onErrorRef.current;\n if (handler) {\n handler(error);\n return;\n }\n logUnhandledStatewireError(surfaceRef.current, error);\n },\n });\n\n const client = useClient(thread);\n clientRef.current = client;\n\n // A locked shared journal can publish failed history after onCommandChange,\n // with no canonical state change to wake recovery. Observe the client that\n // owns the visible failed lane; the controller guards in-flight retries.\n useEffect(() => {\n const unsubscribe = client.subscribe(() => sendRecovery.observeFailedLaneChange());\n sendRecovery.observeFailedLaneChange();\n return unsubscribe;\n }, [client, sendRecovery]);\n\n // Commands travel bare, so the runConfig that drives the run rides the\n // ambient model context: `contextSync: 'command-send'` mirrors it to the\n // server, which reads `config.runConfig` at command admission. The core pulls\n // on model-context notifications; the per-render ref ensures each pull uses\n // the latest closure but does not trigger a pull itself.\n const getRunConfigRef = useRef(runConfig?.get);\n getRunConfigRef.current = runConfig?.get;\n const hasRunConfig = runConfig !== undefined;\n const runConfigKey = runConfig?.key;\n useEffect(() => {\n if (!hasRunConfig) return undefined;\n return registerDeepAgentRunConfig(client, () => getRunConfigRef.current?.() ?? {});\n }, [client, hasRunConfig, runConfigKey]);\n\n return client;\n}\n\n"],"names":["client"],"mappings":"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"}
|
|
@@ -15,6 +15,14 @@ const statewireState = require("./statewire-state.cjs");
|
|
|
15
15
|
const statewireSuspended = require("./statewire-suspended.cjs");
|
|
16
16
|
const statewireTransport = require("./statewire-transport.cjs");
|
|
17
17
|
const threadSnapshotCache = require("./thread-snapshot-cache.cjs");
|
|
18
|
+
function deepAgentAttachId({
|
|
19
|
+
threadId,
|
|
20
|
+
attachKey,
|
|
21
|
+
laneEpoch = 0
|
|
22
|
+
}) {
|
|
23
|
+
const attach = attachKey === void 0 ? threadId : `${threadId}:${attachKey}`;
|
|
24
|
+
return laneEpoch === 0 ? attach : `${attach}#lane-epoch:${laneEpoch}`;
|
|
25
|
+
}
|
|
18
26
|
function readSessionPersisted(state) {
|
|
19
27
|
const flag = state == null ? void 0 : state.sessionPersisted;
|
|
20
28
|
return typeof flag === "boolean" ? flag : null;
|
|
@@ -35,9 +43,11 @@ function useDeepAgentThread({
|
|
|
35
43
|
storage,
|
|
36
44
|
isNewChat = false,
|
|
37
45
|
attachKey,
|
|
46
|
+
laneEpoch = 0,
|
|
38
47
|
suspended = false,
|
|
39
48
|
capabilities,
|
|
40
49
|
laneProjection = "both",
|
|
50
|
+
showQuietTools = false,
|
|
41
51
|
selfAuthor = null,
|
|
42
52
|
getMessageMetadata,
|
|
43
53
|
adapters,
|
|
@@ -116,7 +126,7 @@ function useDeepAgentThread({
|
|
|
116
126
|
const absentStateRunConfigAttachRef = react.useRef(null);
|
|
117
127
|
const threadIdRef = react.useRef(threadId);
|
|
118
128
|
threadIdRef.current = threadId;
|
|
119
|
-
const attachId =
|
|
129
|
+
const attachId = deepAgentAttachId({ threadId, attachKey, laneEpoch });
|
|
120
130
|
const attachIdRef = react.useRef(attachId);
|
|
121
131
|
attachIdRef.current = attachId;
|
|
122
132
|
const [authParked, setAuthParked] = react.useState(null);
|
|
@@ -218,32 +228,38 @@ function useDeepAgentThread({
|
|
|
218
228
|
},
|
|
219
229
|
[]
|
|
220
230
|
);
|
|
221
|
-
const preloadRef = react.useRef({ threadId, preload: isNewChat });
|
|
222
|
-
if (preloadRef.current.threadId !== threadId)
|
|
223
|
-
preloadRef.current = { threadId, preload: isNewChat };
|
|
231
|
+
const preloadRef = react.useRef({ threadId, laneEpoch, preload: isNewChat });
|
|
232
|
+
if (preloadRef.current.threadId !== threadId || preloadRef.current.laneEpoch !== laneEpoch)
|
|
233
|
+
preloadRef.current = { threadId, laneEpoch, preload: isNewChat };
|
|
224
234
|
const snapshotCacheRef = react.useRef(snapshotCache);
|
|
225
235
|
snapshotCacheRef.current = snapshotCache;
|
|
226
236
|
const snapshotSeedRef = react.useRef(null);
|
|
227
237
|
if (snapshotSeedRef.current === null || snapshotSeedRef.current.attachId !== attachId) {
|
|
228
238
|
const preload = preloadRef.current.preload;
|
|
239
|
+
const laneReplaced = snapshotSeedRef.current !== null && snapshotSeedRef.current.laneEpoch !== laneEpoch;
|
|
240
|
+
if (laneReplaced) snapshotCache == null ? void 0 : snapshotCache.forget(threadId);
|
|
229
241
|
let pooledState;
|
|
230
242
|
if (prewarmPool == null ? void 0 : prewarmPool.has(threadId)) {
|
|
231
|
-
if (preload || suspended) {
|
|
243
|
+
if (preload || suspended || laneReplaced) {
|
|
232
244
|
prewarmPool.discard(threadId);
|
|
233
245
|
} else {
|
|
234
246
|
pooledState = (_a = prewarmPool.take(threadId)) == null ? void 0 : _a.state;
|
|
235
247
|
}
|
|
236
248
|
}
|
|
237
|
-
const remembered = preload || suspended ? void 0 : pooledState ?? (snapshotCache == null ? void 0 : snapshotCache.take(threadId));
|
|
249
|
+
const remembered = preload || suspended || laneReplaced ? void 0 : pooledState ?? (snapshotCache == null ? void 0 : snapshotCache.take(threadId));
|
|
238
250
|
const seed = remembered ? threadSnapshotCache.toThreadSnapshotSeed(remembered) : void 0;
|
|
239
|
-
snapshotSeedRef.current = { attachId, seed };
|
|
251
|
+
snapshotSeedRef.current = { attachId, laneEpoch, seed };
|
|
240
252
|
const source = pooledState !== void 0 ? "prewarm" : seed ? "snapshot" : "none";
|
|
241
253
|
const notifySource = onAttachSourceRef.current;
|
|
242
254
|
if (notifySource) queueMicrotask(() => notifySource({ threadId, source }));
|
|
243
255
|
}
|
|
244
256
|
const conversionCacheRef = react.useRef(null);
|
|
245
|
-
if (conversionCacheRef.current === null || conversionCacheRef.current.attachId !== attachId) {
|
|
246
|
-
conversionCacheRef.current = {
|
|
257
|
+
if (conversionCacheRef.current === null || conversionCacheRef.current.attachId !== attachId || conversionCacheRef.current.showQuietTools !== showQuietTools) {
|
|
258
|
+
conversionCacheRef.current = {
|
|
259
|
+
attachId,
|
|
260
|
+
showQuietTools,
|
|
261
|
+
cache: createConversionCache({ showQuietTools })
|
|
262
|
+
};
|
|
247
263
|
}
|
|
248
264
|
const conversionCache = conversionCacheRef.current.cache;
|
|
249
265
|
const lastStateMessagesRef = react.useRef(
|
|
@@ -346,7 +362,7 @@ function useDeepAgentThread({
|
|
|
346
362
|
},
|
|
347
363
|
[attachId, conversionCache, converter, materialize, readMessageWindow, threadId]
|
|
348
364
|
);
|
|
349
|
-
|
|
365
|
+
const thread = reactStatewire.StatewireThread({
|
|
350
366
|
// The transport stamps this protocol on every command; the thread client
|
|
351
367
|
// must declare it too or the core skips the optimistic folds and a send
|
|
352
368
|
// stays invisible until the server frame lands.
|
|
@@ -459,7 +475,9 @@ function useDeepAgentThread({
|
|
|
459
475
|
})
|
|
460
476
|
}
|
|
461
477
|
});
|
|
478
|
+
return laneEpoch === 0 ? thread : tap.withKey(`lane-epoch:${laneEpoch}`, thread);
|
|
462
479
|
}
|
|
480
|
+
exports.deepAgentAttachId = deepAgentAttachId;
|
|
463
481
|
exports.readSessionPersisted = readSessionPersisted;
|
|
464
482
|
exports.shouldNotifyRunningChange = shouldNotifyRunningChange;
|
|
465
483
|
exports.useDeepAgentThread = useDeepAgentThread;
|