@colixsystems/widget-sdk 0.154.0 → 0.155.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -72,7 +72,17 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
72
72
 
73
73
  ## Status
74
74
 
75
- `v0.154.0` — pre-publish. The package surface (types, function names, export paths) is the v1 contract; runtime behaviour for some hooks is stubbed (each hook documents what's wired and what isn't). It is **not yet published to npm**.
75
+ `v0.155.0` — pre-publish. The package surface (types, function names, export paths) is the v1 contract; runtime behaviour for some hooks is stubbed (each hook documents what's wired and what isn't). It is **not yet published to npm**.
76
+
77
+ ### What's new in 0.155.0 (contract 1.121.0)
78
+
79
+ **`useAgent()` returns `cancel()` — and two error codes that tell the truth about a slow turn (sc-8519).** A turn can legitimately run for minutes; the client used to give up after 10 seconds and report it as `NETWORK`, so a widget showed "you appear to be offline" to a user whose connection was fine, while the server finished the turn and the workspace paid for a reply nobody saw.
80
+
81
+ - **`cancel()`** stops WAITING for the turn in flight. The abort never reaches the server, which finishes and bills the turn either way — so label the control **stop**, not *cancel*, and never imply the work was called off.
82
+ - **`TIMEOUT`** — the client stopped waiting. Still running, still billed: say it is taking longer than usual, never that it failed.
83
+ - **`CANCELLED`** — the user pressed your stop control. Render no error at all.
84
+
85
+ Additive: `send` is unchanged for callers who ignore the new return key, and a widget that never calls `cancel()` behaves exactly as before.
76
86
 
77
87
  ### What's new in 0.154.0 (contract 1.120.0)
78
88
 
package/dist/contract.cjs CHANGED
@@ -1308,7 +1308,12 @@ const HOOKS = [
1308
1308
  "AI_QUOTA_EXCEEDED (429) is the workspace's credit ceiling and will " +
1309
1309
  "NOT clear on a retry — never offer 'try again' for it — where " +
1310
1310
  "RATE_LIMITED (429) will; AGENT_DISABLED (409) means the author turned " +
1311
- "it off, so hide the affordance rather than retrying. NEVER surface a " +
1311
+ "it off, so hide the affordance rather than retrying; TIMEOUT means the " +
1312
+ "client stopped waiting and the turn is STILL running and billed, so " +
1313
+ "say it is taking longer, never that it failed or that they are " +
1314
+ "offline; CANCELLED is the user pressing your stop control, so render " +
1315
+ "no error at all. Call cancel() to stop waiting -- it ends the WAIT, " +
1316
+ "not the turn. NEVER surface a " +
1312
1317
  "raw error or the workspace's billing state to an app user. " +
1313
1318
  "Reads ctx.agents.",
1314
1319
  returnShape: {
@@ -4232,6 +4237,12 @@ const CONTRACT = deepFreeze({
4232
4237
  // and the inherited colours are hex-guarded on the way out as well as in.
4233
4238
  // Every colour still falls back to the sidebar's: an untouched workspace is
4234
4239
  // unchanged.
4240
+ // 1.121.0: additive (sc-8519) — `useAgent()` returns `cancel()`, which
4241
+ // stops WAITING for the turn in flight. The abort is client-side: the
4242
+ // server finishes the turn and the workspace is billed, so label the
4243
+ // control 'stop', never 'cancel'. Two new AgentError codes ride with it —
4244
+ // TIMEOUT (the client stopped waiting; still running, still billed) and
4245
+ // CANCELLED (the user pressed stop; render no error at all).
4235
4246
  // 1.120.0: additive (sc-8332) — an assistant `AgentMessage` may carry
4236
4247
  // `activity`, a compact record of what the agent DID with the user's data
4237
4248
  // (the table it read; the table and columns it CHANGED). Absent on a
@@ -4298,7 +4309,7 @@ const CONTRACT = deepFreeze({
4298
4309
  // untouched and renders byte-identically, and an unresolvable binding
4299
4310
  // fails the existing hex guard, dropping the wash rather than painting it
4300
4311
  // blank.
4301
- version: "1.120.0",
4312
+ version: "1.121.0",
4302
4313
  sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
4303
4314
  hooks: HOOKS,
4304
4315
  primitives: PRIMITIVES,
package/dist/contract.js CHANGED
@@ -1308,7 +1308,12 @@ const HOOKS = [
1308
1308
  "AI_QUOTA_EXCEEDED (429) is the workspace's credit ceiling and will " +
1309
1309
  "NOT clear on a retry — never offer 'try again' for it — where " +
1310
1310
  "RATE_LIMITED (429) will; AGENT_DISABLED (409) means the author turned " +
1311
- "it off, so hide the affordance rather than retrying. NEVER surface a " +
1311
+ "it off, so hide the affordance rather than retrying; TIMEOUT means the " +
1312
+ "client stopped waiting and the turn is STILL running and billed, so " +
1313
+ "say it is taking longer, never that it failed or that they are " +
1314
+ "offline; CANCELLED is the user pressing your stop control, so render " +
1315
+ "no error at all. Call cancel() to stop waiting -- it ends the WAIT, " +
1316
+ "not the turn. NEVER surface a " +
1312
1317
  "raw error or the workspace's billing state to an app user. " +
1313
1318
  "Reads ctx.agents.",
1314
1319
  returnShape: {
@@ -4228,6 +4233,12 @@ const CONTRACT = deepFreeze({
4228
4233
  // and the inherited colours are hex-guarded on the way out as well as in.
4229
4234
  // Every colour still falls back to the sidebar's: an untouched workspace is
4230
4235
  // unchanged.
4236
+ // 1.121.0: additive (sc-8519) — `useAgent()` returns `cancel()`, which
4237
+ // stops WAITING for the turn in flight. The abort is client-side: the
4238
+ // server finishes the turn and the workspace is billed, so label the
4239
+ // control 'stop', never 'cancel'. Two new AgentError codes ride with it —
4240
+ // TIMEOUT (the client stopped waiting; still running, still billed) and
4241
+ // CANCELLED (the user pressed stop; render no error at all).
4231
4242
  // 1.120.0: additive (sc-8332) — an assistant `AgentMessage` may carry
4232
4243
  // `activity`, a compact record of what the agent DID with the user's data
4233
4244
  // (the table it read; the table and columns it CHANGED). Absent on a
@@ -4294,7 +4305,7 @@ const CONTRACT = deepFreeze({
4294
4305
  // untouched and renders byte-identically, and an unresolvable binding
4295
4306
  // fails the existing hex guard, dropping the wash rather than painting it
4296
4307
  // blank.
4297
- version: "1.120.0",
4308
+ version: "1.121.0",
4298
4309
  sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
4299
4310
  hooks: HOOKS,
4300
4311
  primitives: PRIMITIVES,
package/dist/hooks.js CHANGED
@@ -2713,7 +2713,15 @@ export function useInterpretDraft(tableId) {
2713
2713
  * - "AI_UNAVAILABLE" — 503 (no AI provider configured)
2714
2714
  * - "AI_GENERATION_FAILED" — 502 (the provider did not answer)
2715
2715
  * - "AUTH_REQUIRED" — 401 (no signed-in app user)
2716
- * - "INTERNAL" — anything else (network, 5xx)
2716
+ * - "NETWORK" — the request never reached the server
2717
+ * - "TIMEOUT" — the client stopped waiting. NOT a failed
2718
+ * turn: the server finishes it and the
2719
+ * workspace is billed, so say "still
2720
+ * working", never "failed"
2721
+ * - "CANCELLED" — `cancel()` was called. The user chose it,
2722
+ * so render no error at all (the turn is
2723
+ * still billed — never imply it stopped)
2724
+ * - "INTERNAL" — anything else (a 5xx carrying no code)
2717
2725
  */
2718
2726
  export class AgentError extends Error {
2719
2727
  constructor(code, message, opts) {
@@ -2816,12 +2824,21 @@ export function useAgent(agentRef) {
2816
2824
  pendingResetRef.current = true;
2817
2825
  }
2818
2826
 
2827
+ const abortRef = useRef(null);
2828
+
2819
2829
  const reset = useCallback(() => {
2820
2830
  conversationRef.current = null;
2821
2831
  setMessages([]);
2822
2832
  setError(null);
2823
2833
  }, []);
2824
2834
 
2835
+ // Ends the WAIT, not the turn: the abort never reaches the server, which
2836
+ // finishes and bills it either way. Never imply the work was stopped.
2837
+ const cancel = useCallback(() => {
2838
+ const controller = abortRef.current;
2839
+ if (controller) controller.abort();
2840
+ }, []);
2841
+
2825
2842
  const send = useCallback(async (text) => {
2826
2843
  const client = clientRef.current;
2827
2844
  const agentId = agentIdRef.current;
@@ -2855,7 +2872,13 @@ export function useAgent(agentRef) {
2855
2872
  conversationRef.current = null;
2856
2873
  throw err;
2857
2874
  }
2858
- const turn = await thread.messages(conversationId).send({ content });
2875
+ const controller =
2876
+ typeof AbortController === "function" ? new AbortController() : null;
2877
+ abortRef.current = controller;
2878
+ const turn = await thread
2879
+ .messages(conversationId)
2880
+ .send({ content }, controller ? { signal: controller.signal } : undefined);
2881
+ abortRef.current = null;
2859
2882
  // A rebind during the round trip makes this the PREVIOUS agent's reply;
2860
2883
  // writing it now would render it under the new one.
2861
2884
  if (agentIdRef.current !== agentId) {
@@ -2874,8 +2897,10 @@ export function useAgent(agentRef) {
2874
2897
  // A stale thread is recoverable on the next send rather than permanently
2875
2898
  // broken: drop it so the widget opens a fresh one.
2876
2899
  if (e.code === "CONVERSATION_NOT_FOUND") conversationRef.current = null;
2900
+ abortRef.current = null;
2877
2901
  // Same rebind guard: a new agent must not inherit the old one's error.
2878
- if (agentIdRef.current === agentId) setError(e);
2902
+ // A cancel is the user's own choice, so it is not a failure to report.
2903
+ if (e.code !== "CANCELLED" && agentIdRef.current === agentId) setError(e);
2879
2904
  setSending(false);
2880
2905
  throw e;
2881
2906
  }
@@ -2897,6 +2922,7 @@ export function useAgent(agentRef) {
2897
2922
 
2898
2923
  return {
2899
2924
  send,
2925
+ cancel,
2900
2926
  reset,
2901
2927
  messages: visibleMessages,
2902
2928
  sending,
package/dist/index.d.ts CHANGED
@@ -1239,6 +1239,12 @@ export interface AgentActivity {
1239
1239
  export interface AgentApi {
1240
1240
  /** Imperative — call from an event handler. The first send opens the thread. */
1241
1241
  send(text: string): Promise<AgentMessage | null>;
1242
+ /**
1243
+ * Stop WAITING for the turn in flight. The abort never reaches the server,
1244
+ * which finishes and bills it either way — label the control "stop", not
1245
+ * "cancel", and show no error when the user presses it.
1246
+ */
1247
+ cancel(): void;
1242
1248
  /** Drop the open thread and clear the transcript this widget has seen. */
1243
1249
  reset(): void;
1244
1250
  /** Oldest first. The SERVER owns the real transcript. */
@@ -1997,6 +2003,9 @@ export class AgentError extends Error {
1997
2003
  | "AI_UNAVAILABLE"
1998
2004
  | "AI_GENERATION_FAILED"
1999
2005
  | "AUTH_REQUIRED"
2006
+ | "NETWORK"
2007
+ | "TIMEOUT"
2008
+ | "CANCELLED"
2000
2009
  | "INTERNAL"
2001
2010
  | (string & {});
2002
2011
  constructor(code: string, message: string, opts?: { cause?: unknown });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@colixsystems/widget-sdk",
3
- "version": "0.154.0",
3
+ "version": "0.155.0",
4
4
  "description": "Common widget interface for AppStudio. Implements WidgetManifest, WidgetContext, property schema, and helper hooks.",
5
5
  "homepage": "https://github.com/Colix-AB/AppStudio",
6
6
  "type": "module",