@guuey/agent-client 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/sse.d.ts CHANGED
@@ -3,6 +3,7 @@
3
3
  * platform dependencies — unit-tested in isolation (`sse.test.ts`) and shared
4
4
  * verbatim across web (Studio) and React-Native (Portal).
5
5
  */
6
+ import type { ProfileConsentRequest, ProfileLinkRequest } from "./types";
6
7
  export interface ParsedSseEvent {
7
8
  event: string;
8
9
  data: unknown;
@@ -40,4 +41,22 @@ export declare function reduceAssistantText(current: string, data: unknown): str
40
41
  export declare function extractAssistantText(data: unknown): string;
41
42
  /** Read a string field off an SSE `data` object, or undefined. */
42
43
  export declare function stringField(data: unknown, key: string): string | undefined;
44
+ /**
45
+ * Parse a `profile-consent-needed` SSE payload into a typed
46
+ * {@link ProfileConsentRequest}, or `null` if it does not conform. `appId`
47
+ * must be a non-empty string and `requested` exactly `"read"` or
48
+ * `"read-write"`; extra keys are tolerated (ignored). Returns a fresh
49
+ * normalized object so callers get exactly the typed shape, never the raw
50
+ * wire payload with unknown extras.
51
+ */
52
+ export declare function parseConsentRequest(data: unknown): ProfileConsentRequest | null;
53
+ /**
54
+ * Parse a `profile-link-needed` SSE payload into a typed
55
+ * {@link ProfileLinkRequest}, or `null` if it does not conform. Same shape +
56
+ * validation as {@link parseConsentRequest} (`appId` non-empty string,
57
+ * `requested` exactly `"read"` or `"read-write"`, extra keys tolerated) — the
58
+ * pod emits an identically-shaped payload for both events; only the event
59
+ * NAME (and what it means to the consumer) differs.
60
+ */
61
+ export declare function parseLinkRequest(data: unknown): ProfileLinkRequest | null;
43
62
  //# sourceMappingURL=sse.d.ts.map
package/dist/sse.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"sse.d.ts","sourceRoot":"","sources":["../src/sse.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,OAAO,CAAC;CACf;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,cAAc,EAAE,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAqBzF;AAUD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,MAAM,CAM1E;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAgC1D;AAED,kEAAkE;AAClE,wBAAgB,WAAW,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAI1E"}
1
+ {"version":3,"file":"sse.d.ts","sourceRoot":"","sources":["../src/sse.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAEzE,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,OAAO,CAAC;CACf;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,cAAc,EAAE,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAqBzF;AAUD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,MAAM,CAM1E;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAgC1D;AAED,kEAAkE;AAClE,wBAAgB,WAAW,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAI1E;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,OAAO,GAAG,qBAAqB,GAAG,IAAI,CAO/E;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,OAAO,GAAG,kBAAkB,GAAG,IAAI,CAOzE"}
package/dist/sse.js CHANGED
@@ -105,3 +105,41 @@ export function stringField(data, key) {
105
105
  const v = data[key];
106
106
  return typeof v === "string" ? v : undefined;
107
107
  }
108
+ /**
109
+ * Parse a `profile-consent-needed` SSE payload into a typed
110
+ * {@link ProfileConsentRequest}, or `null` if it does not conform. `appId`
111
+ * must be a non-empty string and `requested` exactly `"read"` or
112
+ * `"read-write"`; extra keys are tolerated (ignored). Returns a fresh
113
+ * normalized object so callers get exactly the typed shape, never the raw
114
+ * wire payload with unknown extras.
115
+ */
116
+ export function parseConsentRequest(data) {
117
+ if (typeof data !== "object" || data === null || Array.isArray(data))
118
+ return null;
119
+ const appId = data.appId;
120
+ const requested = data.requested;
121
+ if (typeof appId !== "string" || appId.length === 0)
122
+ return null;
123
+ if (requested !== "read" && requested !== "read-write")
124
+ return null;
125
+ return { appId, requested };
126
+ }
127
+ /**
128
+ * Parse a `profile-link-needed` SSE payload into a typed
129
+ * {@link ProfileLinkRequest}, or `null` if it does not conform. Same shape +
130
+ * validation as {@link parseConsentRequest} (`appId` non-empty string,
131
+ * `requested` exactly `"read"` or `"read-write"`, extra keys tolerated) — the
132
+ * pod emits an identically-shaped payload for both events; only the event
133
+ * NAME (and what it means to the consumer) differs.
134
+ */
135
+ export function parseLinkRequest(data) {
136
+ if (typeof data !== "object" || data === null || Array.isArray(data))
137
+ return null;
138
+ const appId = data.appId;
139
+ const requested = data.requested;
140
+ if (typeof appId !== "string" || appId.length === 0)
141
+ return null;
142
+ if (requested !== "read" && requested !== "read-write")
143
+ return null;
144
+ return { appId, requested };
145
+ }
package/dist/types.d.ts CHANGED
@@ -6,7 +6,10 @@
6
6
  * (which also carries anonymous identity) — are INJECTED by the consumer via
7
7
  * {@link AgentInvokeAdapters}. Web (Studio) passes localStorage / crypto /
8
8
  * credentialed-cookie fetch; React-Native (Portal) passes AsyncStorage /
9
- * getRandomValues / header-identity SSE fetch. This mirrors the ggui
9
+ * getRandomValues / header-identity SSE fetch. Anonymous identity is per-host,
10
+ * not per-platform: a web host with no usable cookie jar (an embedded
11
+ * third-party iframe) carries its own guest secret in a header too — see
12
+ * `createWebAdapters`'s `getGuestSecret`. This mirrors the ggui
10
13
  * `MessageStorageAdapter` injection pattern.
11
14
  */
12
15
  import type { AgReduceResult, JsonValue } from "@silverprotocol/core";
@@ -15,6 +18,33 @@ export interface AgentMessage {
15
18
  role: "user" | "assistant";
16
19
  text: string;
17
20
  }
21
+ /**
22
+ * A cross-app profile consent request surfaced mid-stream by the pod's
23
+ * `profile-consent-needed` SSE event (nocode-runtime T6). Emitted when the
24
+ * agent declares a profile intent the caller has NOT yet granted for this app,
25
+ * so the consumer UI can prompt the user to authorize `read` or `read-write`
26
+ * access. `requested` mirrors the pod's `ProfileAccess` posture verbatim; the
27
+ * literal union is inlined rather than imported to keep this client SDK free of
28
+ * any backend-package dependency.
29
+ */
30
+ export interface ProfileConsentRequest {
31
+ appId: string;
32
+ requested: "read" | "read-write";
33
+ }
34
+ /**
35
+ * A cross-app profile LINK invite surfaced mid-stream by the pod's
36
+ * `profile-link-needed` SSE event (nocode-runtime linkcoh T3). Emitted when an
37
+ * unlinked byo end-user's declared profile posture booted, inviting them to
38
+ * link their guuey account (via the named `/link` ceremony) so they earn the
39
+ * guuey-wide cross-app profile. `requested` mirrors the pod's `ProfileAccess`
40
+ * posture verbatim (the builder's declared access, not a live ask) — the
41
+ * literal union is inlined rather than imported, same rationale as
42
+ * {@link ProfileConsentRequest}.
43
+ */
44
+ export interface ProfileLinkRequest {
45
+ appId: string;
46
+ requested: "read" | "read-write";
47
+ }
18
48
  /**
19
49
  * A persisted generative-UI card rehydrated from thread history — the verbatim
20
50
  * `AgArtifact` snapshot the pod stored on a `kind: "card"` row, tagged with its
@@ -51,7 +81,7 @@ export interface InvokeRequest {
51
81
  * Opens an invoke request and yields decoded UTF-8 text chunks of the SSE
52
82
  * stream (the hook accumulates + parses frames itself). MUST throw on a
53
83
  * non-OK response or network failure. Owns headers + identity entirely, so
54
- * the hook never sees cookies or bearer tokens.
84
+ * the hook never sees cookies, bearer tokens, or guest secrets.
55
85
  */
56
86
  export type InvokeTransport = (req: InvokeRequest) => AsyncIterable<string>;
57
87
  /**
@@ -102,10 +132,35 @@ export interface UseAgentInvokeOptions {
102
132
  */
103
133
  preserveBlocks?: boolean;
104
134
  }
135
+ /**
136
+ * The per-turn lifecycle (guuey#91), derived ENTIRELY from frames the pod
137
+ * already emits — no protocol addition:
138
+ *
139
+ * - `ready` — no turn in flight (initial, after `done`/failure/abort).
140
+ * - `connecting` — `send()` fired, no `session` frame yet. With
141
+ * scale-to-zero pods this phase can span a cold start, so hosts typically
142
+ * swap to "waking your agent" copy after a few seconds.
143
+ * - `thinking` — the pod is awake and the turn is running, but no text is
144
+ * flowing and no tool is announced (between `session` and the first
145
+ * content, and between a `tool.done` and whatever follows it).
146
+ * - `using-tool` — a `tool.start` frame arrived; {@link UseAgentInvokeReturn.activeTool}
147
+ * carries the wire tool name until the matching `tool.done`.
148
+ * - `responding` — assistant text is arriving (`text.start`/`text.delta`
149
+ * silver frames, or bypass text/assistant frames).
150
+ *
151
+ * Failure keeps its own channel ({@link UseAgentInvokeReturn.error}) — there
152
+ * is deliberately no `error` status: after any terminal outcome the status
153
+ * returns to `ready` so the composer re-enables.
154
+ */
155
+ export type AgentInvokeStatus = "ready" | "connecting" | "thinking" | "using-tool" | "responding";
105
156
  export interface UseAgentInvokeReturn {
106
157
  messages: AgentMessage[];
107
158
  send: (input: string) => Promise<void>;
108
- isStreaming: boolean;
159
+ /** The per-turn lifecycle — see {@link AgentInvokeStatus}. Anything other
160
+ * than `ready` means a turn is in flight (the old `isStreaming === true`). */
161
+ status: AgentInvokeStatus;
162
+ /** The active tool's wire name while `status === 'using-tool'`, else null. */
163
+ activeTool: string | null;
109
164
  error: string | null;
110
165
  threadId: string | null;
111
166
  /** Abort the in-flight turn (the stream stops; partial text is kept). */
@@ -141,5 +196,29 @@ export interface UseAgentInvokeReturn {
141
196
  * `reset()` clears it back to `[]`.
142
197
  */
143
198
  historyCards: HistoryCard[];
199
+ /**
200
+ * The latest cross-app profile consent request the pod asked for on THIS
201
+ * conversation, or `null`. Set from a well-formed `profile-consent-needed`
202
+ * SSE event (see {@link ProfileConsentRequest}); malformed payloads are
203
+ * dropped and leave the field untouched. `reset()` and an app switch clear
204
+ * it back to `null`. Consumers that never render a consent prompt (e.g.
205
+ * Studio) simply ignore this field.
206
+ */
207
+ profileConsentRequest: ProfileConsentRequest | null;
208
+ /** Dismiss the pending {@link profileConsentRequest} (back to `null`). */
209
+ clearProfileConsentRequest: () => void;
210
+ /**
211
+ * The latest cross-app profile LINK invite the pod asked for on THIS
212
+ * conversation, or `null`. Set from a well-formed `profile-link-needed`
213
+ * SSE event (see {@link ProfileLinkRequest}); malformed payloads are
214
+ * dropped and leave the field untouched. `reset()` and an app switch clear
215
+ * it back to `null`. Consumers that never render a link prompt simply
216
+ * ignore this field. Distinct from {@link profileConsentRequest}: this one
217
+ * invites an UNLINKED byo user to link their account; consent asks an
218
+ * already-linked user to grant an app read/read-write access.
219
+ */
220
+ profileLinkRequest: ProfileLinkRequest | null;
221
+ /** Dismiss the pending {@link profileLinkRequest} (back to `null`). */
222
+ clearProfileLinkRequest: () => void;
144
223
  }
145
224
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAEtE,uDAAuD;AACvD,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,EAAE,MAAM,CAAC;IACX,YAAY,EAAE,SAAS,CAAC;CACzB;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC1D,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3D;AAED,0EAA0E;AAC1E,MAAM,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC;AAEtC,kDAAkD;AAClD,MAAM,WAAW,aAAa;IAC5B,iFAAiF;IACjF,GAAG,EAAE,MAAM,CAAC;IACZ,kEAAkE;IAClE,IAAI,EAAE,OAAO,CAAC;IACd,mCAAmC;IACnC,MAAM,EAAE,WAAW,CAAC;CACrB;AAED;;;;;GAKG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,GAAG,EAAE,aAAa,KAAK,aAAa,CAAC,MAAM,CAAC,CAAC;AAE5E;;;;;;;;GAQG;AACH,MAAM,MAAM,iBAAiB,GACzB;IAAE,QAAQ,EAAE,YAAY,EAAE,CAAC;IAAC,KAAK,CAAC,EAAE,WAAW,EAAE,CAAA;CAAE,GACnD;IAAE,IAAI,EAAE,IAAI,CAAA;CAAE,CAAC;AAEnB;;;;;GAKG;AACH,MAAM,WAAW,yBAAyB;IACxC,sFAAsF;IACtF,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;CACpD;AAED,mEAAmE;AACnE,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,aAAa,CAAC;IACvB,UAAU,EAAE,UAAU,CAAC;IACvB,SAAS,EAAE,eAAe,CAAC;IAC3B,yGAAyG;IACzG,OAAO,CAAC,EAAE,yBAAyB,CAAC;CACrC;AAED,MAAM,WAAW,qBAAqB;IACpC,6FAA6F;IAC7F,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,yDAAyD;IACzD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8DAA8D;IAC9D,QAAQ,EAAE,mBAAmB,CAAC;IAC9B;;;;;OAKG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,WAAW,EAAE,OAAO,CAAC;IACrB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,yEAAyE;IACzE,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB;;;;;;;;;;;;;;;;;;OAkBG;IACH,YAAY,EAAE,cAAc,GAAG,IAAI,CAAC;IACpC;;;;;;;;OAQG;IACH,YAAY,EAAE,WAAW,EAAE,CAAC;CAC7B"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js CHANGED
@@ -6,7 +6,10 @@
6
6
  * (which also carries anonymous identity) — are INJECTED by the consumer via
7
7
  * {@link AgentInvokeAdapters}. Web (Studio) passes localStorage / crypto /
8
8
  * credentialed-cookie fetch; React-Native (Portal) passes AsyncStorage /
9
- * getRandomValues / header-identity SSE fetch. This mirrors the ggui
9
+ * getRandomValues / header-identity SSE fetch. Anonymous identity is per-host,
10
+ * not per-platform: a web host with no usable cookie jar (an embedded
11
+ * third-party iframe) carries its own guest secret in a header too — see
12
+ * `createWebAdapters`'s `getGuestSecret`. This mirrors the ggui
10
13
  * `MessageStorageAdapter` injection pattern.
11
14
  */
12
15
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"useAgentInvoke.d.ts","sourceRoot":"","sources":["../src/useAgentInvoke.ts"],"names":[],"mappings":"AA2BA,OAAO,KAAK,EAEV,YAAY,EAEZ,iBAAiB,EACjB,qBAAqB,EACrB,oBAAoB,EACrB,MAAM,SAAS,CAAC;AAMjB,yEAAyE;AACzE,MAAM,MAAM,kBAAkB,GAC1B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,YAAY,EAAE,CAAA;CAAE,GAC1C;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAEtB;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,iBAAiB,EACzB,eAAe,EAAE,YAAY,EAAE,GAC9B,kBAAkB,CAIpB;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,qBAAqB,GAAG,oBAAoB,CAgPhF"}
1
+ {"version":3,"file":"useAgentInvoke.d.ts","sourceRoot":"","sources":["../src/useAgentInvoke.ts"],"names":[],"mappings":"AAiCA,OAAO,KAAK,EAGV,YAAY,EAEZ,iBAAiB,EAGjB,qBAAqB,EACrB,oBAAoB,EACrB,MAAM,SAAS,CAAC;AAMjB,yEAAyE;AACzE,MAAM,MAAM,kBAAkB,GAC1B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,YAAY,EAAE,CAAA;CAAE,GAC1C;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAEtB;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,iBAAiB,EACzB,eAAe,EAAE,YAAY,EAAE,GAC9B,kBAAkB,CAIpB;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,qBAAqB,GAAG,oBAAoB,CAqUhF"}
@@ -23,7 +23,7 @@
23
23
  */
24
24
  import { useCallback, useEffect, useRef, useState } from "react";
25
25
  import { Reducer } from "@silverprotocol/core";
26
- import { parseSseEvents, reduceAssistantText, stringField } from "./sse";
26
+ import { parseConsentRequest, parseLinkRequest, parseSseEvents, reduceAssistantText, stringField, } from "./sse";
27
27
  import { ingestMessageFrame } from "./blocks";
28
28
  function threadStorageKey(appId) {
29
29
  return `guuey:thread:${appId ?? "default"}`;
@@ -45,7 +45,11 @@ export function applyHistoryResult(result, currentMessages) {
45
45
  export function useAgentInvoke(opts) {
46
46
  const { endpointUrl, appId } = opts;
47
47
  const [messages, setMessages] = useState([]);
48
- const [isStreaming, setIsStreaming] = useState(false);
48
+ // Per-turn lifecycle (guuey#91) — derived purely from the frames below; see
49
+ // the `AgentInvokeStatus` doc for the state meanings. `activeTool` carries
50
+ // the wire tool name only while status is 'using-tool'.
51
+ const [status, setStatus] = useState("ready");
52
+ const [activeTool, setActiveTool] = useState(null);
49
53
  const [error, setError] = useState(null);
50
54
  const [threadId, setThreadId] = useState(null);
51
55
  // Opt-in block-preserving transcript. `reduceResult` follows the
@@ -57,6 +61,15 @@ export function useAgentInvoke(opts) {
57
61
  // contract). Independent of the live `reduceResult` fold — populated only
58
62
  // when a card-carrying history load seeds the transcript.
59
63
  const [historyCards, setHistoryCards] = useState([]);
64
+ // The pod's latest cross-app profile consent ask on this conversation (T6's
65
+ // `profile-consent-needed` SSE event), or null. Cleared on app switch /
66
+ // reset / explicit dismiss. Consumers with no consent UI just ignore it.
67
+ const [profileConsentRequest, setProfileConsentRequest] = useState(null);
68
+ // The pod's latest cross-app profile LINK invite on this conversation (T3's
69
+ // `profile-link-needed` SSE event), or null. Cleared on app switch / reset /
70
+ // explicit dismiss, same lifecycle as `profileConsentRequest` — the two are
71
+ // independent (an unlinked-invite vs an already-linked consent ask).
72
+ const [profileLinkRequest, setProfileLinkRequest] = useState(null);
60
73
  const abortRef = useRef(null);
61
74
  // Mirror the latest threadId + adapters into refs so `send` reads fresh
62
75
  // values without depending on them (keeps the callback identity stable and
@@ -64,9 +77,13 @@ export function useAgentInvoke(opts) {
64
77
  const threadIdRef = useRef(null);
65
78
  const adaptersRef = useRef(opts.adapters);
66
79
  adaptersRef.current = opts.adapters;
67
- // The per-conversation AgJSON reducer (only built when `preserveBlocks`).
80
+ // The per-conversation AgJSON fold (only built when `preserveBlocks`).
68
81
  // Lazily (re)created on the first valid AgEvent after a fresh start / reset,
69
82
  // so an off run never constructs one and a bypass run never allocates.
83
+ // The core Reducer carries `_meta` onto tool-result blocks as of
84
+ // `@silverprotocol/core` 0.4.1 (workspace#9), so BOTH generative-UI channels
85
+ // (MCP-Apps `_meta.ui`, ggui's render bootstrap) survive the fold natively —
86
+ // the old guuey-side `BlockFold` carriage wrapper is deleted.
70
87
  const reducerRef = useRef(null);
71
88
  const preserveBlocksRef = useRef(opts.preserveBlocks ?? false);
72
89
  preserveBlocksRef.current = opts.preserveBlocks ?? false;
@@ -85,12 +102,16 @@ export function useAgentInvoke(opts) {
85
102
  setThreadId(null);
86
103
  setMessages([]);
87
104
  setError(null);
88
- setIsStreaming(false);
105
+ setStatus("ready");
106
+ setActiveTool(null);
89
107
  // Fresh conversation → drop the old fold; the reducer is rebuilt lazily on
90
108
  // the next valid AgEvent. Persisted cards are re-seeded below from history.
91
109
  reducerRef.current = null;
92
110
  setReduceResult(null);
93
111
  setHistoryCards([]);
112
+ // A prior app's consent ask must never leak into the new conversation.
113
+ setProfileConsentRequest(null);
114
+ setProfileLinkRequest(null);
94
115
  let cancelled = false;
95
116
  const key = threadStorageKey(appId);
96
117
  const hydration = Promise.resolve(adaptersRef.current.storage.load(key))
@@ -171,18 +192,27 @@ export function useAgentInvoke(opts) {
171
192
  void adaptersRef.current.storage.save(threadStorageKey(appId), "");
172
193
  setMessages([]);
173
194
  setError(null);
174
- setIsStreaming(false);
195
+ setStatus("ready");
196
+ setActiveTool(null);
175
197
  // Re-create the reducer for the new conversation (rebuilt lazily on the
176
198
  // next valid AgEvent) and clear the exposed fold + any rehydrated cards.
177
199
  reducerRef.current = null;
178
200
  setReduceResult(null);
179
201
  setHistoryCards([]);
202
+ setProfileConsentRequest(null);
203
+ setProfileLinkRequest(null);
180
204
  }, [appId]);
205
+ const clearProfileConsentRequest = useCallback(() => {
206
+ setProfileConsentRequest(null);
207
+ }, []);
208
+ const clearProfileLinkRequest = useCallback(() => {
209
+ setProfileLinkRequest(null);
210
+ }, []);
181
211
  const send = useCallback(async (input) => {
182
- if (!endpointUrl || !input.trim() || isStreaming)
212
+ if (!endpointUrl || !input.trim() || status !== "ready")
183
213
  return;
184
214
  setError(null);
185
- setIsStreaming(true);
215
+ setStatus("connecting");
186
216
  setMessages((prev) => [...prev, { role: "user", text: input }, { role: "assistant", text: "" }]);
187
217
  const controller = new AbortController();
188
218
  abortRef.current = controller;
@@ -194,7 +224,7 @@ export function useAgentInvoke(opts) {
194
224
  await hydrationRef.current;
195
225
  }
196
226
  if (controller.signal.aborted) {
197
- setIsStreaming(false);
227
+ setStatus("ready");
198
228
  abortRef.current = null;
199
229
  return;
200
230
  }
@@ -226,6 +256,10 @@ export function useAgentInvoke(opts) {
226
256
  buffer = rest;
227
257
  for (const ev of events) {
228
258
  if (ev.event === "session") {
259
+ // The pod is awake and the turn is admitted — 'connecting' ends
260
+ // here (this frame arrives within ~1s of a warm pod; a cold
261
+ // scale-to-zero start is exactly the long 'connecting' phase).
262
+ setStatus("thinking");
229
263
  const tid = stringField(ev.data, "threadId");
230
264
  if (tid) {
231
265
  threadIdRef.current = tid;
@@ -234,6 +268,26 @@ export function useAgentInvoke(opts) {
234
268
  }
235
269
  }
236
270
  else if (ev.event === "message") {
271
+ // Status derivation (guuey#91) — read the frame's `type` before
272
+ // the text fold. Silver frames announce tools + text explicitly;
273
+ // bypass frames ('text' / 'assistant' SDKMessages) only ever
274
+ // carry assistant text, so they map to 'responding'. Unknown
275
+ // types deliberately leave the status untouched.
276
+ const frameType = stringField(ev.data, "type");
277
+ if (frameType === "tool.start") {
278
+ setStatus("using-tool");
279
+ setActiveTool(stringField(ev.data, "name") ?? null);
280
+ }
281
+ else if (frameType === "tool.done") {
282
+ setStatus("thinking");
283
+ setActiveTool(null);
284
+ }
285
+ else if (frameType === "text.start" ||
286
+ frameType === "text.delta" ||
287
+ frameType === "text" ||
288
+ frameType === "assistant") {
289
+ setStatus("responding");
290
+ }
237
291
  renderAssistant(reduceAssistantText(assistantText, ev.data));
238
292
  // Additively fold the SAME frame into the AgJSON reducer when
239
293
  // opted in. The text surface above is untouched; only VALID
@@ -253,7 +307,24 @@ export function useAgentInvoke(opts) {
253
307
  else if (ev.event === "error") {
254
308
  setError(stringField(ev.data, "message") ?? "agent error");
255
309
  }
256
- // `done` needs no handling — the stream closes after it.
310
+ else if (ev.event === "profile-consent-needed") {
311
+ // Cross-app profile consent ask (T6). Only a well-formed payload
312
+ // updates state; a malformed one is dropped, leaving any prior
313
+ // valid request untouched (never clobbered to null).
314
+ const parsed = parseConsentRequest(ev.data);
315
+ if (parsed)
316
+ setProfileConsentRequest(parsed);
317
+ }
318
+ else if (ev.event === "profile-link-needed") {
319
+ // Cross-app profile LINK invite (linkcoh T3) for an unlinked byo
320
+ // caller. Same drop-if-malformed contract as consent above.
321
+ const parsed = parseLinkRequest(ev.data);
322
+ if (parsed)
323
+ setProfileLinkRequest(parsed);
324
+ }
325
+ // `done` needs no handling — the stream closes after it. Any other
326
+ // (unknown) event falls through silently — there is no default
327
+ // branch, so a consumer that never renders a field is unaffected.
257
328
  }
258
329
  }
259
330
  }
@@ -263,7 +334,8 @@ export function useAgentInvoke(opts) {
263
334
  }
264
335
  }
265
336
  finally {
266
- setIsStreaming(false);
337
+ setStatus("ready");
338
+ setActiveTool(null);
267
339
  abortRef.current = null;
268
340
  // A turn aborted before any assistant text streamed leaves an empty
269
341
  // placeholder bubble — drop it so a stopped turn doesn't linger as a
@@ -277,6 +349,21 @@ export function useAgentInvoke(opts) {
277
349
  });
278
350
  }
279
351
  }
280
- }, [endpointUrl, appId, isStreaming]);
281
- return { messages, send, isStreaming, error, threadId, abort, reset, reduceResult, historyCards };
352
+ }, [endpointUrl, appId, status]);
353
+ return {
354
+ messages,
355
+ send,
356
+ status,
357
+ activeTool,
358
+ error,
359
+ threadId,
360
+ abort,
361
+ reset,
362
+ reduceResult,
363
+ historyCards,
364
+ profileConsentRequest,
365
+ clearProfileConsentRequest,
366
+ profileLinkRequest,
367
+ clearProfileLinkRequest,
368
+ };
282
369
  }
@@ -24,14 +24,25 @@ export declare const localStorageThreadStore: ThreadIdStore;
24
24
  /** Crypto-strong client-message id, with a non-crypto fallback. */
25
25
  export declare function webGenerateId(): string;
26
26
  /**
27
- * Web SSE transport. When `accessToken` is present the pod identifies the
28
- * caller by their verified Cognito access token (the same identity the
29
- * history read plane uses, so persisted threads round-trip on reload).
30
- * Otherwise it falls back to `credentials: "include"`, which round-trips the
31
- * HttpOnly `guuey_guest` cookie the pod mints for anonymous browser callers.
27
+ * Web SSE transport. Exactly ONE identity carrier per request, in order:
28
+ *
29
+ * 1. `accessToken` `Authorization: Bearer` the pod identifies the caller
30
+ * by their verified access token (the same identity the history read
31
+ * plane uses, so persisted threads round-trip on reload).
32
+ * 2. a well-formed `guestSecret` → `x-guuey-guest` — the caller owns and
33
+ * persists its own anonymous secret. The path for hosts with no usable
34
+ * cookie jar: React-Native, and the embedded widget, whose third-party
35
+ * iframe cannot rely on the pod's cookie surviving browser partitioning.
36
+ * The pod never mints a cookie for a header client.
37
+ * 3. neither → `credentials: "include"`, which round-trips the HttpOnly
38
+ * `guuey_guest` cookie the pod mints for anonymous browser callers.
39
+ *
40
+ * Never two at once: a bearer wins over a guest secret, and a request that
41
+ * carries either header does NOT also send cookie credentials.
42
+ *
32
43
  * Reads the body via `ReadableStream.getReader()` (browser).
33
44
  */
34
- export declare function fetchStreamTransport(req: InvokeRequest, accessToken?: string | null): AsyncGenerator<string>;
45
+ export declare function fetchStreamTransport(req: InvokeRequest, accessToken?: string | null, guestSecret?: string | null): AsyncGenerator<string>;
35
46
  export interface CreateWebAdaptersOptions {
36
47
  /**
37
48
  * Public read-plane base (ending in `/v1`) for transcript history. When
@@ -42,18 +53,71 @@ export interface CreateWebAdaptersOptions {
42
53
  * Resolve the caller's Cognito access token (fresh), or `null` when signed
43
54
  * out. When a token is present the chat transport AND the history read
44
55
  * authenticate as that user, so a reload restores the transcript. Without
45
- * a token the transport falls back to the guest cookie and history is
46
- * skipped the read plane can't identify a cookie-only browser caller
47
- * (it reads the `x-guuey-guest` header or a Bearer, not the HttpOnly
48
- * guest cookie), so there is no identity to replay.
56
+ * a token, identity falls to {@link getGuestSecret} (if supplied) and then
57
+ * to the guest cookie.
58
+ *
59
+ * Called with `{ forceRefresh: true }` exactly once: when the history read
60
+ * gets a 401 on a token this resolver already returned (a token cached
61
+ * before the mount-time history read fired can be stale by the time it
62
+ * runs — the same window the send path's own 401-retry closes). A resolver
63
+ * that caches (Amplify's `fetchAuthSession` does, and so does the widget's
64
+ * `createHostTokenProvider`) MUST bypass that cache for a forced call and
65
+ * obtain a genuinely fresh token — returning the SAME stale value would
66
+ * make the retry indistinguishable from not retrying at all. A resolver
67
+ * with nothing fresher to offer returns `null`, and the read surfaces the
68
+ * ORIGINAL 401 rather than replaying the value that just failed.
69
+ */
70
+ getAccessToken?: (opts?: {
71
+ forceRefresh?: boolean;
72
+ }) => Promise<string | null>;
73
+ /**
74
+ * Resolve the caller's own persisted anonymous guest secret (64 lowercase
75
+ * hex chars), or `null` when there is none. Supply this on hosts whose
76
+ * cookie jar can't carry the pod's HttpOnly `guuey_guest` — notably the
77
+ * embedded widget, a third-party iframe whose cookies browsers partition
78
+ * or block outright.
79
+ *
80
+ * With a secret, BOTH the chat transport and the history read send
81
+ * `x-guuey-guest`, so an anonymous transcript replays on reload the same
82
+ * way a signed-in one does — the read plane identifies a guest by that
83
+ * header (it cannot see the HttpOnly cookie, which is why a cookie-only
84
+ * caller still gets no history).
85
+ *
86
+ * Called once per request, so a rotated secret takes effect immediately.
87
+ * A value that isn't 64 lowercase hex is ignored (never sent) and the
88
+ * request falls through to cookie mode.
89
+ *
90
+ * **Supply at most ONE identity resolver per mode.** Anonymous hosts pass
91
+ * this one; identified hosts pass {@link getAccessToken} and surface a token
92
+ * failure rather than continuing. Passing BOTH is a hazard, not a fallback
93
+ * chain: `getAccessToken` resolving `null` is indistinguishable here from
94
+ * "signed out on purpose", so a merely *expired or unavailable* token
95
+ * silently downgrades the caller to the anonymous identity. The request then
96
+ * SUCCEEDS — the pod accepts anonymous invokes unconditionally — but the
97
+ * turns land in a different thread (the pod forks on an owner mismatch
98
+ * rather than appending), unreachable from the identified session, which
99
+ * gets its own transcript back minus those turns on the next good load. A
100
+ * 401-then-re-request-token retry loop is exactly this window.
101
+ *
102
+ * MUST be synchronous and MUST NOT throw: a throw propagates and fails the
103
+ * invoke. This is a real hazard for the widget, not a formality —
104
+ * `localStorage` access raises `SecurityError` in a third-party iframe with
105
+ * storage blocked (Safari's default for embedded content), which is normal
106
+ * operation here. A host reading storage owns that handling and MUST return
107
+ * `null` on a blocked read, the way {@link localStorageThreadStore} does for
108
+ * the threadId; `null` degrades to cookie mode, whereas a throw takes the
109
+ * chat down. Deliberately NOT caught at this seam: catching a host-supplied
110
+ * callback would also swallow ordinary host bugs into a silent anonymous
111
+ * downgrade — the same failure this docblock warns about above.
49
112
  */
50
- getAccessToken?: () => Promise<string | null>;
113
+ getGuestSecret?: () => string | null;
51
114
  }
52
115
  /**
53
116
  * Build the web host-adapter bundle for {@link useAgentInvoke}. Pass an
54
- * access-token resolver (and the read-plane base) to authenticate the chat
55
- * transport and enable transcript restore on reload; omit them for an
56
- * anonymous, history-less bundle.
117
+ * access-token resolver and/or a guest-secret resolver (plus the read-plane
118
+ * base) to give the chat transport an identity the read plane can also see,
119
+ * which is what enables transcript restore on reload; omit both for a
120
+ * cookie-only, history-less bundle.
57
121
  */
58
122
  export declare function createWebAdapters(opts?: CreateWebAdaptersOptions): AgentInvokeAdapters;
59
123
  //# sourceMappingURL=web-adapters.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"web-adapters.d.ts","sourceRoot":"","sources":["../src/web-adapters.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EACV,mBAAmB,EACnB,aAAa,EAEb,aAAa,EACd,MAAM,SAAS,CAAC;AAGjB;;;;;;GAMG;AACH,qBAAa,kBAAmB,SAAQ,KAAK;IAGzC,QAAQ,CAAC,MAAM,EAAE,MAAM;IACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM;gBAFtB,OAAO,EAAE,MAAM,EACN,MAAM,EAAE,MAAM,EACd,IAAI,CAAC,EAAE,MAAM,YAAA;CAKzB;AAED,sEAAsE;AACtE,eAAO,MAAM,uBAAuB,EAAE,aAiBrC,CAAC;AAEF,mEAAmE;AACnE,wBAAgB,aAAa,IAAI,MAAM,CAKtC;AAED;;;;;;;GAOG;AACH,wBAAuB,oBAAoB,CACzC,GAAG,EAAE,aAAa,EAClB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,GAC1B,cAAc,CAAC,MAAM,CAAC,CAyCxB;AAED,MAAM,WAAW,wBAAwB;IACvC;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;;;;OAQG;IACH,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CAC/C;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,GAAE,wBAA6B,GAClC,mBAAmB,CA+BrB"}
1
+ {"version":3,"file":"web-adapters.d.ts","sourceRoot":"","sources":["../src/web-adapters.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EACV,mBAAmB,EACnB,aAAa,EAEb,aAAa,EACd,MAAM,SAAS,CAAC;AAGjB;;;;;;GAMG;AACH,qBAAa,kBAAmB,SAAQ,KAAK;IAGzC,QAAQ,CAAC,MAAM,EAAE,MAAM;IACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM;gBAFtB,OAAO,EAAE,MAAM,EACN,MAAM,EAAE,MAAM,EACd,IAAI,CAAC,EAAE,MAAM,YAAA;CAKzB;AAED,sEAAsE;AACtE,eAAO,MAAM,uBAAuB,EAAE,aAiBrC,CAAC;AAEF,mEAAmE;AACnE,wBAAgB,aAAa,IAAI,MAAM,CAKtC;AAwCD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAuB,oBAAoB,CACzC,GAAG,EAAE,aAAa,EAClB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,EAC3B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,GAC1B,cAAc,CAAC,MAAM,CAAC,CA4CxB;AAED,MAAM,WAAW,wBAAwB;IACvC;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;;;;;;;;;;;;;OAiBG;IACH,cAAc,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC/E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAuCG;IACH,cAAc,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;CACtC;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,GAAE,wBAA6B,GAClC,mBAAmB,CAqErB"}