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.
package/.claude/settings.json
CHANGED
|
@@ -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
|
@@ -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
|