@guuey/agent-client 0.14.1 → 0.14.2

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.
@@ -87,6 +87,17 @@ export declare const CLIENT_ERROR_CODES: {
87
87
  * the reply if the backend completes later.
88
88
  */
89
89
  readonly STREAM_STALLED: "STREAM_STALLED";
90
+ /**
91
+ * A RESUMED thread's history read was refused (401 after the one
92
+ * forceRefresh retry) — the persisted threadId exists but the CURRENT
93
+ * identity cannot read it (guuey#413: the identity-drift face; an
94
+ * expired-and-unrefreshable session is the benign sibling). Surfaced
95
+ * LOUDLY instead of the silent fresh-looking boot: the transcript the
96
+ * user expects exists and cannot be shown, which is an ERROR, not an
97
+ * empty chat. Sends still work (the pod may fork a fresh thread) — the
98
+ * error names why the history is missing.
99
+ */
100
+ readonly THREAD_HISTORY_UNAVAILABLE: "THREAD_HISTORY_UNAVAILABLE";
90
101
  };
91
102
  /** One of this SDK's client-originated codes — see {@link CLIENT_ERROR_CODES}. */
92
103
  export type ClientErrorCode = (typeof CLIENT_ERROR_CODES)[keyof typeof CLIENT_ERROR_CODES];
@@ -1 +1 @@
1
- {"version":3,"file":"error-codes.d.ts","sourceRoot":"","sources":["../src/error-codes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,iBAAiB;IAC5B,yDAAyD;;IAEzD,gDAAgD;;IAEhD,8DAA8D;;IAE9D;;;;;OAKG;;IAEH,6EAA6E;;IAE7E,qDAAqD;;IAErD;;;;OAIG;;IAEH;;;;;OAKG;;IAEH,oFAAoF;;IAEpF;;;;OAIG;;IAEH,qDAAqD;;IAErD,iEAAiE;;IAEjE,gCAAgC;;CAExB,CAAC;AAEX,mEAAmE;AACnE,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,OAAO,iBAAiB,CAAC,CAAC;AAExF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,kBAAkB;IAC7B;;;;;OAKG;;CAEK,CAAC;AAEX,kFAAkF;AAClF,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,OAAO,kBAAkB,CAAC,CAAC"}
1
+ {"version":3,"file":"error-codes.d.ts","sourceRoot":"","sources":["../src/error-codes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,iBAAiB;IAC5B,yDAAyD;;IAEzD,gDAAgD;;IAEhD,8DAA8D;;IAE9D;;;;;OAKG;;IAEH,6EAA6E;;IAE7E,qDAAqD;;IAErD;;;;OAIG;;IAEH;;;;;OAKG;;IAEH,oFAAoF;;IAEpF;;;;OAIG;;IAEH,qDAAqD;;IAErD,iEAAiE;;IAEjE,gCAAgC;;CAExB,CAAC;AAEX,mEAAmE;AACnE,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,OAAO,iBAAiB,CAAC,CAAC;AAExF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,kBAAkB;IAC7B;;;;;OAKG;;IAEH;;;;;;;;;OASG;;CAEK,CAAC;AAEX,kFAAkF;AAClF,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,OAAO,kBAAkB,CAAC,CAAC"}
@@ -85,4 +85,15 @@ export const CLIENT_ERROR_CODES = {
85
85
  * the reply if the backend completes later.
86
86
  */
87
87
  STREAM_STALLED: "STREAM_STALLED",
88
+ /**
89
+ * A RESUMED thread's history read was refused (401 after the one
90
+ * forceRefresh retry) — the persisted threadId exists but the CURRENT
91
+ * identity cannot read it (guuey#413: the identity-drift face; an
92
+ * expired-and-unrefreshable session is the benign sibling). Surfaced
93
+ * LOUDLY instead of the silent fresh-looking boot: the transcript the
94
+ * user expects exists and cannot be shown, which is an ERROR, not an
95
+ * empty chat. Sends still work (the pod may fork a fresh thread) — the
96
+ * error names why the history is missing.
97
+ */
98
+ THREAD_HISTORY_UNAVAILABLE: "THREAD_HISTORY_UNAVAILABLE",
88
99
  };
@@ -1 +1 @@
1
- {"version":3,"file":"useAgentInvoke.d.ts","sourceRoot":"","sources":["../src/useAgentInvoke.ts"],"names":[],"mappings":"AAwBA,OAAO,EAAW,KAAK,oBAAoB,EAAuB,MAAM,sBAAsB,CAAC;AAK/F,OAAO,KAAK,EAGV,YAAY,EAEZ,iBAAiB,EAGjB,qBAAqB,EACrB,oBAAoB,EACrB,MAAM,YAAY,CAAC;AAMpB;;;;;GAKG;AACH,eAAO,MAAM,qCAAqC,EAAE,oBAEnD,CAAC;AAEF,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,uFAAuF;AACvF,eAAO,MAAM,uBAAuB;;;IAGlC;;;;;;;OAOG;;CAEK,CAAC;AAcX;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,YAAY,EAAE,EACvB,cAAc,EAAE,MAAM,GACrB,OAAO,GAAG,WAAW,CAOvB;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,qBAAqB,GAAG,oBAAoB,CAoehF"}
1
+ {"version":3,"file":"useAgentInvoke.d.ts","sourceRoot":"","sources":["../src/useAgentInvoke.ts"],"names":[],"mappings":"AAwBA,OAAO,EAAW,KAAK,oBAAoB,EAAuB,MAAM,sBAAsB,CAAC;AAM/F,OAAO,KAAK,EAGV,YAAY,EAEZ,iBAAiB,EAGjB,qBAAqB,EACrB,oBAAoB,EACrB,MAAM,YAAY,CAAC;AAMpB;;;;;GAKG;AACH,eAAO,MAAM,qCAAqC,EAAE,oBAEnD,CAAC;AAEF,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,uFAAuF;AACvF,eAAO,MAAM,uBAAuB;;;IAGlC;;;;;;;OAOG;;CAEK,CAAC;AAcX;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,YAAY,EAAE,EACvB,cAAc,EAAE,MAAM,GACrB,OAAO,GAAG,WAAW,CAOvB;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,qBAAqB,GAAG,oBAAoB,CAmgBhF"}
@@ -27,6 +27,7 @@ import { invokeTurn, toInvokeUrl } from "./invoke-turn.js";
27
27
  import { AgentResponseError } from "./errors.js";
28
28
  import { withActivityObserver } from "./transport.js";
29
29
  import { CLIENT_ERROR_CODES } from "./error-codes.js";
30
+ import { HistoryUnauthorizedError } from "./history.js";
30
31
  function threadStorageKey(appId) {
31
32
  return `guuey:thread:${appId ?? "default"}`;
32
33
  }
@@ -228,8 +229,27 @@ export function useAgentInvoke(opts) {
228
229
  try {
229
230
  result = await history.load(tid);
230
231
  }
231
- catch {
232
- return; // best-effort: offline / transient chat continues without history
232
+ catch (err) {
233
+ // guuey#413 fail-LOUD carve-out from the best-effort rule: an
234
+ // UNAUTHORIZED read on a RESUMED threadId means a transcript the
235
+ // user expects exists and cannot be shown — silently booting a
236
+ // fresh-looking empty chat hid the identity-drift outage for
237
+ // exactly the accounts holding old threads. Auth refusal surfaces
238
+ // as an error item; every other failure (offline, transient)
239
+ // keeps the best-effort swallow — chat continues without history.
240
+ if (!cancelled && err instanceof HistoryUnauthorizedError) {
241
+ // Same guard as the `gone` arm below: clear + fresh (functional)
242
+ // + the notice (loud). Same concurrency guard — never clobber a
243
+ // threadId a mid-flight send just established.
244
+ if (threadIdRef.current === tid) {
245
+ threadIdRef.current = null;
246
+ setThreadId(null);
247
+ void adaptersRef.current.storage.save(threadStorageKey(appId), "");
248
+ }
249
+ setError("Couldn't restore your previous conversation — started a new one. (This session was not authorized to read the old thread.)");
250
+ setErrorCode(CLIENT_ERROR_CODES.THREAD_HISTORY_UNAVAILABLE);
251
+ }
252
+ return;
233
253
  }
234
254
  if (cancelled)
235
255
  return;
@@ -243,6 +263,14 @@ export function useAgentInvoke(opts) {
243
263
  threadIdRef.current = null;
244
264
  setThreadId(null);
245
265
  void adaptersRef.current.storage.save(threadStorageKey(appId), "");
266
+ // guuey#413: the hydration guard's LOUD half. `gone` covers 403 as
267
+ // well as 404 (history.ts) — an owner-mismatch refusal on a drifted
268
+ // identity flowed through THIS arm as a silent fresh-looking boot,
269
+ // which is the exact outage face this guard exists to kill. The
270
+ // fresh mint stays (functional); the notice makes it honest. Loud
271
+ // AND functional, never one without the other.
272
+ setError("Couldn't restore your previous conversation — started a new one.");
273
+ setErrorCode(CLIENT_ERROR_CODES.THREAD_HISTORY_UNAVAILABLE);
246
274
  return;
247
275
  }
248
276
  // Single decision authority: `applyHistoryResult` runs INSIDE the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@guuey/agent-client",
3
- "version": "0.14.1",
3
+ "version": "0.14.2",
4
4
  "description": "Client SDK for Guuey's agent runtime: the `useAgentInvoke` React hook + pure SSE helpers that speak the /agent/invoke streaming contract, plus the paginated thread-history read plane. Host adapters (storage / id / transport) are injected, so it runs on web (Next) and React Native alike.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -35,7 +35,7 @@
35
35
  },
36
36
  "dependencies": {
37
37
  "@silverprotocol/core": "0.5.1",
38
- "@guuey/mcp-apps-host": "0.14.1"
38
+ "@guuey/mcp-apps-host": "0.14.2"
39
39
  },
40
40
  "peerDependencies": {
41
41
  "react": ">=18"
@@ -89,6 +89,17 @@ export const CLIENT_ERROR_CODES = {
89
89
  * the reply if the backend completes later.
90
90
  */
91
91
  STREAM_STALLED: "STREAM_STALLED",
92
+ /**
93
+ * A RESUMED thread's history read was refused (401 after the one
94
+ * forceRefresh retry) — the persisted threadId exists but the CURRENT
95
+ * identity cannot read it (guuey#413: the identity-drift face; an
96
+ * expired-and-unrefreshable session is the benign sibling). Surfaced
97
+ * LOUDLY instead of the silent fresh-looking boot: the transcript the
98
+ * user expects exists and cannot be shown, which is an ERROR, not an
99
+ * empty chat. Sends still work (the pod may fork a fresh thread) — the
100
+ * error names why the history is missing.
101
+ */
102
+ THREAD_HISTORY_UNAVAILABLE: "THREAD_HISTORY_UNAVAILABLE",
92
103
  } as const;
93
104
 
94
105
  /** One of this SDK's client-originated codes — see {@link CLIENT_ERROR_CODES}. */
@@ -27,6 +27,7 @@ import { invokeTurn, toInvokeUrl } from "./invoke-turn.js";
27
27
  import { AgentResponseError } from "./errors.js";
28
28
  import { withActivityObserver } from "./transport.js";
29
29
  import { CLIENT_ERROR_CODES } from "./error-codes.js";
30
+ import { HistoryUnauthorizedError } from "./history.js";
30
31
  import type {
31
32
  AgentInvokeAdapters,
32
33
  AgentInvokeStatus,
@@ -256,8 +257,29 @@ export function useAgentInvoke(opts: UseAgentInvokeOptions): UseAgentInvokeRetur
256
257
  let result: HistoryLoadResult;
257
258
  try {
258
259
  result = await history.load(tid);
259
- } catch {
260
- return; // best-effort: offline / transient chat continues without history
260
+ } catch (err) {
261
+ // guuey#413 fail-LOUD carve-out from the best-effort rule: an
262
+ // UNAUTHORIZED read on a RESUMED threadId means a transcript the
263
+ // user expects exists and cannot be shown — silently booting a
264
+ // fresh-looking empty chat hid the identity-drift outage for
265
+ // exactly the accounts holding old threads. Auth refusal surfaces
266
+ // as an error item; every other failure (offline, transient)
267
+ // keeps the best-effort swallow — chat continues without history.
268
+ if (!cancelled && err instanceof HistoryUnauthorizedError) {
269
+ // Same guard as the `gone` arm below: clear + fresh (functional)
270
+ // + the notice (loud). Same concurrency guard — never clobber a
271
+ // threadId a mid-flight send just established.
272
+ if (threadIdRef.current === tid) {
273
+ threadIdRef.current = null;
274
+ setThreadId(null);
275
+ void adaptersRef.current.storage.save(threadStorageKey(appId), "");
276
+ }
277
+ setError(
278
+ "Couldn't restore your previous conversation — started a new one. (This session was not authorized to read the old thread.)",
279
+ );
280
+ setErrorCode(CLIENT_ERROR_CODES.THREAD_HISTORY_UNAVAILABLE);
281
+ }
282
+ return;
261
283
  }
262
284
  if (cancelled) return;
263
285
  if ("gone" in result) {
@@ -269,6 +291,16 @@ export function useAgentInvoke(opts: UseAgentInvokeOptions): UseAgentInvokeRetur
269
291
  threadIdRef.current = null;
270
292
  setThreadId(null);
271
293
  void adaptersRef.current.storage.save(threadStorageKey(appId), "");
294
+ // guuey#413: the hydration guard's LOUD half. `gone` covers 403 as
295
+ // well as 404 (history.ts) — an owner-mismatch refusal on a drifted
296
+ // identity flowed through THIS arm as a silent fresh-looking boot,
297
+ // which is the exact outage face this guard exists to kill. The
298
+ // fresh mint stays (functional); the notice makes it honest. Loud
299
+ // AND functional, never one without the other.
300
+ setError(
301
+ "Couldn't restore your previous conversation — started a new one.",
302
+ );
303
+ setErrorCode(CLIENT_ERROR_CODES.THREAD_HISTORY_UNAVAILABLE);
272
304
  return;
273
305
  }
274
306
  // Single decision authority: `applyHistoryResult` runs INSIDE the