Chat inventory intake: add equipment via the assistant (v5)#30
Open
philosophercode wants to merge 7 commits into
Open
Chat inventory intake: add equipment via the assistant (v5)#30philosophercode wants to merge 7 commits into
philosophercode wants to merge 7 commits into
Conversation
Capability-registry architecture (chat + MCP adapters over one source of truth), agentic intake flow with identification cards, draft-by-default Notion writes, vision/audio/batch input, and phased build order. Builds on the merged PR #25 chat launcher. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Capability-registry architecture: catalog/units/maintenance/intake capabilities defined once, exposed through both the chat and MCP via chat-adapter and mcp-adapter. Adds the agentic intake flow (research_tool -> propose_listing -> create_tool) with draft-by-default Notion writes, duplicate detection, batch handling, and identification cards. Chat route now sends uploaded photos to the model (vision); ChatFab gains card rendering and Web Speech dictation; "Add equipment" front door + nav.add* in all 12 locales. Built via contract-first parallel subagents. typecheck/lint/build green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Wrap shared test render helper in ChatLauncherProvider so component tests (ChatFab/PrimaryNav/GlobalChrome) render — these were red on main from the PR #25 (launcher) / #29 (test-infra) merge collision. - Add webSearch_20250305 to the chat route test's anthropic mock (the refactored route now wires native web_search alongside web_fetch). - Add a `chatOnly` flag to CapabilityTool; mark intake research_tool / propose_listing chat-only (card/orchestration tools) so the MCP surface keeps its read-tool set and exposes only headless-meaningful tools. - Update MCP route tests to the structured-JSON contract (design §3.3): not-found is a `found:false` result, not isError. v5: typecheck/lint/build green, 232/232 tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The agent could fabricate plausible-but-fake resource URLs (observed: an invented YouTube video id that 404s on oEmbed). Add server-side link verification: - verifyUrl/verifyResourceLinks: YouTube checked via oEmbed (authoritative for existence — a watch page 200s even for dead videos), others via an HTTP GET treating only 404/410/DNS/malformed as invalid (so a bot-blocked but real manual isn't false-dropped). - research_tool prunes unverified links from the candidate and returns them as dropped_links so the agent can tell the user. - create_tool re-verifies as a hard gate before writing resources; dropped links are reported in warnings, never written. - Prompt: forbid fabricating/guessing URLs (esp. video URLs), explain that links are verified server-side, and require tight, structured messages instead of multi-paragraph research narration. Resources are already created as drafts (published=false) — confirmed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The chat showed a generic "Something went wrong. Try again." for every failure, hiding what actually happened (e.g. a transient Anthropic 529 overload looked identical to a real bug). - route: add onError (describeChatError) to createUIMessageStream and the merged toUIMessageStream so the SDK's masked default is replaced with a concise, user-facing reason — mapping 529/overloaded, 429/rate-limit, timeouts, and auth errors, and otherwise passing the real message through. - ChatFab: render error.message verbatim, falling back to the generic translated string only when the error carries no message. - tests updated: assert the real message is surfaced + the empty-message fallback. Verified live against an ongoing Anthropic 529: the chat now reads "The AI service is temporarily overloaded… please try again." Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Live testing surfaced a real write bug (now visible thanks to the error surfacing): Notion rejects commas in multi-select option names, so a material like "Wood (plywood, hardwood, veneer)" 400'd the whole tool create, yet the success card still claimed "saved". - notion.ts: multiSelectProp rewrites commas to " / " and de-dupes, so materials/ppe/tags always satisfy Notion's constraint. - intake.ts: create_tool card uses a new "error" state when the write didn't actually land (success===false || no tool_id) instead of a misleading "Saved as a draft" badge; prompt now asks for short discrete tag values (no embedded commas). - types/IdentificationCard/globals: add the "error" card state + banner. Verified live: Glowforge Aura now writes cleanly (tool+unit+2 resources, all published=false, both links live-verified 200). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Pull request overview
Adds the “chat inventory intake” flow to v5 by introducing a capability registry (catalog/units/maintenance/intake) that can be exposed via both the chat route (AI SDK tools + streamed UI cards) and the MCP endpoint (tool registry), plus new UI affordances (Add equipment button, identification cards, dictation mic).
Changes:
- Introduce
src/lib/capabilities/registry + chat/MCP adapters; refactor chat + MCP routes to consume the registry. - Add the intake flow (
research_tool→propose_listing→create_tool) with draft-by-default Notion writes and server-side link verification. - Extend the chat UI with identification-card rendering, a new “Add” nav front door, and Web Speech dictation.
Reviewed changes
Copilot reviewed 35 out of 36 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| v5/test/utils/render.tsx | Wrap test renders with ChatLauncherProvider for nav/chat launch coverage. |
| v5/src/styles/globals.css | Add styles for dictation mic button and identification card layout. |
| v5/src/lib/types.ts | Add write-only image_uploads support for Notion file-upload-backed images. |
| v5/src/lib/notion.ts | Add Notion write helpers + create/find-or-create functions for intake writes. |
| v5/src/lib/capabilities/units.ts | Add units capability tools (get_unit_details, get_maintenance_history). |
| v5/src/lib/capabilities/types.ts | Define shared capability/tool/card contracts and Zod schemas. |
| v5/src/lib/capabilities/mcp-adapter.ts | Register capability tools on MCP server with write gating. |
| v5/src/lib/capabilities/maintenance.ts | Port report_issue into a MCP-callable write capability. |
| v5/src/lib/capabilities/intake.ts | Implement intake tools, duplicate detection, link verification, and draft creation. |
| v5/src/lib/capabilities/index.ts | Export ordered capability registry + adapters/types re-exports. |
| v5/src/lib/capabilities/helpers.ts | Shared unit/tool lookup + maintenance flattening helpers. |
| v5/src/lib/capabilities/chat-adapter.ts | Wrap capability tools for AI SDK + compose shared system prompt. |
| v5/src/lib/capabilities/catalog.ts | Add catalog discovery tools as a capability. |
| v5/src/components/PrimaryNav.tsx | Add “Add equipment” nav entry point using ChatLauncherProvider. |
| v5/src/components/IdentificationCard.tsx | New client component to render streamed identification cards. |
| v5/src/components/ChatLauncherContext.tsx | Update docs/comments for additional launcher entry points. |
| v5/src/components/ChatFab.tsx | Render data-card parts, add dictation mic, improve error display. |
| v5/src/components/ChatFab.test.tsx | Extend tests for real error-message surfacing and fallback behavior. |
| v5/src/app/api/mcp/route.ts | Rewrite MCP route to register tools from capability registry. |
| v5/src/app/api/mcp/route.test.ts | Update MCP tests for structured JSON results via adapter. |
| v5/src/app/api/chat/route.ts | Rewrite chat route to use capability registry; add attachment extraction + improved error mapping. |
| v5/src/app/api/chat/route.test.ts | Mock new web_search tool integration. |
| v5/package-lock.json | Lockfile update (peer flag adjustment). |
| v5/messages/ar.json | Add nav.add* strings. |
| v5/messages/en.json | Add nav.add* strings. |
| v5/messages/es.json | Add nav.add* strings. |
| v5/messages/fr.json | Add nav.add* strings. |
| v5/messages/he.json | Add nav.add* strings. |
| v5/messages/hi.json | Add nav.add* strings. |
| v5/messages/ja.json | Add nav.add* strings. |
| v5/messages/ko.json | Add nav.add* strings. |
| v5/messages/pt-BR.json | Add nav.add* strings. |
| v5/messages/ru.json | Add nav.add* strings. |
| v5/messages/tr.json | Add nav.add* strings. |
| v5/messages/zh-CN.json | Add nav.add* strings. |
| docs/superpowers/specs/2026-06-01-chat-inventory-intake-design.md | Add the design spec referenced by the PR. |
Files not reviewed (1)
- v5/package-lock.json: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+505
to
+508
| const imageUploads = candidate.image_upload_ids.map((id) => ({ | ||
| id, | ||
| name: attachmentNames.get(id) || "photo", | ||
| })); |
Comment on lines
+77
to
+81
| // Convert the UI messages, attach any server-fetched manuals, and surface the | ||
| // uploaded photos for this turn both to the model (image bytes — design spec | ||
| // §6.1) and to the capability layer (file_upload ids the intake `create_tool` | ||
| // re-uses to attach the same photo to the new Notion page). | ||
| const baseMessages = await convertToModelMessages(messages); |
| ))} | ||
| <button | ||
| type="button" | ||
| className="primary-nav-add" |
Comment on lines
+711
to
+715
| className={`chat-mic${isListening ? " chat-mic-active" : ""}`} | ||
| aria-label="Dictate" | ||
| aria-pressed={isListening} | ||
| title="Dictate" | ||
| onClick={toggleDictation} |
Comment on lines
+141
to
+145
| <p className="id-card-banner id-card-banner-success"> | ||
| <span className="id-card-banner-icon"> | ||
| <CheckIcon /> | ||
| </span> | ||
| Saved as a draft — staff will publish it. |
Comment on lines
+226
to
+247
| function promptFragment(env: PromptEnv): string { | ||
| const { tools, focusedTool } = env; | ||
| const sections: string[] = []; | ||
|
|
||
| sections.push( | ||
| `## Browsing the catalog\n\nUse the catalog tools to answer questions about what's in the lab:\n\n- \`list_tools\` — list everything, optionally filtered by category or location (partial match). Use this for "what do you have", "show me the 3D printers", "what's in the wood shop".\n- \`search_tools\` — keyword search across names, descriptions, materials, and tags. Use this when the student describes a need ("something to cut acrylic", "a tool for sanding") rather than naming a tool.\n- \`get_tool_details\` — full details for one tool by id, slug, or name. Use this when the student asks about a specific tool's specs, training, PPE, restrictions, units, or resources, before answering with anything beyond the summary already in the catalog list below.\n\nGround every answer in the catalog. If a student asks about a tool that isn't in the catalog, say so honestly rather than inventing one.` | ||
| ); | ||
|
|
||
| sections.push( | ||
| `## Linking tools\n\nWhenever you mention a tool that exists in the catalog below, **format its name as a markdown link** to its detail page using the slug provided in the catalog: \`[Tool Name](/tools/<slug>)\`. This lets the student jump straight to the tool's page. Examples:\n- "You could use the [Bambu Lab X1-Carbon Combo 3D Printer](/tools/<slug>) for that."\n- "For laser cutting acrylic, check the [Epilog Helix 24](/tools/<slug>)."\n\nDo **not** link the tool the student is already viewing (see Active tool context). Do not invent slugs — only use slugs from the catalog list.` | ||
| ); | ||
|
|
||
| if (focusedTool) { | ||
| sections.push( | ||
| `## Active tool context\n\nThe student is currently viewing the **${focusedTool.name}** detail page in the MakerLab catalog. If they use pronouns like "this", "it", "that tool", or "the machine", or ask things like "how do I use it" / "what can I make with this" without naming a tool, assume they are asking about the ${focusedTool.name}. Do not wrap "${focusedTool.name}" itself in a tool link — the student is already on its page.` | ||
| ); | ||
| } | ||
|
|
||
| sections.push(`## MakerLab catalog (${tools.length} tools)`); | ||
| sections.push(tools.map(describeCatalogEntry).join("\n")); | ||
|
|
||
| return sections.join("\n\n"); |
Resource links added via intake are created as drafts (published=false) alongside the tool. groupResourcesByTool also filtered out published=false resources, so even after staff published a tool, its manuals/SOP/wiki links stayed invisible. Drop that per-resource filter: the catalog only surfaces published tools, so tool-level publishing already gates visibility — a tool's resources now show with it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds a low-barrier way to contribute inventory through the existing chat (v5). A user describes equipment in free text, dictation, photos, or pasted product/manual URLs; an intake agent researches it, shows an identification card to confirm, and on confirmation writes a draft catalog listing to Notion (tool + unit + category + location + manual/video resources, all
published=false).Design spec:
docs/superpowers/specs/2026-06-01-chat-inventory-intake-design.md.Architecture
Introduces a capability registry — each capability (catalog, units, maintenance, intake) is defined once as data + a
run(), then exposed through two adapters: the chat (AI SDK tools) and the MCP endpoint. This removes the prior chat-route/MCP-route duplication and means every capability is automatically available in both surfaces.report_issue(maintenance) is now MCP-callable too.src/lib/capabilities/—types,helpers,index,catalog,units,maintenance,intake,chat-adapter,mcp-adapterChatFabgains identification-card rendering + Web Speech dictation; "Add equipment" front door (reusing the PR v5: "Report a problem" nav button that opens the chat pre-seeded #25 launcher) +nav.add*in all 12 localesIntake flow
research_tool(normalize + dedupe) →propose_listing(identification card, mandatory confirm gate) →create_tool(draft-by-default write, partial-failure reporting). Supports batches and an "add a unit to the existing tool" path on duplicates.Safety / correctness
research_toolandcreate_tool; fabricated/dead links are pruned and reported, never written.errorstate on a failed/partial write.MCP_TOKENis configured.Verification
npm run typecheck,npm run lint,npm run buildall pass inv5/.GalleryShellfailures are pre-existing onmainfrom [codex] Improve mobile catalog UX #27's mobile-catalog change — unrelated to this PR.)🤖 Generated with Claude Code