toga-ai 1.0.728 → 1.0.729

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,8 +6,8 @@ project: TOGa IQ
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-06-16
10
- owners: [akhokhani]
9
+ updated: 2026-09-01
10
+ owners: [akhokhani, apeterson]
11
11
  files:
12
12
  - talos/libs/aegra-api/src/aegra_api/main.py
13
13
  - talos/libs/aegra-api/src/aegra_api/settings.py
@@ -43,6 +43,8 @@ related:
43
43
  - ../architecture.md
44
44
  - mcp-servers.md
45
45
  - observability.md
46
+ - chat-frontend.md
47
+ - ../../toga25-supply/features/talos-integration.md
46
48
  ---
47
49
 
48
50
  ## Summary
@@ -202,5 +204,36 @@ None — uniform across clients. Per-tenant data isolation is enforced by
202
204
  - **Alembic password chars** — keep the `config.attributes` URL passing pattern
203
205
  in `env.py`; configparser interpolation will mangle `%` in passwords.
204
206
 
207
+ ### For browser clients (found 2026-09-01 wiring toga25-supply)
208
+
209
+ - **⚠ CORS headers come back on the OPTIONS PREFLIGHT ONLY — not on the actual response.**
210
+ Verified from both a local dev origin (`http://nychh.dev.sandbox.togasupply:5173`) and a prod
211
+ origin (`https://nychh.togasupply.com`): the preflight returns
212
+ `access-control-allow-origin: *`, but the real `GET` carries **no `access-control-*` header at
213
+ all**. A browser therefore blocks every real response even when the service is perfectly healthy.
214
+ Reported to `akhokhani`; a server-side caller (curl, the old talos proxy route) never sees this.
215
+ - **`AUTH_TYPE=togahub` accepts an api2 JWT issued for a NON-TogaHub client.** Confirmed with a
216
+ **toga25-supply** token whose `aud` was a supply hostname (`https://<tenant>.togasupply.com`) and
217
+ whose `iss`/`sub` were `http://api.beta.togahub.com` — `GET /assistants` succeeded. So a sibling
218
+ app needs **no** token exchange and no `/auth/delegator` handoff; it passes the token it already
219
+ holds. (Do not read `aud` as an audience check — it is not enforced.)
220
+ - **`assistant_id` is per-tenant. Discover it, never pin it in env.**
221
+ `GET /assistants?limit=100&offset=0` returns
222
+ `{ assistants: [{ assistant_id, name, graph_id, metadata: { customer_facing, read_only,
223
+ tenant_slug } }], total }`. Pick on `metadata.customer_facing === true`. Send **no body and no
224
+ `Content-Type`** — `fetch` forbids a body on GET and a JSON content-type only adds a preflight.
225
+ - **An unregistered tenant gets a clean 403 on every call even with a valid token** (control-plane
226
+ DB / `tenant_router` LRU). Provisioning is explicit; there is no auto-create.
227
+ - *Point-in-time note, may be stale:* on **2026-09-01** `https://api.beta.togaiq.com` returned
228
+ **502** on `/health`, `/info` and `/assistants` (nginx up, app behind it down) after a clean 401
229
+ ~10 minutes earlier. Re-probe `/info` before assuming a client bug.
230
+
205
231
  ## Change history
232
+ - 2026-09-01 — Added a **browser-client** gotcha set found while wiring toga25-supply: Aegra returns
233
+ CORS headers **on the OPTIONS preflight only**, so a browser blocks every real response even on a
234
+ healthy service (server-side callers never see it); `AUTH_TYPE=togahub` **accepts an api2 JWT
235
+ issued for a non-TogaHub client** (`aud` is not enforced), so sibling apps need no token exchange;
236
+ and `assistant_id` is **per-tenant** and must be discovered via `GET /assistants` (pick on
237
+ `metadata.customer_facing`, send no body/`Content-Type`). Also recorded a dated 502 outage
238
+ observation on `api.beta.togaiq.com`. (apeterson)
206
239
  - 2026-06-16 — Initial aegra-api feature doc (HTTP layer + execution pipeline). (akhokhani)
@@ -6,7 +6,7 @@ project: TOGa IQ
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-08-28
9
+ updated: 2026-09-01
10
10
  owners: [apeterson]
11
11
  files:
12
12
  - talos/package.json
@@ -154,15 +154,26 @@ rather than an assumption.
154
154
  ## Gotchas
155
155
 
156
156
  - **`@langchain/react` is a different package** from what this app uses. It is a newer wrapper
157
- (v1.0.33) over the same `@langchain/langgraph-sdk`, and it adds a `@langchain/core ^1.1.48` peer
158
- plus media hooks/players. Do not assume talos validates it — talos uses the SDK's own
159
- `/react` subpath.
157
+ (v1.0.33) over the same `@langchain/langgraph-sdk`, and it adds media hooks/players. Do not assume
158
+ talos validates it — talos uses the SDK's own `/react` subpath.
159
+ - **⚠ Correction (verified 2026-09-01): `@langchain/core` is NOT an extra peer of
160
+ `@langchain/react`.** This doc previously said choosing the SDK's `/react` subpath meant one fewer
161
+ peer. It does not. **`@langchain/langgraph-sdk@1.10.0` declares `@langchain/core: ^1.1.48` as a
162
+ required (non-optional) peer itself** — `react`/`react-dom` are the optional ones. Symptom when
163
+ it is absent: Vite/esbuild dep-optimize hard-fails with `Could not resolve
164
+ "@langchain/core/messages"` and the dev server exits. The **decision** to use the SDK subpath
165
+ still stands (proven in-house, smaller surface) — only the "one fewer peer" reason was wrong.
160
166
  - **Do not copy `nuqs`** into a non-Next app (see above).
161
167
  - **`env.ts` is the only sanctioned env read** ("never access `process.env` directly elsewhere") and
162
168
  `validateEnv()` throws at startup on missing vars. Keep new env reads inside it — but note its
163
169
  comments have already drifted from its values once.
164
170
 
165
171
  ## Change history
172
+ - 2026-09-01 — Correction only (no talos code change): the recorded rationale that
173
+ `@langchain/langgraph-sdk/react` means **one fewer peer** than `@langchain/react` is **wrong** —
174
+ the SDK declares `@langchain/core ^1.1.48` as a required peer itself, and omitting it hard-fails
175
+ Vite dep-optimize with `Could not resolve "@langchain/core/messages"`. The package choice itself
176
+ is unchanged. Found while building the same stack in toga25-supply. (apeterson)
166
177
  - 2026-08-28 — Documented the previously-undocumented TOGa IQ chat front-end: `useStream` from
167
178
  `@langchain/langgraph-sdk/react` wrapped in an app-owned context, the browser's **direct
168
179
  cross-origin call to Aegra** (proving a static SPA needs no server proxy) and the vestigial
@@ -11,7 +11,7 @@
11
11
  | [Primary Table templates (server/client, sizing, virtualization)](features/primary-table-templates.md) | `src/templates/PrimaryTable/` is the **production, wired-up table** built on the [Table component](table.md). | toga-blox/src/components/Table/themeConfig/toga.module.css, toga25-supply/src/themeConfig.json, toga-blox/src/templates/PrimaryTable/PrimaryTable.tsx, toga-blox/src/templates/PrimaryTable/PrimaryTableServerTemplate.tsx, toga-blox/src/templates/PrimaryTable/PrimaryTableClientTemplate.tsx, toga-blox/src/templates/PrimaryTable/PrimaryTableHeaderCell.tsx, toga-blox/src/templates/PrimaryTable/PrimaryTableBodyCell.tsx, toga-blox/src/templates/PrimaryTable/PrimaryTableRow.tsx, toga-blox/src/templates/PrimaryTable/PrimaryTableExpandableRow.tsx, toga-blox/src/templates/PrimaryTable/types.ts |
12
12
  | [TableRecordModal (record-detail modal shell)](features/table-record-modal.md) | `TableRecordModal` is a **generic, presentational modal shell** for showing a single table record (row) in detail — typically opened from a table row click. | toga-blox/src/components/TableRecordModal/TableRecordModal.tsx, toga-blox/src/components/TableRecordModal/index.ts, toga-blox/src/components/TableRecordModal/tableRecordModal.module.css |
13
13
  | [Table component (cells, action cells, filters & sorts, hooks, theming)](features/table.md) | The `Table` component (`src/components/Table/`) is the **TanStack Table v8** building block behind the [Primary Table templates](primary-table-templates.md). | toga-blox/src/components/Table/hooks/useFetchTablePageMeta.ts, toga-blox/src/api/types.ts, toga-blox/src/components/Table/index.ts, toga-blox/src/components/Table/types.ts, toga-blox/src/components/Table/utils/buildTanstackColumns.tsx, toga-blox/src/components/Table/utils/resolveCellType.tsx, toga-blox/src/components/Table/components/cellTypes, toga-blox/src/components/Table/components/actionCells, toga-blox/src/components/Table/components/columnFiltersAndSorts, toga-blox/src/components/Table/hooks, toga-blox/src/components/Table/themeConfig |
14
- | [Talos AI-Assistant Component (launcher + docked/drawer chat panel)](features/talos-assistant.md) | `Talos` is a **shared, pure-UI** AI-assistant component in `@agilant/toga-blox`: a header launcher button (`TalosLauncher`) plus a chat panel (`TalosPanel`) tha | toga-blox/src/components/Talos/TalosLauncher.tsx, toga-blox/src/components/Talos/TalosPanel.tsx, toga-blox/src/components/Talos/TalosMessage.tsx, toga-blox/src/components/Talos/types.ts, toga-blox/src/components/Talos/theme.ts, toga-blox/src/components/Talos/helpers.tsx, toga-blox/src/components/Talos/stub.ts, toga-blox/src/components/Talos/Talos.module.css, toga-blox/src/components/Talos/index.ts, toga-blox/src/components/index.ts |
14
+ | [Talos AI-Assistant Component (launcher + docked/drawer chat panel)](features/talos-assistant.md) | `Talos` is a **shared, pure-UI** AI-assistant component in `@agilant/toga-blox`: a header launcher button (`TalosLauncher`) plus a chat panel (`TalosPanel`) tha | toga-blox/src/components/Talos/TalosLauncher.tsx, toga-blox/src/components/Talos/TalosPanel.tsx, toga-blox/src/components/Talos/TalosMessage.tsx, toga-blox/src/components/Talos/types.ts, toga-blox/src/components/Talos/theme.ts, toga-blox/src/components/Talos/helpers.tsx, toga-blox/src/components/Talos/stub.ts, toga-blox/src/components/Talos/Talos.module.css, toga-blox/src/components/Talos/index.ts, toga-blox/src/components/Talos/TalosPanel.test.tsx, toga-blox/src/components/index.ts |
15
15
  | [Toaster — host-token styling contract](features/toaster.md) | The blox `Toaster` is a CSS-Module component whose every visual property reads a `--toaster-*` custom property supplied by the **host app**. | toga-blox/src/components/Toaster/Toaster.module.css, toga-blox/src/components/Toaster/Toaster.tsx, toga-blox/src/components/Toaster/ToasterContext.tsx, toga2-commerce/src/styles/index.css, toga2-commerce/src/components/Toasters/ToasterList.tsx |
16
16
  | [Dynamic npm Publish Pipeline (branch → channel)](workflows/dynamic-publish-pipeline.md) | How `@agilant/toga-blox` (checkout folder `toga-blox-npm`, registry repo key `toga-blox`) publishes a per-environment npm **channel** (dist-tag) from a `_<mode> | toga-blox/.github/workflows/publish.yml, toga-blox/package.json, toga-blox/src/utils/getFontAwesomeIcon.tsx |
17
17
  | [Landing a Large Long-Lived Feature Branch (merge, never rebase)](workflows/landing-a-large-feature-branch.md) | The procedure for landing a long-lived, very large feature branch (months old, hundreds of commits) into `_production` in `toga-blox-npm`. | toga-blox/package.json, toga-blox/.github/workflows/publish.yml |
@@ -6,7 +6,7 @@ project: TOGa Blox
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-08-28
9
+ updated: 2026-09-01
10
10
  owners: [apeterson, tcox]
11
11
  files:
12
12
  - toga-blox/src/components/Talos/TalosLauncher.tsx
@@ -18,6 +18,7 @@ files:
18
18
  - toga-blox/src/components/Talos/stub.ts
19
19
  - toga-blox/src/components/Talos/Talos.module.css
20
20
  - toga-blox/src/components/Talos/index.ts
21
+ - toga-blox/src/components/Talos/TalosPanel.test.tsx
21
22
  - toga-blox/src/components/index.ts
22
23
  related:
23
24
  - ../architecture.md
@@ -40,7 +41,11 @@ across toga25-supply, toga2-desk, toga2-commerce, and `ai-bdr`. Build once, reth
40
41
 
41
42
  The defining constraint: the component holds **no business logic and no `window.*` globals** —
42
43
  every side effect flows through an injected adapter, and every visual token is overridable.
43
- The real chat backend is a **later ticket**; `onSend` currently ships stubbed.
44
+
45
+ Since **2026-09-01** the panel also has a **controlled mode**: the host passes `messages` /
46
+ `isStreaming` / `onSubmit` / `onStop` / `connection` and owns the conversation entirely. That is how
47
+ toga25-supply runs the live streaming Aegra backend. **Uncontrolled mode is unchanged** — `onSend`
48
+ still ships stubbed for hosts that pass no `messages`.
44
49
 
45
50
  ## Architecture (the four load-bearing decisions)
46
51
 
@@ -131,15 +136,27 @@ button, dropping expanded mode) are variants, not theme overrides.
131
136
  (`"comfortable" | "compact"`), and possibly a launcher `variant` + optional panel-mode props.
132
137
  Open questions were handed to the UX/UI team.
133
138
 
134
- ## Where the code lives (status 2026-08-27 — check before planning around it)
139
+ ## Where the code lives — TWO divergent contracts on two branches (verified 2026-09-01)
140
+
141
+ **Talos is on NO mainline branch.** `origin/_main`, `_production`, `_sandbox-client`, `_sandbox-dev`
142
+ and `_beta` all lack `src/components/Talos/types.ts`. It is **in no npm dist-tag** — `latest` is
143
+ `1.0.317`, `production` is `1.1.1-production.139`, neither contains Talos.
144
+
145
+ | Branch | Contract |
146
+ |---|---|
147
+ | `origin/TRUE-80692` | **NARROW** — `paras` required; no `message` / `suggestions` / `cta`; `expanded` / `width` required; no `variant` / `features` |
148
+ | `origin/TRUE-80692-BDR` | **WIDENED** — everything the drawer work added |
135
149
 
136
- - **Talos exists only on `origin/TRUE-80692`** (apeterson; 2 commits 08-20 plus a merge of
137
- `_production` on 08-27). It is **not on the default branch** and **not in any npm dist-tag** —
138
- `latest` is `1.0.317`, `production` is `1.1.1-production.139`, neither contains Talos.
139
- - `onSend` is still the **stub**; peers still cap React at `^18`.
140
- - The blox test suite on `TRUE-80692` has **40 pre-existing failures unrelated to Talos** — the
141
- same 40 fail with the Talos changes stashed. Do not read them as a regression from this work.
142
- - The drawer variant itself was **uncommitted working tree** at capture (2026-08-27).
150
+ > **Correction to the 2026-08-27 record:** tcox's widening commit `484e2a6` is on
151
+ > **`TRUE-80692-BDR`**, *not* on the ticket branch `TRUE-80692`. Anyone branching off
152
+ > `TRUE-80692` expecting the drawer variant gets the narrow contract.
153
+
154
+ The 2026-09-01 controlled-mode work was done on `features-talos-transferorders`, which **merged
155
+ `origin/TRUE-80692-BDR`** first (merge verified not to touch `AdvancedSelect`).
156
+
157
+ - Peers still cap React at `^18`.
158
+ - The blox test suite has **~40 pre-existing failures unrelated to Talos** — the same 40 fail with
159
+ the Talos changes stashed. Do not read them as a regression from this work.
143
160
 
144
161
  ## The contract cannot express streaming — a real backend REQUIRES a contract change
145
162
 
@@ -161,19 +178,43 @@ Aegra/LangGraph backend (which streams tokens, emits tool calls, and surfaces HI
161
178
  see [TOGa IQ chat frontend](../../talos/features/chat-frontend.md)) means changing this component's
162
179
  props. That was accepted deliberately rather than shipping a non-streaming v1.
163
180
 
164
- **Agreed direction (DECIDED 2026-08-28, NOT yet implemented):** make `TalosPanel` **controlled** —
165
- the host passes `messages` / `isStreaming` / `onSubmit` / `onStop` — plus an optional
166
- `connection?: "connecting" | "ready" | "error"` defaulting to `"ready"`.
181
+ ## Controlled mode — BUILT 2026-09-01 (the TRUE-80692 contract change)
182
+
183
+ Implemented on branch `features-talos-transferorders`. New **optional** props on `TalosPanelProps`:
184
+
185
+ ```ts
186
+ messages?: TalosMessageModel[];
187
+ isStreaming?: boolean;
188
+ onSubmit?: (text: string) => void;
189
+ onStop?: () => void;
190
+ connection?: TalosConnection; // new exported type
191
+ // export type TalosConnection = "connecting" | "ready" | "error"; default "ready"
192
+ ```
193
+
194
+ **Controlled mode is keyed off `messages` being PASSED AT ALL** — not off a `controlled` flag. So
195
+ every existing consumer and `createTalosStubAdapter()` are untouched. That non-breaking property
196
+ matters: **BDR is a live consumer today.**
197
+
198
+ Behaviour when controlled:
199
+
200
+ - The panel renders the **host's** messages and **never calls `adapter.onSend`**.
201
+ - **It does not append optimistically.** The host's next `messages` already carries the user turn,
202
+ so appending would duplicate it.
203
+ - **`history` and `newChat` are forced OFF** — they act on the panel's own conversation list, which
204
+ it no longer owns.
205
+ - The composer is **disabled until `connection === "ready"`**, with placeholders
206
+ `"Connecting to Talos…"` (connecting) and `"Talos is unavailable right now."` (error).
207
+ - The send button **swaps to a stop button** when `isStreaming && onStop`.
167
208
 
168
- Two properties make this safe for a published package:
209
+ Why this shape was safe to add: `open` / `expanded` / `width` were *already* host-controlled props,
210
+ so moving `messages` to the host is the same pattern, not a new one. And `connection` gives the
211
+ panel a real "we never got a token to begin with" state — previously a thrown `onSend` was its only
212
+ failure path.
169
213
 
170
- - **Non-breaking.** `connection` defaults to `"ready"`, so existing consumers and
171
- `createTalosStubAdapter()` keep working untouched.
172
- - **Consistent with what is already here.** `open` / `expanded` / `width` are *already* controlled
173
- props owned by the host; moving `messages` to the host is the same pattern, not a new one.
214
+ ### Tests (first ever for this component)
174
215
 
175
- The panel also needs real **"connecting" / "failed to connect"** states: today a thrown `onSend` is
176
- its *only* failure path, which cannot represent "we never got a token to begin with."
216
+ `TalosPanel.test.tsx` — **8 Vitest + RTL tests**, all passing, including **two that pin
217
+ uncontrolled behaviour as unchanged**. (Separate from the ~40 pre-existing unrelated suite failures.)
177
218
 
178
219
  **The stream itself does NOT belong in blox.** Per
179
220
  [frontend.md §21](../../../standards/frontend.md) (promote only after a 2nd consumer validates the
@@ -185,8 +226,18 @@ Rollup *"failed to resolve import"* for a component they never referenced). blox
185
226
 
186
227
  ## Gotchas
187
228
 
188
- - **`onSend` cannot stream** — see the section above. A live streaming backend needs the
189
- `TRUE-80692` contract change, not just an adapter.
229
+ - **⚠ `TalosMessage` reads a DIFFERENT field per role.** A `role: "user"` turn renders
230
+ **`message.text`**; a `role: "talos"` turn renders **`message.message` / `message.paras`**. Put a
231
+ user turn's text in `message` and you get an **empty bubble** — no error, no warning. This bit the
232
+ supply LangGraph mapper and was only caught by writing tests.
233
+ ```ts
234
+ { role: "user", text } // ✅ user turn
235
+ { role: "talos", message } // ✅ assistant turn
236
+ ```
237
+ - **`onSend` cannot stream** — use **controlled mode** (above). A live streaming backend needs the
238
+ controlled props, not a cleverer adapter.
239
+ - **Tool calls and HITL interrupts still have no representation** in `TalosMessageModel`. A host
240
+ mapping from LangGraph must drop them. Surfacing them needs new blox props.
190
241
  - The real chat backend is a later blox ticket — `onSend` is stubbed here; do not assume live
191
242
  responses from blox alone. **A working implementation exists outside blox and is now wired to
192
243
  this component:** `bdr/src/lib/talosChat.ts` implements the same
@@ -212,6 +263,19 @@ Rollup *"failed to resolve import"* for a component they never referenced). blox
212
263
  currently consumes it via a local symlink.
213
264
 
214
265
  ## Change history
266
+ - 2026-09-01 — **BUILT controlled mode** (the TRUE-80692 contract change, previously only decided):
267
+ optional `messages` / `isStreaming` / `onSubmit` / `onStop` / `connection` on `TalosPanelProps`
268
+ plus a new exported `TalosConnection` type. Controlled mode keys off **`messages` being passed at
269
+ all**, so every existing consumer and `createTalosStubAdapter()` are untouched — non-breaking
270
+ matters because **BDR is live on this component**. Controlled panels never call `adapter.onSend`,
271
+ never append optimistically (the host's next `messages` already has the user turn), force
272
+ `history`/`newChat` off, disable the composer until `connection === "ready"`, and swap send→stop
273
+ while streaming. Added the component's **first tests** (`TalosPanel.test.tsx`, 8 Vitest+RTL, two
274
+ pinning uncontrolled behaviour). FIXED a real rendering trap found by those tests: `TalosMessage`
275
+ reads **`text` for user turns** but **`message`/`paras` for Talos turns**, so a user turn written
276
+ to `message` renders an empty bubble. DISCOVERED the branch topology is not what was recorded —
277
+ Talos is on **no** mainline branch, and tcox's widening commit lives on **`TRUE-80692-BDR`**, not
278
+ on `TRUE-80692`; the two branches carry **divergent contracts**. (apeterson)
215
279
  - 2026-08-28 — Review only (no blox code change): established that the `onSend(text) =>
216
280
  Promise<TalosResponse>` contract **cannot express token streaming** — the 08-27 `message`/
217
281
  `suggestions`/`cta` additions widen the *payload* but it still resolves once — so a live
@@ -6,7 +6,7 @@ project: TOGa Blox
6
6
  client: shared
7
7
  type: workflow
8
8
  status: active
9
- updated: 2026-08-31
9
+ updated: 2026-09-01
10
10
  owners: [tcox, apeterson]
11
11
  files:
12
12
  - toga-blox/package.json
@@ -45,6 +45,35 @@ npm creates a **junction** at `<app>/node_modules/@agilant/toga-blox` pointing a
45
45
  `mklink /J` by hand works too. Use a **junction, not `mklink /D`** — a directory symlink needs
46
46
  administrator rights; a junction does not.
47
47
 
48
+ ### 1a. ⚠ EVERY `npm install` in the consumer destroys the link — re-link after each one
49
+
50
+ `npm install` (even installing an unrelated package) replaces the
51
+ `node_modules/@agilant/toga-blox` **symlink** with the registry version. On toga25-supply that is
52
+ `1.1.1-production.139`, which contains **no Talos at all**.
53
+
54
+ The symptom looks nothing like a dependency problem:
55
+
56
+ ```
57
+ Uncaught SyntaxError: The requested module
58
+ '/node_modules/.vite/deps/@agilant_toga-blox.js' does not provide an export named 'TalosLauncher'
59
+ ```
60
+
61
+ **Re-link (from the consumer root):**
62
+
63
+ ```bash
64
+ rm -rf node_modules/@agilant/toga-blox
65
+ ln -s ../../../toga-blox-npm node_modules/@agilant/toga-blox
66
+ rm -rf node_modules/.vite
67
+ # then restart the dev server
68
+ ```
69
+
70
+ - **`ln -sfn` alone is NOT enough** when the path is a real directory — it creates the link
71
+ *inside* it. You must `rm -rf` first.
72
+ - Clearing `node_modules/.vite` is required because the stale pre-bundle is served `immutable`
73
+ (see §2c).
74
+ - Same rule after editing blox source: `npm run build` in `toga-blox-npm`, **then** clear
75
+ `node_modules/.vite` in the consumer.
76
+
48
77
  ### 2. Collapse to ONE React (the React 19 case)
49
78
 
50
79
  `toga-blox-npm/node_modules` ships its own `react` + `react-dom` **18.3.1**. Through a link,
@@ -207,6 +236,11 @@ The consumer reads blox's `dist`, so rebuild blox after every change.
207
236
  drags every component's CSS into the consumer bundle.
208
237
 
209
238
  ## Change history
239
+ - 2026-09-01 — Recorded that **any `npm install` in the consumer silently replaces the blox symlink
240
+ with the registry version** (toga25-supply → `1.1.1-production.139`, which has no Talos), and that
241
+ the symptom is a misleading `does not provide an export named 'TalosLauncher'`. Added the re-link
242
+ recipe and the trap that **`ln -sfn` alone fails when the target path is a real directory** — it
243
+ creates the link inside it, so `rm -rf` first. (apeterson)
210
244
  - 2026-08-31 — Added the "is it actually linked?" check: `toga25-supply` is on a **real registry
211
245
  install** of `1.1.1-production.139` (`LinkType` empty), not a junction, so blox source edits reach
212
246
  it only through publish + pin bump; and recorded that the urgent-case **installed-`dist` patch** is
@@ -2,7 +2,7 @@
2
2
 
3
3
  | Doc | Summary | Files |
4
4
  |-----|---------|-------|
5
- | [TOGa 2.5 Supply — Architecture](architecture.md) | `toga25-supply` ("TOGa 2.5 Supply") is the **React/TypeScript frontend** for the 2.0 Supply application — an iteration and improvement of `toga2-supply`. | toga25-supply/src/main.tsx, toga25-supply/src/App.tsx, toga25-supply/src/routes.tsx, toga25-supply/src/fieldsConfig/index.ts, toga25-supply/src/fieldsConfig/useClientFields.ts, toga25-supply/src/layout/, toga25-supply/src/pages/, toga25-supply/src/hooks/useTableCellInteractions.ts |
5
+ | [TOGa 2.5 Supply — Architecture](architecture.md) | `toga25-supply` ("TOGa 2.5 Supply") is the **React/TypeScript frontend** for the 2.0 Supply application — an iteration and improvement of `toga2-supply`. | toga25-supply/src/main.tsx, toga25-supply/src/App.tsx, toga25-supply/src/routes.tsx, toga25-supply/src/fieldsConfig/index.ts, toga25-supply/src/fieldsConfig/useClientFields.ts, toga25-supply/src/layout/, toga25-supply/src/pages/, toga25-supply/src/hooks/useTableCellInteractions.ts, toga25-supply/src/providers/TalosStreamProvider.tsx |
6
6
  | [Action-Button Rule Engine (Flag / Rule grammar)](features/action-button-rule-engine.md) | A declarative, fully config-driven rule engine that resolves the boolean-ish flags (`isEnabled`, `isVisible`, `isComplete`) on SalesOrder action-button options. | toga25-supply/src/pages/SalesOrders/helpers/evaluateEnableRule.ts, toga25-supply/src/pages/SalesOrders/helpers/evaluateEnableRule.test.ts, toga25-supply/src/pages/SalesOrders/helpers/buildPatchedTenantFields.ts, toga25-supply/src/layout/RecordApprovalModal/view/ApprovalFlowDetailInputs.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderApprovalModalsLayout/viewModel/FIELDS/COMPASS/approvalActionFields.json, toga25-supply/src/pages/SalesOrders/view/SalesOrderApprovalModalsLayout/viewModel/FIELDS/COMPASSCANADA/approvalActionFields.json, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/viewModel/useSalesOrderRecordModalLayoutModel.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderApprovalModalsLayout/viewModel/useApprovalModalViewModel.tsx |
7
7
  | [Client API-Fetch Fields (useClientApiFields / apiFields.json)](features/client-api-fetch-fields.md) | The mechanism that resolves a client's **API-fetch projection** — which `fields` / `ojoin` / `join` / `where` to request from the TOGa API for a given page — fr | toga25-supply/src/fieldsConfig/resolveApiConfig.ts, toga25-supply/src/fieldsConfig/useClientApiFields.ts, toga25-supply/src/fieldsConfig/index.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/viewModel/FIELDS/apiFields.json, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/viewModel/useSalesOrderRecordModalLayoutModel.tsx, toga25-supply/src/layout/ItemRecordModalLayout/viewModel/FIELDS/apiFields.json, toga25-supply/src/layout/ItemRecordModalLayout/viewModel/useItemRecordModalViewModel.tsx |
8
8
  | [Client-Configurable Fields (useClientFields / fieldsConfig)](features/client-configurable-fields.md) | The mechanism for config that **varies by client** (or client × role) — field overrides, filter buttons, group-by options, column pickers, layout toggles — with | toga25-supply/src/fieldsConfig/index.ts, toga25-supply/src/fieldsConfig/useClientFields.ts, toga25-supply/src/pages/SalesOrders/viewModel/FIELDS/, toga25-supply/src/pages/Inventory/viewModel/FIELDS/, toga25-supply/src/pages/Inventory/README.md, toga25-supply/src/layout/ItemRecordModalLayout/viewModel/FIELDS/, toga25-supply/src/layout/VendorItemRecordModalLayout/viewModel/FIELDS/, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/sections/SalesOrderTopBar.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/viewModel/FIELDS/ |
@@ -13,7 +13,7 @@
13
13
  | [Side navigation & default route — an empty nav renders a BLANK PAGE and gets reported as "cannot log in"](features/side-navigation-and-default-route.md) | The 2.5 side nav is **100% backend-driven** by the `navigation` surface bundle, and the same list also decides **which routes exist** and **where `/` lands**. | toga25-supply/src/routes.tsx, toga25-supply/src/layout/AppLayout/viewModel/useAppLayoutViewModel.ts, api2/Component/Api/V2/V2.php, _underscore/Model/Core/Surface.php |
14
14
  | [SSO redirect & public-vs-user session gating (useAuthenticationFlow)](features/sso-redirect-and-session-gating.md) | How 2.5 Supply decides, on every navigation, whether an anonymous visitor should be bounced to their client's SSO IdP instead of the local `/login` form. | toga25-supply/src/hooks/useAuthenticationFlow.ts, toga25-supply/src/routes.tsx, toga25-supply/src/contexts/AuthContext.tsx, toga25-supply/src/api/api.ts |
15
15
  | [Surface Frontend (DB-driven UI consumption, src/surface/)](features/surface-frontend.md) | The frontend consumer of the platform-wide Surface layer — DB-driven UI config fetched from `GET /v2/surfaces/meta?slug=<slug>` instead of statically-imported J | toga25-supply/src/App.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/layoutComponents/DenialBanner.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/SalesOrderRecordModal.module.css, toga25-supply/src/pages/SalesOrders/view/SalesOrderApprovalModalsLayout/SalesOrderApprovalModalsLayout.tsx, toga25-supply/src/layout/RecordApprovalModal/RecordApprovalModalLayout.tsx, toga25-supply/src/layout/RecordApprovalModal/view/ApprovalTimelineView.tsx, toga25-supply/src/layout/RecordApprovalModal/ApprovalModal.module.css, toga25-supply/src/utils/formatDateTime.ts, toga25-supply/src/utils/index.ts, toga25-supply/src/surface/evaluateSurfaceRule.ts, toga25-supply/src/layout/RecordApprovalModal/, toga25-supply/src/contexts/AuthContext.tsx, toga25-supply/src/surface/applyColSpan.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/sectionRenderers.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/getVisibleSections.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/layoutComponents/SalesOrderApprovalSummaryGrid.tsx, toga25-supply/src/surface/useFetchSurfaceMeta.ts, toga25-supply/src/pages/SalesOrders/helpers/surfaceBundleToTenantFields.ts, toga25-supply/src/pages/ServiceRequests/view/ServiceRequestRecordModalLayout/ServiceRequestRecordModalLayout.tsx, toga25-supply/src/pages/ServiceRequests/view/ServiceRequestRecordModalLayout/view/ServiceRequestsView.tsx, toga25-supply/src/pages/ServiceRequests/view/ServiceRequestRecordModalLayout/viewModel/useServiceRequestRecordModalLayoutModel.tsx, toga25-supply/src/pages/ServiceRequests/view/ServiceRequestRecordModalLayout/viewModel/FIELDS/apiFields.json, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/sections/SalesOrderTopBar.tsx, toga25-supply/src/pages/SalesOrders/helpers/buildPatchedTenantFields.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/viewModel/useSalesOrderRecordModalLayoutModel.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/SalesOrderView.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/layoutComponents/SalesOrderSummaryGrid.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/getDetailSections.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/sections/SalesOrderNotesSection.tsx, toga25-supply/src/pages/SalesOrders/helpers/cleanOrder.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/sections/AdminNotesSection.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/getAdminNotes.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/index.ts, toga25-supply/src/surface/evaluateSurfaceRule.ts, toga25-supply/src/surface/resolveElementState.ts, toga25-supply/src/pages/SalesOrders/helpers/evaluateEnableRule.ts, toga25-supply/src/pages/SalesOrders/hooks/useSalesOrderRowRecordState.ts, toga25-supply/src/surface/actionRegistry.ts, toga25-supply/src/surface/componentRegistry.tsx, toga25-supply/src/surface/SurfaceActionBar.tsx, toga25-supply/src/surface/SurfaceSection.tsx, toga25-supply/src/surface/resolve.ts, toga25-supply/src/surface/types.ts, toga25-supply/src/surface/index.ts, toga25-supply/src/pages/Login/LoginPage.tsx, toga25-supply/src/pages/SalesOrders/SalesOrders.tsx, toga25-supply/src/pages/SalesOrders/view/SurfaceRowActions.tsx, toga25-supply/src/pages/SalesOrders/hooks/useSalesOrderVip.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/sections/SalesOrderTopBar.tsx, toga25-supply/src/pages/Items/ItemsPage.tsx, toga25-supply/src/pages/Items/viewModel/useItemsPageViewModel.tsx, toga25-supply/src/pages/VendorItems/VendorItemsPage.tsx, toga25-supply/src/pages/VendorItems/viewModel/useVendorItemsPageViewModel.tsx, toga25-supply/src/pages/Inventory/Inventory.tsx, toga25-supply/src/pages/Inventory/viewModel/useInventoryPageViewModel.tsx, toga25-supply/src/pages/Inventory/viewModel/FIELDS/index.ts, toga25-supply/src/fieldsConfig/index.ts, toga25-supply/src/layout/ItemRecordModalLayout/helpers/surfaceBundleToItemFields.ts, toga25-supply/src/layout/ItemRecordModalLayout/helpers/index.ts, toga25-supply/src/layout/ItemRecordModalLayout/viewModel/useItemRecordModalViewModel.tsx, toga25-supply/src/layout/ItemRecordModalLayout/ItemRecordModalLayout.tsx, toga25-supply/src/layout/ItemRecordModalLayout/components/ItemRecordView.tsx, toga25-supply/src/surface/useStatusColors.ts, toga25-supply/src/surface/SurfaceHeader.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderApprovalModalsLayout/helpers/surfaceBundlesToDecisionFields.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderApprovalModalsLayout/viewModel/useApprovalModalViewModel.tsx |
16
- | [Talos Integration (AppLayout host + adapter wiring)](features/talos-integration.md) | toga25-supply is the first host of the shared blox [Talos assistant](../../toga-blox/features/talos-assistant.md). | toga25-supply/src/layout/AppLayout/AppLayout.tsx, toga25-supply/src/components/Header/Header.tsx, toga25-supply/src/components/Header/Header.module.css, toga25-supply/src/index.css, toga25-supply/src/assets/talos-owl.png |
16
+ | [Talos Integration (AppLayout host, live Aegra streaming, LangGraph→blox mapper)](features/talos-integration.md) | toga25-supply is the first host of the shared blox [Talos assistant](../../toga-blox/features/talos-assistant.md). | toga25-supply/src/api/talos.ts, toga25-supply/src/hooks/useTalosSession.ts, toga25-supply/src/providers/TalosStreamProvider.tsx, toga25-supply/src/utils/talosMessages.ts, toga25-supply/src/utils/talosMessages.test.ts, toga25-supply/src/layout/AppLayout/AppLayout.tsx, toga25-supply/src/components/Header/Header.tsx, toga25-supply/src/components/Header/Header.module.css, toga25-supply/src/index.css, toga25-supply/src/assets/talos-owl.png |
17
17
  | [Transfer Orders page (TableView → Core surfaces → React page + record modal)](features/transfer-orders-page.md) | The Transfer Orders screen — list + read-only record modal — built end to end on 2026-08-28 from a Claude Design prototype. | toga25-supply/src/pages/TransferOrders/TransferOrders.tsx, toga25-supply/src/pages/TransferOrders/hooks/useTransferOrdersTableState.ts, toga25-supply/src/pages/TransferOrders/viewModel/useTransferOrdersPageViewModel.tsx, toga25-supply/src/pages/TransferOrders/view/TransferOrdersTableLayout/TransferOrdersTableLayout.tsx, toga25-supply/src/pages/TransferOrders/view/TransferOrderRecordModalLayout/TransferOrderRecordModalLayout.tsx, toga25-supply/src/pages/TransferOrders/view/TransferOrderRecordModalLayout/hooks/useTransferOrderRecord.ts, toga25-supply/src/pages/TransferOrders/view/TransferOrderRecordModalLayout/viewModel/useTransferOrderRecordModalLayoutModel.ts, toga25-supply/src/pages/TransferOrders/view/TransferOrderRecordModalLayout/view/TransferOrderRecordView.tsx, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/CreateTransferOrderModal.tsx, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/index.ts, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/hooks/usePurchaseOrderItemRows.ts, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/hooks/useTargetLocationOptions.ts, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/hooks/useTransferSourceLocation.ts, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/hooks/useLocationContacts.ts, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/hooks/useCreateLocationContact.ts, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/view/AddContactModal.tsx, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/view/TransferNotesField.tsx, dbchanges2/Client/2026-09-01a - PurchaseOrderItemQtyFieldsApiRoleRead.sql, toga25-supply/src/surface/useColumnVisibilityModalConfig.ts, toga25-supply/src/routes.tsx, dbchanges2/Core/2026-08-28a - TransferOrderSurfaceSeed.sql, dbchanges2/Core/2026-08-28b - TransferOrderListActionsSurfaceSeed.sql, dbchanges2/Client/2026-08-28a - TransferOrderStageThemeTokens.sql, dbchanges2/Client/2026-08-28b - TransferOrderItemsTimestamps.sql, dbchanges2/Client_Nychh/2026-08-28a - TransferOrdersTableView.sql, dbchanges2/Client_Nychh/2026-08-28b - TransferOrdersNavigationEnable.sql, dbchanges2/Client_Nychh/2026-08-28c - TransferOrderRecordAcl.sql |
18
18
  | [AWS Amplify Multi-Environment Deployment](workflows/amplify-deployment.md) | How `toga25-supply` deploys to **all** of its environments on AWS Amplify from a **single shared `amplify.yml`**. | toga25-supply/amplify.yml, toga25-supply/src/api/api.ts, toga25-supply/src/hooks/useAuthenticationFlow.ts, toga25-supply/vite.config.ts, toga25-supply/package.json, toga25-supply/.env.development |
19
19
  | [Cypress Testing Harness (component + e2e)](workflows/cypress-testing.md) | The Cypress test harness for the `toga25-supply` frontend, bootstrapped from scratch (`cypress` was already a dependency but there was no config, no `cypress/` | toga25-supply/cypress.config.ts, toga25-supply/cypress/tsconfig.json, toga25-supply/cypress/support/component.tsx, toga25-supply/cypress/support/component-index.html, toga25-supply/cypress/support/e2e.ts, toga25-supply/cypress/support/commands.ts, toga25-supply/cypress/support/fixtures.ts, toga25-supply/cypress/support/mocks/useApprovalModalViewModel.ts, toga25-supply/cypress/component/SalesOrderApprovalModalsLayout.cy.tsx, toga25-supply/cypress/component/RecordApprovalModalLayout.cy.tsx, toga25-supply/cypress/component/EnterPoNumberModal.cy.tsx, toga25-supply/cypress/e2e/salesOrderApproval.cy.ts |
@@ -17,7 +17,9 @@ files:
17
17
  - toga25-supply/src/layout/
18
18
  - toga25-supply/src/pages/
19
19
  - toga25-supply/src/hooks/useTableCellInteractions.ts
20
+ - toga25-supply/src/providers/TalosStreamProvider.tsx
20
21
  related:
22
+ - features/talos-integration.md
21
23
  - features/client-configurable-fields.md
22
24
  - features/meta-driven-table-data.md
23
25
  - features/column-visibility.md
@@ -118,5 +120,14 @@ its primary consumer and resolves it via a symlinked `node_modules/@agilant/toga
118
120
  can serve a persisted entry from a **previous session** and never fetch — the symptom is an empty
119
121
  list/dropdown with **nothing in the network tab**, which reads as a backend fault and is not one.
120
122
  (`staleTime: Infinity` on the **surface-meta** hooks is a deliberate, separate exception.)
121
- - **Typecheck with `npx tsc --noEmit` / `npx tsc -b`** after changes; adding a required key to
122
- `ClientFields` surfaces every bundle missing it.
123
+ - **⚠ `npx tsc --noEmit` at the repo root checks NOTHING — it is a silent no-op.** Verified
124
+ 2026-09-01: the root `tsconfig.json` is references-only (`files: []`), so it compiles nothing
125
+ and **always exits 0**, reporting success on a codebase full of type errors.
126
+ **Always type-check with `npx tsc --noEmit -p tsconfig.app.json`** (or `npx tsc -b`, which
127
+ walks the references). Adding a required key to `ClientFields` then surfaces every bundle
128
+ missing it. The identical trap is documented for `toga2-view`
129
+ ([architecture](../toga2-view/architecture.md)); **any app sharing this tsconfig shape (likely
130
+ `toga25-desk`) is probably affected too — verify before asserting it.**
131
+ - **Talos streaming lives in `src/providers/TalosStreamProvider.tsx`, above the router outlet.**
132
+ `AppLayout` wraps `AppLayoutInner` so it can consume the provider it renders. See
133
+ [talos-integration](features/talos-integration.md).
@@ -1,14 +1,19 @@
1
1
  ---
2
- title: Talos Integration (AppLayout host + adapter wiring)
2
+ title: Talos Integration (AppLayout host, live Aegra streaming, LangGraph→blox mapper)
3
3
  framework: "2.0"
4
4
  repo: toga25-supply
5
5
  project: TOGa 2.5 Supply
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-08-28
9
+ updated: 2026-09-01
10
10
  owners: [apeterson]
11
11
  files:
12
+ - toga25-supply/src/api/talos.ts
13
+ - toga25-supply/src/hooks/useTalosSession.ts
14
+ - toga25-supply/src/providers/TalosStreamProvider.tsx
15
+ - toga25-supply/src/utils/talosMessages.ts
16
+ - toga25-supply/src/utils/talosMessages.test.ts
12
17
  - toga25-supply/src/layout/AppLayout/AppLayout.tsx
13
18
  - toga25-supply/src/components/Header/Header.tsx
14
19
  - toga25-supply/src/components/Header/Header.module.css
@@ -16,132 +21,206 @@ files:
16
21
  - toga25-supply/src/assets/talos-owl.png
17
22
  related:
18
23
  - ../../toga-blox/features/talos-assistant.md
24
+ - ../../toga-blox/workflows/local-link-into-a-consumer-app.md
19
25
  - ../../talos/features/chat-frontend.md
26
+ - ../../talos/features/aegra-api.md
20
27
  - ../../../standards/frontend.md
21
28
  ---
22
29
 
23
30
  ## Summary
24
31
 
25
32
  toga25-supply is the first host of the shared blox [Talos assistant](../../toga-blox/features/talos-assistant.md).
26
- **Enabled for ALL clients (no per-client gating).** `AppLayout` is the host: it owns Talos
27
- state and injects the adapter; the launcher renders in the header, the panel at app root.
28
- (TRUE-80693)
33
+ **Enabled for ALL clients (no per-client gating).** `AppLayout` is the host: the launcher renders
34
+ in the header, the panel at app root.
35
+
36
+ As of **2026-09-01 the live Aegra/LangGraph streaming backend is BUILT** (TRUE-80693). `onSend` is
37
+ no longer a stub — the panel runs in blox's new **controlled mode** and the stream lives in a
38
+ supply-owned provider. All LangChain code is **app-local**; blox stays pure UI.
29
39
 
30
40
  ## How it works
31
41
 
32
- - **AppLayout owns the state** — `open` / `expanded` / `width` / `dockWidth` — and injects the
33
- adapter:
34
- - `onSend`: **still the stub.** The live streaming backend is designed but unbuilt — see
35
- *Real streaming backend* below.
36
- - `onOpenRecord(id)` → navigate `/sales-orders?sales-orders=<id>`.
37
- - `onAction("approvals")` → navigate `/sales-orders`.
38
- - `onDockWidthChange(px)` → reserve layout width for the docked panel.
39
- - **Rendering** — `<TalosLauncher>` renders in the header via a new presentational
40
- `talosLauncher` slot prop on the blox `Header` component, laid out through a `.headerActions`
41
- flex wrapper. `<TalosPanel>` renders at app root.
42
- - **Branding/font** — the owl asset (`src/assets/talos-owl.png`) is passed as
43
- `branding.avatarSrc`; Plus Jakarta Sans is loaded in `src/index.css` (the shared component
44
- does not import fonts itself).
42
+ ```
43
+ src/api/talos.ts — sole VITE_TALOS_API read chokepoint + assistants lookup
44
+ src/hooks/useTalosSession.ts — resolves token + assistantId → a `connection` state
45
+ src/providers/TalosStreamProvider.tsx — the single useStream() call, exposed via own context
46
+ src/utils/talosMessages.ts — LangGraph Message[] → blox TalosMessageModel[]
47
+ src/layout/AppLayout/AppLayout.tsx — host: provider wrapper + controlled panel props
48
+ ```
45
49
 
46
- ## Gotcha — reserve dock width on the OUTER wrapper, not just `<main>`
50
+ ### `src/api/talos.ts` — the env chokepoint
47
51
 
48
- The docked panel is `position: fixed`, full-height on the right. The blox `Header` is in
49
- **normal document flow** (NOT fixed). Reserving the dock width only on `<main>` leaves the
50
- header's right edge sitting **under** the panel, hiding the launcher + avatar. **Fix:** apply
51
- the reserved dock width (`marginRight` = the `onDockWidthChange` value) to the **outer app
52
- wrapper** so the header shifts along with the content.
52
+ The only place `VITE_TALOS_API` is read. Exposes `getTalosApiUrl()`, `isTalosConfigured()`,
53
+ `fetchTalosAssistants()`, `pickTalosAssistant()`.
53
54
 
54
- ## Real streaming backend — design (DECIDED 2026-08-28, NOT yet implemented)
55
+ `fetchTalosAssistants()` **logs the HTTP status before throwing** — `401` = token/issuer mismatch,
56
+ `403` = tenant not provisioned. Without that log React Query reduces both to a silent boolean and
57
+ you debug blind.
55
58
 
56
- `onSend` is still the stub. The plan below replaces it with the live Aegra/LangGraph backend,
57
- copying the proven approach in [TOGa IQ's chat front-end](../../talos/features/chat-frontend.md).
58
- No code has been written yet.
59
+ **Do NOT send a body or a `Content-Type` on the `GET /assistants` call.** `fetch` forbids a body on
60
+ GET, and a JSON content-type only adds a CORS preflight. (The curl example circulated internally
61
+ used `--data '{}'`; do not copy it into the browser client.)
59
62
 
60
- ### Package choice: `@langchain/langgraph-sdk/react`, app-local
63
+ ### `assistantId` is PER-TENANT — fetch it, never pin it in env
61
64
 
62
- - **Use `useStream` from `@langchain/langgraph-sdk/react`** — the same import talos uses — **not
63
- `@langchain/react`.** Reasons: one fewer peer (`@langchain/react` additionally needs
64
- `@langchain/core ^1.1.48`); the path is already proven in-house so
65
- `talos/src/providers/Stream.tsx` can be lifted nearly verbatim; and `@langchain/react`'s extra
66
- surface is largely **media hooks/players** that blox has no components to render.
67
- - **Take only `useStream` for v1.** Add `useToolCalls` when tool activity is actually shown, and the
68
- headless interrupt helpers when doing human-in-the-loop — *that* is where `@langchain/react` earns
69
- its keep, so revisit it then, not now.
70
- - **Write our own context provider** (as talos does) rather than the library's `StreamProvider` —
71
- supply-specific token / tenant / `threadId` / Aegra URL must be injected.
72
- - **It stays in supply, never in blox.** Per [frontend.md §21](../../../standards/frontend.md) this
73
- is n=1, and per §13(b) a blox peer would force `@langchain/core` on all five consumer apps. blox
74
- keeps `TalosPanel` as pure UI.
65
+ `GET {aegra}/assistants?limit=100&offset=0` with the bearer returns:
66
+
67
+ ```jsonc
68
+ { "assistants": [ { "assistant_id": "…", "name": "…", "graph_id": "…",
69
+ "metadata": { "customer_facing": true, "read_only": true, "tenant_slug": "…" } } ],
70
+ "total": 1 }
71
+ ```
75
72
 
76
- ### Why a provider at all — three jobs
73
+ NYCHH returns exactly one — name `TOGa Supply`, `graph_id: deep_agent`, `read_only: true`,
74
+ `customer_facing: true`.
77
75
 
78
- 1. **Singleton.** `useStream` is stateful; two call sites = two independent conversations.
79
- 2. **Survive route changes.** The panel is docked in `AppLayout` while the user navigates. If the
80
- stream lived inside the panel, any unmount would kill an in-flight response and lose history — so
81
- it must sit **above the router outlet**.
82
- 3. **Be the app↔blox boundary.** Supply's token/tenant/`threadId`/Aegra URL enter here and LangGraph
83
- message shapes are translated here, keeping `TalosPanel` app-agnostic and reusable by desk and
84
- commerce later.
76
+ **Selection rule (`pickTalosAssistant`):** prefer `metadata.customer_facing === true`; else fall
77
+ back to the full list; take the first; `console.warn` when there is more than one candidate.
78
+ Never silently pick, never throw.
85
79
 
86
- ### Planned shape
80
+ **Design consequence:** `useStream` takes **both** the token and `assistantId` as *values*, so there
81
+ are now **two** async prerequisites that must resolve above the hook. That is why the panel's
82
+ `connection` prop is load-bearing rather than polish.
87
83
 
84
+ ### `useTalosSession()` — the gate
85
+
86
+ Returns `connection: "disabled" | "connecting" | "ready" | "error"` plus the token and assistantId.
87
+
88
+ - **Gate on `isAuthenticated` (the `"user"` key), NOT on the presence of an accessToken.** blox
89
+ `fetchPublicToken` leaves a **public** token in storage for logged-out visitors, so "there is a
90
+ token" is not "there is a user."
91
+ - **Key the assistants query by USER UUID, never by the token.** Supply persists the React Query
92
+ cache to `localStorage` (`supply-chain-query-cache`), so a token in a query key gets written to
93
+ disk.
94
+ - `staleTime: Infinity`, `gcTime: Infinity`, `retry: false`.
95
+ - **Anti-pattern — do not gate with `useSuspenseQuery`/Suspense.** It suspends the subtree, so the
96
+ panel **unmounts and remounts** — flashing and dropping whatever the user typed in the composer.
97
+ Keep it a plain query and gate on the returned `connection`.
98
+
99
+ ### `TalosStreamProvider` — outer gate + inner session
100
+
101
+ One `useStream` call from `@langchain/langgraph-sdk/react`, exposed through a supply-owned context.
102
+ `threadId` lives in the URL via react-router `useSearchParams` (**never `nuqs`** — Next-only).
103
+
104
+ It is deliberately **split in two**: an outer gate component and an inner `TalosStreamSession`.
105
+ Hooks cannot be called conditionally, and the stream must not mount until the token *and* the
106
+ assistantId are real values — so the gate renders the session only once both resolve.
107
+
108
+ Why a provider at all: `useStream` is stateful (two call sites = two conversations), it must sit
109
+ **above the router outlet** so navigation does not kill an in-flight response, and it is the
110
+ app↔blox boundary where LangGraph shapes are translated.
111
+
112
+ ### `AppLayout` — split into `AppLayoutInner`
113
+
114
+ `AppLayout` now wraps `AppLayoutInner` in `TalosStreamProvider`, **because a component cannot
115
+ consume a provider it renders**. The panel is driven by the controlled props
116
+ (`messages` / `isStreaming` / `onSubmit` / `onStop` / `connection`); the adapter now carries only
117
+ navigation side effects (`onOpenRecord` → `/sales-orders?sales-orders=<id>`, `onAction("approvals")`
118
+ → `/sales-orders`, `onDockWidthChange` → reserve layout width).
119
+
120
+ **Maps the app-only `"disabled"` state to `"error"`** when passing `connection` down — blox has no
121
+ `"disabled"` value.
122
+
123
+ ### `talosMessages.ts` — the LangGraph → blox mapper
124
+
125
+ - Handles LangGraph's **string-OR-content-blocks** union for `content`.
126
+ - Filters to `human` / `ai` only — `system`, `tool` and `remove` are dropped.
127
+ - An **empty AI turn becomes `{ thinking: true }`** (the stream opening). An empty human turn is
128
+ dropped.
129
+ - Streamed text goes to **`message`** (light markdown), *not* `paras`: during a stream the text is
130
+ partial, and splitting it into finished paragraphs reflows the bubble on every token.
131
+ - **Deliberate loss:** tool calls and HITL interrupts have **no representation** in
132
+ `TalosMessageModel` and are dropped. Surfacing them needs new blox props — not a mapper change.
133
+
134
+ 13 unit tests in `src/utils/talosMessages.test.ts`.
135
+
136
+ ## Gotcha — user turns use `text`, Talos turns use `message`/`paras`
137
+
138
+ blox `TalosMessage` renders **`message.text` for a `role: "user"` turn** but
139
+ **`message.message` / `message.paras` for a `role: "talos"` turn**. The mapper originally put user
140
+ turns in `message`, which renders an **empty bubble**. Caught only by writing the tests.
141
+
142
+ ```ts
143
+ // CORRECT
144
+ { role: "user", text } // user turn
145
+ { role: "talos", message } // assistant turn
88
146
  ```
89
- src/api/talos.ts — sole VITE_TALOS_API read chokepoint + token exchange
90
- src/providers/TalosStreamProvider.tsx — the single useStream call, exposed via context
91
- src/layout/AppLayout/viewModel/useTalosViewModel.ts — flat shape for the panel
92
- ```
93
147
 
94
- - **The mapper is where the estimate lives.** LangGraph `Message[]` → blox `TalosMessageModel[]` do
95
- **not** line up: `type: "ai" | "human"` vs `role: "talos" | "user"`; streamed content blocks vs a
96
- finished `paras: string[]`; and tool calls and interrupts have **no representation at all** in
97
- blox's model. This is the same gap that forces the `TRUE-80692` contract change.
98
- - **`threadId` in URL search params via react-router 7 `useSearchParams`** — **not `nuqs`**, which is
99
- Next-only. Do not copy that part of talos's provider (§5, URL as source of truth).
100
- - **Token held in React Query** per [frontend.md §3](../../../standards/frontend.md) — fetched server
101
- state, never Zustand.
102
- - **Tear down token + `threadId` + conversation on logout and tenant switch** per §18.
103
-
104
- ### Async auth gating — and the anti-pattern
105
-
106
- `useStream` takes the token as a **value** (`defaultHeaders`), not a promise, so you **cannot await
107
- it inside the hook**. That single constraint decides the design:
108
-
109
- - Awaiting the token inside `onSend` (via `queryClient.ensureQueryData`, reusing the panel's existing
110
- thinking/error states, lazily fetching on first send) works **only** for the non-streaming one-shot
111
- contract.
112
- - For **streaming**, the gate must sit **above** the hook — do not mount the stream until the token
113
- query resolves. This is exactly why the panel's `connection` prop stops being optional polish and
114
- becomes required.
115
-
116
- **Anti-pattern — do not use `useSuspenseQuery`/Suspense for this gate.** It suspends the subtree, so
117
- the panel **unmounts and remounts** — flashing and dropping whatever the user typed in the composer —
118
- and in a shared library the thrown promise surfaces in host apps that never opted into a boundary.
119
- Keep it a plain query.
120
-
121
- **Open product decision:** mid-stream 401 behavior — silent refresh and resume, vs. surface an error
122
- and let the user resend. Decide deliberately rather than discovering it in QA.
123
-
124
- ## Aegra-side blockers — backend config, zero front-end code
125
-
126
- Each of these masquerades as a front-end bug. **Confirm them with the Aegra owners
127
- (`akhokhani`, `jcardinal`) BEFORE building the provider** — answer (c) especially decides whether the
128
- auth/loading layer gets built at all.
129
-
130
- - **(a) CORS allowlist** must include supply's **per-client hostnames** (nychh, compass, quad, …).
131
- The symptom is a request that never leaves the browser — no response to debug. Note the browser
132
- calls Aegra **directly** cross-origin (proven by talos), and a static Amplify SPA has no server
133
- route handler to proxy through, so this is unavoidable. Related: Aegra also validates the
134
- requesting domain against a `Domains` table, and `Origin` is a forbidden header a browser cannot
135
- set — so a new origin needs its **own `Domains` row**.
136
- - **(b) Tenant provisioning is explicit, with no auto-create.** An unregistered org gets a clean
137
- **403 on every call even with a valid token** (control-plane DB / `tenant_router` LRU — see
138
- [talos architecture](../../talos/architecture.md)).
139
- - **(c) Token audience.** Aegra `AUTH_TYPE=togahub` validates api2 JWTs, but supply's token is issued
140
- for the **supply** client, not TogaHub. Either Aegra accepts it (trivial) or supply needs a
141
- handoff — api2 already exposes `/auth/delegator`, `/auth/encrypted`, and
142
- `/auth/encrypted-user-uuid` for exactly this cross-app identity pass.
148
+ Anyone writing to `TalosMessageModel` by hand will hit this.
149
+
150
+ ## Gotcha — reserve dock width on the OUTER wrapper, not just `<main>`
151
+
152
+ The docked panel is `position: fixed`, full-height on the right. The blox `Header` is in **normal
153
+ document flow** (NOT fixed). Reserving the dock width only on `<main>` leaves the header's right
154
+ edge sitting **under** the panel, hiding the launcher + avatar. **Fix:** apply the reserved dock
155
+ width (`marginRight` = the `onDockWidthChange` value) to the **outer app wrapper**.
156
+
157
+ ## Gotcha — `npm install` silently un-links blox and Talos disappears
158
+
159
+ Any `npm install` in this repo replaces the `node_modules/@agilant/toga-blox` **symlink** with the
160
+ registry version, which contains no Talos. The symptom looks nothing like a dependency problem.
161
+ Full symptom + relink recipe:
162
+ [Local-linking blox into a consumer app §1a](../../toga-blox/workflows/local-link-into-a-consumer-app.md).
163
+
164
+ ## Environment config
165
+
166
+ `VITE_TALOS_API` is set to `https://api.beta.togaiq.com` in **7 non-prod env files**. It is left
167
+ **commented out in `.env` (production)** because no live production Aegra host is confirmed —
168
+ `isTalosConfigured()` therefore returns false there and `connection` resolves to `"disabled"`.
169
+
170
+ ## Package choice: `@langchain/langgraph-sdk/react`, app-local
171
+
172
+ - **`useStream` from `@langchain/langgraph-sdk/react`** — the same import talos uses — **not
173
+ `@langchain/react`.** The decision stands: the path is proven in-house so
174
+ `talos/src/providers/Stream.tsx` lifts nearly verbatim, and `@langchain/react`'s extra surface is
175
+ largely **media hooks/players** blox has no components to render.
176
+ - **⚠ Correction (verified 2026-09-01): the old "one fewer peer" reason was WRONG.**
177
+ `@langchain/langgraph-sdk@1.10.0` declares `@langchain/core: ^1.1.48` as a **required
178
+ (non-optional) peer** itself; `react`/`react-dom` are the optional ones. Symptom when it is
179
+ missing: Vite/esbuild dep-optimize **hard-fails** with `Could not resolve
180
+ "@langchain/core/messages"` and the dev server exits. Install `@langchain/core` alongside the SDK.
181
+ - **Take only `useStream` for v1.** Add `useToolCalls` when tool activity is actually shown, and the
182
+ headless interrupt helpers for human-in-the-loop — that is when `@langchain/react` earns its keep.
183
+ - **It stays in supply, never in blox.** Per [frontend.md §21](../../../standards/frontend.md) this
184
+ is n=1, and per §13(b) a blox peer would force `@langchain/core` on all five consumer apps.
185
+
186
+ **Still open:** mid-stream 401 behavior — silent refresh and resume vs. surface an error and let the
187
+ user resend. Decide deliberately rather than discovering it in QA.
188
+
189
+ ## Aegra-side status (was "three blockers")
190
+
191
+ - **(a) CORS — NARROWED, still open.** The old worry that Aegra's `Domains` gate needs a spoofed
192
+ `Origin` (talos pins `Origin: http://talos` server-side, and `Origin` is a forbidden header) is
193
+ **moot for supply**: supply's real browser origin *is* the registered hostname, so the browser
194
+ sets it correctly for free. What **is** still a blocker: **Aegra returns CORS headers on the
195
+ OPTIONS preflight only, not on the actual response** — verified 2026-09-01 from both
196
+ `http://nychh.dev.sandbox.togasupply:5173` and `https://nychh.togasupply.com`
197
+ (`access-control-allow-origin: *` on the preflight, **no** `access-control-*` header on the real
198
+ GET). The browser will block every real response until this is fixed. Reported to `akhokhani`.
199
+ - **(b) Tenant provisioning is explicit, no auto-create.** Unchanged — an unregistered org gets a
200
+ clean **403 on every call even with a valid token**.
201
+ - **(c) Token audience — RESOLVED, and it is the good outcome.** Aegra `AUTH_TYPE=togahub`
202
+ **accepts a supply-issued api2 JWT.** Proven by a working `GET /assistants` whose bearer carried
203
+ `aud` = a **supply** hostname (`https://nychh.togasupply.com`), `iss`/`sub` =
204
+ `http://api.beta.togahub.com`, `id.client.slug: Nychh`, 3600s life. Answer from `akhokhani`.
205
+ **Consequence: no token exchange, no `/auth/delegator` handoff, no auth-exchange layer.** Supply
206
+ passes the token it already has straight into `defaultHeaders`.
143
207
 
144
208
  ## Change history
209
+ - 2026-09-01 — **BUILT the live streaming integration.** Added `src/api/talos.ts` (env chokepoint +
210
+ runtime `GET /assistants` lookup — `assistantId` is **per-tenant**, never pinned in env),
211
+ `useTalosSession` (gates on `isAuthenticated`, not on a token, because blox leaves a **public**
212
+ token in storage; assistants query keyed by user UUID because the query cache is persisted to
213
+ disk), `TalosStreamProvider` (one `useStream`, own context, `threadId` via react-router
214
+ `useSearchParams`, split into outer gate + inner session because hooks can't be conditional), the
215
+ LangGraph→blox mapper, and `AppLayout` → `AppLayoutInner` (a component can't consume a provider it
216
+ renders). RESOLVED blocker (c): Aegra `AUTH_TYPE=togahub` accepts a **supply-issued** api2 JWT
217
+ (`aud` = a supply hostname), so there is no token exchange. Narrowed blocker (a): the `Origin`
218
+ spoof worry is moot for supply, but Aegra sends CORS headers **on the preflight only**, so real
219
+ responses are still browser-blocked. FIXED a real bug found by the new tests: blox renders
220
+ `text` for user turns and `message`/`paras` for Talos turns — the mapper's original shape rendered
221
+ an empty user bubble. CORRECTED the recorded package rationale: `@langchain/langgraph-sdk`
222
+ **does** require `@langchain/core ^1.1.48` as a peer, so "one fewer peer" was wrong (the decision
223
+ stands for other reasons). (apeterson)
145
224
  - 2026-08-28 — Design/research only, no code: chose `useStream` from
146
225
  `@langchain/langgraph-sdk/react` (not `@langchain/react`), kept **app-local** rather than in blox
147
226
  (frontend.md §13b/§21), and designed a route-surviving singleton `TalosStreamProvider` above the
@@ -5,7 +5,7 @@ project: _Underscore
5
5
  client: shared
6
6
  type: standard
7
7
  status: active
8
- updated: 2026-08-27
8
+ updated: 2026-09-01
9
9
  owners: [jcardinal, apeterson]
10
10
  files: []
11
11
  related:
@@ -122,6 +122,22 @@ ship the **identical** app-config flag set — the two files differ only in whit
122
122
  `resolve.alias`). A path alias that exists in only one of the two type-checks-but-won't-build (or
123
123
  the reverse). Keep them in lockstep.
124
124
 
125
+ **MUST: type-check with `-p tsconfig.app.json` (or `tsc -b`) — never bare `npx tsc --noEmit`.**
126
+ Because the root `tsconfig.json` is references-only (`files: []`), a bare root `tsc --noEmit`
127
+ compiles nothing and **always exits 0**. It reports a clean build on a repo full of type errors,
128
+ which makes it a false green in any pre-commit or CI-style check.
129
+
130
+ ```bash
131
+ npx tsc --noEmit -p tsconfig.app.json # ✅
132
+ npx tsc --noEmit # ❌ silent no-op
133
+ ```
134
+
135
+ Documented independently in two repos on this tsconfig shape — `toga25-supply` (verified
136
+ 2026-09-01) and `toga2-view` ([architecture](../apps/toga2-view/architecture.md)) — so assume it
137
+ for any app sharing the shape, but **verify per repo before asserting it**. Note also that a repo
138
+ may carry **pre-existing** errors under the app config (toga2-view does), in which case the pass
139
+ criterion is "no *new* errors vs. a stashed baseline", not zero.
140
+
125
141
  ## 3. State management doctrine — **MUST** (high-value)
126
142
 
127
143
  There are exactly **three homes** for state, and they do not overlap. Verified 2026-08-18 across
@@ -664,6 +680,11 @@ consuming app, guard against them and move on.
664
680
 
665
681
  ## Change history
666
682
 
683
+ - 2026-09-01 — §2: added a MUST to **type-check with `-p tsconfig.app.json` (or `tsc -b`), never
684
+ bare `npx tsc --noEmit`** — the root `tsconfig.json` is references-only (`files: []`), so a bare
685
+ root run compiles nothing and always exits 0, a false green in any pre-commit/CI check. Verified
686
+ in `toga25-supply` 2026-09-01 and already documented for `toga2-view`; assume it for any app on
687
+ this tsconfig shape but verify per repo. (apeterson)
667
688
  - 2026-08-27 — §13(a): corrected the caret rationale — a caret on a **prerelease is inert**, not
668
689
  floating; the real floating risk is `toga2-commerce`'s `amplify.yml` overwriting the manifest pin
669
690
  at build time. §22: added three blox auth gotchas found debugging toga25-supply SSO
@@ -12,6 +12,7 @@ apps:
12
12
  - worker
13
13
  - library
14
14
  - talos-backend
15
+ - talos
15
16
  project: _Underscore
16
17
  client: nychh
17
18
  type: profile
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.728",
3
+ "version": "1.0.729",
4
4
  "description": "TOGA Technology Team Claude Knowledge System — shared AI coding harness with skills, knowledge base CLI, and project installer for Claude Code.",
5
5
  "keywords": [
6
6
  "claude",