@marketrix.ai/widget 4.0.135 → 4.0.136

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.
@@ -6,7 +6,11 @@
6
6
  * shell's toolbar button fills with a share toggle, and the composer textarea ref. `MODES` pairs the
7
7
  * mode chips' display order with the tenant setting enabling each, so the composer offers only what
8
8
  * the workspace turned on. `ChatView` owns the draft text; `handleSendMessage` posts the turn and
9
- * `handleModeChange` announces a mode switch in the transcript before flipping state.
9
+ * `handleModeChange` announces a mode switch in the transcript before flipping state. A POST failure
10
+ * restores the composed text to the (now-cleared) composer — `messageDispatch`'s resolved `false` —
11
+ * unless the visitor already started typing a new message in the meantime, so a resend is one tap on
12
+ * Send rather than a retype, and the failed turn's own bubble (added optimistically, unconditionally)
13
+ * still shows what was said either way.
10
14
  *
11
15
  * The composer is locked both while a reply is outstanding and while a screen-access request is open,
12
16
  * since a second turn queued behind an unanswered permission card has nowhere to land. `use_screenshare`
@@ -8,15 +8,30 @@
8
8
  * defers updaters and loses the effects captured in them, so tool calls never execute. `messageDispatch`
9
9
  * POSTs fire-and-forget: the reply arrives over SSE, so only a POST failure resolves the placeholder
10
10
  * locally, and a stale-reply watchdog keyed on id AND part count re-arms on every progress line.
11
+ * `messageDispatch` resolves `false` on that same POST failure (`true` otherwise, including preview
12
+ * mode) so a caller holding the composed text — `ChatView`'s composer — can restore it instead of the
13
+ * visitor having to retype a message the widget already dropped from the input box.
11
14
  *
12
15
  * The stream effect subscribes to the `StreamClient` singleton: `handleMessage` does the bookkeeping the
13
- * pure reducer cannot hold (`tool_call_id` dedupe in a bounded set, cleared on a terminal status), then
14
- * each effect runs its browser tool, stamps progress, replies `tool/response` and only then fires
15
- * `afterResponseAttempt`. `handleError` converts only a `StreamGaveUpError` into a transport failure, as a
16
- * retriable blip settles when the reply lands on the reconnected stream. `stopTask` sends `chat/stop`,
17
- * which carries no task id. Every failure on the tool and stop paths reaches `uiActions.setError` as well
18
- * as the console: an undelivered `tool/response` leaves the agent waiting on a reply that never comes, so
19
- * the run stalls with nothing on screen unless the visitor is told, and `do` mode may still be clicking.
16
+ * pure reducer cannot hold (`tool_call_id` dedupe in a bounded set, cleared on a terminal status; the
17
+ * mirror-shaped `respondedRequestIds` below), then each effect runs its browser tool, stamps progress,
18
+ * replies `tool/response` and only then fires `afterResponseAttempt`. `handleError` converts only a
19
+ * `StreamGaveUpError` into a transport failure, as a retriable blip settles when the reply lands on the
20
+ * reconnected stream. `stopTask` sends `chat/stop`, which carries no task id. Every failure on the tool
21
+ * and stop paths reaches `uiActions.setError` as well as the console: an undelivered `tool/response`
22
+ * leaves the agent waiting on a reply that never comes, so the run stalls with nothing on screen unless
23
+ * the visitor is told, and `do` mode may still be clicking.
24
+ *
25
+ * `respondedRequestIds` closes the other half of the reconnect-replay contract `StreamClient.ts`
26
+ * documents (the api replays a chat_id's whole turn history, not just the tail a client missed):
27
+ * `reduceText`'s own exact-repeat guard (`sseReducer.ts`) only catches an identical FINAL
28
+ * `chat/response` replayed after the answer already closed, because it only ever compares against the
29
+ * message's LAST part. A replayed `chat/delta` fails that same-string check (it's a partial fragment,
30
+ * not the full closed text) and would otherwise be appended as a brand-new, visibly duplicated text
31
+ * segment ahead of the closing response that then only overwrites the one it just added — leaving two
32
+ * copies of the same answer on screen. Scoped by `request_id`, not globally, so an unrelated later
33
+ * turn's genuinely new `chat/delta`/`chat/response` is untouched; marked closed only once, on the
34
+ * request's own terminal `chat/response`, since a delta is by definition still open.
20
35
  *
21
36
  * `lastStreamErrorRef`/`currentErrorRef` close the loop `handleError` alone leaves open: `StreamClient`
22
37
  * calls `onError` on every failed dial, not only a terminal give-up, so the visitor sees "Stream
@@ -43,7 +58,7 @@ interface ChatActions {
43
58
  removeMessage: (messageId: string) => void;
44
59
  setMessages: (messages: ChatMessage[]) => void;
45
60
  clearMessages: () => void;
46
- messageDispatch: (content: string, mode?: InstructionType, skipUserMessage?: boolean) => Promise<void>;
61
+ messageDispatch: (content: string, mode?: InstructionType, skipUserMessage?: boolean) => Promise<boolean>;
47
62
  }
48
63
  interface TaskActions {
49
64
  resetTask: () => void;
@@ -21,7 +21,7 @@ export declare const useWidget: () => {
21
21
  removeMessage: (messageId: string) => void;
22
22
  setMessages: (messages: import("..").ChatMessage[]) => void;
23
23
  clearMessages: () => void;
24
- messageDispatch: (content: string, mode?: import("..").InstructionType, skipUserMessage?: boolean) => Promise<void>;
24
+ messageDispatch: (content: string, mode?: import("..").InstructionType, skipUserMessage?: boolean) => Promise<boolean>;
25
25
  resetTask: () => void;
26
26
  stopTask: () => Promise<void>;
27
27
  setActiveView: (view: import("../types").WidgetView) => void;
@@ -135,6 +135,12 @@ export declare const WorkspaceSummarySchema: z.ZodObject<{
135
135
  notify_all_members_on_question: z.ZodBoolean;
136
136
  }, z.core.$strip>;
137
137
  export type WorkspaceSummary = z.infer<typeof WorkspaceSummarySchema>;
138
+ export declare const ApplicationSkillDistillationStatusSchema: z.ZodEnum<{
139
+ failed: "failed";
140
+ idle: "idle";
141
+ pending: "pending";
142
+ }>;
143
+ export type ApplicationSkillDistillationStatus = z.infer<typeof ApplicationSkillDistillationStatusSchema>;
138
144
  export declare const ApplicationEntitySchema: z.ZodObject<{
139
145
  id: z.ZodNumber;
140
146
  created_at: z.ZodCoercedDate<unknown>;
@@ -150,6 +156,12 @@ export declare const ApplicationEntitySchema: z.ZodObject<{
150
156
  username: z.ZodNullable<z.ZodString>;
151
157
  password: z.ZodNullable<z.ZodString>;
152
158
  allowed_domains: z.ZodArray<z.ZodString>;
159
+ skill_distillation_status: z.ZodEnum<{
160
+ failed: "failed";
161
+ idle: "idle";
162
+ pending: "pending";
163
+ }>;
164
+ skill_distillation_error: z.ZodNullable<z.ZodString>;
153
165
  }, z.core.$strip>;
154
166
  export type ApplicationData = z.infer<typeof ApplicationEntitySchema>;
155
167
  /** Used for all API responses; password is write-only and never returned to clients. */
@@ -167,6 +179,12 @@ export declare const ApplicationReadSchema: z.ZodObject<{
167
179
  workspace_id: z.ZodNumber;
168
180
  username: z.ZodNullable<z.ZodString>;
169
181
  allowed_domains: z.ZodArray<z.ZodString>;
182
+ skill_distillation_status: z.ZodEnum<{
183
+ failed: "failed";
184
+ idle: "idle";
185
+ pending: "pending";
186
+ }>;
187
+ skill_distillation_error: z.ZodNullable<z.ZodString>;
170
188
  }, z.core.$strip>;
171
189
  export type ApplicationReadData = z.infer<typeof ApplicationReadSchema>;
172
190
  export declare const WidgetChipSchema: z.ZodObject<{
@@ -5,7 +5,12 @@
5
5
  * browser-local key shares via `scopedKey`, so the chat-context, drag-position and resize keys all
6
6
  * partition by tenant identically. `readLocal`/`writeLocal` are the only `localStorage` access in `src/`;
7
7
  * a host page can deny storage outright (third-party cookies off, sandboxed iframe), so both degrade to
8
- * a warn and the widget keeps working unpersisted.
8
+ * memory and the widget keeps working unpersisted. `updateContext` calls `writeLocal` on every UI-state
9
+ * change (a drag, a resize, every chat message), so without `warnOnce` a denied host page would spam one
10
+ * console line per write for the visitor's whole session; a single warn on first denial says everything
11
+ * a customer's console needs. `warnOnce` is keyed by read vs. write since `readLocal` also fires once on
12
+ * its own (`loadContext` at module init, before any write) and both should still surface if a page
13
+ * somehow denies one and not the other.
9
14
  *
10
15
  * `loadContext` merges one parsed key over `DEFAULT_CONTEXT`, so an older widget version's payload reads
11
16
  * as incomplete rather than corrupt, and discards anything past `CONTEXT_EXPIRY_MS` (7 days).
@@ -55,6 +60,10 @@ type MarketrixChatContext = Omit<ChatSnapshot, 'messages'> & {
55
60
  };
56
61
  export declare function tenantScope(config: MarketrixConfig): string;
57
62
  export declare function scopedKey(name: string, config: MarketrixConfig): string;
63
+ /** Test-only: `warned` is module-level so a "denies storage, keeps working" proof isn't the last test in
64
+ * the file to touch a denied `Storage.prototype`, and a later test in the same `bun test` process (one
65
+ * process per file, not per test) would otherwise see zero warns instead of one. Never called from `src/`. */
66
+ export declare function resetStorageWarningsForTests(): void;
58
67
  export declare function readLocal(key: string): string | null;
59
68
  export declare function writeLocal(key: string, value: string): void;
60
69
  declare class StorageService {
@@ -6,7 +6,10 @@
6
6
  *
7
7
  * `open` is the transport, `registered` is the chat: only the latter can carry a reply, so `isConnected` reads
8
8
  * `registered` and nothing waits on `open`. Backoff counters reset only on `registered` — resetting at `open` would
9
- * defeat the max-attempts cap if registration never lands and the stream flaps open→closed. Tabs share the
9
+ * defeat the max-attempts cap if registration never lands and the stream flaps open→closed. `scheduleReconnect`'s
10
+ * EQUAL JITTER (the doubling delay's own second half, chosen uniformly) keeps every dial within the documented
11
+ * schedule's bound while stopping every tab across every open customer page from redialing on the exact same
12
+ * clock tick after a shared outage — a thundering herd the deterministic schedule alone cannot prevent. Tabs share the
10
13
  * localStorage chat id, so the server keys SSE by (chat_id, tab_id) and `tabId` stops tabs evicting each other's
11
14
  * stream. Credentials are read at connect time, not captured at init, so a reconnect after `updateMarketrixConfig`
12
15
  * dials with the current ones.