feat(home): seed onboarding-sourced facts into relationship-state [JARVIS-471]#25434
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c5e69922e2
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| }): void { | ||
| writeOnboardingSidecar(onboarding); | ||
|
|
||
| const assistantName = onboarding.assistantName?.trim(); |
There was a problem hiding this comment.
Guard onboarding names before calling trim
handleSendMessage treats req.json() as a typed object but never validates onboarding at runtime, so onboarding.assistantName?.trim() and onboarding.userName?.trim() can throw when those fields are non-strings (for example, a buggy or older client sending numbers). In that case the first message request fails before enqueueing, which contradicts the helper’s “never throws” contract; add a typeof === "string" guard (or schema validation) before trimming.
Useful? React with 👍 / 👎.
| if (!parsed || !Array.isArray(parsed.tools) || !Array.isArray(parsed.tasks)) | ||
| return null; |
There was a problem hiding this comment.
Validate sidecar field types before using string methods
readOnboardingSidecar() only checks that tools and tasks are arrays, but computeRelationshipState() later assumes every entry (and tone) is a string and calls .trim() on them. A malformed persisted payload (e.g. numeric tool/tone values) will throw during state computation, causing live home-state computation to fail and relationship-state writes to degrade. The sidecar parser should validate/sanitize element types (and tone/name types) before returning data.
Useful? React with 👍 / 👎.
| // loop has unwound (whether via success, error, or | ||
| // cancellation), so the AsyncBytes iterator on the | ||
| // cooperative thread pool is never freed out from under | ||
| // itself (preserves PR #25396's EXC_BAD_ACCESS fix). |
There was a problem hiding this comment.
🟡 Comment references past PR number, violating clients/AGENTS.md comment quality rules
The comment at line 280 includes (preserves PR #25396's EXC_BAD_ACCESS fix), which narrates code history by referencing a past PR. clients/AGENTS.md § "Comment Quality" states: "Comments and docstrings must describe the code's intent and behavior, not its refactoring history." The comment should describe the current behavior (why defer is needed) without referencing past PRs — future readers shouldn't need to know about PR #25396 to understand why the code is structured this way.
| // itself (preserves PR #25396's EXC_BAD_ACCESS fix). | |
| // itself. |
Was this helpful? React with 👍 or 👎 to provide feedback.
…RVIS-471] Phase 4 backend of the Onboarding & New User Retention TDD. On the very first message of a fresh conversation, persist the pre-chat onboarding payload so the Home page renders dashed-border onboarding chips immediately instead of waiting for the first inferred-fact pass. Three artifacts get produced from the onboarding JSON: - `data/onboarding-context.json` — durable sidecar read on every `computeRelationshipState()` call. Required because the relationship-state writer is a pure recomputation on every turn boundary — without the sidecar, onboarding chips would vanish on turn 2. - `IDENTITY.md` / `USER.md` — seed files, only written when missing so we never clobber existing persona content. Feed the system prompt and the writer's `parseIdentity` / `parseUserName` helpers after a daemon restart when the in-memory onboarding context is gone. - `data/relationship-state.json` — kicked off fire-and-forget via the existing `writeRelationshipState()` entry so the Home page can populate on first visit instead of waiting for the first agent-turn boundary. Onboarding facts render first in the chip list (tools -> world, tasks -> priorities, tone -> voice), tagged `source: "onboarding"` so the frontend applies its dashed-border styling. Sidecar userName / assistantName now also fill in as fallbacks when IDENTITY.md / USER.md have not yielded names yet. Nine new tests cover sidecar path, write, fact extraction, name fallbacks + precedence, onboarding/inferred coexistence, empty input skipping, missing-sidecar behavior, and corrupt-JSON degradation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
0e6ae0a to
49cc970
Compare
Summary
Phase 4 backend of the Onboarding & New User Retention TDD. On the very first message of a fresh conversation, persist the pre-chat onboarding payload so the Home page renders dashed-border onboarding chips immediately.
What changed
assistant/src/home/relationship-state-writer.ts— newwriteOnboardingSidecar()/getOnboardingSidecarPath()exports + a privatereadOnboardingSidecar().extractFacts()now accepts anonboarding?arg and emits tools → world / tasks → priorities / tone → voice facts taggedsource: "onboarding"(rendered first so they lead the Home chip list).computeRelationshipState()reads the sidecar and falls back to itsuserName/assistantNamewhen IDENTITY.md / USER.md haven't yielded names yet.assistant/src/runtime/routes/conversation-routes.ts— newpersistOnboardingArtifacts()helper called from the existingbody.onboarding && conversation.messages.length === 0gate inhandleSendMessage. Writes the sidecar, seedsIDENTITY.md/USER.mdonly when missing (never clobbers existing persona content), and kickswriteRelationshipState()fire-and-forget so the Home page populates on first visit.assistant/src/home/__tests__/relationship-state-writer.test.ts— newonboarding sidecar (JARVIS-471)block with 9 tests.Critical design note
The relationship-state writer is a pure recomputation on every turn boundary — it reads USER.md / SOUL.md / IDENTITY.md from disk and rebuilds
facts[]from scratch, never merging with the on-disk JSON. Without a durable source for onboarding selections, chips written on the first message would vanish on the second turn. The sidecar (data/onboarding-context.json) is exactly that durable source — read on every recomputation so onboarding facts survive restarts and subsequent writes.IDENTITY.md / USER.md are seeded from onboarding too, but only when missing, so we never clobber existing persona content. They're the durable source for the system prompt and the writer's
parseIdentity/parseUserNamehelpers after a daemon restart drops the in-memoryconversation.onboardingContext.No frontend changes
Phase 3 already baked in the dashed-border
source: "onboarding"fact chips + the empty-state "Start chatting and I'll pick up a lot more" nudge as forward-compat for Phase 4. The macOS client renders this PR's new facts correctly with no additional work. LUM-806 (Jason's frontend ticket) is effectively superseded by Phase 3's forward-compat.Test plan
bun test src/home/__tests__/relationship-state-writer.test.ts— 40 tests pass (9 new + 31 existing)bun run typecheck— cleanhome-tabon, run pre-chat onboarding, confirm Home tab shows 4–8 dashed-border chips immediately on first visit🤖 Generated with Claude Code