@sales-bot-llm/sdk 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/biome.json +36 -0
- package/docs/superpowers/plans/2026-05-08-sales-bot-sdk-plan.md +258 -0
- package/docs/superpowers/plans/2026-05-11-w3-sales-tool-polish-plan.md +476 -0
- package/docs/superpowers/specs/2026-05-08-sales-bot-sdk-design.md +587 -0
- package/example/.env.example +5 -0
- package/example/README.md +90 -0
- package/example/index.html +12 -0
- package/example/package.json +27 -0
- package/example/public/vanilla.global.js +345 -0
- package/example/src/App.tsx +50 -0
- package/example/src/main.tsx +16 -0
- package/example/src/routes/HookDemo.tsx +174 -0
- package/example/src/routes/VanillaDemo.tsx +67 -0
- package/example/src/routes/WidgetDemo.tsx +55 -0
- package/example/src/styles.css +18 -0
- package/example/tsconfig.json +19 -0
- package/example/tsconfig.tsbuildinfo +1 -0
- package/example/vite.config.ts +4 -0
- package/package.json +106 -0
- package/pnpm-workspace.yaml +3 -0
- package/src/core/client.ts +245 -0
- package/src/core/conversation.ts +34 -0
- package/src/core/index.ts +6 -0
- package/src/core/sse-parser.ts +87 -0
- package/src/core/storage.ts +72 -0
- package/src/core/transport.ts +271 -0
- package/src/core/types.ts +314 -0
- package/src/core/visitor.ts +21 -0
- package/src/react/index.ts +2 -0
- package/src/react/use-sales-bot.tsx +182 -0
- package/src/vanilla/index.ts +38 -0
- package/src/vue/index.ts +2 -0
- package/src/vue/use-sales-bot.ts +152 -0
- package/src/widget/index.ts +3 -0
- package/src/widget/markdown.ts +69 -0
- package/src/widget/styles.ts +350 -0
- package/src/widget/widget.ts +442 -0
- package/tests/contract/wire-format.test.ts +158 -0
- package/tests/core/client.test.ts +292 -0
- package/tests/core/conversation.test.ts +41 -0
- package/tests/core/sse-parser.test.ts +142 -0
- package/tests/core/storage.test.ts +78 -0
- package/tests/core/transport.test.ts +204 -0
- package/tests/core/visitor.test.ts +42 -0
- package/tests/react/use-sales-bot.test.tsx +188 -0
- package/tests/sales-tool-discriminator.test.ts +45 -0
- package/tests/setup.ts +3 -0
- package/tests/vanilla/vanilla.test.ts +37 -0
- package/tests/vue/use-sales-bot.test.ts +163 -0
- package/tests/widget/markdown.test.ts +113 -0
- package/tests/widget/widget.test.ts +388 -0
- package/tsconfig.json +28 -0
- package/tsup.config.ts +38 -0
- package/vitest.config.ts +26 -0
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
# Workstream 3: SDK Sales-Tool Polish — Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development. Steps use checkbox (`- [ ]`) syntax.
|
|
4
|
+
|
|
5
|
+
**Goal:** Surface the new backend sales-workflow tool events as first-class SDK citizens — typed name discriminators, a hook callback for consumers, and friendly widget UI pills. Additive only; no breaking changes.
|
|
6
|
+
|
|
7
|
+
**Architecture:** Tiny additions to `src/core/types.ts` (literal union + type guard), `src/core/index.ts` (re-export), `src/react/use-sales-bot.tsx` and `src/vue/use-sales-bot.ts` (new optional `onSalesToolCall` callback), `src/widget/widget.ts` + `src/widget/styles.ts` (ephemeral status pill). One test file.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** TypeScript SDK (tsup build), Vitest, vanilla DOM widget.
|
|
10
|
+
|
|
11
|
+
**Design spec:** `/Users/artemzaitsev/projects/chaindoc/sales_bot/docs/superpowers/specs/2026-05-11-sales-workflow-dashboard-and-sdk-design.md` §6 + §3 rows 17-20.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Hard constraints
|
|
16
|
+
|
|
17
|
+
1. **Additive only — zero breaking changes.** Existing consumers continue to work without code changes.
|
|
18
|
+
2. **No new dependencies.** No npm installs.
|
|
19
|
+
3. **Existing tests stay green.**
|
|
20
|
+
4. **Version bump to `0.2.0`** in `package.json` reflects non-breaking additions.
|
|
21
|
+
5. **Wire types unchanged.** The new exports describe known tool *names*; they don't add new event types to `SalesBotEvent`.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## File map
|
|
26
|
+
|
|
27
|
+
| File | Action | Responsibility |
|
|
28
|
+
|---|---|---|
|
|
29
|
+
| `src/core/types.ts` | **Modify** | Add `SALES_WORKFLOW_TOOL_NAMES`, `SalesWorkflowToolName`, `isSalesWorkflowTool`. |
|
|
30
|
+
| `src/core/index.ts` | **Modify** | Already does `export * from './types'`, so the new exports flow through automatically. No edit needed if `*` re-export is in place. |
|
|
31
|
+
| `src/react/use-sales-bot.tsx` | **Modify** | Add `onSalesToolCall?: (e: ToolCallStartedEvent) => void` to `UseSalesBotOptions`; subscribe to `tool_call_started`, narrow on `isSalesWorkflowTool`, fire callback. |
|
|
32
|
+
| `src/vue/use-sales-bot.ts` | **Modify** | Same as the React hook. |
|
|
33
|
+
| `src/widget/widget.ts` | **Modify** | When a `tool_call_started` event matches a sales tool name, render an ephemeral status pill above the next assistant message. Remove on `tool_call_finished` with matching id. |
|
|
34
|
+
| `src/widget/styles.ts` | **Modify** | CSS for the status pill. |
|
|
35
|
+
| `tests/sales-tool-discriminator.spec.ts` | **Create** | Vitest unit tests for `isSalesWorkflowTool` + the React hook callback firing on the right events. |
|
|
36
|
+
| `package.json` | **Modify** | Bump version `0.1.0` → `0.2.0`. |
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Task 1: Baseline
|
|
41
|
+
|
|
42
|
+
- [ ] **Step 1.1:** Confirm baseline:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pnpm install
|
|
46
|
+
pnpm test --run 2>&1 | tail -8
|
|
47
|
+
pnpm tsc --noEmit
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Expected: existing tests pass; tsc clean.
|
|
51
|
+
|
|
52
|
+
- [ ] **Step 1.2:** Note baseline test count. No commit.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Task 2: Types + type guard + tests (RED → GREEN)
|
|
57
|
+
|
|
58
|
+
**Files:**
|
|
59
|
+
- Modify: `src/core/types.ts`
|
|
60
|
+
- Create: `tests/sales-tool-discriminator.spec.ts`
|
|
61
|
+
|
|
62
|
+
- [ ] **Step 2.1:** Write the failing test first.
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
// tests/sales-tool-discriminator.spec.ts
|
|
66
|
+
import { describe, it, expect } from 'vitest';
|
|
67
|
+
import {
|
|
68
|
+
SALES_WORKFLOW_TOOL_NAMES,
|
|
69
|
+
isSalesWorkflowTool,
|
|
70
|
+
type SalesWorkflowToolName,
|
|
71
|
+
} from '../src/core/types';
|
|
72
|
+
|
|
73
|
+
describe('SALES_WORKFLOW_TOOL_NAMES', () => {
|
|
74
|
+
it('is a readonly tuple containing all five sales-workflow tool names', () => {
|
|
75
|
+
expect(SALES_WORKFLOW_TOOL_NAMES).toEqual([
|
|
76
|
+
'project_brief__set_field',
|
|
77
|
+
'project_brief__get_current',
|
|
78
|
+
'quotes__generate',
|
|
79
|
+
'quotes__send_as_pdf',
|
|
80
|
+
'quotes__send_as_proposal',
|
|
81
|
+
]);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe('isSalesWorkflowTool', () => {
|
|
86
|
+
it('returns true for each sales-workflow tool name', () => {
|
|
87
|
+
expect(isSalesWorkflowTool('project_brief__set_field')).toBe(true);
|
|
88
|
+
expect(isSalesWorkflowTool('project_brief__get_current')).toBe(true);
|
|
89
|
+
expect(isSalesWorkflowTool('quotes__generate')).toBe(true);
|
|
90
|
+
expect(isSalesWorkflowTool('quotes__send_as_pdf')).toBe(true);
|
|
91
|
+
expect(isSalesWorkflowTool('quotes__send_as_proposal')).toBe(true);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('returns false for unrelated tool names', () => {
|
|
95
|
+
expect(isSalesWorkflowTool('chaindoc_media_upload')).toBe(false);
|
|
96
|
+
expect(isSalesWorkflowTool('llms_txt__search')).toBe(false);
|
|
97
|
+
expect(isSalesWorkflowTool('')).toBe(false);
|
|
98
|
+
expect(isSalesWorkflowTool('quotes__')).toBe(false);
|
|
99
|
+
expect(isSalesWorkflowTool('project_brief__unknown')).toBe(false);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('narrows the type when used as a type guard', () => {
|
|
103
|
+
const name: string = 'quotes__generate';
|
|
104
|
+
if (isSalesWorkflowTool(name)) {
|
|
105
|
+
// Inside this branch, TypeScript narrows `name` to `SalesWorkflowToolName`.
|
|
106
|
+
const narrowed: SalesWorkflowToolName = name;
|
|
107
|
+
expect(narrowed).toBe('quotes__generate');
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
- [ ] **Step 2.2:** Run the test — confirm it fails with "Cannot find module" or undefined-export.
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
pnpm test --run tests/sales-tool-discriminator.spec.ts 2>&1 | tail -10
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Expected: red.
|
|
120
|
+
|
|
121
|
+
- [ ] **Step 2.3:** Add to `src/core/types.ts` — append at the bottom of the file (after the existing exports):
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
// ---------------------------------------------------------------------------
|
|
125
|
+
// Sales-workflow tool name discriminators
|
|
126
|
+
// ---------------------------------------------------------------------------
|
|
127
|
+
//
|
|
128
|
+
// The backend's SalesWorkflowDispatcher registers these tools with the LLM
|
|
129
|
+
// alongside MCP tools. They surface to the SDK via standard `tool_call_started`
|
|
130
|
+
// / `tool_call_finished` events with the names below. Consumers can narrow on
|
|
131
|
+
// these names via `isSalesWorkflowTool` for typed handling.
|
|
132
|
+
|
|
133
|
+
/** All sales-workflow tool names. Order is intentional (set_field/get_current
|
|
134
|
+
* first, quotes after). */
|
|
135
|
+
export const SALES_WORKFLOW_TOOL_NAMES = [
|
|
136
|
+
'project_brief__set_field',
|
|
137
|
+
'project_brief__get_current',
|
|
138
|
+
'quotes__generate',
|
|
139
|
+
'quotes__send_as_pdf',
|
|
140
|
+
'quotes__send_as_proposal',
|
|
141
|
+
] as const;
|
|
142
|
+
|
|
143
|
+
export type SalesWorkflowToolName = (typeof SALES_WORKFLOW_TOOL_NAMES)[number];
|
|
144
|
+
|
|
145
|
+
/** Type guard for narrowing tool-call event names to SalesWorkflowToolName. */
|
|
146
|
+
export function isSalesWorkflowTool(name: string): name is SalesWorkflowToolName {
|
|
147
|
+
return (SALES_WORKFLOW_TOOL_NAMES as readonly string[]).includes(name);
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
- [ ] **Step 2.4:** Verify `src/core/index.ts` already does `export * from './types'`. If yes, no edit needed — the new exports flow through automatically. If it lists named re-exports instead, add the three names there.
|
|
152
|
+
|
|
153
|
+
- [ ] **Step 2.5:** Run the test — confirm 8/8 pass:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
pnpm test --run tests/sales-tool-discriminator.spec.ts 2>&1 | tail -10
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Expected: green.
|
|
160
|
+
|
|
161
|
+
- [ ] **Step 2.6:** `pnpm tsc --noEmit` — clean.
|
|
162
|
+
|
|
163
|
+
- [ ] **Step 2.7:** Commit:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
git add src/core/types.ts src/core/index.ts tests/sales-tool-discriminator.spec.ts
|
|
167
|
+
git commit -m "$(cat <<'EOF'
|
|
168
|
+
feat(core): SalesWorkflowToolName + isSalesWorkflowTool guard
|
|
169
|
+
|
|
170
|
+
Adds typed discriminators for the five sales-workflow tool events
|
|
171
|
+
the backend dispatches alongside MCP tools. Wire format is unchanged
|
|
172
|
+
— these names already arrive through the existing `tool_call_started`
|
|
173
|
+
/ `tool_call_finished` events. Consumers can now narrow on them with
|
|
174
|
+
type safety:
|
|
175
|
+
|
|
176
|
+
client.on('tool_call_started', (e) => {
|
|
177
|
+
if (isSalesWorkflowTool(e.name)) {
|
|
178
|
+
// e.name is narrowed to SalesWorkflowToolName
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
183
|
+
EOF
|
|
184
|
+
)"
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Task 3: React + Vue hook `onSalesToolCall` callback
|
|
190
|
+
|
|
191
|
+
**Files:**
|
|
192
|
+
- Modify: `src/react/use-sales-bot.tsx`
|
|
193
|
+
- Modify: `src/vue/use-sales-bot.ts`
|
|
194
|
+
|
|
195
|
+
- [ ] **Step 3.1:** React hook — read the existing `UseSalesBotOptions` definition, then extend it:
|
|
196
|
+
|
|
197
|
+
In `src/react/use-sales-bot.tsx`, find:
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
export interface UseSalesBotOptions extends SalesBotClientOptions {
|
|
201
|
+
/** Optional initial conversationId (e.g. restored from URL hash) */
|
|
202
|
+
conversationId?: string
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Add to this interface:
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
/**
|
|
210
|
+
* Fires when a sales-workflow tool call starts (e.g. quotes__send_as_pdf).
|
|
211
|
+
* Convenience over manually filtering tool_call_started events via
|
|
212
|
+
* isSalesWorkflowTool. Pass-through; receives the same payload.
|
|
213
|
+
*/
|
|
214
|
+
onSalesToolCall?: (event: import('../core/types').ToolCallStartedEvent) => void
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Inside the hook body, after the client is constructed and the event subscription block, wire up the callback. Find the existing `tool_call_started` handler (if any) or the `client.on(...)` subscription pattern. If the hook currently subscribes to events in a `useEffect`, add a sibling subscription:
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
useEffect(() => {
|
|
221
|
+
if (!opts.onSalesToolCall) return;
|
|
222
|
+
const client = clientRef.current;
|
|
223
|
+
if (!client) return;
|
|
224
|
+
const handler = (e: import('../core/types').ToolCallStartedEvent) => {
|
|
225
|
+
// Lazy-load isSalesWorkflowTool from the same module to avoid widening
|
|
226
|
+
// the runtime cost when consumers don't use this callback.
|
|
227
|
+
const { isSalesWorkflowTool } = require('../core/types') as typeof import('../core/types');
|
|
228
|
+
if (isSalesWorkflowTool(e.name)) {
|
|
229
|
+
opts.onSalesToolCall!(e);
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
const off = client.on('tool_call_started', handler);
|
|
233
|
+
return () => { if (typeof off === 'function') off(); };
|
|
234
|
+
}, [opts.onSalesToolCall]);
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
If the existing hook subscribes via `client.on(...)` returning an unsubscribe function, use that. If it uses async iteration over `client.events()` or similar, adapt this to match the existing subscription pattern. Read the actual file before writing the snippet — the comment above is the intent, not the literal final code.
|
|
238
|
+
|
|
239
|
+
`import('../core/types').ToolCallStartedEvent` is used in-line (no top-level type import) to keep the existing imports list minimal — change to a top-of-file import if the file's style prefers that.
|
|
240
|
+
|
|
241
|
+
- [ ] **Step 3.2:** Vue hook — same change in `src/vue/use-sales-bot.ts`. The Vue composable structure differs (uses Vue refs / watchers); follow the existing event-subscription pattern in that file.
|
|
242
|
+
|
|
243
|
+
- [ ] **Step 3.3:** Type-check:
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
pnpm tsc --noEmit
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Expected: clean.
|
|
250
|
+
|
|
251
|
+
- [ ] **Step 3.4:** Run the existing test suite — confirm nothing regressed:
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
pnpm test --run 2>&1 | tail -8
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Expected: all pass.
|
|
258
|
+
|
|
259
|
+
- [ ] **Step 3.5:** Commit:
|
|
260
|
+
|
|
261
|
+
```bash
|
|
262
|
+
git add src/react/ src/vue/
|
|
263
|
+
git commit -m "$(cat <<'EOF'
|
|
264
|
+
feat(hooks): onSalesToolCall callback in React + Vue hooks
|
|
265
|
+
|
|
266
|
+
Optional hook option that fires for sales-workflow tool calls
|
|
267
|
+
(quotes__*, project_brief__*) only. Convenience over manually
|
|
268
|
+
filtering tool_call_started events with isSalesWorkflowTool.
|
|
269
|
+
Additive — existing consumers continue to work unchanged.
|
|
270
|
+
|
|
271
|
+
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
272
|
+
EOF
|
|
273
|
+
)"
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## Task 4: Widget UI pills
|
|
279
|
+
|
|
280
|
+
**Files:**
|
|
281
|
+
- Modify: `src/widget/widget.ts`
|
|
282
|
+
- Modify: `src/widget/styles.ts`
|
|
283
|
+
|
|
284
|
+
- [ ] **Step 4.1:** Pill text mapping. In `src/widget/widget.ts`, near the top (after imports), add:
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
import { isSalesWorkflowTool, type SalesWorkflowToolName } from '../core/types';
|
|
288
|
+
|
|
289
|
+
const SALES_TOOL_PILL_TEXT: Record<SalesWorkflowToolName, string> = {
|
|
290
|
+
'project_brief__set_field': 'Saving project details…',
|
|
291
|
+
'project_brief__get_current': 'Checking project details…',
|
|
292
|
+
'quotes__generate': 'Generating quote…',
|
|
293
|
+
'quotes__send_as_pdf': 'Sending quote PDF…',
|
|
294
|
+
'quotes__send_as_proposal': 'Sending proposal for signing…',
|
|
295
|
+
};
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
- [ ] **Step 4.2:** Find the existing `tool_call_started` / `tool_call_finished` handlers in the widget. They currently call `ensureThinking()` etc. Augment them so:
|
|
299
|
+
|
|
300
|
+
- On `tool_call_started` with a sales tool name: also create a pill DOM element with the matching text, append to the current message-stream container, and track it by the event's `id`.
|
|
301
|
+
- On `tool_call_finished` with a sales tool name (matching id): remove the pill. If `ok: false`, briefly (4 seconds) replace the pill text with "Error" before removing.
|
|
302
|
+
|
|
303
|
+
Suggested implementation — adapt to match the existing widget file's idioms:
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
// State at the module / instance scope (where other widget state lives):
|
|
307
|
+
const salesPills = new Map<string, HTMLElement>();
|
|
308
|
+
|
|
309
|
+
// Inside the tool_call_started handler:
|
|
310
|
+
case 'tool_call_started': {
|
|
311
|
+
ensureThinking();
|
|
312
|
+
if (isSalesWorkflowTool(event.data.name)) {
|
|
313
|
+
const pill = document.createElement('div');
|
|
314
|
+
pill.className = 'sb-sales-pill';
|
|
315
|
+
pill.textContent = SALES_TOOL_PILL_TEXT[event.data.name as SalesWorkflowToolName];
|
|
316
|
+
// Append to whatever container the widget uses for the active message stream.
|
|
317
|
+
// If you keep a reference to the "current assistant message" container, append there;
|
|
318
|
+
// otherwise append to a known stream root.
|
|
319
|
+
streamContainer.appendChild(pill);
|
|
320
|
+
salesPills.set(event.data.id, pill);
|
|
321
|
+
}
|
|
322
|
+
break;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
case 'tool_call_finished': {
|
|
326
|
+
const pill = salesPills.get(event.data.id);
|
|
327
|
+
if (pill) {
|
|
328
|
+
if (event.data.ok === false) {
|
|
329
|
+
pill.textContent = 'Error';
|
|
330
|
+
pill.classList.add('sb-sales-pill-error');
|
|
331
|
+
setTimeout(() => {
|
|
332
|
+
pill.remove();
|
|
333
|
+
salesPills.delete(event.data.id);
|
|
334
|
+
}, 4000);
|
|
335
|
+
} else {
|
|
336
|
+
pill.remove();
|
|
337
|
+
salesPills.delete(event.data.id);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
// existing tool_call_finished handling continues:
|
|
341
|
+
// hideThinkingIfDone(); etc.
|
|
342
|
+
break;
|
|
343
|
+
}
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
Read the current widget file before applying this; the `streamContainer` reference and the `ensureThinking()` / `hideThinkingIfDone()` helpers must come from the existing widget code.
|
|
347
|
+
|
|
348
|
+
- [ ] **Step 4.3:** Append pill styles in `src/widget/styles.ts`. The widget styles are CSS-in-template-strings or similar; add a block:
|
|
349
|
+
|
|
350
|
+
```css
|
|
351
|
+
.sb-sales-pill {
|
|
352
|
+
display: inline-flex;
|
|
353
|
+
align-items: center;
|
|
354
|
+
margin: 4px 0;
|
|
355
|
+
padding: 4px 10px;
|
|
356
|
+
border-radius: 999px;
|
|
357
|
+
background: var(--sb-pill-bg, rgba(0, 0, 0, 0.05));
|
|
358
|
+
color: var(--sb-pill-fg, rgba(0, 0, 0, 0.7));
|
|
359
|
+
font-size: 12px;
|
|
360
|
+
line-height: 1.3;
|
|
361
|
+
animation: sb-sales-pill-pulse 1.8s ease-in-out infinite;
|
|
362
|
+
}
|
|
363
|
+
.sb-sales-pill-error {
|
|
364
|
+
background: var(--sb-pill-error-bg, rgba(255, 69, 58, 0.12));
|
|
365
|
+
color: var(--sb-pill-error-fg, rgba(155, 28, 28, 0.95));
|
|
366
|
+
animation: none;
|
|
367
|
+
}
|
|
368
|
+
@keyframes sb-sales-pill-pulse {
|
|
369
|
+
0%, 100% { opacity: 1; }
|
|
370
|
+
50% { opacity: 0.55; }
|
|
371
|
+
}
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
If `styles.ts` exports a single template string of CSS, append the rules into that string. If it exports a function that returns CSS, add inside the function body. Match the file's actual structure.
|
|
375
|
+
|
|
376
|
+
- [ ] **Step 4.4:** Manually verify the widget compiles and renders (open `example/` against a local backend if available — optional). At minimum:
|
|
377
|
+
|
|
378
|
+
```bash
|
|
379
|
+
pnpm tsc --noEmit
|
|
380
|
+
pnpm test --run 2>&1 | tail -8
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
Expected: clean + green.
|
|
384
|
+
|
|
385
|
+
- [ ] **Step 4.5:** Commit:
|
|
386
|
+
|
|
387
|
+
```bash
|
|
388
|
+
git add src/widget/
|
|
389
|
+
git commit -m "$(cat <<'EOF'
|
|
390
|
+
feat(widget): ephemeral status pills for sales-workflow tool calls
|
|
391
|
+
|
|
392
|
+
When the widget receives tool_call_started for project_brief__* or
|
|
393
|
+
quotes__* events, it renders a small pulsing pill above the active
|
|
394
|
+
assistant-message stream with friendly text ("Generating quote…",
|
|
395
|
+
"Sending proposal for signing…", etc.). On tool_call_finished the
|
|
396
|
+
pill is removed; on failure (ok=false) it briefly switches to an
|
|
397
|
+
"Error" state for 4 seconds before vanishing.
|
|
398
|
+
|
|
399
|
+
No-op for backends that don't dispatch these tool names — older
|
|
400
|
+
SDK behavior is preserved.
|
|
401
|
+
|
|
402
|
+
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
403
|
+
EOF
|
|
404
|
+
)"
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
---
|
|
408
|
+
|
|
409
|
+
## Task 5: Version bump + final verification
|
|
410
|
+
|
|
411
|
+
**Files:**
|
|
412
|
+
- Modify: `package.json`
|
|
413
|
+
|
|
414
|
+
- [ ] **Step 5.1:** Bump version in `package.json` from `0.1.0` to `0.2.0`:
|
|
415
|
+
|
|
416
|
+
```bash
|
|
417
|
+
# Edit package.json:
|
|
418
|
+
# "version": "0.1.0" → "version": "0.2.0"
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
- [ ] **Step 5.2:** Run the build to confirm the SDK still bundles cleanly:
|
|
422
|
+
|
|
423
|
+
```bash
|
|
424
|
+
pnpm build 2>&1 | tail -15
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
Expected: tsup outputs `dist/` files without errors. Size-limit checks (if present) should still pass.
|
|
428
|
+
|
|
429
|
+
- [ ] **Step 5.3:** Final tests + tsc:
|
|
430
|
+
|
|
431
|
+
```bash
|
|
432
|
+
pnpm test --run 2>&1 | tail -8
|
|
433
|
+
pnpm tsc --noEmit
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
Expected: all green.
|
|
437
|
+
|
|
438
|
+
- [ ] **Step 5.4:** Commit:
|
|
439
|
+
|
|
440
|
+
```bash
|
|
441
|
+
git add package.json
|
|
442
|
+
git commit -m "$(cat <<'EOF'
|
|
443
|
+
chore: bump SDK version to 0.2.0
|
|
444
|
+
|
|
445
|
+
Reflects additive sales-workflow polish:
|
|
446
|
+
- typed SalesWorkflowToolName + isSalesWorkflowTool guard
|
|
447
|
+
- onSalesToolCall callback on React + Vue hooks
|
|
448
|
+
- widget status pills for the new tool events
|
|
449
|
+
|
|
450
|
+
Non-breaking; existing consumers continue to work without changes.
|
|
451
|
+
|
|
452
|
+
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
453
|
+
EOF
|
|
454
|
+
)"
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
---
|
|
458
|
+
|
|
459
|
+
## Out-of-scope follow-ups
|
|
460
|
+
|
|
461
|
+
- Tool-call-specific entries on the `SalesBotEvent` discriminated union (current solution piggybacks on `tool_call_started`).
|
|
462
|
+
- Operator-customizable pill text (operator sets the "Generating quote…" string per tenant).
|
|
463
|
+
- Localization of pill text.
|
|
464
|
+
|
|
465
|
+
---
|
|
466
|
+
|
|
467
|
+
## Self-review notes
|
|
468
|
+
|
|
469
|
+
Spec coverage:
|
|
470
|
+
|
|
471
|
+
- §6.1 typed discriminators — Task 2.
|
|
472
|
+
- §6.2 widget UI hints — Task 4.
|
|
473
|
+
- §6.3 React/Vue hook callbacks — Task 3.
|
|
474
|
+
- §6.5 version bump — Task 5.
|
|
475
|
+
|
|
476
|
+
No placeholders. Each step's intent is spelled out with code blocks; the few places where the implementer must read the existing file before adapting (the widget pill insertion point, the hook subscription pattern) are explicitly flagged.
|