@llui/agent 0.0.50 → 0.0.52

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.
Files changed (50) hide show
  1. package/dist/client/agentChat.d.ts +9 -6
  2. package/dist/client/agentChat.d.ts.map +1 -1
  3. package/dist/client/agentChat.js +8 -2
  4. package/dist/client/agentChat.js.map +1 -1
  5. package/dist/client/agentConnect.d.ts.map +1 -1
  6. package/dist/client/agentConnect.js +45 -31
  7. package/dist/client/agentConnect.js.map +1 -1
  8. package/dist/client/effect-handler.d.ts +0 -21
  9. package/dist/client/effect-handler.d.ts.map +1 -1
  10. package/dist/client/effect-handler.js +0 -26
  11. package/dist/client/effect-handler.js.map +1 -1
  12. package/dist/client/effects.d.ts +0 -23
  13. package/dist/client/effects.d.ts.map +1 -1
  14. package/dist/client/effects.js.map +1 -1
  15. package/dist/client/factory.d.ts +0 -9
  16. package/dist/client/factory.d.ts.map +1 -1
  17. package/dist/client/factory.js +0 -2
  18. package/dist/client/factory.js.map +1 -1
  19. package/dist/client/index.d.ts +0 -1
  20. package/dist/client/index.d.ts.map +1 -1
  21. package/dist/client/index.js +0 -1
  22. package/dist/client/index.js.map +1 -1
  23. package/dist/client/ws-client.d.ts +0 -9
  24. package/dist/client/ws-client.d.ts.map +1 -1
  25. package/dist/client/ws-client.js +0 -32
  26. package/dist/client/ws-client.js.map +1 -1
  27. package/dist/protocol.d.ts +1 -50
  28. package/dist/protocol.d.ts.map +1 -1
  29. package/dist/protocol.js.map +1 -1
  30. package/dist/server/cloudflare/durable-object.d.ts +0 -41
  31. package/dist/server/cloudflare/durable-object.d.ts.map +1 -1
  32. package/dist/server/cloudflare/durable-object.js +0 -46
  33. package/dist/server/cloudflare/durable-object.js.map +1 -1
  34. package/dist/server/cloudflare/index.d.ts +3 -10
  35. package/dist/server/cloudflare/index.d.ts.map +1 -1
  36. package/dist/server/cloudflare/index.js +3 -10
  37. package/dist/server/cloudflare/index.js.map +1 -1
  38. package/dist/server/core.d.ts +1 -11
  39. package/dist/server/core.d.ts.map +1 -1
  40. package/dist/server/core.js +0 -1
  41. package/dist/server/core.js.map +1 -1
  42. package/dist/server/lap/router.d.ts.map +1 -1
  43. package/dist/server/lap/router.js +0 -3
  44. package/dist/server/lap/router.js.map +1 -1
  45. package/dist/server/ws/pairing-registry.d.ts +0 -101
  46. package/dist/server/ws/pairing-registry.d.ts.map +1 -1
  47. package/dist/server/ws/pairing-registry.js +0 -160
  48. package/dist/server/ws/pairing-registry.js.map +1 -1
  49. package/package.json +3 -3
  50. package/styles/agent-panel.css +0 -13
@@ -36,7 +36,7 @@ export type AgentChatInitOpts = {
36
36
  initialInput?: string;
37
37
  };
38
38
  export type AgentChatMsg = {
39
- /** Bound to the input's `oninput` event. Round-trip stays in the slice. */
39
+ /** Bound to the input's `onInput` event. Round-trip stays in the slice. */
40
40
  type: 'SetInput';
41
41
  value: string;
42
42
  } | {
@@ -65,11 +65,14 @@ import { type Send } from '@llui/dom';
65
65
  * element helpers; matches the convention of the other agent
66
66
  * namespaces.
67
67
  *
68
- * The `input` bag carries both `oninput` and `onkeydown` because
68
+ * The `input` bag carries both `onInput` and `onKeyDown` because
69
69
  * the canonical chat-composer affordance is "type, press Enter"
70
70
  * (Shift+Enter for newline if the host wraps it themselves). Hosts
71
- * that want a multiline textarea can spread `input.oninput` and
72
- * skip `onkeydown`, wiring submit to the button only.
71
+ * that want a multiline textarea can spread `input.onInput` and
72
+ * skip `onKeyDown`, wiring submit to the button only. Handler keys
73
+ * are camelCase because LLui's element runtime only attaches keys
74
+ * matching `/^on[A-Z]/` as event listeners — lowercase `oninput`
75
+ * would silently degrade to a string attribute.
73
76
  */
74
77
  export type ConnectBag<S> = {
75
78
  root: {
@@ -80,8 +83,8 @@ export type ConnectBag<S> = {
80
83
  'data-part': 'input';
81
84
  value: (s: S) => string;
82
85
  disabled: (s: S) => boolean;
83
- oninput: (e: Event) => void;
84
- onkeydown: (e: KeyboardEvent) => void;
86
+ onInput: (e: Event) => void;
87
+ onKeyDown: (e: KeyboardEvent) => void;
85
88
  };
86
89
  submitButton: {
87
90
  'data-part': 'submit';
@@ -1 +1 @@
1
- {"version":3,"file":"agentChat.d.ts","sourceRoot":"","sources":["../../src/client/agentChat.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAE/C;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,yEAAyE;IACzE,YAAY,EAAE,MAAM,CAAA;IACpB;;;;OAIG;IACH,UAAU,EAAE,OAAO,CAAA;CACpB,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,0DAA0D;IAC1D,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB,CAAA;AAED,MAAM,MAAM,YAAY,GACpB;IACE,2EAA2E;IAC3E,IAAI,EAAE,UAAU,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;CACd,GACD;IACE;;;;;;;OAOG;IACH,IAAI,EAAE,QAAQ,CAAA;CACf,GACD;IACE;;;;OAIG;IACH,IAAI,EAAE,gBAAgB,CAAA;CACvB,CAAA;AAEL,wBAAgB,IAAI,CAAC,IAAI,GAAE,iBAAsB,GAAG,CAAC,cAAc,EAAE,WAAW,EAAE,CAAC,CAQlF;AAED,wBAAgB,MAAM,CAAC,KAAK,EAAE,cAAc,EAAE,GAAG,EAAE,YAAY,GAAG,CAAC,cAAc,EAAE,WAAW,EAAE,CAAC,CAyBhG;AAED,OAAO,EAAW,KAAK,IAAI,EAAE,MAAM,WAAW,CAAA;AAE9C;;;;;;;;;;GAUG;AACH,MAAM,MAAM,UAAU,CAAC,CAAC,IAAI;IAC1B,IAAI,EAAE;QAAE,YAAY,EAAE,YAAY,CAAC;QAAC,iBAAiB,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,OAAO,CAAA;KAAE,CAAA;IAC1E,KAAK,EAAE;QACL,WAAW,EAAE,OAAO,CAAA;QACpB,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,MAAM,CAAA;QACvB,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,OAAO,CAAA;QAC3B,OAAO,EAAE,CAAC,CAAC,EAAE,KAAK,KAAK,IAAI,CAAA;QAC3B,SAAS,EAAE,CAAC,CAAC,EAAE,aAAa,KAAK,IAAI,CAAA;KACtC,CAAA;IACD,YAAY,EAAE;QACZ,WAAW,EAAE,QAAQ,CAAA;QACrB,OAAO,EAAE,MAAM,IAAI,CAAA;QACnB,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,OAAO,CAAA;KAC5B,CAAA;IACD;;;;;OAKG;IACH,SAAS,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,OAAO,CAAA;CAC7B,CAAA;AAED,wBAAgB,OAAO,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,cAAc,EAAE,IAAI,EAAE,IAAI,CAAC,YAAY,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAyCjG"}
1
+ {"version":3,"file":"agentChat.d.ts","sourceRoot":"","sources":["../../src/client/agentChat.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAE/C;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,yEAAyE;IACzE,YAAY,EAAE,MAAM,CAAA;IACpB;;;;OAIG;IACH,UAAU,EAAE,OAAO,CAAA;CACpB,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,0DAA0D;IAC1D,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB,CAAA;AAED,MAAM,MAAM,YAAY,GACpB;IACE,2EAA2E;IAC3E,IAAI,EAAE,UAAU,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;CACd,GACD;IACE;;;;;;;OAOG;IACH,IAAI,EAAE,QAAQ,CAAA;CACf,GACD;IACE;;;;OAIG;IACH,IAAI,EAAE,gBAAgB,CAAA;CACvB,CAAA;AAEL,wBAAgB,IAAI,CAAC,IAAI,GAAE,iBAAsB,GAAG,CAAC,cAAc,EAAE,WAAW,EAAE,CAAC,CAQlF;AAED,wBAAgB,MAAM,CAAC,KAAK,EAAE,cAAc,EAAE,GAAG,EAAE,YAAY,GAAG,CAAC,cAAc,EAAE,WAAW,EAAE,CAAC,CAyBhG;AAED,OAAO,EAAW,KAAK,IAAI,EAAE,MAAM,WAAW,CAAA;AAE9C;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,UAAU,CAAC,CAAC,IAAI;IAC1B,IAAI,EAAE;QAAE,YAAY,EAAE,YAAY,CAAC;QAAC,iBAAiB,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,OAAO,CAAA;KAAE,CAAA;IAC1E,KAAK,EAAE;QACL,WAAW,EAAE,OAAO,CAAA;QACpB,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,MAAM,CAAA;QACvB,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,OAAO,CAAA;QAC3B,OAAO,EAAE,CAAC,CAAC,EAAE,KAAK,KAAK,IAAI,CAAA;QAC3B,SAAS,EAAE,CAAC,CAAC,EAAE,aAAa,KAAK,IAAI,CAAA;KACtC,CAAA;IACD,YAAY,EAAE;QACZ,WAAW,EAAE,QAAQ,CAAA;QACrB,OAAO,EAAE,MAAM,IAAI,CAAA;QACnB,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,OAAO,CAAA;KAC5B,CAAA;IACD;;;;;OAKG;IACH,SAAS,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,OAAO,CAAA;CAC7B,CAAA;AAED,wBAAgB,OAAO,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,cAAc,EAAE,IAAI,EAAE,IAAI,CAAC,YAAY,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CA+CjG"}
@@ -50,7 +50,13 @@ export function connect(get, send) {
50
50
  'data-part': 'input',
51
51
  value: (s) => get(s).pendingInput,
52
52
  disabled: (s) => get(s).submitting,
53
- oninput: (e) => {
53
+ // Event handler keys MUST be camelCase: LLui's elements pass only
54
+ // keys matching `/^on[A-Z]/` to `addEventListener` (see
55
+ // packages/dom/src/elements.ts). Lowercase `oninput` would silently
56
+ // be set as a string attribute and never fire — the input field
57
+ // would freeze with no SetInput dispatches and the submit button
58
+ // would stay disabled forever.
59
+ onInput: (e) => {
54
60
  // The cast is the standard "input event target is an
55
61
  // HTMLInputElement / HTMLTextAreaElement" assumption; we
56
62
  // coerce via `.value`. Hosts using non-DOM inputs (custom
@@ -60,7 +66,7 @@ export function connect(get, send) {
60
66
  if (target && typeof target.value === 'string')
61
67
  setInput(target.value);
62
68
  },
63
- onkeydown: (e) => {
69
+ onKeyDown: (e) => {
64
70
  if (e.key === 'Enter' && !e.shiftKey) {
65
71
  e.preventDefault();
66
72
  submit();
@@ -1 +1 @@
1
- {"version":3,"file":"agentChat.js","sourceRoot":"","sources":["../../src/client/agentChat.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentEffect } from './effects.js'\n\n/**\n * In-app chat composer slice. Owns the editor-state half of the\n * conversational surface (`pendingInput`, `submitting`); the\n * timeline half lives in `agentLog` (which renders the user's\n * submission as a `LogEntry { kind: 'user-input' }` alongside agent\n * actions, so the conversation reads chronologically).\n *\n * Pattern: host wires the slice into its app state via\n * `sliceHandler`, spreads `connect()`'s prop bag into the host's\n * own input/button layout, and the `Submit` Msg fires an effect\n * that the framework's effect handler turns into a WS\n * `user-input-submitted` frame + a synthesized log entry. The\n * agent's `wait_for_user_input` LAP tool picks up the frame at the\n * server.\n *\n * The composer is NOT an LLM. It's a relay surface: the user's text\n * is delivered to the user's own LLM (Claude desktop / IDE / wherever\n * is mounted on the MCP bridge), which already has cross-app context.\n * The framework just provides the in-app rendezvous so the user\n * doesn't have to alt-tab to a different window to talk to their\n * agent about the app they're looking at.\n */\nexport type AgentChatState = {\n /** Current contents of the input field. Bound to the input's `value`. */\n pendingInput: string\n /**\n * True between `Submit` and the effect handler completing the\n * frame send. Disables the submit button and (typically) the\n * input itself; reducer-driven so the UI never disagrees.\n */\n submitting: boolean\n}\n\nexport type AgentChatInitOpts = {\n /** Pre-fill the input on mount — e.g. session-restore. */\n initialInput?: string\n}\n\nexport type AgentChatMsg =\n | {\n /** Bound to the input's `oninput` event. Round-trip stays in the slice. */\n type: 'SetInput'\n value: string\n }\n | {\n /**\n * Bound to the submit button's `onClick` and the input's\n * `onKeyDown` (Enter, no shift). Reducer:\n *\n * - Empty/whitespace → no-op (no effect, no state change).\n * - Has content → clear `pendingInput`, set `submitting: true`,\n * emit `AgentChatSendInput { text, at }` effect.\n */\n type: 'Submit'\n }\n | {\n /**\n * Fired by the effect handler after the frame sends so the\n * UI re-enables the input. Always paired 1:1 with each\n * dispatched `AgentChatSendInput` effect.\n */\n type: 'SubmitComplete'\n }\n\nexport function init(opts: AgentChatInitOpts = {}): [AgentChatState, AgentEffect[]] {\n return [\n {\n pendingInput: opts.initialInput ?? '',\n submitting: false,\n },\n [],\n ]\n}\n\nexport function update(state: AgentChatState, msg: AgentChatMsg): [AgentChatState, AgentEffect[]] {\n switch (msg.type) {\n case 'SetInput':\n // No-op when the value is identical — keeps state ref stable\n // for memoization above this slice.\n if (msg.value === state.pendingInput) return [state, []]\n return [{ ...state, pendingInput: msg.value }, []]\n case 'Submit': {\n // Don't double-submit while a previous send is in flight, and\n // don't send empty/whitespace messages — neither is a useful\n // signal for the agent and both are common bounce events\n // (keyboard auto-repeat on Enter, click-then-click-again).\n if (state.submitting) return [state, []]\n const trimmed = state.pendingInput.trim()\n if (trimmed.length === 0) return [state, []]\n const at = Date.now()\n return [\n { ...state, pendingInput: '', submitting: true },\n [{ type: 'AgentChatSendInput', text: trimmed, at }],\n ]\n }\n case 'SubmitComplete':\n if (!state.submitting) return [state, []]\n return [{ ...state, submitting: false }, []]\n }\n}\n\nimport { tagSend, type Send } from '@llui/dom'\n\n/**\n * Static-prop-bag-with-reactive-accessors. Spread directly into\n * element helpers; matches the convention of the other agent\n * namespaces.\n *\n * The `input` bag carries both `oninput` and `onkeydown` because\n * the canonical chat-composer affordance is \"type, press Enter\"\n * (Shift+Enter for newline if the host wraps it themselves). Hosts\n * that want a multiline textarea can spread `input.oninput` and\n * skip `onkeydown`, wiring submit to the button only.\n */\nexport type ConnectBag<S> = {\n root: { 'data-scope': 'agent-chat'; 'data-submitting': (s: S) => boolean }\n input: {\n 'data-part': 'input'\n value: (s: S) => string\n disabled: (s: S) => boolean\n oninput: (e: Event) => void\n onkeydown: (e: KeyboardEvent) => void\n }\n submitButton: {\n 'data-part': 'submit'\n onClick: () => void\n disabled: (s: S) => boolean\n }\n /**\n * True iff the input has non-whitespace content AND we're not\n * mid-submit. Useful as the predicate for a \"send\" affordance\n * separate from the button's own `disabled` field (e.g. a\n * keyboard-shortcut hint).\n */\n canSubmit: (s: S) => boolean\n}\n\nexport function connect<S>(get: (s: S) => AgentChatState, send: Send<AgentChatMsg>): ConnectBag<S> {\n const submit = tagSend(send, ['Submit'], () => send({ type: 'Submit' }))\n const setInput = (value: string) => send({ type: 'SetInput', value })\n return {\n root: {\n 'data-scope': 'agent-chat',\n 'data-submitting': (s) => get(s).submitting,\n },\n input: {\n 'data-part': 'input',\n value: (s) => get(s).pendingInput,\n disabled: (s) => get(s).submitting,\n oninput: (e) => {\n // The cast is the standard \"input event target is an\n // HTMLInputElement / HTMLTextAreaElement\" assumption; we\n // coerce via `.value`. Hosts using non-DOM inputs (custom\n // contenteditable etc.) bypass this prop bag and dispatch\n // SetInput directly.\n const target = e.target as { value?: string } | null\n if (target && typeof target.value === 'string') setInput(target.value)\n },\n onkeydown: (e) => {\n if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault()\n submit()\n }\n },\n },\n submitButton: {\n 'data-part': 'submit',\n onClick: submit,\n disabled: (s) => {\n const cs = get(s)\n return cs.submitting || cs.pendingInput.trim().length === 0\n },\n },\n canSubmit: (s) => {\n const cs = get(s)\n return !cs.submitting && cs.pendingInput.trim().length > 0\n },\n }\n}\n"]}
1
+ {"version":3,"file":"agentChat.js","sourceRoot":"","sources":["../../src/client/agentChat.ts"],"names":[],"mappings":"AAkEA,MAAM,UAAU,IAAI,CAAC,OAA0B,EAAE;IAC/C,OAAO;QACL;YACE,YAAY,EAAE,IAAI,CAAC,YAAY,IAAI,EAAE;YACrC,UAAU,EAAE,KAAK;SAClB;QACD,EAAE;KACH,CAAA;AACH,CAAC;AAED,MAAM,UAAU,MAAM,CAAC,KAAqB,EAAE,GAAiB;IAC7D,QAAQ,GAAG,CAAC,IAAI,EAAE,CAAC;QACjB,KAAK,UAAU;YACb,6DAA6D;YAC7D,oCAAoC;YACpC,IAAI,GAAG,CAAC,KAAK,KAAK,KAAK,CAAC,YAAY;gBAAE,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAA;YACxD,OAAO,CAAC,EAAE,GAAG,KAAK,EAAE,YAAY,EAAE,GAAG,CAAC,KAAK,EAAE,EAAE,EAAE,CAAC,CAAA;QACpD,KAAK,QAAQ,CAAC,CAAC,CAAC;YACd,8DAA8D;YAC9D,6DAA6D;YAC7D,yDAAyD;YACzD,2DAA2D;YAC3D,IAAI,KAAK,CAAC,UAAU;gBAAE,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAA;YACxC,MAAM,OAAO,GAAG,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,CAAA;YACzC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAA;YAC5C,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;YACrB,OAAO;gBACL,EAAE,GAAG,KAAK,EAAE,YAAY,EAAE,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE;gBAChD,CAAC,EAAE,IAAI,EAAE,oBAAoB,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;aACpD,CAAA;QACH,CAAC;QACD,KAAK,gBAAgB;YACnB,IAAI,CAAC,KAAK,CAAC,UAAU;gBAAE,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAA;YACzC,OAAO,CAAC,EAAE,GAAG,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAA;IAChD,CAAC;AACH,CAAC;AAED,OAAO,EAAE,OAAO,EAAa,MAAM,WAAW,CAAA;AAuC9C,MAAM,UAAU,OAAO,CAAI,GAA6B,EAAE,IAAwB;IAChF,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAA;IACxE,MAAM,QAAQ,GAAG,CAAC,KAAa,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,CAAA;IACrE,OAAO;QACL,IAAI,EAAE;YACJ,YAAY,EAAE,YAAY;YAC1B,iBAAiB,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU;SAC5C;QACD,KAAK,EAAE;YACL,WAAW,EAAE,OAAO;YACpB,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,YAAY;YACjC,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU;YAClC,kEAAkE;YAClE,wDAAwD;YACxD,oEAAoE;YACpE,gEAAgE;YAChE,iEAAiE;YACjE,+BAA+B;YAC/B,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE;gBACb,qDAAqD;gBACrD,yDAAyD;gBACzD,0DAA0D;gBAC1D,0DAA0D;gBAC1D,qBAAqB;gBACrB,MAAM,MAAM,GAAG,CAAC,CAAC,MAAmC,CAAA;gBACpD,IAAI,MAAM,IAAI,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ;oBAAE,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;YACxE,CAAC;YACD,SAAS,EAAE,CAAC,CAAC,EAAE,EAAE;gBACf,IAAI,CAAC,CAAC,GAAG,KAAK,OAAO,IAAI,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;oBACrC,CAAC,CAAC,cAAc,EAAE,CAAA;oBAClB,MAAM,EAAE,CAAA;gBACV,CAAC;YACH,CAAC;SACF;QACD,YAAY,EAAE;YACZ,WAAW,EAAE,QAAQ;YACrB,OAAO,EAAE,MAAM;YACf,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE;gBACd,MAAM,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC,CAAA;gBACjB,OAAO,EAAE,CAAC,UAAU,IAAI,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,CAAA;YAC7D,CAAC;SACF;QACD,SAAS,EAAE,CAAC,CAAC,EAAE,EAAE;YACf,MAAM,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC,CAAA;YACjB,OAAO,CAAC,EAAE,CAAC,UAAU,IAAI,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAA;QAC5D,CAAC;KACF,CAAA;AACH,CAAC","sourcesContent":["import type { AgentEffect } from './effects.js'\n\n/**\n * In-app chat composer slice. Owns the editor-state half of the\n * conversational surface (`pendingInput`, `submitting`); the\n * timeline half lives in `agentLog` (which renders the user's\n * submission as a `LogEntry { kind: 'user-input' }` alongside agent\n * actions, so the conversation reads chronologically).\n *\n * Pattern: host wires the slice into its app state via\n * `sliceHandler`, spreads `connect()`'s prop bag into the host's\n * own input/button layout, and the `Submit` Msg fires an effect\n * that the framework's effect handler turns into a WS\n * `user-input-submitted` frame + a synthesized log entry. The\n * agent's `wait_for_user_input` LAP tool picks up the frame at the\n * server.\n *\n * The composer is NOT an LLM. It's a relay surface: the user's text\n * is delivered to the user's own LLM (Claude desktop / IDE / wherever\n * is mounted on the MCP bridge), which already has cross-app context.\n * The framework just provides the in-app rendezvous so the user\n * doesn't have to alt-tab to a different window to talk to their\n * agent about the app they're looking at.\n */\nexport type AgentChatState = {\n /** Current contents of the input field. Bound to the input's `value`. */\n pendingInput: string\n /**\n * True between `Submit` and the effect handler completing the\n * frame send. Disables the submit button and (typically) the\n * input itself; reducer-driven so the UI never disagrees.\n */\n submitting: boolean\n}\n\nexport type AgentChatInitOpts = {\n /** Pre-fill the input on mount — e.g. session-restore. */\n initialInput?: string\n}\n\nexport type AgentChatMsg =\n | {\n /** Bound to the input's `onInput` event. Round-trip stays in the slice. */\n type: 'SetInput'\n value: string\n }\n | {\n /**\n * Bound to the submit button's `onClick` and the input's\n * `onKeyDown` (Enter, no shift). Reducer:\n *\n * - Empty/whitespace → no-op (no effect, no state change).\n * - Has content → clear `pendingInput`, set `submitting: true`,\n * emit `AgentChatSendInput { text, at }` effect.\n */\n type: 'Submit'\n }\n | {\n /**\n * Fired by the effect handler after the frame sends so the\n * UI re-enables the input. Always paired 1:1 with each\n * dispatched `AgentChatSendInput` effect.\n */\n type: 'SubmitComplete'\n }\n\nexport function init(opts: AgentChatInitOpts = {}): [AgentChatState, AgentEffect[]] {\n return [\n {\n pendingInput: opts.initialInput ?? '',\n submitting: false,\n },\n [],\n ]\n}\n\nexport function update(state: AgentChatState, msg: AgentChatMsg): [AgentChatState, AgentEffect[]] {\n switch (msg.type) {\n case 'SetInput':\n // No-op when the value is identical — keeps state ref stable\n // for memoization above this slice.\n if (msg.value === state.pendingInput) return [state, []]\n return [{ ...state, pendingInput: msg.value }, []]\n case 'Submit': {\n // Don't double-submit while a previous send is in flight, and\n // don't send empty/whitespace messages — neither is a useful\n // signal for the agent and both are common bounce events\n // (keyboard auto-repeat on Enter, click-then-click-again).\n if (state.submitting) return [state, []]\n const trimmed = state.pendingInput.trim()\n if (trimmed.length === 0) return [state, []]\n const at = Date.now()\n return [\n { ...state, pendingInput: '', submitting: true },\n [{ type: 'AgentChatSendInput', text: trimmed, at }],\n ]\n }\n case 'SubmitComplete':\n if (!state.submitting) return [state, []]\n return [{ ...state, submitting: false }, []]\n }\n}\n\nimport { tagSend, type Send } from '@llui/dom'\n\n/**\n * Static-prop-bag-with-reactive-accessors. Spread directly into\n * element helpers; matches the convention of the other agent\n * namespaces.\n *\n * The `input` bag carries both `onInput` and `onKeyDown` because\n * the canonical chat-composer affordance is \"type, press Enter\"\n * (Shift+Enter for newline if the host wraps it themselves). Hosts\n * that want a multiline textarea can spread `input.onInput` and\n * skip `onKeyDown`, wiring submit to the button only. Handler keys\n * are camelCase because LLui's element runtime only attaches keys\n * matching `/^on[A-Z]/` as event listeners — lowercase `oninput`\n * would silently degrade to a string attribute.\n */\nexport type ConnectBag<S> = {\n root: { 'data-scope': 'agent-chat'; 'data-submitting': (s: S) => boolean }\n input: {\n 'data-part': 'input'\n value: (s: S) => string\n disabled: (s: S) => boolean\n onInput: (e: Event) => void\n onKeyDown: (e: KeyboardEvent) => void\n }\n submitButton: {\n 'data-part': 'submit'\n onClick: () => void\n disabled: (s: S) => boolean\n }\n /**\n * True iff the input has non-whitespace content AND we're not\n * mid-submit. Useful as the predicate for a \"send\" affordance\n * separate from the button's own `disabled` field (e.g. a\n * keyboard-shortcut hint).\n */\n canSubmit: (s: S) => boolean\n}\n\nexport function connect<S>(get: (s: S) => AgentChatState, send: Send<AgentChatMsg>): ConnectBag<S> {\n const submit = tagSend(send, ['Submit'], () => send({ type: 'Submit' }))\n const setInput = (value: string) => send({ type: 'SetInput', value })\n return {\n root: {\n 'data-scope': 'agent-chat',\n 'data-submitting': (s) => get(s).submitting,\n },\n input: {\n 'data-part': 'input',\n value: (s) => get(s).pendingInput,\n disabled: (s) => get(s).submitting,\n // Event handler keys MUST be camelCase: LLui's elements pass only\n // keys matching `/^on[A-Z]/` to `addEventListener` (see\n // packages/dom/src/elements.ts). Lowercase `oninput` would silently\n // be set as a string attribute and never fire — the input field\n // would freeze with no SetInput dispatches and the submit button\n // would stay disabled forever.\n onInput: (e) => {\n // The cast is the standard \"input event target is an\n // HTMLInputElement / HTMLTextAreaElement\" assumption; we\n // coerce via `.value`. Hosts using non-DOM inputs (custom\n // contenteditable etc.) bypass this prop bag and dispatch\n // SetInput directly.\n const target = e.target as { value?: string } | null\n if (target && typeof target.value === 'string') setInput(target.value)\n },\n onKeyDown: (e) => {\n if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault()\n submit()\n }\n },\n },\n submitButton: {\n 'data-part': 'submit',\n onClick: submit,\n disabled: (s) => {\n const cs = get(s)\n return cs.submitting || cs.pendingInput.trim().length === 0\n },\n },\n canSubmit: (s) => {\n const cs = get(s)\n return !cs.submitting && cs.pendingInput.trim().length > 0\n },\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"agentConnect.d.ts","sourceRoot":"","sources":["../../src/client/agentConnect.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"agentConnect.d.ts","sourceRoot":"","sources":["../../src/client/agentConnect.ts"],"names":[],"mappings":"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"}
@@ -23,6 +23,49 @@ function reconnectDelayMs(attempt) {
23
23
  * silently spinning.
24
24
  */
25
25
  const RECONNECT_GIVE_UP_MS = 5 * 60 * 1000;
26
+ /**
27
+ * Build the user-pasted connect snippet that lands in the LLM's chat
28
+ * window. Three things in one paragraph that an LLM will follow on
29
+ * first read:
30
+ *
31
+ * 1. Tell the LLM which tool to call to bind the session
32
+ * (`connect_session`).
33
+ *
34
+ * 2. Encourage `narrate` for surfacing intent during multi-step
35
+ * actions. Without the nudge the LLM tends to dispatch silently;
36
+ * with it, the user sees a running commentary in the app's
37
+ * activity log alongside the action entries. One-way channel
38
+ * (LLM → user); the conversation itself stays in the LLM's own
39
+ * chat window.
40
+ *
41
+ * 3. Survive tool-namespacing edge cases: Claude Desktop exposes MCP
42
+ * tools as bare names (`connect_session`) but Claude Code and
43
+ * other namespacing clients emit them as
44
+ * `mcp__llui__connect_session` and may defer-load them — so an
45
+ * LLM that searches its tool list for a literal name won't find
46
+ * it. Naming the LLui MCP server explicitly (with its canonical
47
+ * install name `llui`) gives the model enough to resolve the
48
+ * right tool on either platform; the parenthetical surfaces the
49
+ * edge case for deferred-tool clients.
50
+ *
51
+ * Phrased generically (`AI assistant`, `Some MCP clients`) since
52
+ * MCP support is rapidly expanding past Claude — the snippet
53
+ * shouldn't telegraph "this is Claude-only" when it works against
54
+ * any compliant client. The framework owns this string; updates
55
+ * ride along the `@llui/agent` package version.
56
+ */
57
+ function buildConnectSnippet(lapUrl, token) {
58
+ return (`Connect this AI assistant to the LLui app. Call the LLui MCP server's ` +
59
+ `\`connect_session\` tool with url=${JSON.stringify(lapUrl)} and ` +
60
+ `token=${JSON.stringify(token)}. ` +
61
+ `When you're working through a multi-step task, call \`narrate\` to ` +
62
+ `surface what you're doing — the user sees your prose in the app's ` +
63
+ `activity log alongside each action you dispatch. ` +
64
+ `(Some MCP clients namespace tools as ` +
65
+ `\`mcp__llui__connect_session\` / \`mcp__llui__narrate\` and load them ` +
66
+ `lazily — search the tool list if the bare names aren't immediately ` +
67
+ `available.)`);
68
+ }
26
69
  /** Component shape is [State, Effect[]] — consistent with @llui/components. */
27
70
  export function init(_opts) {
28
71
  return [
@@ -50,32 +93,11 @@ export function update(state, msg, opts = {}) {
50
93
  return [{ ...state, status: 'minting' }, [mintEffect]];
51
94
  }
52
95
  case 'MintSucceeded': {
53
- // The connect snippet has to work across every MCP surface.
54
- // Claude Desktop and similar clients expose MCP tools as bare
55
- // names (`connect_session`), but Claude Code (and other tool-list-
56
- // namespacing clients) emit them as `mcp__llui__connect_session`
57
- // and may defer-load them — so an LLM that searches its tool list
58
- // for a literal `connect_session` won't find it. Naming the LLui
59
- // MCP server explicitly (with its canonical `llui` install name,
60
- // matching the install docs) gives the model enough to resolve
61
- // the right tool on either platform; the parenthetical names the
62
- // edge case so a deferred-tool client doesn't bail out.
63
- //
64
- // Phrased generically (`AI assistant`, `Some MCP clients`) since
65
- // MCP support is rapidly expanding past Claude — the snippet
66
- // shouldn't telegraph "this is Claude-only" when it works against
67
- // any compliant client. The literal `mcp__llui__` prefix matches
68
- // the install command in `site/content/agents.md`; users who
69
- // renamed the server in their config can substitute their name.
70
96
  const pending = {
71
97
  token: msg.token,
72
98
  tid: msg.tid,
73
99
  lapUrl: msg.lapUrl,
74
- connectSnippet: `Connect this AI assistant to the LLui app. Call the LLui MCP server's ` +
75
- `\`connect_session\` tool with url=${JSON.stringify(msg.lapUrl)} and ` +
76
- `token=${JSON.stringify(msg.token)}. ` +
77
- `(Some MCP clients namespace tools as ` +
78
- `\`mcp__llui__connect_session\` and load them lazily — search the tool list if \`connect_session\` isn't immediately available.)`,
100
+ connectSnippet: buildConnectSnippet(msg.lapUrl, msg.token),
79
101
  expiresAt: msg.expiresAt,
80
102
  wsUrl: msg.wsUrl,
81
103
  };
@@ -113,19 +135,11 @@ export function update(state, msg, opts = {}) {
113
135
  // than to coordinate the race in the host.
114
136
  if (state.status !== 'idle')
115
137
  return [state, []];
116
- // Regenerate the connectSnippet so the user can re-paste if
117
- // their AI lost the original tool call (same shape as
118
- // MintSucceeded — the framework owns this string and updates
119
- // to it ride along the agent package version).
120
138
  const restored = {
121
139
  token: msg.token,
122
140
  tid: msg.tid,
123
141
  lapUrl: msg.lapUrl,
124
- connectSnippet: `Connect this AI assistant to the LLui app. Call the LLui MCP server's ` +
125
- `\`connect_session\` tool with url=${JSON.stringify(msg.lapUrl)} and ` +
126
- `token=${JSON.stringify(msg.token)}. ` +
127
- `(Some MCP clients namespace tools as ` +
128
- `\`mcp__llui__connect_session\` and load them lazily — search the tool list if \`connect_session\` isn't immediately available.)`,
142
+ connectSnippet: buildConnectSnippet(msg.lapUrl, msg.token),
129
143
  expiresAt: msg.expiresAt,
130
144
  wsUrl: msg.wsUrl,
131
145
  };
@@ -1 +1 @@
1
- {"version":3,"file":"agentConnect.js","sourceRoot":"","sources":["../../src/client/agentConnect.ts"],"names":[],"mappings":"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","sourcesContent":["import { tagSend, type Send } from '@llui/dom'\nimport type { AgentSession, AgentToken } from '../protocol.js'\nimport type { AgentEffect } from './effects.js'\n\nexport type AgentConnectStatus =\n | 'idle'\n | 'minting'\n | 'pending-claude'\n | 'active'\n | 'reconnecting'\n | 'failed'\n | 'error'\n\nexport type AgentConnectPendingToken = {\n token: AgentToken\n tid: string\n lapUrl: string\n /**\n * Natural-language connect instruction the user copies into Claude.\n * Includes URL, token, and the explicit `connect_session` tool\n * call. Works in any Claude client (Desktop, CC CLI, etc.) — the\n * Desktop-specific `/llui-connect` slash command is sugar over the\n * same tool call.\n */\n connectSnippet: string\n expiresAt: number\n /**\n * Cached so the auto-reconnect path can re-open the WS without\n * re-minting. The MintSucceeded → AgentOpenWS path stores it; the\n * RestoreSession path also fills it in. Cleared by `Disconnect`.\n */\n wsUrl: string\n}\n\nexport type AgentConnectState = {\n status: AgentConnectStatus\n pendingToken: AgentConnectPendingToken | null\n sessions: AgentSession[]\n resumable: AgentSession[]\n error: { code: string; detail: string } | null\n /**\n * Reconnect attempt counter. Incremented on each WS-close that\n * triggers an auto-reconnect; reset on `WsOpened` and on user\n * actions (`Disconnect`, fresh `Mint`). Drives the backoff schedule\n * (1s, 2s, 4s, 8s, 16s, 30s, 30s, …) and surfaces to UI as\n * \"reconnecting (attempt 3 / next in 4s)\".\n */\n reconnectAttempt: number\n /**\n * Total cumulative ms spent in `reconnecting` for the current\n * outage. Compared against `reconnectGiveUpMs` (effect-side option,\n * default 5 min) to decide when to surface `failed` to the user.\n * Reset whenever a WS opens successfully.\n */\n reconnectElapsedMs: number\n}\n\nexport type AgentConnectMsg =\n /** @intent(\"Mint a new agent token and open the pairing WebSocket\") */\n | { type: 'Mint' }\n /**\n * @humanOnly — internal: dispatched by the AgentMintRequest effect\n * handler when the mint endpoint replies success. Carries the token\n * and connection URLs into state.\n */\n | {\n type: 'MintSucceeded'\n token: AgentToken\n tid: string\n lapUrl: string\n wsUrl: string\n expiresAt: number\n }\n /** @humanOnly — internal: dispatched by the AgentMintRequest handler on failure. */\n | { type: 'MintFailed'; error: { code: string; detail: string } }\n /** @humanOnly — internal: WS adapter signalled the pairing socket is open. */\n | { type: 'WsOpened' }\n /** @humanOnly — internal: WS adapter signalled the pairing socket is closed. */\n | { type: 'WsClosed' }\n /** @humanOnly — internal: Claude bound the session via /agent/claim. */\n | { type: 'ActivatedByClaude' }\n /** @intent(\"Check which previously-issued agent sessions can be resumed\") */\n | { type: 'ResumeList'; tids: string[] }\n /** @humanOnly — internal: AgentResumeCheck effect handler returned the list. */\n | { type: 'ResumeListLoaded'; sessions: AgentSession[] }\n /** @intent(\"Resume an existing agent session by tid\") */\n | { type: 'Resume'; tid: string }\n /** @intent(\"Revoke an agent session by tid\") */\n | { type: 'Revoke'; tid: string }\n /** @intent(\"Dismiss the current agent connect error\") */\n | { type: 'ClearError' }\n /** @humanOnly — internal: AgentSessionsList effect handler returned the list. */\n | { type: 'SessionsLoaded'; sessions: AgentSession[] }\n /** @intent(\"Refresh the list of active agent sessions\") */\n | { type: 'RefreshSessions' }\n /**\n * @intent(\"Copy the agent connect snippet to the clipboard\")\n * Resolves the pendingToken's snippet in update() (state-reading is\n * what update() is for) and dispatches a clipboard-write effect.\n */\n | { type: 'CopyConnectSnippet' }\n /**\n * @humanOnly — internal: app boot dispatches this with credentials\n * read from sessionStorage to skip the mint round-trip after page\n * refresh. The agent's token (still alive on the server) keeps\n * working since we don't go through the rotate-on-resume path. The\n * reducer is idempotent against an in-flight Mint — only fires from\n * `idle`.\n */\n | {\n type: 'RestoreSession'\n token: AgentToken\n tid: string\n lapUrl: string\n wsUrl: string\n expiresAt: number\n }\n /**\n * @intent(\"Disconnect the active agent session and clear all\n * persisted credentials. Stops any in-flight reconnect attempt;\n * subsequent WS closures stay in `idle` instead of triggering\n * auto-reconnect. Use when the user explicitly clicks Disconnect\n * in the panel — for transient drops, do nothing and let the\n * reconnect loop run.\")\n */\n | { type: 'Disconnect' }\n /**\n * @humanOnly — internal: scheduler effect dispatched this when the\n * backoff timer fired. The reducer increments the attempt counter,\n * adds the just-elapsed delay to `reconnectElapsedMs`, and emits\n * `AgentOpenWS` with the cached pendingToken/wsUrl so the WS can\n * reattach without minting.\n */\n | { type: 'ReconnectAttempt'; elapsedMs: number }\n /**\n * @humanOnly — internal: scheduler effect dispatched this when the\n * give-up ceiling was reached without a successful WS open.\n * Reducer flips status to `failed` so the UI can surface a clear\n * error and offer a manual reconnect.\n */\n | { type: 'ReconnectGaveUp' }\n\n/**\n * Options threaded through `init()` and `update()`. `mintUrl` is\n * optional — when omitted the agent effect handler derives it from\n * `EffectHandlerHost.agentBasePath` (default `/agent` → `/agent/mint`).\n * Set explicitly only when the mint endpoint lives outside the\n * configured base path.\n */\nexport type AgentConnectInitOpts = { mintUrl?: string }\n\n/**\n * Backoff schedule for the auto-reconnect loop. Doubles starting at\n * 1s, caps at 30s. Translates `state.reconnectAttempt` into the next\n * delay; the effect handler schedules a `setTimeout` for that long\n * and dispatches `ReconnectAttempt` when it fires.\n *\n * Lives in the reducer so tests can pin the timings without poking\n * effect-handler internals; the constants are not exported because\n * tweaking them changes UX more than tweaks to the give-up ceiling.\n */\nconst RECONNECT_BASE_MS = 1000\nconst RECONNECT_CAP_MS = 30_000\nfunction reconnectDelayMs(attempt: number): number {\n const factor = Math.min(Math.pow(2, attempt), RECONNECT_CAP_MS / RECONNECT_BASE_MS)\n return Math.min(RECONNECT_BASE_MS * factor, RECONNECT_CAP_MS)\n}\n\n/**\n * Total cumulative wait, across all reconnect attempts, before the\n * loop gives up and transitions to `'failed'`. 5 minutes is long\n * enough to weather a brief server outage but short enough that a\n * permanently-down endpoint surfaces clearly to the user instead of\n * silently spinning.\n */\nconst RECONNECT_GIVE_UP_MS = 5 * 60 * 1000\n\n/** Component shape is [State, Effect[]] — consistent with @llui/components. */\nexport function init(_opts: AgentConnectInitOpts): [AgentConnectState, AgentEffect[]] {\n return [\n {\n status: 'idle',\n pendingToken: null,\n sessions: [],\n resumable: [],\n error: null,\n reconnectAttempt: 0,\n reconnectElapsedMs: 0,\n },\n [],\n ]\n}\n\nexport function update(\n state: AgentConnectState,\n msg: AgentConnectMsg,\n opts: AgentConnectInitOpts = {},\n): [AgentConnectState, AgentEffect[]] {\n switch (msg.type) {\n case 'Mint': {\n // mintUrl: undefined means \"let the effect handler derive it\n // from agentBasePath\". Only include the property when explicitly\n // set, so the effect's discriminated shape stays clean.\n const mintEffect: AgentEffect =\n opts.mintUrl !== undefined\n ? { type: 'AgentMintRequest', mintUrl: opts.mintUrl }\n : { type: 'AgentMintRequest' }\n return [{ ...state, status: 'minting' }, [mintEffect]]\n }\n case 'MintSucceeded': {\n // The connect snippet has to work across every MCP surface.\n // Claude Desktop and similar clients expose MCP tools as bare\n // names (`connect_session`), but Claude Code (and other tool-list-\n // namespacing clients) emit them as `mcp__llui__connect_session`\n // and may defer-load them — so an LLM that searches its tool list\n // for a literal `connect_session` won't find it. Naming the LLui\n // MCP server explicitly (with its canonical `llui` install name,\n // matching the install docs) gives the model enough to resolve\n // the right tool on either platform; the parenthetical names the\n // edge case so a deferred-tool client doesn't bail out.\n //\n // Phrased generically (`AI assistant`, `Some MCP clients`) since\n // MCP support is rapidly expanding past Claude — the snippet\n // shouldn't telegraph \"this is Claude-only\" when it works against\n // any compliant client. The literal `mcp__llui__` prefix matches\n // the install command in `site/content/agents.md`; users who\n // renamed the server in their config can substitute their name.\n const pending: AgentConnectPendingToken = {\n token: msg.token,\n tid: msg.tid,\n lapUrl: msg.lapUrl,\n connectSnippet:\n `Connect this AI assistant to the LLui app. Call the LLui MCP server's ` +\n `\\`connect_session\\` tool with url=${JSON.stringify(msg.lapUrl)} and ` +\n `token=${JSON.stringify(msg.token)}. ` +\n `(Some MCP clients namespace tools as ` +\n `\\`mcp__llui__connect_session\\` and load them lazily — search the tool list if \\`connect_session\\` isn't immediately available.)`,\n expiresAt: msg.expiresAt,\n wsUrl: msg.wsUrl,\n }\n return [\n {\n ...state,\n status: 'pending-claude',\n pendingToken: pending,\n error: null,\n reconnectAttempt: 0,\n reconnectElapsedMs: 0,\n },\n [\n { type: 'AgentOpenWS', token: msg.token, wsUrl: msg.wsUrl },\n // Persist alongside opening the WS so the host can store the\n // credentials in sessionStorage; on page refresh, app boot\n // dispatches `RestoreSession` with the same shape and we\n // re-enter the same state without re-minting.\n {\n type: 'AgentSessionPersist',\n token: msg.token,\n tid: msg.tid,\n lapUrl: msg.lapUrl,\n wsUrl: msg.wsUrl,\n expiresAt: msg.expiresAt,\n },\n ],\n ]\n }\n case 'RestoreSession': {\n // Idempotent guard: only fires from idle. A racing Mint click\n // would already have moved us to `minting` — restoring on top\n // would clobber the in-flight pending state with stale\n // credentials read from sessionStorage. Easier to no-op here\n // than to coordinate the race in the host.\n if (state.status !== 'idle') return [state, []]\n // Regenerate the connectSnippet so the user can re-paste if\n // their AI lost the original tool call (same shape as\n // MintSucceeded — the framework owns this string and updates\n // to it ride along the agent package version).\n const restored: AgentConnectPendingToken = {\n token: msg.token,\n tid: msg.tid,\n lapUrl: msg.lapUrl,\n connectSnippet:\n `Connect this AI assistant to the LLui app. Call the LLui MCP server's ` +\n `\\`connect_session\\` tool with url=${JSON.stringify(msg.lapUrl)} and ` +\n `token=${JSON.stringify(msg.token)}. ` +\n `(Some MCP clients namespace tools as ` +\n `\\`mcp__llui__connect_session\\` and load them lazily — search the tool list if \\`connect_session\\` isn't immediately available.)`,\n expiresAt: msg.expiresAt,\n wsUrl: msg.wsUrl,\n }\n return [\n {\n ...state,\n status: 'pending-claude',\n pendingToken: restored,\n error: null,\n reconnectAttempt: 0,\n reconnectElapsedMs: 0,\n },\n [{ type: 'AgentOpenWS', token: msg.token, wsUrl: msg.wsUrl }],\n ]\n }\n case 'MintFailed':\n return [{ ...state, status: 'error', error: msg.error }, []]\n case 'WsOpened': {\n // WS is open but Claude hasn't bound yet; stay at pending-claude.\n // If we were `reconnecting`, this is a successful reattach —\n // back to pending-claude and reset the attempt counters.\n if (state.status === 'reconnecting') {\n return [\n { ...state, status: 'pending-claude', reconnectAttempt: 0, reconnectElapsedMs: 0 },\n [],\n ]\n }\n return [state, []]\n }\n case 'WsClosed': {\n // Three cases:\n // 1. We had no pendingToken (already idle / pre-mint) → no-op.\n // 2. Status is `idle` or `failed` (Disconnect already cleared,\n // or we previously gave up) → no-op so a delayed close\n // event after Disconnect doesn't accidentally restart the\n // loop.\n // 3. We're connected/connecting and the close was unsolicited\n // → schedule a reconnect with backoff.\n if (state.pendingToken === null) return [{ ...state, status: 'idle' }, []]\n if (state.status === 'idle' || state.status === 'failed') return [state, []]\n const delayMs = reconnectDelayMs(state.reconnectAttempt)\n return [\n { ...state, status: 'reconnecting', error: null },\n [{ type: 'AgentReconnectSchedule', delayMs }],\n ]\n }\n case 'ReconnectAttempt': {\n // Backoff timer fired. If the user disconnected in the gap, we\n // moved to idle and ignore. Otherwise, increment attempt + add\n // the elapsed delay to the cumulative window. Past the give-up\n // ceiling, transition to `failed` so the UI can offer a manual\n // reconnect; otherwise re-open the WS with the cached\n // credentials (no mint, same token — the server's grace window\n // is what makes this transparent to the agent).\n if (state.status !== 'reconnecting' || state.pendingToken === null) {\n return [state, []]\n }\n const newElapsed = state.reconnectElapsedMs + msg.elapsedMs\n if (newElapsed >= RECONNECT_GIVE_UP_MS) {\n return [{ ...state, status: 'failed', reconnectElapsedMs: newElapsed }, []]\n }\n return [\n {\n ...state,\n reconnectAttempt: state.reconnectAttempt + 1,\n reconnectElapsedMs: newElapsed,\n },\n [\n {\n type: 'AgentOpenWS',\n token: state.pendingToken.token,\n wsUrl: state.pendingToken.wsUrl,\n },\n ],\n ]\n }\n case 'ReconnectGaveUp':\n return [{ ...state, status: 'failed' }, []]\n case 'Disconnect': {\n // User-initiated. Revoke the active tid (server kills the\n // pairing), wipe the persisted credentials so a refresh can't\n // restore them, and zero the reconnect counters so any in-\n // flight backoff timer that fires post-disconnect becomes a\n // no-op (the status guard in `ReconnectAttempt` keeps it from\n // re-opening the WS).\n const tid = state.pendingToken?.tid\n const effects: AgentEffect[] = []\n if (tid !== undefined) effects.push({ type: 'AgentRevoke', tid })\n effects.push({ type: 'AgentSessionClear' })\n effects.push({ type: 'AgentCloseWS' })\n return [\n {\n ...state,\n status: 'idle',\n pendingToken: null,\n error: null,\n reconnectAttempt: 0,\n reconnectElapsedMs: 0,\n },\n effects,\n ]\n }\n case 'ActivatedByClaude':\n return [{ ...state, status: 'active' }, []]\n case 'ResumeList':\n return [state, [{ type: 'AgentResumeCheck', tids: msg.tids }]]\n case 'ResumeListLoaded':\n return [{ ...state, resumable: msg.sessions }, []]\n case 'Resume':\n return [state, [{ type: 'AgentResumeClaim', tid: msg.tid }]]\n case 'Revoke': {\n // Optimistically remove from sessions + resumable. If the\n // revoked tid matches the currently-pending session, also fire\n // AgentSessionClear so the host wipes its persisted credentials\n // — otherwise a refresh would try to RestoreSession with a\n // server-side-revoked token and end up at an auth-failed WS.\n const isActiveTid = state.pendingToken !== null && state.pendingToken.tid === msg.tid\n const effects: AgentEffect[] = [{ type: 'AgentRevoke', tid: msg.tid }]\n if (isActiveTid) effects.push({ type: 'AgentSessionClear' })\n return [\n {\n ...state,\n sessions: state.sessions.filter((s) => s.tid !== msg.tid),\n resumable: state.resumable.filter((s) => s.tid !== msg.tid),\n },\n effects,\n ]\n }\n case 'ClearError':\n return [{ ...state, error: null }, []]\n case 'SessionsLoaded':\n return [{ ...state, sessions: msg.sessions }, []]\n case 'RefreshSessions':\n return [state, [{ type: 'AgentSessionsList' }]]\n case 'CopyConnectSnippet': {\n // No-op when there's no pending token — the button's\n // `disabled` accessor already gates the click, but we accept\n // the message for runtime safety.\n if (!state.pendingToken) return [state, []]\n return [state, [{ type: 'AgentClipboardWrite', text: state.pendingToken.connectSnippet }]]\n }\n }\n}\n\n// ── Connect helper ────────────────────────────────────────────────────────────\n\nexport type AgentConnectConnectOptions = {\n id?: string // optional DOM id prefix\n}\n\n/**\n * Static prop bag with reactive accessors. Mirrors the @llui/components\n * pattern (e.g. `dialog.connect`): callers spread bag keys directly\n * into element helpers, and function-valued props re-evaluate per\n * binding-mask hit. The previous shape — `(state) => bag` — required\n * callers to wrap every prop access in their own arrow, which the\n * documented usage didn't do (and silently produced `undefined` props\n * when spread).\n */\nexport type ConnectBag<S> = {\n root: { 'data-scope': 'agent-connect'; 'data-state': (s: S) => AgentConnectStatus }\n mintTrigger: { onClick: () => void; disabled: (s: S) => boolean }\n pendingTokenBox: { 'data-part': 'pending-token'; 'data-visible': (s: S) => boolean }\n copyConnectSnippetButton: { onClick: () => void; disabled: (s: S) => boolean }\n sessionsList: { 'data-part': 'sessions-list' }\n sessionItem: (tid: string) => { 'data-part': 'session-item'; 'data-tid': string }\n revokeButton: (tid: string) => { onClick: () => void }\n resumeBanner: { 'data-part': 'resume-banner'; 'data-visible': (s: S) => boolean }\n resumeItem: (tid: string) => { 'data-part': 'resume-item'; 'data-tid': string }\n resumeButton: (tid: string) => { onClick: () => void }\n dismissButton: (tid: string) => { onClick: () => void }\n error: {\n 'data-part': 'error'\n 'data-visible': (s: S) => boolean\n onClick: () => void\n }\n}\n\n/**\n * Builds prop bags for the view. Static-bag-with-reactive-accessors\n * shape (matches the @llui/components convention); spread directly\n * into element helpers.\n */\nexport function connect<S>(\n get: (s: S) => AgentConnectState,\n send: Send<AgentConnectMsg>,\n _opts: AgentConnectConnectOptions = {},\n): ConnectBag<S> {\n return {\n root: {\n 'data-scope': 'agent-connect',\n 'data-state': (s) => get(s).status,\n },\n mintTrigger: {\n onClick: tagSend(send, ['Mint'], () => send({ type: 'Mint' })),\n disabled: (s) => {\n const cs = get(s)\n return cs.status === 'minting' || cs.status === 'pending-claude' || cs.status === 'active'\n },\n },\n pendingTokenBox: {\n 'data-part': 'pending-token',\n 'data-visible': (s) => get(s).pendingToken !== null,\n },\n copyConnectSnippetButton: {\n // The handler reads state at click time via the Msg/effect path:\n // CopyConnectSnippet → update() reads pendingToken.connectSnippet\n // → effect AgentClipboardWrite writes to navigator.clipboard.\n // Routing through update() keeps state reads out of event\n // handlers, which is what makes the static-bag-with-reactive-\n // accessors shape work cleanly.\n onClick: tagSend(send, ['CopyConnectSnippet'], () => send({ type: 'CopyConnectSnippet' })),\n disabled: (s) => get(s).pendingToken === null,\n },\n sessionsList: { 'data-part': 'sessions-list' },\n sessionItem: (tid) => ({ 'data-part': 'session-item', 'data-tid': tid }),\n revokeButton: (tid) => ({\n onClick: tagSend(send, ['Revoke'], () => send({ type: 'Revoke', tid })),\n }),\n resumeBanner: {\n 'data-part': 'resume-banner',\n 'data-visible': (s) => get(s).resumable.length > 0,\n },\n resumeItem: (tid) => ({ 'data-part': 'resume-item', 'data-tid': tid }),\n resumeButton: (tid) => ({\n onClick: tagSend(send, ['Resume'], () => send({ type: 'Resume', tid })),\n }),\n dismissButton: (tid) => ({\n // For dismiss, we currently just remove the resumable record\n // locally. A \"dismiss forever\" flag could land in a follow-up;\n // for v1, dismiss is a client-side-only state prune by reusing\n // the Revoke Msg path with intent-split; for now emit Revoke\n // which both revokes server-side AND removes locally.\n onClick: tagSend(send, ['Revoke'], () => send({ type: 'Revoke', tid })),\n }),\n error: {\n 'data-part': 'error',\n 'data-visible': (s) => get(s).error !== null,\n onClick: tagSend(send, ['ClearError'], () => send({ type: 'ClearError' })),\n },\n }\n}\n"]}
1
+ {"version":3,"file":"agentConnect.js","sourceRoot":"","sources":["../../src/client/agentConnect.ts"],"names":[],"mappings":"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","sourcesContent":["import { tagSend, type Send } from '@llui/dom'\nimport type { AgentSession, AgentToken } from '../protocol.js'\nimport type { AgentEffect } from './effects.js'\n\nexport type AgentConnectStatus =\n | 'idle'\n | 'minting'\n | 'pending-claude'\n | 'active'\n | 'reconnecting'\n | 'failed'\n | 'error'\n\nexport type AgentConnectPendingToken = {\n token: AgentToken\n tid: string\n lapUrl: string\n /**\n * Natural-language connect instruction the user copies into Claude.\n * Includes URL, token, and the explicit `connect_session` tool\n * call. Works in any Claude client (Desktop, CC CLI, etc.) — the\n * Desktop-specific `/llui-connect` slash command is sugar over the\n * same tool call.\n */\n connectSnippet: string\n expiresAt: number\n /**\n * Cached so the auto-reconnect path can re-open the WS without\n * re-minting. The MintSucceeded → AgentOpenWS path stores it; the\n * RestoreSession path also fills it in. Cleared by `Disconnect`.\n */\n wsUrl: string\n}\n\nexport type AgentConnectState = {\n status: AgentConnectStatus\n pendingToken: AgentConnectPendingToken | null\n sessions: AgentSession[]\n resumable: AgentSession[]\n error: { code: string; detail: string } | null\n /**\n * Reconnect attempt counter. Incremented on each WS-close that\n * triggers an auto-reconnect; reset on `WsOpened` and on user\n * actions (`Disconnect`, fresh `Mint`). Drives the backoff schedule\n * (1s, 2s, 4s, 8s, 16s, 30s, 30s, …) and surfaces to UI as\n * \"reconnecting (attempt 3 / next in 4s)\".\n */\n reconnectAttempt: number\n /**\n * Total cumulative ms spent in `reconnecting` for the current\n * outage. Compared against `reconnectGiveUpMs` (effect-side option,\n * default 5 min) to decide when to surface `failed` to the user.\n * Reset whenever a WS opens successfully.\n */\n reconnectElapsedMs: number\n}\n\nexport type AgentConnectMsg =\n /** @intent(\"Mint a new agent token and open the pairing WebSocket\") */\n | { type: 'Mint' }\n /**\n * @humanOnly — internal: dispatched by the AgentMintRequest effect\n * handler when the mint endpoint replies success. Carries the token\n * and connection URLs into state.\n */\n | {\n type: 'MintSucceeded'\n token: AgentToken\n tid: string\n lapUrl: string\n wsUrl: string\n expiresAt: number\n }\n /** @humanOnly — internal: dispatched by the AgentMintRequest handler on failure. */\n | { type: 'MintFailed'; error: { code: string; detail: string } }\n /** @humanOnly — internal: WS adapter signalled the pairing socket is open. */\n | { type: 'WsOpened' }\n /** @humanOnly — internal: WS adapter signalled the pairing socket is closed. */\n | { type: 'WsClosed' }\n /** @humanOnly — internal: Claude bound the session via /agent/claim. */\n | { type: 'ActivatedByClaude' }\n /** @intent(\"Check which previously-issued agent sessions can be resumed\") */\n | { type: 'ResumeList'; tids: string[] }\n /** @humanOnly — internal: AgentResumeCheck effect handler returned the list. */\n | { type: 'ResumeListLoaded'; sessions: AgentSession[] }\n /** @intent(\"Resume an existing agent session by tid\") */\n | { type: 'Resume'; tid: string }\n /** @intent(\"Revoke an agent session by tid\") */\n | { type: 'Revoke'; tid: string }\n /** @intent(\"Dismiss the current agent connect error\") */\n | { type: 'ClearError' }\n /** @humanOnly — internal: AgentSessionsList effect handler returned the list. */\n | { type: 'SessionsLoaded'; sessions: AgentSession[] }\n /** @intent(\"Refresh the list of active agent sessions\") */\n | { type: 'RefreshSessions' }\n /**\n * @intent(\"Copy the agent connect snippet to the clipboard\")\n * Resolves the pendingToken's snippet in update() (state-reading is\n * what update() is for) and dispatches a clipboard-write effect.\n */\n | { type: 'CopyConnectSnippet' }\n /**\n * @humanOnly — internal: app boot dispatches this with credentials\n * read from sessionStorage to skip the mint round-trip after page\n * refresh. The agent's token (still alive on the server) keeps\n * working since we don't go through the rotate-on-resume path. The\n * reducer is idempotent against an in-flight Mint — only fires from\n * `idle`.\n */\n | {\n type: 'RestoreSession'\n token: AgentToken\n tid: string\n lapUrl: string\n wsUrl: string\n expiresAt: number\n }\n /**\n * @intent(\"Disconnect the active agent session and clear all\n * persisted credentials. Stops any in-flight reconnect attempt;\n * subsequent WS closures stay in `idle` instead of triggering\n * auto-reconnect. Use when the user explicitly clicks Disconnect\n * in the panel — for transient drops, do nothing and let the\n * reconnect loop run.\")\n */\n | { type: 'Disconnect' }\n /**\n * @humanOnly — internal: scheduler effect dispatched this when the\n * backoff timer fired. The reducer increments the attempt counter,\n * adds the just-elapsed delay to `reconnectElapsedMs`, and emits\n * `AgentOpenWS` with the cached pendingToken/wsUrl so the WS can\n * reattach without minting.\n */\n | { type: 'ReconnectAttempt'; elapsedMs: number }\n /**\n * @humanOnly — internal: scheduler effect dispatched this when the\n * give-up ceiling was reached without a successful WS open.\n * Reducer flips status to `failed` so the UI can surface a clear\n * error and offer a manual reconnect.\n */\n | { type: 'ReconnectGaveUp' }\n\n/**\n * Options threaded through `init()` and `update()`. `mintUrl` is\n * optional — when omitted the agent effect handler derives it from\n * `EffectHandlerHost.agentBasePath` (default `/agent` → `/agent/mint`).\n * Set explicitly only when the mint endpoint lives outside the\n * configured base path.\n */\nexport type AgentConnectInitOpts = { mintUrl?: string }\n\n/**\n * Backoff schedule for the auto-reconnect loop. Doubles starting at\n * 1s, caps at 30s. Translates `state.reconnectAttempt` into the next\n * delay; the effect handler schedules a `setTimeout` for that long\n * and dispatches `ReconnectAttempt` when it fires.\n *\n * Lives in the reducer so tests can pin the timings without poking\n * effect-handler internals; the constants are not exported because\n * tweaking them changes UX more than tweaks to the give-up ceiling.\n */\nconst RECONNECT_BASE_MS = 1000\nconst RECONNECT_CAP_MS = 30_000\nfunction reconnectDelayMs(attempt: number): number {\n const factor = Math.min(Math.pow(2, attempt), RECONNECT_CAP_MS / RECONNECT_BASE_MS)\n return Math.min(RECONNECT_BASE_MS * factor, RECONNECT_CAP_MS)\n}\n\n/**\n * Total cumulative wait, across all reconnect attempts, before the\n * loop gives up and transitions to `'failed'`. 5 minutes is long\n * enough to weather a brief server outage but short enough that a\n * permanently-down endpoint surfaces clearly to the user instead of\n * silently spinning.\n */\nconst RECONNECT_GIVE_UP_MS = 5 * 60 * 1000\n\n/**\n * Build the user-pasted connect snippet that lands in the LLM's chat\n * window. Three things in one paragraph that an LLM will follow on\n * first read:\n *\n * 1. Tell the LLM which tool to call to bind the session\n * (`connect_session`).\n *\n * 2. Encourage `narrate` for surfacing intent during multi-step\n * actions. Without the nudge the LLM tends to dispatch silently;\n * with it, the user sees a running commentary in the app's\n * activity log alongside the action entries. One-way channel\n * (LLM → user); the conversation itself stays in the LLM's own\n * chat window.\n *\n * 3. Survive tool-namespacing edge cases: Claude Desktop exposes MCP\n * tools as bare names (`connect_session`) but Claude Code and\n * other namespacing clients emit them as\n * `mcp__llui__connect_session` and may defer-load them — so an\n * LLM that searches its tool list for a literal name won't find\n * it. Naming the LLui MCP server explicitly (with its canonical\n * install name `llui`) gives the model enough to resolve the\n * right tool on either platform; the parenthetical surfaces the\n * edge case for deferred-tool clients.\n *\n * Phrased generically (`AI assistant`, `Some MCP clients`) since\n * MCP support is rapidly expanding past Claude — the snippet\n * shouldn't telegraph \"this is Claude-only\" when it works against\n * any compliant client. The framework owns this string; updates\n * ride along the `@llui/agent` package version.\n */\nfunction buildConnectSnippet(lapUrl: string, token: string): string {\n return (\n `Connect this AI assistant to the LLui app. Call the LLui MCP server's ` +\n `\\`connect_session\\` tool with url=${JSON.stringify(lapUrl)} and ` +\n `token=${JSON.stringify(token)}. ` +\n `When you're working through a multi-step task, call \\`narrate\\` to ` +\n `surface what you're doing — the user sees your prose in the app's ` +\n `activity log alongside each action you dispatch. ` +\n `(Some MCP clients namespace tools as ` +\n `\\`mcp__llui__connect_session\\` / \\`mcp__llui__narrate\\` and load them ` +\n `lazily — search the tool list if the bare names aren't immediately ` +\n `available.)`\n )\n}\n\n/** Component shape is [State, Effect[]] — consistent with @llui/components. */\nexport function init(_opts: AgentConnectInitOpts): [AgentConnectState, AgentEffect[]] {\n return [\n {\n status: 'idle',\n pendingToken: null,\n sessions: [],\n resumable: [],\n error: null,\n reconnectAttempt: 0,\n reconnectElapsedMs: 0,\n },\n [],\n ]\n}\n\nexport function update(\n state: AgentConnectState,\n msg: AgentConnectMsg,\n opts: AgentConnectInitOpts = {},\n): [AgentConnectState, AgentEffect[]] {\n switch (msg.type) {\n case 'Mint': {\n // mintUrl: undefined means \"let the effect handler derive it\n // from agentBasePath\". Only include the property when explicitly\n // set, so the effect's discriminated shape stays clean.\n const mintEffect: AgentEffect =\n opts.mintUrl !== undefined\n ? { type: 'AgentMintRequest', mintUrl: opts.mintUrl }\n : { type: 'AgentMintRequest' }\n return [{ ...state, status: 'minting' }, [mintEffect]]\n }\n case 'MintSucceeded': {\n const pending: AgentConnectPendingToken = {\n token: msg.token,\n tid: msg.tid,\n lapUrl: msg.lapUrl,\n connectSnippet: buildConnectSnippet(msg.lapUrl, msg.token),\n expiresAt: msg.expiresAt,\n wsUrl: msg.wsUrl,\n }\n return [\n {\n ...state,\n status: 'pending-claude',\n pendingToken: pending,\n error: null,\n reconnectAttempt: 0,\n reconnectElapsedMs: 0,\n },\n [\n { type: 'AgentOpenWS', token: msg.token, wsUrl: msg.wsUrl },\n // Persist alongside opening the WS so the host can store the\n // credentials in sessionStorage; on page refresh, app boot\n // dispatches `RestoreSession` with the same shape and we\n // re-enter the same state without re-minting.\n {\n type: 'AgentSessionPersist',\n token: msg.token,\n tid: msg.tid,\n lapUrl: msg.lapUrl,\n wsUrl: msg.wsUrl,\n expiresAt: msg.expiresAt,\n },\n ],\n ]\n }\n case 'RestoreSession': {\n // Idempotent guard: only fires from idle. A racing Mint click\n // would already have moved us to `minting` — restoring on top\n // would clobber the in-flight pending state with stale\n // credentials read from sessionStorage. Easier to no-op here\n // than to coordinate the race in the host.\n if (state.status !== 'idle') return [state, []]\n const restored: AgentConnectPendingToken = {\n token: msg.token,\n tid: msg.tid,\n lapUrl: msg.lapUrl,\n connectSnippet: buildConnectSnippet(msg.lapUrl, msg.token),\n expiresAt: msg.expiresAt,\n wsUrl: msg.wsUrl,\n }\n return [\n {\n ...state,\n status: 'pending-claude',\n pendingToken: restored,\n error: null,\n reconnectAttempt: 0,\n reconnectElapsedMs: 0,\n },\n [{ type: 'AgentOpenWS', token: msg.token, wsUrl: msg.wsUrl }],\n ]\n }\n case 'MintFailed':\n return [{ ...state, status: 'error', error: msg.error }, []]\n case 'WsOpened': {\n // WS is open but Claude hasn't bound yet; stay at pending-claude.\n // If we were `reconnecting`, this is a successful reattach —\n // back to pending-claude and reset the attempt counters.\n if (state.status === 'reconnecting') {\n return [\n { ...state, status: 'pending-claude', reconnectAttempt: 0, reconnectElapsedMs: 0 },\n [],\n ]\n }\n return [state, []]\n }\n case 'WsClosed': {\n // Three cases:\n // 1. We had no pendingToken (already idle / pre-mint) → no-op.\n // 2. Status is `idle` or `failed` (Disconnect already cleared,\n // or we previously gave up) → no-op so a delayed close\n // event after Disconnect doesn't accidentally restart the\n // loop.\n // 3. We're connected/connecting and the close was unsolicited\n // → schedule a reconnect with backoff.\n if (state.pendingToken === null) return [{ ...state, status: 'idle' }, []]\n if (state.status === 'idle' || state.status === 'failed') return [state, []]\n const delayMs = reconnectDelayMs(state.reconnectAttempt)\n return [\n { ...state, status: 'reconnecting', error: null },\n [{ type: 'AgentReconnectSchedule', delayMs }],\n ]\n }\n case 'ReconnectAttempt': {\n // Backoff timer fired. If the user disconnected in the gap, we\n // moved to idle and ignore. Otherwise, increment attempt + add\n // the elapsed delay to the cumulative window. Past the give-up\n // ceiling, transition to `failed` so the UI can offer a manual\n // reconnect; otherwise re-open the WS with the cached\n // credentials (no mint, same token — the server's grace window\n // is what makes this transparent to the agent).\n if (state.status !== 'reconnecting' || state.pendingToken === null) {\n return [state, []]\n }\n const newElapsed = state.reconnectElapsedMs + msg.elapsedMs\n if (newElapsed >= RECONNECT_GIVE_UP_MS) {\n return [{ ...state, status: 'failed', reconnectElapsedMs: newElapsed }, []]\n }\n return [\n {\n ...state,\n reconnectAttempt: state.reconnectAttempt + 1,\n reconnectElapsedMs: newElapsed,\n },\n [\n {\n type: 'AgentOpenWS',\n token: state.pendingToken.token,\n wsUrl: state.pendingToken.wsUrl,\n },\n ],\n ]\n }\n case 'ReconnectGaveUp':\n return [{ ...state, status: 'failed' }, []]\n case 'Disconnect': {\n // User-initiated. Revoke the active tid (server kills the\n // pairing), wipe the persisted credentials so a refresh can't\n // restore them, and zero the reconnect counters so any in-\n // flight backoff timer that fires post-disconnect becomes a\n // no-op (the status guard in `ReconnectAttempt` keeps it from\n // re-opening the WS).\n const tid = state.pendingToken?.tid\n const effects: AgentEffect[] = []\n if (tid !== undefined) effects.push({ type: 'AgentRevoke', tid })\n effects.push({ type: 'AgentSessionClear' })\n effects.push({ type: 'AgentCloseWS' })\n return [\n {\n ...state,\n status: 'idle',\n pendingToken: null,\n error: null,\n reconnectAttempt: 0,\n reconnectElapsedMs: 0,\n },\n effects,\n ]\n }\n case 'ActivatedByClaude':\n return [{ ...state, status: 'active' }, []]\n case 'ResumeList':\n return [state, [{ type: 'AgentResumeCheck', tids: msg.tids }]]\n case 'ResumeListLoaded':\n return [{ ...state, resumable: msg.sessions }, []]\n case 'Resume':\n return [state, [{ type: 'AgentResumeClaim', tid: msg.tid }]]\n case 'Revoke': {\n // Optimistically remove from sessions + resumable. If the\n // revoked tid matches the currently-pending session, also fire\n // AgentSessionClear so the host wipes its persisted credentials\n // — otherwise a refresh would try to RestoreSession with a\n // server-side-revoked token and end up at an auth-failed WS.\n const isActiveTid = state.pendingToken !== null && state.pendingToken.tid === msg.tid\n const effects: AgentEffect[] = [{ type: 'AgentRevoke', tid: msg.tid }]\n if (isActiveTid) effects.push({ type: 'AgentSessionClear' })\n return [\n {\n ...state,\n sessions: state.sessions.filter((s) => s.tid !== msg.tid),\n resumable: state.resumable.filter((s) => s.tid !== msg.tid),\n },\n effects,\n ]\n }\n case 'ClearError':\n return [{ ...state, error: null }, []]\n case 'SessionsLoaded':\n return [{ ...state, sessions: msg.sessions }, []]\n case 'RefreshSessions':\n return [state, [{ type: 'AgentSessionsList' }]]\n case 'CopyConnectSnippet': {\n // No-op when there's no pending token — the button's\n // `disabled` accessor already gates the click, but we accept\n // the message for runtime safety.\n if (!state.pendingToken) return [state, []]\n return [state, [{ type: 'AgentClipboardWrite', text: state.pendingToken.connectSnippet }]]\n }\n }\n}\n\n// ── Connect helper ────────────────────────────────────────────────────────────\n\nexport type AgentConnectConnectOptions = {\n id?: string // optional DOM id prefix\n}\n\n/**\n * Static prop bag with reactive accessors. Mirrors the @llui/components\n * pattern (e.g. `dialog.connect`): callers spread bag keys directly\n * into element helpers, and function-valued props re-evaluate per\n * binding-mask hit. The previous shape — `(state) => bag` — required\n * callers to wrap every prop access in their own arrow, which the\n * documented usage didn't do (and silently produced `undefined` props\n * when spread).\n */\nexport type ConnectBag<S> = {\n root: { 'data-scope': 'agent-connect'; 'data-state': (s: S) => AgentConnectStatus }\n mintTrigger: { onClick: () => void; disabled: (s: S) => boolean }\n pendingTokenBox: { 'data-part': 'pending-token'; 'data-visible': (s: S) => boolean }\n copyConnectSnippetButton: { onClick: () => void; disabled: (s: S) => boolean }\n sessionsList: { 'data-part': 'sessions-list' }\n sessionItem: (tid: string) => { 'data-part': 'session-item'; 'data-tid': string }\n revokeButton: (tid: string) => { onClick: () => void }\n resumeBanner: { 'data-part': 'resume-banner'; 'data-visible': (s: S) => boolean }\n resumeItem: (tid: string) => { 'data-part': 'resume-item'; 'data-tid': string }\n resumeButton: (tid: string) => { onClick: () => void }\n dismissButton: (tid: string) => { onClick: () => void }\n error: {\n 'data-part': 'error'\n 'data-visible': (s: S) => boolean\n onClick: () => void\n }\n}\n\n/**\n * Builds prop bags for the view. Static-bag-with-reactive-accessors\n * shape (matches the @llui/components convention); spread directly\n * into element helpers.\n */\nexport function connect<S>(\n get: (s: S) => AgentConnectState,\n send: Send<AgentConnectMsg>,\n _opts: AgentConnectConnectOptions = {},\n): ConnectBag<S> {\n return {\n root: {\n 'data-scope': 'agent-connect',\n 'data-state': (s) => get(s).status,\n },\n mintTrigger: {\n onClick: tagSend(send, ['Mint'], () => send({ type: 'Mint' })),\n disabled: (s) => {\n const cs = get(s)\n return cs.status === 'minting' || cs.status === 'pending-claude' || cs.status === 'active'\n },\n },\n pendingTokenBox: {\n 'data-part': 'pending-token',\n 'data-visible': (s) => get(s).pendingToken !== null,\n },\n copyConnectSnippetButton: {\n // The handler reads state at click time via the Msg/effect path:\n // CopyConnectSnippet → update() reads pendingToken.connectSnippet\n // → effect AgentClipboardWrite writes to navigator.clipboard.\n // Routing through update() keeps state reads out of event\n // handlers, which is what makes the static-bag-with-reactive-\n // accessors shape work cleanly.\n onClick: tagSend(send, ['CopyConnectSnippet'], () => send({ type: 'CopyConnectSnippet' })),\n disabled: (s) => get(s).pendingToken === null,\n },\n sessionsList: { 'data-part': 'sessions-list' },\n sessionItem: (tid) => ({ 'data-part': 'session-item', 'data-tid': tid }),\n revokeButton: (tid) => ({\n onClick: tagSend(send, ['Revoke'], () => send({ type: 'Revoke', tid })),\n }),\n resumeBanner: {\n 'data-part': 'resume-banner',\n 'data-visible': (s) => get(s).resumable.length > 0,\n },\n resumeItem: (tid) => ({ 'data-part': 'resume-item', 'data-tid': tid }),\n resumeButton: (tid) => ({\n onClick: tagSend(send, ['Resume'], () => send({ type: 'Resume', tid })),\n }),\n dismissButton: (tid) => ({\n // For dismiss, we currently just remove the resumable record\n // locally. A \"dismiss forever\" flag could land in a follow-up;\n // for v1, dismiss is a client-side-only state prune by reusing\n // the Revoke Msg path with intent-split; for now emit Revoke\n // which both revokes server-side AND removes locally.\n onClick: tagSend(send, ['Revoke'], () => send({ type: 'Revoke', tid })),\n }),\n error: {\n 'data-part': 'error',\n 'data-visible': (s) => get(s).error !== null,\n onClick: tagSend(send, ['ClearError'], () => send({ type: 'ClearError' })),\n },\n }\n}\n"]}
@@ -12,27 +12,6 @@ export type EffectHandlerHost = {
12
12
  * provide this; hosts that don't can leave it unset.
13
13
  */
14
14
  wrapAgentAttention?(m: unknown): unknown;
15
- /**
16
- * Wraps an agentChat msg into an app-Msg. Required for hosts
17
- * wiring the chat composer — the `AgentChatSendInput` handler
18
- * dispatches `SubmitComplete` back into the slice via this wrapper
19
- * after the frame send completes (success or failure), so the
20
- * input field re-enables. Hosts that don't render chat leave it
21
- * unset and the chat-send effect no-ops (the agentChat slice in
22
- * that case has no live presence to begin with).
23
- */
24
- wrapAgentChat?(m: unknown): unknown;
25
- /**
26
- * Active WS client (if any) — set lazily by the factory after
27
- * `openWs` resolves a connection. The chat-send handler reads
28
- * `submitUserInput` from here. Marked optional because the
29
- * handler sits across the connection lifecycle: pre-WS / post-close
30
- * `submitUserInput` is null and the handler degrades gracefully
31
- * (still dispatches SubmitComplete so the UI re-enables).
32
- */
33
- getWsClient?: () => {
34
- submitUserInput(text: string, at?: number): void;
35
- } | null;
36
15
  /** Called for AgentForwardMsg — the payload is re-dispatched via send. */
37
16
  forward(payload: unknown): void;
38
17
  /** fetch for HTTP effects; override in tests. */
@@ -1 +1 @@
1
- {"version":3,"file":"effect-handler.d.ts","sourceRoot":"","sources":["../../src/client/effect-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAO/C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAA;AAEvD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAAA;IACxB,iDAAiD;IACjD,gBAAgB,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAAA;IACrC;;;;;;OAMG;IACH,kBAAkB,CAAC,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAAA;IACxC;;;;;;;;OAQG;IACH,aAAa,CAAC,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAAA;IACnC;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,MAAM;QAAE,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAA;IAC/E,0EAA0E;IAC1E,OAAO,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAA;IAC/B,iDAAiD;IACjD,KAAK,CAAC,EAAE,OAAO,KAAK,CAAA;IACpB,yDAAyD;IACzD,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1C,OAAO,IAAI,IAAI,CAAA;IACf;;;;;;;;OAQG;IACH,cAAc,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAAA;IAC3C;;;;;;;;;;;;;OAaG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB,CAAA;AAID;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,iBAAiB,IAG5B,QAAQ,WAAW,KAAG,OAAO,CAAC,IAAI,CAAC,CA8CjE"}
1
+ {"version":3,"file":"effect-handler.d.ts","sourceRoot":"","sources":["../../src/client/effect-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAO/C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAA;AAEvD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAAA;IACxB,iDAAiD;IACjD,gBAAgB,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAAA;IACrC;;;;;;OAMG;IACH,kBAAkB,CAAC,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAAA;IACxC,0EAA0E;IAC1E,OAAO,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAA;IAC/B,iDAAiD;IACjD,KAAK,CAAC,EAAE,OAAO,KAAK,CAAA;IACpB,yDAAyD;IACzD,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1C,OAAO,IAAI,IAAI,CAAA;IACf;;;;;;;;OAQG;IACH,cAAc,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAAA;IAC3C;;;;;;;;;;;;;OAaG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB,CAAA;AAID;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,iBAAiB,IAG5B,QAAQ,WAAW,KAAG,OAAO,CAAC,IAAI,CAAC,CA4CjE"}
@@ -51,8 +51,6 @@ export function createEffectHandler(host) {
51
51
  return handleReconnectSchedule(host, effect);
52
52
  case 'AgentAttentionFlashTimeout':
53
53
  return handleAttentionFlashTimeout(host, effect);
54
- case 'AgentChatSendInput':
55
- return handleChatSendInput(host, effect);
56
54
  }
57
55
  };
58
56
  }
@@ -75,30 +73,6 @@ async function handleAttentionFlashTimeout(host, effect) {
75
73
  await new Promise((resolve) => setTimeout(resolve, effect.delayMs));
76
74
  host.send(host.wrapAgentAttention({ type: 'Clear', entryId: effect.entryId }));
77
75
  }
78
- async function handleChatSendInput(host, effect) {
79
- const wrapper = host.wrapAgentChat;
80
- // Frame send: best-effort. `submitUserInput` swallows its own
81
- // throws (it's just `ws.send(JSON.stringify(...))` × 2), but a
82
- // missing client (pre-open or post-close) silently no-ops the send
83
- // and we just hit the SubmitComplete fall-through. The user's
84
- // text was already cleared from `pendingInput` by the reducer; if
85
- // the send fails, the host can render a session-status indicator
86
- // separately (the `agentConnect.status` field is the canonical
87
- // source of "are we online").
88
- const client = host.getWsClient?.() ?? null;
89
- if (client) {
90
- try {
91
- client.submitUserInput(effect.text, effect.at);
92
- }
93
- catch {
94
- // best-effort — same contract as the frame-send path inside
95
- // submitUserInput itself
96
- }
97
- }
98
- if (wrapper) {
99
- host.send(wrapper({ type: 'SubmitComplete' }));
100
- }
101
- }
102
76
  // ── HTTP-bound handlers ─────────────────────────────────────────────
103
77
  async function handleMintRequest(host, effect, doFetch) {
104
78
  // Derive a default `mintUrl` from `agentBasePath` so consumers can
@@ -1 +1 @@
1
- {"version":3,"file":"effect-handler.js","sourceRoot":"","sources":["../../src/client/effect-handler.ts"],"names":[],"mappings":"AA4EA;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAuB;IACzD,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;IAEpD,OAAO,KAAK,UAAU,MAAM,CAAC,MAAmB;QAC9C,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;YACpB,KAAK,kBAAkB;gBACrB,OAAO,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,CAAA;YACjD,KAAK,aAAa;gBAChB,OAAO,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;YACnC,KAAK,cAAc;gBACjB,OAAO,aAAa,CAAC,IAAI,CAAC,CAAA;YAC5B,KAAK,kBAAkB;gBACrB,OAAO,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,CAAA;YACjD,KAAK,kBAAkB;gBACrB,OAAO,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,CAAA;YACjD,KAAK,aAAa;gBAChB,OAAO,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,CAAA;YAC5C,KAAK,mBAAmB;gBACtB,OAAO,kBAAkB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;YAC1C,KAAK,iBAAiB;gBACpB,OAAO,gBAAgB,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;YACvC,KAAK,qBAAqB;gBACxB,OAAO,oBAAoB,CAAC,MAAM,CAAC,CAAA;YACrC,KAAK,qBAAqB;gBACxB,wDAAwD;gBACxD,yDAAyD;gBACzD,mDAAmD;gBACnD,2BAA2B;gBAC3B,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;oBACxB,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC;wBACxB,KAAK,EAAE,MAAM,CAAC,KAAK;wBACnB,GAAG,EAAE,MAAM,CAAC,GAAG;wBACf,MAAM,EAAE,MAAM,CAAC,MAAM;wBACrB,KAAK,EAAE,MAAM,CAAC,KAAK;wBACnB,SAAS,EAAE,MAAM,CAAC,SAAS;qBAC5B,CAAC,CAAA;gBACJ,CAAC;gBACD,OAAM;YACR,KAAK,mBAAmB;gBACtB,IAAI,IAAI,CAAC,cAAc;oBAAE,IAAI,CAAC,cAAc,CAAC,KAAK,EAAE,CAAA;gBACpD,OAAM;YACR,KAAK,wBAAwB;gBAC3B,OAAO,uBAAuB,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;YAC9C,KAAK,4BAA4B;gBAC/B,OAAO,2BAA2B,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;YAClD,KAAK,oBAAoB;gBACvB,OAAO,mBAAmB,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;QAC5C,CAAC;IACH,CAAC,CAAA;AACH,CAAC;AAED,KAAK,UAAU,uBAAuB,CACpC,IAAuB,EACvB,MAAgE;IAEhE,iEAAiE;IACjE,kEAAkE;IAClE,iEAAiE;IACjE,6DAA6D;IAC7D,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,CAAA;IACzE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,EAAE,IAAI,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,CAAA;AAC3F,CAAC;AAED,KAAK,UAAU,2BAA2B,CACxC,IAAuB,EACvB,MAAoE;IAEpE,mEAAmE;IACnE,kEAAkE;IAClE,mEAAmE;IACnE,oEAAoE;IACpE,uDAAuD;IACvD,IAAI,CAAC,IAAI,CAAC,kBAAkB;QAAE,OAAM,CAAC,yCAAyC;IAC9E,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,CAAA;IACzE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,kBAAkB,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,CAAA;AAChF,CAAC;AAED,KAAK,UAAU,mBAAmB,CAChC,IAAuB,EACvB,MAA4D;IAE5D,MAAM,OAAO,GAAG,IAAI,CAAC,aAAa,CAAA;IAClC,8DAA8D;IAC9D,+DAA+D;IAC/D,mEAAmE;IACnE,8DAA8D;IAC9D,kEAAkE;IAClE,iEAAiE;IACjE,+DAA+D;IAC/D,8BAA8B;IAC9B,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,EAAE,EAAE,IAAI,IAAI,CAAA;IAC3C,IAAI,MAAM,EAAE,CAAC;QACX,IAAI,CAAC;YACH,MAAM,CAAC,eAAe,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC,CAAA;QAChD,CAAC;QAAC,MAAM,CAAC;YACP,4DAA4D;YAC5D,yBAAyB;QAC3B,CAAC;IACH,CAAC;IACD,IAAI,OAAO,EAAE,CAAC;QACZ,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,gBAAgB,EAAE,CAAC,CAAC,CAAA;IAChD,CAAC;AACH,CAAC;AAED,uEAAuE;AAEvE,KAAK,UAAU,iBAAiB,CAC9B,IAAuB,EACvB,MAA0D,EAC1D,OAAc;IAEd,mEAAmE;IACnE,iEAAiE;IACjE,mEAAmE;IACnE,6CAA6C;IAC7C,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC,CAAA;IAC5B,IAAI,CAAC,IAAI;QAAE,OAAM;IACjB,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,IAAI,GAAG,IAAI,OAAO,CAAA;IAChD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,CAAC,CAAA;QAC9E,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,GAAG,CAAC,CAAA;YAClC,IAAI,CAAC,IAAI,CACP,IAAI,CAAC,gBAAgB,CAAC;gBACpB,IAAI,EAAE,YAAY;gBAClB,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,GAAG,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE;aAC9C,CAAC,CACH,CAAA;YACD,OAAM;QACR,CAAC;QACD,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAiB,CAAA;QAC/C,IAAI,CAAC,IAAI,CACP,IAAI,CAAC,gBAAgB,CAAC;YACpB,IAAI,EAAE,eAAe;YACrB,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,GAAG,EAAE,IAAI,CAAC,GAAG;YACb,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,SAAS,EAAE,IAAI,CAAC,SAAS;SAC1B,CAAC,CACH,CAAA;IACH,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,IAAI,CAAC,IAAI,CACP,IAAI,CAAC,gBAAgB,CAAC;YACpB,IAAI,EAAE,YAAY;YAClB,KAAK,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE;SAC9C,CAAC,CACH,CAAA;IACH,CAAC;AACH,CAAC;AAED,KAAK,UAAU,iBAAiB,CAC9B,IAAuB,EACvB,MAA0D,EAC1D,OAAc;IAEd,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC,CAAA;IAC5B,IAAI,CAAC,IAAI;QAAE,OAAM;IACjB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,GAAG,IAAI,cAAc,EAAE;YAC/C,MAAM,EAAE,MAAM;YACd,WAAW,EAAE,SAAS;YACtB,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC;SAC5C,CAAC,CAAA;QACF,IAAI,CAAC,GAAG,CAAC,EAAE;YAAE,OAAM;QACnB,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAuB,CAAA;QACrD,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,EAAE,IAAI,EAAE,kBAAkB,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAA;IACzF,CAAC;IAAC,MAAM,CAAC;QACP,mCAAmC;IACrC,CAAC;AACH,CAAC;AAED,KAAK,UAAU,iBAAiB,CAC9B,IAAuB,EACvB,MAA0D,EAC1D,OAAc;IAEd,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC,CAAA;IAC5B,IAAI,CAAC,IAAI;QAAE,OAAM;IACjB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,GAAG,IAAI,eAAe,EAAE;YAChD,MAAM,EAAE,MAAM;YACd,WAAW,EAAE,SAAS;YACtB,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,CAAC;SAC1C,CAAC,CAAA;QACF,IAAI,CAAC,GAAG,CAAC,EAAE;YAAE,OAAM;QACnB,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAwB,CAAA;QACtD,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,CAAA;QACnC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC,CAAA;IACxD,CAAC;IAAC,MAAM,CAAC;QACP,WAAW;IACb,CAAC;AACH,CAAC;AAED,KAAK,UAAU,YAAY,CACzB,IAAuB,EACvB,MAAqD,EACrD,OAAc;IAEd,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC,CAAA;IAC5B,IAAI,CAAC,IAAI;QAAE,OAAM;IACjB,IAAI,CAAC;QACH,MAAM,OAAO,CAAC,GAAG,IAAI,SAAS,EAAE;YAC9B,MAAM,EAAE,MAAM;YACd,WAAW,EAAE,SAAS;YACtB,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,CAAC;SAC1C,CAAC,CAAA;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,WAAW;IACb,CAAC;AACH,CAAC;AAED,KAAK,UAAU,kBAAkB,CAAC,IAAuB,EAAE,OAAc;IACvE,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC,CAAA;IAC5B,IAAI,CAAC,IAAI;QAAE,OAAM;IACjB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,GAAG,IAAI,WAAW,EAAE;YAC5C,MAAM,EAAE,KAAK;YACb,WAAW,EAAE,SAAS;SACvB,CAAC,CAAA;QACF,IAAI,CAAC,GAAG,CAAC,EAAE;YAAE,OAAM;QACnB,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAqB,CAAA;QACnD,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,EAAE,IAAI,EAAE,gBAAgB,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAA;IACvF,CAAC;IAAC,MAAM,CAAC;QACP,WAAW;IACb,CAAC;AACH,CAAC;AAED,uEAAuE;AAEvE,SAAS,YAAY,CACnB,IAAuB,EACvB,MAAqD;IAErD,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,CAAA;AACzC,CAAC;AAED,SAAS,aAAa,CAAC,IAAuB;IAC5C,IAAI,CAAC,OAAO,EAAE,CAAA;AAChB,CAAC;AAED,uEAAuE;AAEvE,SAAS,gBAAgB,CACvB,IAAuB,EACvB,MAAyD;IAEzD,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;AAC9B,CAAC;AAED,KAAK,UAAU,oBAAoB,CACjC,MAA6D;IAE7D,kEAAkE;IAClE,oEAAoE;IACpE,4DAA4D;IAC5D,IAAI,OAAO,SAAS,KAAK,WAAW,IAAI,CAAC,CAAC,WAAW,IAAI,SAAS,CAAC;QAAE,OAAM;IAC3E,IAAI,CAAC;QACH,MAAM,SAAS,CAAC,SAAS,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IAClD,CAAC;IAAC,MAAM,CAAC;QACP,iEAAiE;IACnE,CAAC;AACH,CAAC;AAED,uEAAuE;AAEvE,KAAK,UAAU,QAAQ,CAAC,GAAa;IACnC,IAAI,CAAC;QACH,OAAO,MAAM,GAAG,CAAC,IAAI,EAAE,CAAA;IACzB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAA;IACX,CAAC;AACH,CAAC;AAED,SAAS,YAAY;IACnB,gEAAgE;IAChE,+DAA+D;IAC/D,wCAAwC;IACxC,IAAI,OAAO,QAAQ,KAAK,WAAW;QAAE,OAAO,QAAQ,CAAC,MAAM,CAAA;IAC3D,OAAO,IAAI,CAAA;AACb,CAAC;AAED,MAAM,eAAe,GAAG,eAAe,CAAA;AAEvC;;;;;;GAMG;AACH,SAAS,SAAS,CAAC,IAAuB;IACxC,MAAM,GAAG,GAAG,IAAI,CAAC,aAAa,IAAI,QAAQ,CAAA;IAC1C,MAAM,OAAO,GAAG,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAA;IAC1D,IAAI,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,OAAO,CAAA;IACjD,MAAM,MAAM,GAAG,YAAY,EAAE,CAAA;IAC7B,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAA;IACxB,OAAO,GAAG,MAAM,GAAG,OAAO,EAAE,CAAA;AAC9B,CAAC","sourcesContent":["import type { AgentEffect } from './effects.js'\nimport type {\n MintResponse,\n ResumeListResponse,\n ResumeClaimResponse,\n SessionsResponse,\n} from '../protocol.js'\nimport type { AgentSessionStorage } from './factory.js'\n\nexport type EffectHandlerHost = {\n send(msg: unknown): void // root app send; wraps agent sub-msgs into the app Msg envelope\n /** Wraps an agentConnect msg into an app-Msg. */\n wrapAgentConnect(m: unknown): unknown\n /**\n * Wraps an agentAttention msg into an app-Msg. Optional; when\n * undefined, the `AgentAttentionFlashTimeout` effect no-ops on\n * fire and the attention spotlight stays set until the next\n * dispatch replaces it. Hosts that wire the attention slice\n * provide this; hosts that don't can leave it unset.\n */\n wrapAgentAttention?(m: unknown): unknown\n /**\n * Wraps an agentChat msg into an app-Msg. Required for hosts\n * wiring the chat composer — the `AgentChatSendInput` handler\n * dispatches `SubmitComplete` back into the slice via this wrapper\n * after the frame send completes (success or failure), so the\n * input field re-enables. Hosts that don't render chat leave it\n * unset and the chat-send effect no-ops (the agentChat slice in\n * that case has no live presence to begin with).\n */\n wrapAgentChat?(m: unknown): unknown\n /**\n * Active WS client (if any) — set lazily by the factory after\n * `openWs` resolves a connection. The chat-send handler reads\n * `submitUserInput` from here. Marked optional because the\n * handler sits across the connection lifecycle: pre-WS / post-close\n * `submitUserInput` is null and the handler degrades gracefully\n * (still dispatches SubmitComplete so the UI re-enables).\n */\n getWsClient?: () => { submitUserInput(text: string, at?: number): void } | null\n /** Called for AgentForwardMsg — the payload is re-dispatched via send. */\n forward(payload: unknown): void\n /** fetch for HTTP effects; override in tests. */\n fetch?: typeof fetch\n /** Called before opening WS / on WS lifecycle events. */\n openWs(token: string, wsUrl: string): void\n closeWs(): void\n /**\n * Optional storage adapter. When set, `AgentSessionPersist` writes\n * to it and `AgentSessionClear` clears it; the host doesn't need\n * to handle these effects itself. When `null` or `undefined`, the\n * effects no-op here and host code (if any) handles them in the\n * outer effect router. The factory passes\n * `defaultSessionStorage()` by default, so the framework is\n * refresh-survival-ready out of the box.\n */\n sessionStorage?: AgentSessionStorage | null\n /**\n * Base path for agent HTTP endpoints. Default: `'/agent'` (matches\n * the canonical paths in `@llui/vite-plugin`'s dev middleware and\n * `@llui/agent/server/http/router.ts`).\n *\n * Override when the consumer ships `@cloudflare/vite-plugin` in\n * dev — that plugin routes every non-`/cdn-cgi/*` path to the\n * worker, shadowing canonical `/agent/*` URLs. The vite-plugin\n * registers a parallel handler at `/cdn-cgi/agent/*`; pass\n * `agentBasePath: '/cdn-cgi/agent'` here so the client hits that.\n *\n * Production deployments without cloudflare-vite leave this\n * unset; the agent server's router serves the canonical paths.\n */\n agentBasePath?: string\n}\n\ntype Fetch = typeof fetch\n\n/**\n * Top-level dispatcher. The switch is intentionally thin — each\n * `case` delegates to a per-effect function below. Splitting was\n * motivated by the previous 150-line monolith mixing HTTP, WS, and\n * browser-only side effects in one switch; per-effect handlers are\n * directly unit-testable and the dispatcher reads as a flat catalogue\n * of supported effect types.\n */\nexport function createEffectHandler(host: EffectHandlerHost) {\n const doFetch = host.fetch ?? fetch.bind(globalThis)\n\n return async function handle(effect: AgentEffect): Promise<void> {\n switch (effect.type) {\n case 'AgentMintRequest':\n return handleMintRequest(host, effect, doFetch)\n case 'AgentOpenWS':\n return handleOpenWs(host, effect)\n case 'AgentCloseWS':\n return handleCloseWs(host)\n case 'AgentResumeCheck':\n return handleResumeCheck(host, effect, doFetch)\n case 'AgentResumeClaim':\n return handleResumeClaim(host, effect, doFetch)\n case 'AgentRevoke':\n return handleRevoke(host, effect, doFetch)\n case 'AgentSessionsList':\n return handleSessionsList(host, doFetch)\n case 'AgentForwardMsg':\n return handleForwardMsg(host, effect)\n case 'AgentClipboardWrite':\n return handleClipboardWrite(effect)\n case 'AgentSessionPersist':\n // Framework-owned when a storage adapter is configured;\n // otherwise no-op and let the host's outer effect router\n // handle it (the legacy contract). See factory.ts'\n // `sessionStorage` option.\n if (host.sessionStorage) {\n host.sessionStorage.write({\n token: effect.token,\n tid: effect.tid,\n lapUrl: effect.lapUrl,\n wsUrl: effect.wsUrl,\n expiresAt: effect.expiresAt,\n })\n }\n return\n case 'AgentSessionClear':\n if (host.sessionStorage) host.sessionStorage.clear()\n return\n case 'AgentReconnectSchedule':\n return handleReconnectSchedule(host, effect)\n case 'AgentAttentionFlashTimeout':\n return handleAttentionFlashTimeout(host, effect)\n case 'AgentChatSendInput':\n return handleChatSendInput(host, effect)\n }\n }\n}\n\nasync function handleReconnectSchedule(\n host: EffectHandlerHost,\n effect: Extract<AgentEffect, { type: 'AgentReconnectSchedule' }>,\n): Promise<void> {\n // Single-shot timer. The reducer owns cancellation semantics via\n // the status guard in `ReconnectAttempt` — if the user dispatches\n // `Disconnect` while we're sleeping, the dispatched message hits\n // an `idle` reducer and is a no-op. No cancel handle needed.\n await new Promise<void>((resolve) => setTimeout(resolve, effect.delayMs))\n host.send(host.wrapAgentConnect({ type: 'ReconnectAttempt', elapsedMs: effect.delayMs }))\n}\n\nasync function handleAttentionFlashTimeout(\n host: EffectHandlerHost,\n effect: Extract<AgentEffect, { type: 'AgentAttentionFlashTimeout' }>,\n): Promise<void> {\n // Mirror of handleReconnectSchedule's race-tolerant pattern: timer\n // fires, the reducer's `Clear { entryId }` guard handles the case\n // where a newer dispatch already replaced the spotlight. No cancel\n // handle — keeping the timer-cancel logic out of the effect handler\n // simplifies it and reduces the surface area for bugs.\n if (!host.wrapAgentAttention) return // Host opted out — graceful degradation.\n await new Promise<void>((resolve) => setTimeout(resolve, effect.delayMs))\n host.send(host.wrapAgentAttention({ type: 'Clear', entryId: effect.entryId }))\n}\n\nasync function handleChatSendInput(\n host: EffectHandlerHost,\n effect: Extract<AgentEffect, { type: 'AgentChatSendInput' }>,\n): Promise<void> {\n const wrapper = host.wrapAgentChat\n // Frame send: best-effort. `submitUserInput` swallows its own\n // throws (it's just `ws.send(JSON.stringify(...))` × 2), but a\n // missing client (pre-open or post-close) silently no-ops the send\n // and we just hit the SubmitComplete fall-through. The user's\n // text was already cleared from `pendingInput` by the reducer; if\n // the send fails, the host can render a session-status indicator\n // separately (the `agentConnect.status` field is the canonical\n // source of \"are we online\").\n const client = host.getWsClient?.() ?? null\n if (client) {\n try {\n client.submitUserInput(effect.text, effect.at)\n } catch {\n // best-effort — same contract as the frame-send path inside\n // submitUserInput itself\n }\n }\n if (wrapper) {\n host.send(wrapper({ type: 'SubmitComplete' }))\n }\n}\n\n// ── HTTP-bound handlers ─────────────────────────────────────────────\n\nasync function handleMintRequest(\n host: EffectHandlerHost,\n effect: Extract<AgentEffect, { type: 'AgentMintRequest' }>,\n doFetch: Fetch,\n): Promise<void> {\n // Derive a default `mintUrl` from `agentBasePath` so consumers can\n // change the base path in one place (the effect handler) without\n // also having to keep the `agentConnect` opts in sync. `agentBase`\n // accepts both absolute paths and full URLs.\n const base = agentBase(host)\n if (!base) return\n const mintUrl = effect.mintUrl ?? `${base}/mint`\n try {\n const res = await doFetch(mintUrl, { method: 'POST', credentials: 'include' })\n if (!res.ok) {\n const detail = await safeText(res)\n host.send(\n host.wrapAgentConnect({\n type: 'MintFailed',\n error: { code: `http-${res.status}`, detail },\n }),\n )\n return\n }\n const body = (await res.json()) as MintResponse\n host.send(\n host.wrapAgentConnect({\n type: 'MintSucceeded',\n token: body.token,\n tid: body.tid,\n lapUrl: body.lapUrl,\n wsUrl: body.wsUrl,\n expiresAt: body.expiresAt,\n }),\n )\n } catch (e) {\n host.send(\n host.wrapAgentConnect({\n type: 'MintFailed',\n error: { code: 'network', detail: String(e) },\n }),\n )\n }\n}\n\nasync function handleResumeCheck(\n host: EffectHandlerHost,\n effect: Extract<AgentEffect, { type: 'AgentResumeCheck' }>,\n doFetch: Fetch,\n): Promise<void> {\n const base = agentBase(host)\n if (!base) return\n try {\n const res = await doFetch(`${base}/resume/list`, {\n method: 'POST',\n credentials: 'include',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ tids: effect.tids }),\n })\n if (!res.ok) return\n const body = (await res.json()) as ResumeListResponse\n host.send(host.wrapAgentConnect({ type: 'ResumeListLoaded', sessions: body.sessions }))\n } catch {\n /* quiet failure; user can retry */\n }\n}\n\nasync function handleResumeClaim(\n host: EffectHandlerHost,\n effect: Extract<AgentEffect, { type: 'AgentResumeClaim' }>,\n doFetch: Fetch,\n): Promise<void> {\n const base = agentBase(host)\n if (!base) return\n try {\n const res = await doFetch(`${base}/resume/claim`, {\n method: 'POST',\n credentials: 'include',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ tid: effect.tid }),\n })\n if (!res.ok) return\n const body = (await res.json()) as ResumeClaimResponse\n host.openWs(body.token, body.wsUrl)\n host.send(host.wrapAgentConnect({ type: 'WsOpened' }))\n } catch {\n /* quiet */\n }\n}\n\nasync function handleRevoke(\n host: EffectHandlerHost,\n effect: Extract<AgentEffect, { type: 'AgentRevoke' }>,\n doFetch: Fetch,\n): Promise<void> {\n const base = agentBase(host)\n if (!base) return\n try {\n await doFetch(`${base}/revoke`, {\n method: 'POST',\n credentials: 'include',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ tid: effect.tid }),\n })\n } catch {\n /* quiet */\n }\n}\n\nasync function handleSessionsList(host: EffectHandlerHost, doFetch: Fetch): Promise<void> {\n const base = agentBase(host)\n if (!base) return\n try {\n const res = await doFetch(`${base}/sessions`, {\n method: 'GET',\n credentials: 'include',\n })\n if (!res.ok) return\n const body = (await res.json()) as SessionsResponse\n host.send(host.wrapAgentConnect({ type: 'SessionsLoaded', sessions: body.sessions }))\n } catch {\n /* quiet */\n }\n}\n\n// ── WS-bound handlers ───────────────────────────────────────────────\n\nfunction handleOpenWs(\n host: EffectHandlerHost,\n effect: Extract<AgentEffect, { type: 'AgentOpenWS' }>,\n): void {\n host.openWs(effect.token, effect.wsUrl)\n}\n\nfunction handleCloseWs(host: EffectHandlerHost): void {\n host.closeWs()\n}\n\n// ── Local handlers (no network) ─────────────────────────────────────\n\nfunction handleForwardMsg(\n host: EffectHandlerHost,\n effect: Extract<AgentEffect, { type: 'AgentForwardMsg' }>,\n): void {\n host.forward(effect.payload)\n}\n\nasync function handleClipboardWrite(\n effect: Extract<AgentEffect, { type: 'AgentClipboardWrite' }>,\n): Promise<void> {\n // Browser-only — `navigator.clipboard` is undefined in Node/jsdom\n // test environments. Silently no-op rather than throw, matching the\n // rest of the agent effect handlers' failure-quiet pattern.\n if (typeof navigator === 'undefined' || !('clipboard' in navigator)) return\n try {\n await navigator.clipboard.writeText(effect.text)\n } catch {\n /* quiet — clipboard permission denied or document not focused */\n }\n}\n\n// ── Helpers ─────────────────────────────────────────────────────────\n\nasync function safeText(res: Response): Promise<string> {\n try {\n return await res.text()\n } catch {\n return ''\n }\n}\n\nfunction deriveOrigin(): string | null {\n // When running in the browser, `location.origin` is correct for\n // same-origin agent endpoints. Tests override `host.fetch` and\n // short-circuit before this is reached.\n if (typeof location !== 'undefined') return location.origin\n return null\n}\n\nconst ABSOLUTE_URL_RE = /^https?:\\/\\//i\n\n/**\n * Resolve the absolute base URL for agent HTTP endpoints. Accepts both\n * absolute paths (`/agent`) and full URLs (`https://api.example/agent`)\n * — the absolute URL form lets consumers point at a cross-origin agent\n * server without pre-composing every endpoint URL. Trailing slashes\n * are normalized so callers can always concatenate `${base}/mint`.\n */\nfunction agentBase(host: EffectHandlerHost): string | null {\n const raw = host.agentBasePath ?? '/agent'\n const trimmed = raw.endsWith('/') ? raw.slice(0, -1) : raw\n if (ABSOLUTE_URL_RE.test(trimmed)) return trimmed\n const origin = deriveOrigin()\n if (!origin) return null\n return `${origin}${trimmed}`\n}\n"]}
1
+ {"version":3,"file":"effect-handler.js","sourceRoot":"","sources":["../../src/client/effect-handler.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentEffect } from './effects.js'\nimport type {\n MintResponse,\n ResumeListResponse,\n ResumeClaimResponse,\n SessionsResponse,\n} from '../protocol.js'\nimport type { AgentSessionStorage } from './factory.js'\n\nexport type EffectHandlerHost = {\n send(msg: unknown): void // root app send; wraps agent sub-msgs into the app Msg envelope\n /** Wraps an agentConnect msg into an app-Msg. */\n wrapAgentConnect(m: unknown): unknown\n /**\n * Wraps an agentAttention msg into an app-Msg. Optional; when\n * undefined, the `AgentAttentionFlashTimeout` effect no-ops on\n * fire and the attention spotlight stays set until the next\n * dispatch replaces it. Hosts that wire the attention slice\n * provide this; hosts that don't can leave it unset.\n */\n wrapAgentAttention?(m: unknown): unknown\n /** Called for AgentForwardMsg — the payload is re-dispatched via send. */\n forward(payload: unknown): void\n /** fetch for HTTP effects; override in tests. */\n fetch?: typeof fetch\n /** Called before opening WS / on WS lifecycle events. */\n openWs(token: string, wsUrl: string): void\n closeWs(): void\n /**\n * Optional storage adapter. When set, `AgentSessionPersist` writes\n * to it and `AgentSessionClear` clears it; the host doesn't need\n * to handle these effects itself. When `null` or `undefined`, the\n * effects no-op here and host code (if any) handles them in the\n * outer effect router. The factory passes\n * `defaultSessionStorage()` by default, so the framework is\n * refresh-survival-ready out of the box.\n */\n sessionStorage?: AgentSessionStorage | null\n /**\n * Base path for agent HTTP endpoints. Default: `'/agent'` (matches\n * the canonical paths in `@llui/vite-plugin`'s dev middleware and\n * `@llui/agent/server/http/router.ts`).\n *\n * Override when the consumer ships `@cloudflare/vite-plugin` in\n * dev — that plugin routes every non-`/cdn-cgi/*` path to the\n * worker, shadowing canonical `/agent/*` URLs. The vite-plugin\n * registers a parallel handler at `/cdn-cgi/agent/*`; pass\n * `agentBasePath: '/cdn-cgi/agent'` here so the client hits that.\n *\n * Production deployments without cloudflare-vite leave this\n * unset; the agent server's router serves the canonical paths.\n */\n agentBasePath?: string\n}\n\ntype Fetch = typeof fetch\n\n/**\n * Top-level dispatcher. The switch is intentionally thin — each\n * `case` delegates to a per-effect function below. Splitting was\n * motivated by the previous 150-line monolith mixing HTTP, WS, and\n * browser-only side effects in one switch; per-effect handlers are\n * directly unit-testable and the dispatcher reads as a flat catalogue\n * of supported effect types.\n */\nexport function createEffectHandler(host: EffectHandlerHost) {\n const doFetch = host.fetch ?? fetch.bind(globalThis)\n\n return async function handle(effect: AgentEffect): Promise<void> {\n switch (effect.type) {\n case 'AgentMintRequest':\n return handleMintRequest(host, effect, doFetch)\n case 'AgentOpenWS':\n return handleOpenWs(host, effect)\n case 'AgentCloseWS':\n return handleCloseWs(host)\n case 'AgentResumeCheck':\n return handleResumeCheck(host, effect, doFetch)\n case 'AgentResumeClaim':\n return handleResumeClaim(host, effect, doFetch)\n case 'AgentRevoke':\n return handleRevoke(host, effect, doFetch)\n case 'AgentSessionsList':\n return handleSessionsList(host, doFetch)\n case 'AgentForwardMsg':\n return handleForwardMsg(host, effect)\n case 'AgentClipboardWrite':\n return handleClipboardWrite(effect)\n case 'AgentSessionPersist':\n // Framework-owned when a storage adapter is configured;\n // otherwise no-op and let the host's outer effect router\n // handle it (the legacy contract). See factory.ts'\n // `sessionStorage` option.\n if (host.sessionStorage) {\n host.sessionStorage.write({\n token: effect.token,\n tid: effect.tid,\n lapUrl: effect.lapUrl,\n wsUrl: effect.wsUrl,\n expiresAt: effect.expiresAt,\n })\n }\n return\n case 'AgentSessionClear':\n if (host.sessionStorage) host.sessionStorage.clear()\n return\n case 'AgentReconnectSchedule':\n return handleReconnectSchedule(host, effect)\n case 'AgentAttentionFlashTimeout':\n return handleAttentionFlashTimeout(host, effect)\n }\n }\n}\n\nasync function handleReconnectSchedule(\n host: EffectHandlerHost,\n effect: Extract<AgentEffect, { type: 'AgentReconnectSchedule' }>,\n): Promise<void> {\n // Single-shot timer. The reducer owns cancellation semantics via\n // the status guard in `ReconnectAttempt` — if the user dispatches\n // `Disconnect` while we're sleeping, the dispatched message hits\n // an `idle` reducer and is a no-op. No cancel handle needed.\n await new Promise<void>((resolve) => setTimeout(resolve, effect.delayMs))\n host.send(host.wrapAgentConnect({ type: 'ReconnectAttempt', elapsedMs: effect.delayMs }))\n}\n\nasync function handleAttentionFlashTimeout(\n host: EffectHandlerHost,\n effect: Extract<AgentEffect, { type: 'AgentAttentionFlashTimeout' }>,\n): Promise<void> {\n // Mirror of handleReconnectSchedule's race-tolerant pattern: timer\n // fires, the reducer's `Clear { entryId }` guard handles the case\n // where a newer dispatch already replaced the spotlight. No cancel\n // handle — keeping the timer-cancel logic out of the effect handler\n // simplifies it and reduces the surface area for bugs.\n if (!host.wrapAgentAttention) return // Host opted out — graceful degradation.\n await new Promise<void>((resolve) => setTimeout(resolve, effect.delayMs))\n host.send(host.wrapAgentAttention({ type: 'Clear', entryId: effect.entryId }))\n}\n\n// ── HTTP-bound handlers ─────────────────────────────────────────────\n\nasync function handleMintRequest(\n host: EffectHandlerHost,\n effect: Extract<AgentEffect, { type: 'AgentMintRequest' }>,\n doFetch: Fetch,\n): Promise<void> {\n // Derive a default `mintUrl` from `agentBasePath` so consumers can\n // change the base path in one place (the effect handler) without\n // also having to keep the `agentConnect` opts in sync. `agentBase`\n // accepts both absolute paths and full URLs.\n const base = agentBase(host)\n if (!base) return\n const mintUrl = effect.mintUrl ?? `${base}/mint`\n try {\n const res = await doFetch(mintUrl, { method: 'POST', credentials: 'include' })\n if (!res.ok) {\n const detail = await safeText(res)\n host.send(\n host.wrapAgentConnect({\n type: 'MintFailed',\n error: { code: `http-${res.status}`, detail },\n }),\n )\n return\n }\n const body = (await res.json()) as MintResponse\n host.send(\n host.wrapAgentConnect({\n type: 'MintSucceeded',\n token: body.token,\n tid: body.tid,\n lapUrl: body.lapUrl,\n wsUrl: body.wsUrl,\n expiresAt: body.expiresAt,\n }),\n )\n } catch (e) {\n host.send(\n host.wrapAgentConnect({\n type: 'MintFailed',\n error: { code: 'network', detail: String(e) },\n }),\n )\n }\n}\n\nasync function handleResumeCheck(\n host: EffectHandlerHost,\n effect: Extract<AgentEffect, { type: 'AgentResumeCheck' }>,\n doFetch: Fetch,\n): Promise<void> {\n const base = agentBase(host)\n if (!base) return\n try {\n const res = await doFetch(`${base}/resume/list`, {\n method: 'POST',\n credentials: 'include',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ tids: effect.tids }),\n })\n if (!res.ok) return\n const body = (await res.json()) as ResumeListResponse\n host.send(host.wrapAgentConnect({ type: 'ResumeListLoaded', sessions: body.sessions }))\n } catch {\n /* quiet failure; user can retry */\n }\n}\n\nasync function handleResumeClaim(\n host: EffectHandlerHost,\n effect: Extract<AgentEffect, { type: 'AgentResumeClaim' }>,\n doFetch: Fetch,\n): Promise<void> {\n const base = agentBase(host)\n if (!base) return\n try {\n const res = await doFetch(`${base}/resume/claim`, {\n method: 'POST',\n credentials: 'include',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ tid: effect.tid }),\n })\n if (!res.ok) return\n const body = (await res.json()) as ResumeClaimResponse\n host.openWs(body.token, body.wsUrl)\n host.send(host.wrapAgentConnect({ type: 'WsOpened' }))\n } catch {\n /* quiet */\n }\n}\n\nasync function handleRevoke(\n host: EffectHandlerHost,\n effect: Extract<AgentEffect, { type: 'AgentRevoke' }>,\n doFetch: Fetch,\n): Promise<void> {\n const base = agentBase(host)\n if (!base) return\n try {\n await doFetch(`${base}/revoke`, {\n method: 'POST',\n credentials: 'include',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ tid: effect.tid }),\n })\n } catch {\n /* quiet */\n }\n}\n\nasync function handleSessionsList(host: EffectHandlerHost, doFetch: Fetch): Promise<void> {\n const base = agentBase(host)\n if (!base) return\n try {\n const res = await doFetch(`${base}/sessions`, {\n method: 'GET',\n credentials: 'include',\n })\n if (!res.ok) return\n const body = (await res.json()) as SessionsResponse\n host.send(host.wrapAgentConnect({ type: 'SessionsLoaded', sessions: body.sessions }))\n } catch {\n /* quiet */\n }\n}\n\n// ── WS-bound handlers ───────────────────────────────────────────────\n\nfunction handleOpenWs(\n host: EffectHandlerHost,\n effect: Extract<AgentEffect, { type: 'AgentOpenWS' }>,\n): void {\n host.openWs(effect.token, effect.wsUrl)\n}\n\nfunction handleCloseWs(host: EffectHandlerHost): void {\n host.closeWs()\n}\n\n// ── Local handlers (no network) ─────────────────────────────────────\n\nfunction handleForwardMsg(\n host: EffectHandlerHost,\n effect: Extract<AgentEffect, { type: 'AgentForwardMsg' }>,\n): void {\n host.forward(effect.payload)\n}\n\nasync function handleClipboardWrite(\n effect: Extract<AgentEffect, { type: 'AgentClipboardWrite' }>,\n): Promise<void> {\n // Browser-only — `navigator.clipboard` is undefined in Node/jsdom\n // test environments. Silently no-op rather than throw, matching the\n // rest of the agent effect handlers' failure-quiet pattern.\n if (typeof navigator === 'undefined' || !('clipboard' in navigator)) return\n try {\n await navigator.clipboard.writeText(effect.text)\n } catch {\n /* quiet — clipboard permission denied or document not focused */\n }\n}\n\n// ── Helpers ─────────────────────────────────────────────────────────\n\nasync function safeText(res: Response): Promise<string> {\n try {\n return await res.text()\n } catch {\n return ''\n }\n}\n\nfunction deriveOrigin(): string | null {\n // When running in the browser, `location.origin` is correct for\n // same-origin agent endpoints. Tests override `host.fetch` and\n // short-circuit before this is reached.\n if (typeof location !== 'undefined') return location.origin\n return null\n}\n\nconst ABSOLUTE_URL_RE = /^https?:\\/\\//i\n\n/**\n * Resolve the absolute base URL for agent HTTP endpoints. Accepts both\n * absolute paths (`/agent`) and full URLs (`https://api.example/agent`)\n * — the absolute URL form lets consumers point at a cross-origin agent\n * server without pre-composing every endpoint URL. Trailing slashes\n * are normalized so callers can always concatenate `${base}/mint`.\n */\nfunction agentBase(host: EffectHandlerHost): string | null {\n const raw = host.agentBasePath ?? '/agent'\n const trimmed = raw.endsWith('/') ? raw.slice(0, -1) : raw\n if (ABSOLUTE_URL_RE.test(trimmed)) return trimmed\n const origin = deriveOrigin()\n if (!origin) return null\n return `${origin}${trimmed}`\n}\n"]}