toga-ai 1.0.735 → 1.0.737

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.
@@ -9,6 +9,15 @@
9
9
  "timeout": 3000
10
10
  }
11
11
  ]
12
+ },
13
+ {
14
+ "hooks": [
15
+ {
16
+ "type": "command",
17
+ "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/toga/plain-talk.js\"",
18
+ "timeout": 3000
19
+ }
20
+ ]
12
21
  }
13
22
  ],
14
23
  "PreToolUse": [
@@ -0,0 +1,307 @@
1
+ ---
2
+ type: session
3
+ slug: talos-streaming-supply
4
+ title: Talos streaming chat BUILT in toga25-supply + blox controlled panel
5
+ author: apeterson
6
+ repos: [toga25-supply, toga-blox, talos]
7
+ framework: "2.0"
8
+ client: shared
9
+ status: active
10
+ created: 2026-09-02
11
+ updated: 2026-09-02
12
+ ---
13
+
14
+ # Session: talos-streaming-supply
15
+ **Date:** 2026-09-02
16
+ **Project/Repo:** toga25-supply + toga-blox (2.0)
17
+ **Task:** Build the live Aegra/LangGraph Talos chat in `toga25-supply` — app-local stream provider, controlled `TalosPanel` in blox, markdown/link rendering, dock-aware modal geometry — and align it to the backend team's implementation handoff.
18
+
19
+ > **Supersedes `2026-08-28-talos-streaming-supply-apeterson`**, which was research/design only
20
+ > ("NO application code was written"). This session wrote the code. Read that file only for the
21
+ > original decision rationale; its status lines are stale.
22
+
23
+ > **⚠ READ FIRST: `~/Desktop/toga-supply-streaming-chat-handoff.md`** (1230 lines, from
24
+ > akhokhani/backend, contract date 2026-08-28, "implementation-ready"). It is the authoritative
25
+ > contract for endpoints, the event/privacy boundary, progress copy, feedback, and markdown scope.
26
+ > It was NOT in this session's context until late — it had been uploaded to a *different* Claude
27
+ > conversation ("cont: talos-streaming-supply"), and `/session-resume` restores the saved summary,
28
+ > not attachments. **Keep it on disk and name the path in every future session file.**
29
+
30
+ ---
31
+
32
+ ## What WORKED
33
+
34
+ - **Aegra accepts a supply-issued api2 JWT — blocker (c) from the prior session is CLOSED (World A).**
35
+ akhokhani supplied a working `GET /assistants` call whose bearer had `aud: https://nychh.togasupply.com`
36
+ (a **supply** hostname), `iss/sub: http://api.beta.togahub.com`, 3600s life. No token exchange,
37
+ no `/auth/delegator`, no auth-exchange layer needed.
38
+ - **`assistantId` is PER-TENANT and must be fetched at runtime.**
39
+ `GET /assistants?limit=100&offset=0` with the bearer. NYCHH returns one: name "TOGa Supply",
40
+ `graph_id: deep_agent`, `metadata.customer_facing: true`, `read_only: true`,
41
+ `tenant_slug: "NYC Health + Hospitals"`. Selection rule adopted: prefer
42
+ `metadata.customer_facing === true`, else the full list, take the first, `console.warn` when >1.
43
+ - **CORS is NOT a blocker — blocker (a) is CLOSED.** Aegra returns
44
+ `access-control-allow-origin: *` on the OPTIONS preflight for **both** a local dev origin
45
+ (`http://nychh.dev.sandbox.togasupply:5173`) and the prod origin. The old worry about needing to
46
+ spoof `Origin` for the `Domains` gate is **moot for supply**: supply's real browser origin *is*
47
+ the registered hostname, so the browser sets it correctly for free. (talos had to spoof
48
+ server-side only because its origin differed.)
49
+ - **`https://api.togaiq.com` (production Aegra) is LIVE.** `/health` 200, `/info` 200,
50
+ `/assistants` 401 without a token. Developer is running against it and it works end-to-end
51
+ (real answers about real NYCHH orders). `.env.development` now has
52
+ `VITE_TALOS_API = 'https://api.togaiq.com'` and `VITE_API = 'https://api.togahub.com/v2'`.
53
+ - **The blox `TalosPanel` controlled-mode contract change is BUILT** (was only DECIDED before).
54
+ New optional props: `messages`, `isStreaming`, `progress`, `onSubmit`, `onStop`, `connection`,
55
+ `renderMarkdown`. Controlled mode keys off `messages` being **passed at all**, so BDR and
56
+ `createTalosStubAdapter()` are untouched. 22 blox Talos tests pass, including two that pin
57
+ uncontrolled behaviour as unchanged.
58
+ - **Final-answer-only reveal implemented** per handoff §8.6/§8.8. Chunks buffer privately; the
59
+ assistant turn is revealed only on `talos.run.terminal.v1` with `status: "completed"`. Fails
60
+ closed — a run that ends with no terminal event shows approved error copy, never the partial.
61
+ - **Progress copy** per §8.7 verbatim: `Thinking…` → `Checking your Supply information…` (narration
62
+ event **or** ~1500ms) → `Preparing your answer…` (first buffered chunk). Narration text is never
63
+ echoed, only used as proof work started. Rendered via a new blox `progress` prop with
64
+ `aria-live="polite"` and a `prefers-reduced-motion` guard.
65
+ - **Markdown option (b) implemented**: blox got a `renderMarkdown?: (text) => ReactNode` slot;
66
+ `toga25-supply/src/components/TalosMarkdown/` fills it with `react-markdown@10.1.0` +
67
+ `remark-gfm@4.0.1`. Covers the full §10 list (headings, ordered/nested lists, inline+fenced code,
68
+ blockquotes, GFM tables). No raw HTML, links allowlisted to `http:`/`https:`/`mailto:`,
69
+ external → new tab, same-origin → react-router. 10 tests.
70
+ - **Clickable links in replies.** blox `RichText` now parses `[label](url)`; same-origin links go
71
+ through a new `adapter.onNavigate` (supply wires it to react-router `navigate()`), cross-origin
72
+ open in a new tab with `rel="noopener noreferrer"`, unsafe schemes render as inert text.
73
+ - **Record modal now responds to the docked panel.** Read the real design source rather than
74
+ guessing: `demo/demo-modal.jsx` binds the overlay's `right` to the dock width with
75
+ `260ms cubic-bezier(0.2,0.8,0.2,1)`, confirmed by `slide-up-modal-geometry.md`.
76
+ `AppLayout` publishes `--talos-dock-width` on `document.documentElement`; blox
77
+ `TableRecordModal` reads it (`.backdrop`, `.modal-bottom`, `.modal-center`, `.modal-right`).
78
+ Defaults to `0px` so all other blox consumers are unaffected.
79
+ - **Reading the Claude Design project via the `DesignSync` MCP worked** without a design-login
80
+ prompt: `get_project` / `list_files` / `get_file` on project
81
+ `05d4a4a0-9062-4479-9e10-225021e45cc9` ("TOGa Supply", owner David). The self-contained HTML is
82
+ too large for `get_file` (256KiB cap) — read the **JSX sources** (`demo/demo-shell.jsx`,
83
+ `demo/demo-modal.jsx`) and the `*.md` geometry notes instead.
84
+ - **Two real bugs caught by writing tests**, both described under *Decisions*/*Not retry* below.
85
+
86
+ ## What did NOT work — DO NOT RETRY THESE
87
+
88
+ - **`npx tsc --noEmit` at the `toga25-supply` repo root is a SILENT NO-OP.** The root
89
+ `tsconfig.json` is references-only (`files: []`), so it compiles nothing and **always exits 0**.
90
+ It reported "clean" twice on a codebase with real type errors. **Always use
91
+ `npx tsc --noEmit -p tsconfig.app.json`** (or `tsc -b`). The repo architecture doc used to
92
+ recommend the broken command; that has been corrected in the KB this session, and the rule is
93
+ now in `2.0/standards/frontend.md` §2.
94
+ - **Mapping LangGraph user turns to `TalosMessageModel.message` renders an EMPTY BUBBLE.**
95
+ `TalosMessage` renders `message.text` for a **user** turn and `message.message`/`paras` for a
96
+ **talos** turn. The mapper must emit `{role:"user", text}` vs `{role:"talos", message}`.
97
+ - **Mapping empty AI turns to `{thinking: true}` leaves a permanent row of stuck loading dots.**
98
+ An empty AI turn is a **tool-call turn** — LangGraph puts the call in `tool_calls` and leaves
99
+ `content` blank — and a thread accumulates several. Drop every empty turn; the live thinking
100
+ indicator comes from `isStreaming`, rendered as ONE synthetic trailing bubble by the panel.
101
+ - **`ln -sfn <target> node_modules/@agilant/toga-blox` does NOT replace a real directory** — it
102
+ creates the link *inside* it. Must `rm -rf` the directory first.
103
+ - **"one fewer peer" was the WRONG reason to pick `@langchain/langgraph-sdk` over
104
+ `@langchain/react`.** The SDK (`1.10.0`) itself declares `@langchain/core: ^1.1.48` as a
105
+ **required, non-optional** peer. Symptom when missing: Vite/esbuild dep-optimize hard-fails with
106
+ `Could not resolve "@langchain/core/messages"` and the dev server exits. The decision still
107
+ stands (proven in-house, smaller surface) — only that rationale is wrong. Corrected in the KB.
108
+ - **`@assistant-ui/react-markdown` cannot be used "markdown only, without the runtime."** It
109
+ declares a **required peer on `@assistant-ui/react` ^0.15.0`**, which pulls `zustand ^5.0.15`
110
+ (supply pins `5.0.14`), `radix-ui`, `assistant-cloud`, `assistant-stream`. Its actual engine is
111
+ `react-markdown@^10` — so if you only want markdown, use `react-markdown` + `remark-gfm`
112
+ directly (what this session did).
113
+ - **`stream.stop()` does not stop the run server-side** — it only drops the client transport; the
114
+ run keeps executing and billing. The real stop is
115
+ `POST /threads/{id}/runs/{run_id}/cancel?wait=0&action=cancel`. **Still wired to `stream.stop()`
116
+ — this is an open defect** (see Blockers).
117
+ - **Putting the thread id in the URL query string wiped the conversation.** Any navigation that
118
+ rewrote the search string — a record link in a reply (`?sales-orders=<uuid>`), a side-nav click —
119
+ dropped `talosThread`, remounted `useStream` with no thread, and cleared the visible chat. Do not
120
+ reintroduce a URL-param thread. It is now `sessionStorage`, keyed by tenant + user.
121
+ - **Guessing at Aegra host names is a waste of time.** `api.gamma.togaiq.com`,
122
+ `api.prod.togaiq.com`, `api.dev.togaiq.com`, `api.sandbox.togaiq.com`, `togaiq.com` all fail to
123
+ connect (`000`). Only `api.beta.togaiq.com` and `api.togaiq.com` exist.
124
+ - **`api.beta.togaiq.com` was returning 502 for most of this session** (`/health`, `/info`,
125
+ `/assistants`) after being healthy ~10 minutes earlier. Not a code problem. Production was used
126
+ instead. Also note: **Aegra sends CORS headers on the OPTIONS preflight but NOT on actual
127
+ responses** — reported to akhokhani, still unfixed as far as this session knows.
128
+ - **`knowledge.js kickoff-preflight` returned `features: []`** for toga25-supply, toga-blox and
129
+ talos on `--q="talos streaming chat"` even though three highly relevant feature docs exist. That
130
+ is the **second** session in a row. Search `knowledge/2.0/apps/<repo>/features/` directly.
131
+
132
+ ## Not tried yet (candidates for next session)
133
+
134
+ **This is the remaining build, and it is the bulk of the feature. All endpoints are already
135
+ implemented in `src/api/talos.ts`; what is missing is the parser, the hook, and the blox views.**
136
+
137
+ - **`fromTalosTranscript()` — the centralized transcript parser.** `GET /threads/{id}/transcript`
138
+ returns `{messages:[{id, logical_message_id, type, content, run_id, created_at, active}], next_cursor}`,
139
+ oldest-first. `content` is **polymorphic**: normalize a string directly; for an array of
140
+ LangChain content blocks concatenate only ordered `type:"text"` blocks with a string `text`;
141
+ ignore reasoning, tool-use/tool-result, binary and unknown block types; **do not create an empty
142
+ bubble** if no visible text remains. Render only **active** `human`/`ai`. Deduplicate by stable
143
+ `id`; for revisions use `logical_message_id` + `active`, **never content matching**. The handoff
144
+ requires this parser be centralized and regression-tested.
145
+ - **Terminal-completion reconciliation (§8.8).** On `completed`: refetch the newest transcript page,
146
+ select the active AI message for this `run_id` (prefer `final_message_id`), replace the optimistic
147
+ human message with its durable record, publish in ONE update, then refetch the thread for its
148
+ generated title and refresh the list. **The transcript is authoritative — if buffer and transcript
149
+ disagree, display the transcript.** Currently the buffer is displayed on completion.
150
+ - **`useTalosThreads` hook** — React Query list/rename/delete/select over the client functions.
151
+ Cache scope MUST include the authenticated subject + org identity, and must be cleared on
152
+ logout/account switch (§8.2). Page 20 at a time, sort by `updated_at` desc, dedupe by `thread_id`.
153
+ - **blox view props for the history drawer**: `threads`, `activeThreadId`, `onSelectThread`,
154
+ `onNewChat`, `onRenameThread`, `onDeleteThread`. blox already has a history-drawer UI built for
155
+ its *internal* conversation list (`feat.history`, `showHistory`, `styles.history`) — make it
156
+ render host-supplied threads when controlled. Title = `metadata.thread_name` falling back to
157
+ `New conversation`.
158
+ - **Re-enable `history` / `newChat` in controlled mode** once the host supplies threads. They are
159
+ currently **force-disabled** in `TalosPanel` (`isControlled ? {history:false,newChat:false} : {}`)
160
+ — that is why the design's chat-history and `+` buttons are missing from supply. The `+` is the
161
+ cheap half: the context already exposes `newThread()`; it needs an `onNewChat` prop.
162
+ - **Message actions**: copy, plus thumbs up/down. Show actions **only for completed AI messages**.
163
+ - **Thumbs-down dialog (§9)** — must match standalone Talos: title `What went wrong?`; multi-select
164
+ reasons where selecting one does not clear another; always show a comments textarea; placeholder
165
+ `Additional comments (optional)` until `other` is selected, then `Please describe the issue…` and
166
+ **required non-empty**; Submit disabled until a reason is selected (and text present for `other`);
167
+ Cancel submits nothing and restores focus to the thumbs-down button.
168
+ - **Rename ETag/`If-Match` flow (§8.10).** `renameTalosThread` already sends `If-Match` when given
169
+ `metadataVersion`, but nothing fetches/threads the ETag yet.
170
+ - **Reconnect/rejoin (§8.13)** — `GET /threads/{id}/runs/{run_id}/stream` on transport loss,
171
+ `talos.run.transport.v1` → delayed `Reconnecting…`, `talos.run.replay_gap.v1` → refetch run +
172
+ transcript. Also `GET /threads/{id}/runs?limit=1` to recover the latest run after reload.
173
+ - **Wire Stop to `cancelTalosRun`** instead of `stream.stop()` — needs the `run_id`, which comes
174
+ from the `metadata` SSE event (§8.5, "capture run identity"). Not captured yet.
175
+ - **Fix the pre-existing `AdvancedSelect` type error** at
176
+ `src/pages/TransferOrders/view/CreateTransferOrderModal/CreateTransferOrderModal.tsx:603` —
177
+ passes `name` to single-mode `AdvancedSelect`, which does not accept it. Predates this session
178
+ and is unrelated to Talos; left alone deliberately.
179
+ - **Confirm which api2 issuer each Aegra environment trusts.** Never resolved: the demo token came
180
+ from `api.beta.togahub.com`, supply's envs use `api.togahub.com` / `api.gamma.togahub.com` /
181
+ `api.dev.sandbox.togahub.com`. Production works today, so this only matters for beta testing.
182
+ - **Ask akhokhani to point MCP at beta** if beta testing is wanted — MCP currently targets
183
+ production `api.togahub.com`, so a beta/dev token cannot answer data questions like "what is the
184
+ last sales order?"
185
+ - **Confirm the per-client CORS/`Domains` coverage** for the other supply hostnames (compass, quad,
186
+ prudential, growrk, …). Only nychh has been exercised.
187
+ - **No test covers `TalosStreamProvider`** — `useStream` would need mocking. The mapper, the panel
188
+ and the markdown renderer are covered; the provider wiring is hand-verified only.
189
+
190
+ ## Current file state
191
+
192
+ **toga25-supply** (branch `features-talos-transferorders`)
193
+
194
+ | File | Status | Notes |
195
+ |------|--------|-------|
196
+ | `src/api/talos.ts` | CREATED | Sole `VITE_TALOS_API` chokepoint. `isTalosConfigured`, `getTalosApiUrl`, `fetchTalosAssistants`, `pickTalosAssistant`, plus **threads/transcript/feedback/cancel**: `fetchTalosThreads`, `threadTitle`, `renameTalosThread` (sends `If-Match`), `deleteTalosThread`, `fetchTalosTranscript`, `fetchTalosFeedback` (indexed by `message_id`), `putTalosFeedback`, `deleteTalosFeedback`, `cancelTalosRun`. All route through one `talosRequest` helper that logs status before throwing. **Written and typechecking; only the assistants half is exercised at runtime.** |
197
+ | `src/utils/talosMessages.ts` | CREATED | `extractMessageText` (string-or-blocks union), `toTalosMessage`, `toTalosMessages`, `withoutTrailingAssistant`. Drops all empty turns and every non-`human`/`ai` type. Tool calls + interrupts deliberately dropped. |
198
+ | `src/utils/talosMessages.test.ts` | CREATED | 13 tests. |
199
+ | `src/hooks/useTalosSession.ts` | CREATED | Resolves token + assistantId → `connection: "disabled"\|"connecting"\|"ready"\|"error"`. Gates on `isAuthenticated` (the `user` key) **not** on an accessToken's presence (blox `fetchPublicToken` leaves a PUBLIC token in storage). Assistants query keyed by **user uuid, never the token** — supply persists the query cache to storage. |
200
+ | `src/providers/TalosStreamProvider.tsx` | CREATED | Single `useStream`; outer gate + inner `TalosStreamSession` (hooks cannot be conditional). Thread in `sessionStorage` keyed by tenant+user, re-seeded when the key changes. Terminal-truth reveal gate, progress stage machine, `TERMINAL_TEXT` copy map. Exposes `messages`/`isStreaming`/`connection`/`progress`/`submit`/`stop`/`newThread`. |
201
+ | `src/components/TalosMarkdown/{TalosMarkdown.tsx,TalosMarkdown.module.css,index.ts}` | CREATED | `react-markdown` + `remark-gfm`; no raw HTML, link scheme allowlist, external → new tab, same-origin → react-router, tables/code scroll in their own box, Supply tokens. |
202
+ | `src/components/TalosMarkdown/TalosMarkdown.test.tsx` | CREATED | 10 tests incl. raw-HTML and `javascript:` rejection. |
203
+ | `src/layout/AppLayout/AppLayout.tsx` | MODIFIED | Split into `AppLayoutInner` wrapped by `TalosStreamProvider`. Panel driven by controlled props + `progress` + `renderMarkdown`; adapter carries navigation only (`onOpenRecord`, `onAction`, `onNavigate`, `onDockWidthChange`). Publishes `--talos-dock-width` on `document.documentElement`. |
204
+ | `.env.development` and 6 other non-prod env files | MODIFIED | `VITE_TALOS_API` added. `.env.development` now points at `https://api.togaiq.com` (production Aegra); the others still at `https://api.beta.togaiq.com`. `.env` (production) leaves it **commented out**. |
205
+ | `package.json` / `package-lock.json` | MODIFIED | Added `@langchain/langgraph-sdk@1.10.0`, `@langchain/core@1.2.9`, `react-markdown@10.1.0`, `remark-gfm@4.0.1`. |
206
+ | `src/pages/TransferOrders/.../CreateTransferOrderModal.tsx` | UNTOUCHED | Pre-existing `AdvancedSelect` `name` type error at line 603. Not mine, not fixed. |
207
+
208
+ **toga-blox** (branch `features-talos-transferorders`, `origin/TRUE-80692-BDR` merged in this session)
209
+
210
+ | File | Status | Notes |
211
+ |------|--------|-------|
212
+ | `src/components/Talos/types.ts` | MODIFIED | Added `TalosConnection`; on `TalosPanelProps`: `messages`, `isStreaming`, `progress`, `onSubmit`, `onStop`, `connection`, `renderMarkdown`. On `TalosAdapter`: `onNavigate`. On `TalosMessageModel`: `thinkingLabel`. On `TalosMessageHandlers`: `onNavigate`, `renderMarkdown`. |
213
+ | `src/components/Talos/TalosPanel.tsx` | MODIFIED | Controlled mode keyed off `messages !== undefined`; `history`/`newChat` **force-disabled** when controlled; composer disabled unless `connection === "ready"` with staged placeholder copy; send↔stop swap; ONE synthetic trailing thinking bubble driven by `isStreaming`. Docblock added. |
214
+ | `src/components/Talos/TalosMessage.tsx` | MODIFIED | Uses `renderMarkdown` when supplied, else built-in `TalosMarkdown`; renders `thinkingLabel`; threads `onNavigate`. |
215
+ | `src/components/Talos/helpers.tsx` | MODIFIED | `RichText` parses `[label](url)` with a scheme allowlist + same-origin `onNavigate`; `toMarkdownBlocks` gained GFM **table** parsing; `TalosMarkdown` renders tables and takes `onNavigate`. |
216
+ | `src/components/Talos/Talos.module.css` | MODIFIED | Added `.mdTable`, `.mdLink`, `.thinkingRow`, `.thinkingLabel`, `.mdHost` (+ reduced-motion guard). |
217
+ | `src/components/Talos/TalosPanel.test.tsx` | CREATED | 12 tests — first tests ever for Talos. |
218
+ | `src/components/Talos/helpers.test.tsx` | CREATED | 10 tests — table parsing + link safety. |
219
+ | `src/components/Talos/README.md` | CREATED | 225 lines: the two modes, prop/adapter tables, `features` defaults, message model with the `text`-vs-`message` trap, markdown rules, theming + density ceiling, dock-width rule, file map, gotchas. |
220
+ | `src/components/TableRecordModal/tableRecordModal.module.css` | MODIFIED | `.backdrop`/`.modal-right` stop at `var(--talos-dock-width, 0px)`; `.modal-bottom`/`.modal-center` re-centre via `left: calc(50% - var(--talos-dock-width,0px)/2)` and cap `max-width` to the remaining space; 260ms easing. Defaults to 0px ⇒ non-breaking. |
221
+ | `package-lock.json` | MODIFIED | pre-existing dirty before this session. |
222
+
223
+ **Test status:** blox Talos 22 pass; supply 57 pass (10 files). blox's ~40 pre-existing unrelated
224
+ failures elsewhere in the suite still stand.
225
+
226
+ ## Decisions made
227
+
228
+ - **Merged `origin/TRUE-80692-BDR` into `features-talos-transferorders` before implementing.**
229
+ Rationale: Talos exists on NO mainline blox branch; `origin/TRUE-80692` has the **narrow**
230
+ contract while tcox's widening landed on `TRUE-80692-BDR` — two divergent contracts. BDR
231
+ validated the widened one live. *Rejected:* building on `TRUE-80692` and merging tcox later.
232
+ - **Kept blox `TalosPanel` as the conversation surface; did NOT adopt `@assistant-ui/react`.**
233
+ The handoff §1 recommends assistant-ui's `ExternalStoreRuntime`. Rationale for deviating: our
234
+ provider already implements the principle that matters (host owns message state, view is dumb);
235
+ the blox panel is built, themed, tested and has a **second consumer in BDR**; and David's Claude
236
+ Design mockup shows the blox panel, not assistant-ui. **This is a live, unratified deviation from
237
+ the handoff — flag it to the doc's author.**
238
+ - **Markdown option (b): `renderMarkdown` slot in blox, `react-markdown` in the app.**
239
+ Rationale: blox peers do not hoist (frontend.md §13b), so a markdown dependency in blox lands on
240
+ all five consumer apps; the slot mirrors blox's existing `footer`/`titleIcon` pattern and is
241
+ reusable by desk/commerce. *Rejected:* (a) hand-extending `TalosMarkdown` with headings/ordered
242
+ lists/fenced code/blockquotes — exactly the edge-case work a parser exists for.
243
+ - **Thread id in `sessionStorage`, NOT the URL.** Rationale: a thread is scoped to one user on
244
+ Aegra so the URL was never meaningfully shareable; it only bought reload-persistence, which
245
+ storage gives without the coupling that wiped the chat on every query-string rewrite. Keyed by
246
+ tenant+user per §18. *Rejected:* URL search param (the original design decision — reverse it
247
+ knowingly); `localStorage` (survives too long for "which chat is open").
248
+ - **`--talos-dock-width` CSS custom property, not a `window` event bus.** Rationale: the design
249
+ prototype uses `window.__talosDockW` + `window.__talosDockSubs`, but blox deliberately removed a
250
+ `window.dispatchEvent` side channel during its own review; a CSS var crosses the portal boundary
251
+ with no global mutable state and defaults cleanly to `0px`. *Rejected:* copying the prototype's
252
+ subscriber set.
253
+ - **Reveal gate keyed on the terminal event, failing closed.** Rationale: §8.6 requires
254
+ `messages`/`messages/partial` never be revealed and §8.8 makes the terminal authoritative. A run
255
+ that ends with no terminal shows approved copy rather than the buffer. *Rejected:* treating
256
+ `isLoading === false` as success.
257
+ - **Progress copy comes from the host, never inferred by blox.** blox renders `thinkingLabel`; it
258
+ never invents a step name, percentage or ETA (§8.7 forbids those).
259
+ - **`max-width` cap rather than the design's `width` swap for the docked modal.** The design does
260
+ `width: dockW ? "calc(100% - 72px)" : "90%"`; `width` here is a % of the viewport and cannot see
261
+ the dock, so capping `max-width` to the remaining space achieves the same visible result with a
262
+ `--tableRecordModal-gutter` (24px) knob.
263
+ - **Assistant selection prefers `metadata.customer_facing`, warns when ambiguous, never throws.**
264
+ Matches the frontend.md §20 resolver doctrine (unknown ⇒ disabled + `console.warn`, never a
265
+ silent no-op or a throw).
266
+
267
+ ## Blockers
268
+
269
+ - **Stop does not actually stop the run.** `onStop` → `stream.stop()` drops only the client
270
+ transport; the run continues executing and billing. Fixing it needs the `run_id` captured from
271
+ the `metadata` SSE event (§8.5), then `cancelTalosRun`. `cancelTalosRun` is written; the run_id
272
+ capture is not. **Treat as a real defect, not a nicety.**
273
+ - **The buffer, not the transcript, is displayed on completion.** §8.8 makes the transcript
274
+ authoritative. Until the reconciliation is built, a divergence between buffer and durable record
275
+ shows the wrong text, and message ids needed for feedback are unavailable.
276
+ - **Chat history and `+ new chat` are missing from the supply UI** because `TalosPanel`
277
+ force-disables them in controlled mode. This is the visible gap the developer reported against
278
+ the design.
279
+ - **Aegra `api.beta.togaiq.com` was 502 throughout most of the session** and, separately, **Aegra
280
+ returns CORS headers on the OPTIONS preflight only, not on actual responses.** Both reported to
281
+ akhokhani; neither confirmed fixed. Production (`api.togaiq.com`) is healthy and is what works.
282
+ - **Deviating from the handoff on the conversation surface (blox vs assistant-ui) is unratified.**
283
+ Every remaining item (history, message actions, feedback) is view work, which is exactly where
284
+ the two paths diverge. Get agreement before building those, or expect rework.
285
+ - **`npm install` in `toga25-supply` destroys the blox symlink** every time, replacing it with
286
+ registry `1.1.1-production.139` which has **no Talos** — symptom is
287
+ `does not provide an export named 'TalosLauncher'`. Always follow an install with
288
+ `rm -rf node_modules/@agilant/toga-blox && ln -s ../../../toga-blox-npm node_modules/@agilant/toga-blox`
289
+ then `rm -rf node_modules/.vite`.
290
+ - **Nothing is committed.** Both repos have substantial uncommitted work on
291
+ `features-talos-transferorders`. blox `dist/` is gitignored, so other consumers see none of this
292
+ until blox is published.
293
+
294
+ ## Exact next step
295
+
296
+ > Decide the blox-vs-`@assistant-ui/react` question with the handoff doc's author **before**
297
+ > building any more view code — every remaining item is view work.
298
+ >
299
+ > Then, assuming blox: implement `fromTalosTranscript()` in
300
+ > `toga25-supply/src/utils/talosMessages.ts` with tests, using the shape in
301
+ > `~/Desktop/toga-supply-streaming-chat-handoff.md` §8.11 (polymorphic `content`, active
302
+ > `human`/`ai` only, dedupe by `id`, `logical_message_id` + `active` for revisions, no empty
303
+ > bubbles). It is the prerequisite for terminal reconciliation, for message ids that feedback
304
+ > needs, and for opening an existing thread from the history drawer.
305
+
306
+ ---
307
+ _Saved by /session-save on 2026-09-02_
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.735",
3
+ "version": "1.0.737",
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",
@@ -21,6 +21,12 @@ thinking, your plans, and your final answers. This is a hard team rule, not a st
21
21
  5. **Use lists.** More than two parts? Use a numbered or bulleted list, not a paragraph.
22
22
  6. **Stay technical, stay exact.** Keep the real detail — file names, error text, exact steps.
23
23
  Short does not mean vague. Be brief AND precise.
24
+ 7. **Mirror the person.** Read how each teammate writes to you — the words they use, the
25
+ terms they pick for things, and how long their sentences run — and answer back in that
26
+ same style. Reuse their own words. If they write short, you write short. This makes your
27
+ answers feel familiar and easy to follow.
28
+ - Never fake misspellings or bad grammar to "match" someone. Be natural and correct —
29
+ just familiar. Mirroring means their vocabulary and length, not their typos.
24
30
 
25
31
  ## When a human must do something — always use a numbered list
26
32
 
@@ -55,3 +61,14 @@ What you need to do:
55
61
 
56
62
  Wordy, jargon-heavy output is hard and slow to read — worse for non-native English readers.
57
63
  The team moves faster when you are brief and plain. Clear beats clever.
64
+
65
+ ## How this is enforced
66
+
67
+ This rule kept drifting: it lived only here, so on long or technical sessions the model slid
68
+ back to dense wording and teammates missed critical details. To stop that, a hook backs it up.
69
+
70
+ - `scripts/hooks/plain-talk.js` runs on **every** developer turn (`UserPromptSubmit`). It
71
+ injects a short reminder of these rules — plus the "mirror the person" instruction — into
72
+ context right before each reply. So the model is reminded every turn, not just once.
73
+ - The hook is wired in `.claude/settings.json` and ships to every repo on `npx toga-ai`.
74
+ - It never blocks anything; it only nudges. To silence it, set `TOGA_PLAIN_TALK_DISABLED=1`.
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /*
5
+ * plain-talk.js — EVERY-TURN enforcement of the team communication rule.
6
+ *
7
+ * Why this exists: rules/common/communication.md tells the model to write short,
8
+ * plain, jargon-free answers and to mirror how each teammate talks. But a rule that
9
+ * only lives in a file is passive — over a long or technical session the model drifts
10
+ * back to dense, jargon-heavy wording. Teammates (many reading English as a second
11
+ * language) then miss critical details and sign off on bad decisions. Prose alone did
12
+ * not hold; this is the mechanical backstop.
13
+ *
14
+ * How it works: registered on UserPromptSubmit. On every developer turn it prints a
15
+ * short <system-reminder> to stdout, which Claude Code injects into the model's context
16
+ * immediately before the reply is generated. So the plain-talk + mirror instruction is
17
+ * the last thing in context every single turn — the model cannot quietly forget it.
18
+ *
19
+ * It reads nothing and writes nothing. It never blocks (always exit 0) — a communication
20
+ * nudge must never stop a tool call. Fail-open on any error: a crashing nudge must never
21
+ * brick a session.
22
+ *
23
+ * Escape hatch: set TOGA_PLAIN_TALK_DISABLED=1 to silence the reminder.
24
+ */
25
+
26
+ const fs = require('fs');
27
+
28
+ /* Read the CC hook payload from stdin (fall back to env), so we only fire on the
29
+ * UserPromptSubmit event and stay quiet on anything else that reuses this script. */
30
+ function readPayload() {
31
+ let raw = '';
32
+ try { raw = fs.readFileSync(0, 'utf8'); } catch (e) { /* no stdin */ }
33
+ let data = {};
34
+ if (raw && raw.trim()) {
35
+ try { data = JSON.parse(raw); } catch (e) { data = {}; }
36
+ }
37
+ if (!data.prompt && process.env.CLAUDE_USER_PROMPT) data.prompt = process.env.CLAUDE_USER_PROMPT;
38
+ return data;
39
+ }
40
+
41
+ function reminder() {
42
+ return [
43
+ '<system-reminder>',
44
+ 'TEAM COMMUNICATION RULE (rules/common/communication.md) — applies to THIS reply:',
45
+ 'Write short and plain. Many teammates read English as a second language; dense,',
46
+ 'jargon-heavy answers make them miss critical details and accept bad decisions.',
47
+ '',
48
+ ' 1. Answer first. Main point on line 1, reasons after (only if needed).',
49
+ ' 2. Cut words. Say it, then stop. One line beats three.',
50
+ ' 3. Common words only. No rare jargon (idempotent, cardinality, orthogonal, canonical);',
51
+ ' say what it means in plain words instead. Stay exact — keep file names, errors, steps.',
52
+ ' 4. Any manual action (deploy, SQL, config, restart) goes in a numbered list under a',
53
+ ' "What you need to do:" heading. One action per step, in order.',
54
+ '',
55
+ 'MIRROR THIS PERSON: read how the developer writes to you in this session — the words',
56
+ 'and phrases they use, and how long their sentences run — and answer back in that same',
57
+ 'register. Reuse their own terms for things. Match their length: if they write short,',
58
+ 'you write short. Never fake misspellings or bad grammar; be natural, just familiar.',
59
+ '',
60
+ 'Before you send: could you cut half the words and keep the meaning? Is any word too',
61
+ 'rare for a non-native reader? Is the main point on the first line?',
62
+ '</system-reminder>',
63
+ ].join('\n');
64
+ }
65
+
66
+ function main() {
67
+ if (process.env.TOGA_PLAIN_TALK_DISABLED === '1') process.exit(0);
68
+
69
+ const data = readPayload();
70
+
71
+ // Only fire on a real user turn. UserPromptSubmit carries a prompt and no tool_name.
72
+ const isPromptEvent =
73
+ data.hook_event_name === 'UserPromptSubmit' ||
74
+ (typeof data.prompt === 'string' && !data.tool_name);
75
+
76
+ if (isPromptEvent) {
77
+ // stdout from a UserPromptSubmit hook is injected into the model's context.
78
+ console.log(reminder());
79
+ }
80
+ process.exit(0);
81
+ }
82
+
83
+ try { main(); } catch (e) { process.exit(0); } // fail-open: never brick a session