@wireai/activation 0.13.5 → 0.13.6-next.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 +131 -0
- package/dist/analytics/index.d.mts +2 -2
- package/dist/analytics/index.d.ts +2 -2
- package/dist/{currentSession-DgJf0fRz.d.mts → currentSession-Cs3lweFZ.d.mts} +13 -0
- package/dist/{currentSession-EOHU64QD.d.ts → currentSession-DD6dKB0i.d.ts} +13 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +24 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +24 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/OnboardingFlow.tsx +8 -1
- package/src/types.ts +13 -0
- package/src/utils/readPlan.ts +74 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wireai/activation",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.6-next.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Premium, fully-themable drop-in AI onboarding kit for React Native / Expo, on top of the open-source wireai-rn SDK.",
|
|
6
6
|
"author": "Malik Chohra <malik@getwireai.com>",
|
package/src/OnboardingFlow.tsx
CHANGED
|
@@ -33,6 +33,7 @@ import { ErrorBlock } from "./components/ErrorBlock";
|
|
|
33
33
|
import { CompletionView } from "./components/CompletionView";
|
|
34
34
|
import { CardHandoff } from "./components/CardHandoff";
|
|
35
35
|
import { deriveAnswers } from "./utils/deriveAnswers";
|
|
36
|
+
import { readPlan } from "./utils/readPlan";
|
|
36
37
|
import { readProgress } from "./utils/readProgress";
|
|
37
38
|
import { reportClientEvent, type ClientEventTarget } from "./analytics/reportClientEvent";
|
|
38
39
|
import { sendPreview } from "./analytics/sendPreview";
|
|
@@ -366,7 +367,13 @@ export const OnboardingFlow: React.FC<OnboardingFlowProps> = ({
|
|
|
366
367
|
const handleFinish = useCallback(() => {
|
|
367
368
|
if (finished.current) return;
|
|
368
369
|
finished.current = true;
|
|
369
|
-
|
|
370
|
+
const result: OnboardingResult = { answers: deriveAnswers(messages), raw: messages };
|
|
371
|
+
// The plan rides a second DataPart on the AI path only; the static flow carries none. Setting
|
|
372
|
+
// the key ONLY when there is one keeps the no-plan result byte-identical to the pre-plan
|
|
373
|
+
// object — a host that inspects `Object.keys(result)` or `"plan" in result` sees no change.
|
|
374
|
+
const plan = readPlan(messages);
|
|
375
|
+
if (plan !== undefined) result.plan = plan;
|
|
376
|
+
onCompleteRef.current(result);
|
|
370
377
|
}, [messages]);
|
|
371
378
|
|
|
372
379
|
// Manual retry (the no-fallback ErrorBlock button): clear the degrade + counter so
|
package/src/types.ts
CHANGED
|
@@ -53,6 +53,19 @@ export type OnboardingResult = {
|
|
|
53
53
|
answers: Record<string, unknown>;
|
|
54
54
|
/** The raw message thread, for custom downstream parsing. */
|
|
55
55
|
raw: Message[];
|
|
56
|
+
/**
|
|
57
|
+
* The backend's onboarding plan, when it sent one. Present ONLY on the AI path: a tenant running
|
|
58
|
+
* the static flow, or any run the server finished without a plan, leaves this `undefined` AND
|
|
59
|
+
* leaves the key off the result object entirely — so a host written before plans existed sees
|
|
60
|
+
* byte-identically what it always saw.
|
|
61
|
+
*
|
|
62
|
+
* ⚠️ The kit does NOT interpret this and does NOT validate it. It checks one structural fact (a
|
|
63
|
+
* plan is an object) and hands the payload straight through, unread, unlogged, and never attached
|
|
64
|
+
* to an analytics event. THE HOST MUST VALIDATE IT before applying it: the fields are
|
|
65
|
+
* backend-authored, they may gain new ones without a kit release, and what any of them mean is
|
|
66
|
+
* the host's decision, not the kit's.
|
|
67
|
+
*/
|
|
68
|
+
plan?: unknown;
|
|
56
69
|
};
|
|
57
70
|
|
|
58
71
|
/**
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* readPlan — lift the backend's onboarding plan off the message thread.
|
|
3
|
+
*
|
|
4
|
+
* WHY IT EXISTS. On the AI path the server appends a SECOND A2A DataPart to the turn it finishes
|
|
5
|
+
* on: `{ kind: "onboarding_plan", plan: {...} }`, alongside the component envelope the renderer
|
|
6
|
+
* already consumes. The kit carries that payload out through `OnboardingResult.plan` and stops
|
|
7
|
+
* there. It does NOT interpret it, does NOT validate its fields, does NOT log it and does NOT
|
|
8
|
+
* attach it to any event — the plan is user-derived content, and deciding what it MEANS is the
|
|
9
|
+
* host's job (the kit/host boundary in `ai_rules/context_map.md`: the kit ends at the completion
|
|
10
|
+
* CTA).
|
|
11
|
+
*
|
|
12
|
+
* THE ONE THING IT IS ALLOWED TO REJECT is a structural fact: a plan is an object. A scalar, an
|
|
13
|
+
* array, or a missing payload is not a plan, and yields `undefined` so the host falls back to
|
|
14
|
+
* exactly its pre-plan behavior. Nothing below that line is checked: no field is required and no
|
|
15
|
+
* unknown field is stripped (the server's lenient path adds its own markers), because a kit-side
|
|
16
|
+
* schema would make the kit the thing that rejects a plan the host could have used.
|
|
17
|
+
*
|
|
18
|
+
* MALFORMED INPUT NEVER THROWS. This runs inside `handleFinish`, the single path to `onComplete`,
|
|
19
|
+
* so a throw here would cost the user the completion of an onboarding they already finished. Every
|
|
20
|
+
* step below is a runtime-guarded read.
|
|
21
|
+
*
|
|
22
|
+
* The static (non-AI) flow carries no plan at all. That path is unchanged and fully supported.
|
|
23
|
+
*/
|
|
24
|
+
import type { Message } from "wireai-rn";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The read-back member the SDK surfaces for every A2A DataPart past the first, in wire order and
|
|
28
|
+
* uninterpreted. Declared HERE, structurally, because the installed `wireai-rn` peer (0.2.4) does
|
|
29
|
+
* not declare it on `Message` yet.
|
|
30
|
+
*
|
|
31
|
+
* TODO(re-pin): delete this local type and read the member straight off `Message` once a
|
|
32
|
+
* `wireai-rn` that declares it is published and the peer range is re-pinned. Until then the two
|
|
33
|
+
* shapes are equal by CONVENTION, not by the compiler, and `hasDataParts` below is what makes that
|
|
34
|
+
* safe at runtime. Deliberately NOT an `as` cast: a cast silences the compiler in BOTH directions,
|
|
35
|
+
* including the day the published type disagrees with this one.
|
|
36
|
+
*/
|
|
37
|
+
type WithDataParts = { dataParts: readonly unknown[] };
|
|
38
|
+
|
|
39
|
+
/** The marker the server stamps on the plan DataPart. */
|
|
40
|
+
const PLAN_PART_KIND = "onboarding_plan";
|
|
41
|
+
|
|
42
|
+
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
43
|
+
typeof value === "object" && value !== null && !Array.isArray(value);
|
|
44
|
+
|
|
45
|
+
/** RUNTIME proof that a value carries a `dataParts` ARRAY, before anything indexes into one. */
|
|
46
|
+
const hasDataParts = (value: unknown): value is WithDataParts =>
|
|
47
|
+
isRecord(value) && Array.isArray(value.dataParts);
|
|
48
|
+
|
|
49
|
+
/** The plan a single DataPart carries, or `undefined` when that part is not a well-formed plan. */
|
|
50
|
+
const planFromPart = (part: unknown): unknown => {
|
|
51
|
+
if (!isRecord(part) || part.kind !== PLAN_PART_KIND) return undefined;
|
|
52
|
+
return isRecord(part.plan) ? part.plan : undefined;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The plan the backend sent, or `undefined` when it sent none (or sent something that is not a
|
|
57
|
+
* plan). Never throws.
|
|
58
|
+
*
|
|
59
|
+
* ORDERING, stated so it is not undefined behavior: the thread is scanned NEWEST TURN FIRST, so a
|
|
60
|
+
* thread carrying two plans resolves to the LAST one — the same turn `handleFinish` completes on.
|
|
61
|
+
* Within a single turn, wire order decides: the first well-formed plan part wins. A malformed part
|
|
62
|
+
* is skipped rather than treated as a terminator, so a broken payload can never shadow a good one.
|
|
63
|
+
*/
|
|
64
|
+
export const readPlan = (messages: readonly Message[]): unknown => {
|
|
65
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
66
|
+
const message = messages[i];
|
|
67
|
+
if (!hasDataParts(message)) continue;
|
|
68
|
+
for (const part of message.dataParts) {
|
|
69
|
+
const plan = planFromPart(part);
|
|
70
|
+
if (plan !== undefined) return plan;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return undefined;
|
|
74
|
+
};
|