Skip to content

feat(phase21): world echo lore drawer, verify gate, and memory recall fixes#10

Merged
moyunzero merged 4 commits into
mainfrom
feat/phase21-world-echo
Jun 22, 2026
Merged

feat(phase21): world echo lore drawer, verify gate, and memory recall fixes#10
moyunzero merged 4 commits into
mainfrom
feat/phase21-world-echo

Conversation

@moyunzero

@moyunzero moyunzero commented Jun 21, 2026

Copy link
Copy Markdown
Owner

Summary

  • Phase 21 world echo: DiscoveredLorePanel in shell drawer (history tab), chunk-lore hook/toast wiring, remove legacy JournalQuestStrip.
  • verify:phase21 E2E gate: memory seed → chunk cross → lore toast → drawer discoveries → move speak → rude banner → reload recall + npc-memory-callback; verify:phase15 delegates to shared memory helpers.
  • Memory recall reliability (ISSUE-055): worker httpx trust_env=False for localhost game-server; sync append_player_memory before emit done; retry fetch_recent_memories; recency fallback when embed recall misses after reload.

Test plan

  • pnpm --filter @aetherlife/game-server test
  • pnpm --filter @aetherlife/web test
  • cd workers/agent-worker && LLM_MOCK=1 uv run pytest -q (245 passed)
  • pnpm verify:phase21 (real LLM, pnpm dev:stack, ~107s)
  • pnpm verify:phase20 (memory/speak trust regression)
  • Golden flows GF-02 / GF-03 / GF-10 before merge if touching speak/move/memory paths

Made with Cursor

Summary by CodeRabbit

Release Notes

  • New Features
    • Added a “Discoveries” drawer tab (已发现) to show discovered lore locations (name + story hook).
    • Added streaming/incremental NPC reply support in the dialogue overlay, with live updates and improved “thinking”/status handling.
  • Bug Fixes
    • Improved NPC wandering: malformed/unknown zone identifiers now safely fall back instead of failing.
    • Ensured streaming replies take precedence over stale thinking-only output.
    • Removed the journal quest strip from the exploration UI.
  • Tests
    • Added/updated unit tests for the discoveries panel and streaming overlay behavior, plus supporting E2E verification coverage.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@coderabbitai

coderabbitai Bot commented Jun 21, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@moyunzero, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 55 minutes and 35 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f18584a8-c4bc-468b-bae9-a88d59babb02

📥 Commits

Reviewing files that changed from the base of the PR and between 2dafb8c and 122ce95.

📒 Files selected for processing (44)
  • apps/web/src/ChatPage.tsx
  • apps/web/src/components/DialogueBar.tsx
  • apps/web/src/components/DiscoveredLorePanel.test.ts
  • apps/web/src/components/DiscoveredLorePanel.tsx
  • apps/web/src/components/JournalQuestStrip.test.ts
  • apps/web/src/components/JournalQuestStrip.tsx
  • apps/web/src/components/PhaserGame.tsx
  • apps/web/src/components/ShellDrawer.test.ts
  • apps/web/src/components/ShellDrawer.tsx
  • apps/web/src/hooks/useChunkLore.test.ts
  • apps/web/src/hooks/useChunkLore.ts
  • apps/web/src/hooks/useColyseusRoom.ts
  • apps/web/src/index.css
  • docs/ISSUE-LOG.md
  • eslint.config.js
  • package.json
  • packages/shared/src/speakIntent.test.ts
  • packages/shared/src/speakIntent.ts
  • scripts/benchmark-speak-browser.mjs
  • scripts/lib/dialogue-engage.mjs
  • scripts/lib/e2e-memory-helpers.mjs
  • scripts/lib/playtest-findings-patch.mjs
  • scripts/lib/speak-browser-round.mjs
  • scripts/lib/speak-browser-stack.mjs
  • scripts/lib/uat-phase21-helpers.mjs
  • scripts/uat-phase21-playwright.mjs
  • scripts/verify-phase13.mjs
  • scripts/verify-phase15.mjs
  • scripts/verify-phase20.mjs
  • scripts/verify-phase21.mjs
  • workers/agent-worker/src/graph/ambient_intent.py
  • workers/agent-worker/src/graph/casual_fast_lane.py
  • workers/agent-worker/src/graph/lore_loop.py
  • workers/agent-worker/src/graph/npc_loop.py
  • workers/agent-worker/src/graph/social_edge_fast_lane.py
  • workers/agent-worker/src/http_json.py
  • workers/agent-worker/src/main.py
  • workers/agent-worker/src/memory/client.py
  • workers/agent-worker/tests/test_http_json.py
  • workers/agent-worker/tests/test_llm_social_memory.py
  • workers/agent-worker/tests/test_load_memory_recall_fallback.py
  • workers/agent-worker/tests/test_memory_client.py
  • workers/agent-worker/tests/test_npc_loop_mock.py
  • workers/agent-worker/tests/test_npc_social_order.py
📝 Walkthrough

Walkthrough

This PR delivers three parallel workstreams: (1) a web frontend "Discoveries" drawer tab backed by a new DiscoveredLorePanel component and discoveredLoreRows helper, plus streamingReply support in DialogueOverlay; (2) a major agent-worker overhaul covering deterministic recall-merge logic, hot in-process worker snapshot caching, centralized httpx client with trust_env=False, social-turn reply-first JSON ordering, and move-intent pattern expansion; (3) a new suite of E2E verify scripts (Phase 20/21, overlay-streaming), automated SPEAK-SLA playtest, overhauled speak benchmark, and GSD cross-AI review CLI tooling.

Changes

Web Frontend: Lore Discovery Tab + Dialogue Streaming

Layer / File(s) Summary
DiscoveredLoreRow type and discoveredLoreRows helper
apps/web/src/hooks/useChunkLore.ts, apps/web/src/hooks/useChunkLore.test.ts, apps/web/src/hooks/useColyseusRoom.ts
Exports DiscoveredLoreRow type and discoveredLoreRows() filter/sort helper; exposes loreByChunk from useColyseusRoom; tests filter/sort/coord-omission behavior.
DiscoveredLorePanel component and ShellDrawer discoveries tab
apps/web/src/components/DiscoveredLorePanel.tsx, apps/web/src/components/DiscoveredLorePanel.test.ts, apps/web/src/components/ShellDrawer.tsx, apps/web/src/components/ShellDrawer.test.ts, apps/web/src/components/DialogueBar.tsx, apps/web/src/index.css
New DiscoveredLorePanel renders empty state or lore row list; ShellDrawer gains a required discoveredLoreRows prop and "discoveries" tab; DialogueBar adds a 已发现 button; CSS replaces .journal-quest-strip with .discovered-lore-panel styles.
ChatPage wiring and JournalQuestStrip removal
apps/web/src/ChatPage.tsx, apps/web/src/components/PhaserGame.tsx
ChatPage memoizes discoveredRows and passes them to ShellDrawer plus streamingReply to DialogueOverlay; PhaserGame removes JournalQuestStrip and its useMemo.
DialogueOverlay streaming partial support
apps/web/src/components/DialogueOverlay.tsx, apps/web/src/components/DialogueOverlay.test.ts, apps/web/src/index.css
Adds streamingReply prop; derives partialText/displayLine/isStreaming; applies streaming CSS modifier and aria-live attributes; new CSS class; Vitest tests for three streaming-vs-placeholder precedence scenarios.
Zone-wander malformed zoneId fix
apps/game-server/src/ambient/zone-wander.ts, apps/game-server/src/ambient/zone-wander.test.ts
findZone wraps parseZoneId in try/catch; test asserts NPC falls back to its current cell on invalid zoneId.

Agent Worker: Recall Merge, Memory Pipeline, and HTTP Client

Layer / File(s) Summary
create_http_client with trust_env=False
workers/agent-worker/src/http_json.py, workers/agent-worker/src/graph/ambient_intent.py, workers/agent-worker/src/graph/casual_fast_lane.py, workers/agent-worker/src/graph/lore_loop.py, workers/agent-worker/src/graph/social_edge_fast_lane.py, workers/agent-worker/src/graph/npc_loop.py, workers/agent-worker/src/main.py, workers/agent-worker/tests/test_http_json.py
New create_http_client() factory defaults trust_env=False; propagated across all call sites replacing direct httpx.Client(); verified by a new test.
recall_merge.py: overhauled question detection and memory selection
workers/agent-worker/src/graph/recall_merge.py
Rewrites is_recall_question with strong/disclosure cue classification; adds pick_recall_memory, recall_no_memory_reply, extract_nickname, extract_food_preference, password-ambiguity detection, seed-vs-paraphrase scoring, and deterministic merge_recall_into_reply.
recall_merge unit tests
workers/agent-worker/tests/test_recall_merge.py, workers/agent-worker/tests/test_social_turn.py, workers/agent-worker/tests/test_speak_intent.py, workers/agent-worker/tests/test_memory_quote.py, workers/agent-worker/tests/test_action_intent.py
26+ new cases covering password/nickname/food extraction, seed preference, ambiguity replacement, no-memory guard, non-recall passthrough, help-heuristic false-positive prevention, and move-intent UAT assertions.
npc_loop: hot snapshot cache, recall fallback, and compose/persist
workers/agent-worker/src/graph/npc_loop.py
Adds _hot_worker_snapshot TTL cache; updates fetch_state to use cache; overhauls load_memory_context with recency augmentation and recall-question fallback; apply_tools injects relative-move with dialogue_ctx and re-caches snapshot; compose_reply emits partial on recall; reorders persist_turn_memory.
npc_loop tests and recall fallback test
workers/agent-worker/tests/test_fetch_state_and_memory.py, workers/agent-worker/tests/test_load_memory_recall_fallback.py, workers/agent-worker/tests/test_npc_loop_mock.py, workers/agent-worker/tests/test_npc_social_order.py
Autouse cache-clearing fixture; tests hot cache skips HTTP; tests apply_tools refreshes snapshot; recall fallback when embed misses seed; FakeClient explicit __init__ for instantiation robustness.
Memory client retry and recall timeout
workers/agent-worker/src/memory/client.py, workers/agent-worker/tests/test_memory_client.py
Adds recall timeout/attempts constants; fetch_recent_memories uses _get_with_retry with configurable attempts; 502-retry test.
Social turn: reply-first JSON, recall-aware streaming, max_tokens
workers/agent-worker/src/collective/schemas.py, workers/agent-worker/src/graph/nodes/llm_social_turn.py, workers/agent-worker/src/config.py, workers/agent-worker/src/llm/factory.py, workers/agent-worker/tests/test_social_stream_extract.py, workers/agent-worker/tests/test_llm_social_memory.py
SocialTurnOut declares reply before social; prompt requires reply-first JSON; streaming disabled for recall questions; llm_social_max_tokens config; max_tokens param for create_chat_model; stream extract and memory tests.
Social help heuristic, move patterns, casual/main integration
workers/agent-worker/src/collective/social_turn.py, packages/shared/src/speakIntent.ts, workers/agent-worker/src/graph/action_intent.py, workers/agent-worker/src/graph/memory_quote.py, workers/agent-worker/src/graph/casual_fast_lane.py, workers/agent-worker/src/main.py, workers/agent-worker/tests/test_casual_fast_lane.py, workers/agent-worker/tests/test_tool_gate.py
Reduces help false positives from 回复/请假; expands MOVE_PATTERNS for 去/找你/那里 variants; pick_memory_quote uses pick_recall_memory for recall questions; casual fast-lane short-circuits zero side-effects; main.py adds emit retry, player memory persistence, physical-action guard, and total timing.

E2E Scripts, Benchmarking, and GSD Review Tooling

Layer / File(s) Summary
Shared Playwright helper libraries
scripts/lib/speak-browser-stack.mjs, scripts/lib/speak-browser-round.mjs, scripts/lib/dialogue-engage.mjs, scripts/lib/e2e-memory-helpers.mjs
Extracts engageDialogue, room/NPC helpers, sendSpeakOverlay orchestration, waitForMemoryContext, REFUSAL_MARKERS, and drawer controls into reusable modules.
verify-phase20: cross-session memory recall
scripts/verify-phase20.mjs
E2E script seeding password/nickname, polling memory persistence, reloading, asserting recall content and refusal guards with latency smoke checks and single retry on transient failures.
verify-phase21, phase15 shim, phase19 refactor, and uat-phase21-helpers
scripts/verify-phase21.mjs, scripts/verify-phase15.mjs, scripts/verify-phase19.mjs, scripts/lib/uat-phase21-helpers.mjs, scripts/uat-phase21-playwright.mjs
New verify-phase21 drives exploration, lore discovery, and recall; phase-15 becomes shim delegating to phase-21; phase-19 imports shared engageDialogue; uat-phase21-helpers provides exploration/drawer/hook utilities; uat-phase21-playwright automates UI/world validation across multiple subtests.
verify-overlay-streaming and refusal parity
scripts/verify-overlay-streaming.mjs, scripts/assert-refusal-markers-parity.mjs
E2E asserting overlay streaming partial arrives within threshold; parity script comparing Python and JS refusal marker sets.
benchmark-speak-browser.mjs overhaul
scripts/benchmark-speak-browser.mjs
Rewrites benchmark with Colyseus connection wait, per-round HTTP reset with retry, overlay-based reply detection, intent metadata, selective case filtering, and JSON report with percentile summaries.
Playtest SPEAK-SLA and findings patch
scripts/playtest-speak-sla.mjs, scripts/lib/playtest-findings-patch.mjs
Automated multi-session SLA playtest with scripted turns, retry/backoff, subjective band recording, JSON report, and optional findings-markdown scorecard patching.
GSD cross-AI review tooling
scripts/gsd-review-providers.mjs, scripts/lib/gsd-openai-review.mjs, scripts/lib/gsd-review-merge.mjs, scripts/lib/gsd-review-providers-config.mjs
CLI to run reviews against Agnes/NVIDIA providers, probe connectivity, merge results into phase review markdown with frontmatter upsert.
Config, package.json, eslint, and agent-verify-map
package.json, eslint.config.js, scripts/lib/agent-verify-map.mjs, docs/ISSUE-LOG.md
Adds verify/playtest/gsd:review scripts; expands eslint globals for browser APIs; adds GF-10 golden flow for verify:phase20; appends ISSUE-050–055 and guardrails #75–#82.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • moyunzero/AetherLife#3: The zone-wander.ts fix wraps parseZoneId(...) in try/catch, directly depending on the multi-region parseZoneId behavior introduced by that PR's world-region system.
  • moyunzero/AetherLife#7: This PR extends the same immersive-shell dialogue stack by adding streamingReply wiring in ChatPage/DialogueOverlay and expanding ShellDrawer/DialogueBar with the new "discoveries" tab.
  • moyunzero/AetherLife#9: Both PRs directly touch the same server ambient-zone selection logic in apps/game-server/src/ambient/zone-wander.ts with the same parseZoneId(...) try/catch fallback pattern.

Poem

🐇 Hop! The drawers bloom with lore discovered bright,
Passwords recalled with deterministic might,
No more proxy traps — trust_env set to False,
The recall merge is fixed, no echoes exhaust.
Phase 20, Phase 21, the scripts now stand tall,
This bunny shipped them all! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.09% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and specifically summarizes the main changes: Phase 21 world echo lore features, verify gate implementation, and memory recall reliability fixes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/phase21-world-echo

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🧹 Nitpick comments (4)
scripts/assert-refusal-markers-parity.mjs (1)

1-8: 💤 Low value

Add shebang for consistency with other scripts.

This script lacks the #!/usr/bin/env node shebang that verify-overlay-streaming.mjs and other scripts in this directory have, which prevents direct execution.

Proposed fix
+#!/usr/bin/env node
 /**
  * Compare JS REFUSAL_MARKERS (e2e-memory-helpers) vs Python recall_merge._REFUSAL_MARKERS.
  * Exit 1 on mismatch — run in CI / before verify:phase20.
  */
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/assert-refusal-markers-parity.mjs` around lines 1 - 8, The script
assert-refusal-markers-parity.mjs is missing the shebang line at the beginning
of the file. Add #!/usr/bin/env node as the first line before the JSDoc comment
block to enable direct execution of this script, consistent with other script
files in the same directory like verify-overlay-streaming.mjs.
scripts/lib/gsd-review-merge.mjs (1)

114-146: 💤 Low value

Consider using a proper YAML parser for frontmatter manipulation.

The regex-based YAML parsing (lines 115-127 for reviewers: array, line 134 for reviewers_failed: removal) is tailored to the format this code generates and works correctly for that case. However, it's fragile:

  • The array parser assumes inline YAML [item, item] format and won't handle multi-line arrays.
  • The reviewers_failed regex requires exactly 2-space indentation and non-empty line content.

If the frontmatter format diverges or is hand-edited, the regex may silently fail to match or incorrectly parse. A proper YAML library would be more robust.

That said, for a closed-loop script that only manipulates files it creates, the current approach is acceptable.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/lib/gsd-review-merge.mjs` around lines 114 - 146, Replace the
regex-based YAML frontmatter manipulation with a proper YAML parser to handle
the reviewers array parsing and modifications. Instead of using the regex
pattern to extract and parse the reviewers array (splitting by comma, trimming,
and removing quotes), parse the frontmatter YAML object directly, manipulate the
reviewers array property, and serialize it back. Similarly, replace the
regex-based removal and addition of the reviewers_failed section by directly
modifying the YAML object's reviewers_failed property. Apply the same approach
to the reviewed_at timestamp update. This makes the code more robust to format
variations and hand-edited frontmatter while maintaining the same functionality.
scripts/verify-phase21.mjs (1)

127-135: ⚡ Quick win

Avoid duplicating loadPlaywright() — import from shared library.

The speak-browser-stack.mjs library (already imported on line 22) exports a more robust loadPlaywright() with three fallback paths. The local implementation here only tries one path before throwing.

♻️ Proposed refactor
+import {
+  loadPlaywright,
+  gameServerHttpBase,
+  loadRootEnv,
+} from "./lib/speak-browser-stack.mjs";
-import { gameServerHttpBase, loadRootEnv } from "./lib/env.mjs";

... (remove lines 127-135)

-async function loadPlaywright() {
-  const pwEntry = resolve(root, "scripts", ".pw-deps", "node_modules", "playwright", "index.mjs");
-  const pw = await import(pathToFileURL(pwEntry).href);
-  const chromium = pw.chromium ?? pw.default?.chromium;
-  if (!chromium) {
-    throw new Error("playwright not installed — cd scripts/.pw-deps && npm install");
-  }
-  return chromium;
-}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/verify-phase21.mjs` around lines 127 - 135, The `loadPlaywright()`
function is being duplicated locally when a more robust version already exists
in the `speak-browser-stack.mjs` library that's imported at the top of the file.
Remove the local `loadPlaywright()` function definition (the entire async
function block from line 127-135) and instead use the imported
`loadPlaywright()` from `speak-browser-stack.mjs`, which handles multiple
fallback paths instead of just one.
workers/agent-worker/src/main.py (1)

108-123: ⚡ Quick win

Remove unreachable code at lines 122-123.

The retry logic correctly handles up to 3 attempts with exponential backoff for retryable errors (502/503/504). However, lines 122-123 are unreachable because:

  • Successful responses return at line 116
  • Retryable errors on the final attempt (attempt 2) raise at line 119 due to attempt >= 2
  • Non-retryable errors raise immediately at line 119
🧹 Proposed cleanup to remove dead code
     for attempt in range(3):
         res = client.post(url, json=payload, headers=headers, timeout=10.0)
         try:
             res.raise_for_status()
             return
         except httpx.HTTPStatusError as exc:
             if exc.response.status_code not in retryable or attempt >= 2:
                 raise
             last = exc
             time.sleep(1 + attempt)
-    if last is not None:
-        raise last
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@workers/agent-worker/src/main.py` around lines 108 - 123, The code at the end
of the retry logic (the if last is not None: raise last block) is unreachable
because all code paths either return successfully at the raise_for_status()
call, raise an exception during error handling when attempt >= 2, or raise
immediately for non-retryable errors. Remove the unreachable code block that
follows the for loop in the retry logic to clean up the dead code.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/src/index.css`:
- Around line 217-219: Replace the raw CSS rule for
`.dialogue-overlay__last-line--streaming` with Tailwind 4 utility classes or
patterns instead of using plain CSS color-mix syntax. The color-mixing behavior
defined in the style block should be converted to use Tailwind 4's color
utilities and theme variables, following the repository's styling standard that
requires all CSS/SCSS files to use Tailwind CSS 4.x for UI styling. Apply the
same conversion approach to the other affected style blocks at lines 830-837 and
1708-1748.

In `@docs/ISSUE-LOG.md`:
- Around line 155-161: The guardrails list in the main section has a missing
entry - it jumps from guardrail `#76` directly to `#78`, skipping `#77`. Guardrail `#77`
about "social JSON parse 失败" is currently misplaced at line 1112 in the "## 记录"
section instead of being in the main guardrails section. Locate this guardrail
entry and move it to its correct sequential position between guardrail `#76` and
the current `#78` in the main guardrails list to restore the proper numbered
sequence (75, 76, 77, 78, 79, 80, 81, 82).

In `@packages/shared/src/speakIntent.ts`:
- Around line 55-58: The move-intent regex patterns in the speakIntent.ts file
are missing several patterns that exist in the Python worker classifier at
workers/agent-worker/src/graph/action_intent.py, specifically patterns for
`找一下`, `麻烦…去`, and `您去一下`. To fix this cross-layer intent drift, review the
regex patterns in the Python worker classifier file and add the missing patterns
to the corresponding move-intent regex patterns in speakIntent.ts (in the
section containing patterns like `/找你有事|有事找|.../, /(去|到|往).{0,8}(那边|那里|那儿)/,
etc.`) to ensure both layers classify intents consistently for the same
messages.

In `@scripts/benchmark-speak-browser.mjs`:
- Around line 223-237: The OVERLAY_NPC_REPLY constant contains two non-existent
CSS selectors (.dialogue-overlay__npc-text and .dialogue-overlay__line--npc)
that will never match DOM elements. Replace these selectors with .message--npc,
which is the canonical selector used in scripts/lib/speak-browser-round.mjs and
exists in DialogueBar.tsx and MessageList.tsx. Additionally, review the
THINKING_LOCATOR constant to ensure its selectors (.dialogue-overlay__thinking
and .dialogue-bar__summary-text--thinking) align with the canonical
.message--thinking selector from speak-browser-round.mjs. If the benchmark
intentionally targets different DOM locations, document this discrepancy with a
comment and verify those selectors actually exist in the DOM; otherwise, align
both locators to use the canonical selectors.

In `@scripts/lib/playtest-findings-patch.mjs`:
- Around line 47-81: The regex replacements are fragile because they assume
specific section markers exist without validating them first. Before each
md.replace() call, verify that the expected markers or delimiters exist in the
markdown string. For the three replacements targeting the Status line, the
Playtest scorecard section with the "## Benchmark p50/p95" delimiter, and the
Note section with the "**LOCK rule:**" delimiter, add checks using includes() or
a regex test to ensure these markers are present. If a marker is missing, either
skip that replacement or throw an informative error rather than silently failing
or corrupting the file.

In `@scripts/lib/speak-browser-stack.mjs`:
- Around line 46-48: The error message in the throw statement that handles the
case when playwright is not installed currently instructs users to run npm
install, but the monorepo uses pnpm for package management. Update the error
message in the throw statement (around the playwright not installed check) to
replace "npm install" with "pnpm install" to align with the monorepo's coding
guidelines.

In `@scripts/verify-phase20.mjs`:
- Around line 89-105: The conditional validation in the healthOk function for
the "game-server" label at line 98 uses AND operators which only throws an error
when all three conditions are simultaneously true. This logic is inverted—it
should throw an error if the service is not "game-server" OR if both status and
ok do not indicate success. Change the AND operators to OR in the condition
checking body.service !== "game-server" && body.status !== "ok" && body.ok !==
true so that the validation properly rejects responses where service is not
"game-server" or where the health indicators do not show success.

In `@workers/agent-worker/src/graph/action_intent.py`:
- Line 26: The regex pattern in the move-intent regex at line 26 includes a bare
`找一下` which is too generic and matches non-movement requests, causing incorrect
intent classification. Remove or refine the `找一下` pattern to be more specific to
movement contexts, ensuring it only matches when actually referring to physical
movement or searching actions. Apply the same fix to the similar pattern at line
197 to maintain consistency across the codebase.

In `@workers/agent-worker/tests/test_llm_social_memory.py`:
- Line 26: The assertion in the test is too permissive because checking for a
generic "reply" string will pass even if the explicit reply-first instruction is
missing. Tighten the assertion by removing the overly broad "reply" check and
instead verify the presence of a more specific pattern that directly confirms
the reply-first instruction contract is explicitly documented in the system
prompt. This ensures the test reliably catches regressions if someone removes or
changes the actual reply-first instruction.

---

Nitpick comments:
In `@scripts/assert-refusal-markers-parity.mjs`:
- Around line 1-8: The script assert-refusal-markers-parity.mjs is missing the
shebang line at the beginning of the file. Add #!/usr/bin/env node as the first
line before the JSDoc comment block to enable direct execution of this script,
consistent with other script files in the same directory like
verify-overlay-streaming.mjs.

In `@scripts/lib/gsd-review-merge.mjs`:
- Around line 114-146: Replace the regex-based YAML frontmatter manipulation
with a proper YAML parser to handle the reviewers array parsing and
modifications. Instead of using the regex pattern to extract and parse the
reviewers array (splitting by comma, trimming, and removing quotes), parse the
frontmatter YAML object directly, manipulate the reviewers array property, and
serialize it back. Similarly, replace the regex-based removal and addition of
the reviewers_failed section by directly modifying the YAML object's
reviewers_failed property. Apply the same approach to the reviewed_at timestamp
update. This makes the code more robust to format variations and hand-edited
frontmatter while maintaining the same functionality.

In `@scripts/verify-phase21.mjs`:
- Around line 127-135: The `loadPlaywright()` function is being duplicated
locally when a more robust version already exists in the
`speak-browser-stack.mjs` library that's imported at the top of the file. Remove
the local `loadPlaywright()` function definition (the entire async function
block from line 127-135) and instead use the imported `loadPlaywright()` from
`speak-browser-stack.mjs`, which handles multiple fallback paths instead of just
one.

In `@workers/agent-worker/src/main.py`:
- Around line 108-123: The code at the end of the retry logic (the if last is
not None: raise last block) is unreachable because all code paths either return
successfully at the raise_for_status() call, raise an exception during error
handling when attempt >= 2, or raise immediately for non-retryable errors.
Remove the unreachable code block that follows the for loop in the retry logic
to clean up the dead code.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7f18d4ca-bd58-4af2-9bf9-f1fe551dd23e

📥 Commits

Reviewing files that changed from the base of the PR and between 7733608 and fb182c3.

📒 Files selected for processing (71)
  • apps/game-server/src/ambient/zone-wander.test.ts
  • apps/game-server/src/ambient/zone-wander.ts
  • apps/web/src/ChatPage.tsx
  • apps/web/src/components/DialogueBar.tsx
  • apps/web/src/components/DialogueOverlay.test.ts
  • apps/web/src/components/DialogueOverlay.tsx
  • apps/web/src/components/DiscoveredLorePanel.test.ts
  • apps/web/src/components/DiscoveredLorePanel.tsx
  • apps/web/src/components/JournalQuestStrip.test.ts
  • apps/web/src/components/JournalQuestStrip.tsx
  • apps/web/src/components/PhaserGame.tsx
  • apps/web/src/components/ShellDrawer.test.ts
  • apps/web/src/components/ShellDrawer.tsx
  • apps/web/src/hooks/useChunkLore.test.ts
  • apps/web/src/hooks/useChunkLore.ts
  • apps/web/src/hooks/useColyseusRoom.ts
  • apps/web/src/index.css
  • docs/ISSUE-LOG.md
  • eslint.config.js
  • package.json
  • packages/shared/src/speakIntent.test.ts
  • packages/shared/src/speakIntent.ts
  • scripts/assert-refusal-markers-parity.mjs
  • scripts/benchmark-speak-browser.mjs
  • scripts/gsd-review-providers.mjs
  • scripts/lib/agent-verify-map.mjs
  • scripts/lib/dialogue-engage.mjs
  • scripts/lib/e2e-memory-helpers.mjs
  • scripts/lib/gsd-openai-review.mjs
  • scripts/lib/gsd-review-merge.mjs
  • scripts/lib/gsd-review-providers-config.mjs
  • scripts/lib/playtest-findings-patch.mjs
  • scripts/lib/speak-browser-round.mjs
  • scripts/lib/speak-browser-stack.mjs
  • scripts/playtest-speak-sla.mjs
  • scripts/verify-overlay-streaming.mjs
  • scripts/verify-phase15.mjs
  • scripts/verify-phase19.mjs
  • scripts/verify-phase20.mjs
  • scripts/verify-phase21.mjs
  • workers/agent-worker/src/collective/schemas.py
  • workers/agent-worker/src/collective/social_turn.py
  • workers/agent-worker/src/config.py
  • workers/agent-worker/src/graph/action_intent.py
  • workers/agent-worker/src/graph/ambient_intent.py
  • workers/agent-worker/src/graph/casual_fast_lane.py
  • workers/agent-worker/src/graph/lore_loop.py
  • workers/agent-worker/src/graph/memory_quote.py
  • workers/agent-worker/src/graph/nodes/llm_social_turn.py
  • workers/agent-worker/src/graph/npc_loop.py
  • workers/agent-worker/src/graph/recall_merge.py
  • workers/agent-worker/src/graph/social_edge_fast_lane.py
  • workers/agent-worker/src/http_json.py
  • workers/agent-worker/src/llm/factory.py
  • workers/agent-worker/src/main.py
  • workers/agent-worker/src/memory/client.py
  • workers/agent-worker/tests/test_action_intent.py
  • workers/agent-worker/tests/test_casual_fast_lane.py
  • workers/agent-worker/tests/test_fetch_state_and_memory.py
  • workers/agent-worker/tests/test_http_json.py
  • workers/agent-worker/tests/test_llm_social_memory.py
  • workers/agent-worker/tests/test_load_memory_recall_fallback.py
  • workers/agent-worker/tests/test_memory_client.py
  • workers/agent-worker/tests/test_memory_quote.py
  • workers/agent-worker/tests/test_npc_loop_mock.py
  • workers/agent-worker/tests/test_npc_social_order.py
  • workers/agent-worker/tests/test_recall_merge.py
  • workers/agent-worker/tests/test_social_stream_extract.py
  • workers/agent-worker/tests/test_social_turn.py
  • workers/agent-worker/tests/test_speak_intent.py
  • workers/agent-worker/tests/test_tool_gate.py
💤 Files with no reviewable changes (3)
  • apps/web/src/components/JournalQuestStrip.test.ts
  • apps/web/src/components/JournalQuestStrip.tsx
  • apps/web/src/components/PhaserGame.tsx

Comment thread apps/web/src/index.css
Comment on lines +217 to +219
.dialogue-overlay__last-line--streaming {
color: color-mix(in srgb, var(--shell-ink) 92%, var(--shell-accent));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Move new UI styling to Tailwind 4 patterns instead of raw CSS.

These newly added style blocks are implemented as plain CSS rules, which conflicts with the repository styling standard for CSS/SCSS files.

As per coding guidelines, **/*.{css,scss}: Use Tailwind CSS 4.x for UI styling.

Also applies to: 830-837, 1708-1748

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/index.css` around lines 217 - 219, Replace the raw CSS rule for
`.dialogue-overlay__last-line--streaming` with Tailwind 4 utility classes or
patterns instead of using plain CSS color-mix syntax. The color-mixing behavior
defined in the style block should be converted to use Tailwind 4's color
utilities and theme variables, following the repository's styling standard that
requires all CSS/SCSS files to use Tailwind CSS 4.x for UI styling. Apply the
same conversion approach to the other affected style blocks at lines 830-837 and
1708-1748.

Source: Coding guidelines

Comment thread docs/ISSUE-LOG.md
Comment thread packages/shared/src/speakIntent.ts
Comment thread scripts/benchmark-speak-browser.mjs Outdated
Comment thread scripts/lib/playtest-findings-patch.mjs
Comment thread scripts/lib/speak-browser-stack.mjs
Comment thread scripts/verify-phase20.mjs
r"来这边|这边(儿)?|来一趟|过来一趟|来一下|叫你来|让你来|你来|你来不",
r"找你有事|有事找|传话|叫你去|你去不|你去吗|去不去|你去一趟|你去一下",
r"找你有事|有事找|有事情找|事情找|传话|叫你去|你去不|你去吗|去不去|你去一趟|你去一下",
r"(?<![有])找你|找一下",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid bare 找一下 in move-intent regex; it over-triggers movement.

找一下 is not movement-specific and will match many non-move requests, which can incorrectly classify intent as physical and inject unintended move tool calls.

Suggested tightening
-    r"(?<![有])找你|找一下",
+    r"(?<![有])找你|找你一下",
...
-        r"旁边|附近|旁白|那边|那里|那儿|去找|去找她|去找他|找你有事|有事找|传话|叫你去|(?<![有])找你|找一下",
+        r"旁边|附近|旁白|那边|那里|那儿|去找|去找她|去找他|找你有事|有事找|传话|叫你去|(?<![有])找你|找你一下",

Also applies to: 197-197

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@workers/agent-worker/src/graph/action_intent.py` at line 26, The regex
pattern in the move-intent regex at line 26 includes a bare `找一下` which is too
generic and matches non-movement requests, causing incorrect intent
classification. Remove or refine the `找一下` pattern to be more specific to
movement contexts, ensuring it only matches when actually referring to physical
movement or searching actions. Apply the same fix to the similar pattern at line
197 to maintain consistency across the codebase.

Comment thread workers/agent-worker/tests/test_llm_social_memory.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/uat-phase21-playwright.mjs`:
- Around line 49-56: The script uses Node.js and browser runtime globals like
process, console, fetch, and setTimeout without declaring them, causing ESLint
no-undef violations throughout the file. Add a file-level ESLint global
declaration comment at the very top of the scripts/uat-phase21-playwright.mjs
file (before any code) to declare all these globals, including process, console,
fetch, setTimeout, and any other browser-context symbols used in callbacks. This
minimal change will resolve the lint failures without requiring additional
ESLint configuration changes.
- Around line 294-305: The npcMoved check is comparing current NPC grid
positions against the player's pre-move grid position (gridBefore), which
produces false positives since the player has moved. Instead, you need to
capture the NPC positions before the player speaks (similar to how gridBefore
captures player position), then after movement compare the current NPC positions
against those captured before positions. Modify the code to first evaluate and
store the NPC sprite positions before the speak action occurs, then later
compare the current NPC positions against those stored positions to accurately
detect if NPCs actually moved.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 81b46585-4ee7-4d2a-a9eb-3b1e6f649af9

📥 Commits

Reviewing files that changed from the base of the PR and between fb182c3 and 54529ce.

📒 Files selected for processing (6)
  • package.json
  • scripts/lib/dialogue-engage.mjs
  • scripts/lib/uat-phase21-helpers.mjs
  • scripts/uat-phase21-playwright.mjs
  • scripts/verify-phase13.mjs
  • scripts/verify-phase21.mjs
🚧 Files skipped from review as they are similar to previous changes (2)
  • scripts/lib/dialogue-engage.mjs
  • package.json

Comment on lines +49 to +56
const webBase = process.env.WEB_URL || "http://localhost:5173";
const roomId = process.env.UAT_PHASE21_ROOM_ID || `uat-p21-${Date.now()}`;
const webUrl = `${webBase}${webBase.includes("?") ? "&" : "?"}room=${encodeURIComponent(roomId)}`;
const speakTimeoutMs = Math.max(90_000, e2eSpeakTimeoutMs());
const E2E_LORE_TIMEOUT_MS = Number.parseInt(process.env.E2E_LORE_TIMEOUT_MS || "", 10) || 240_000;
const RUN_VERIFY_GATES = process.env.RUN_VERIFY_GATES !== "0";
const RUN_VERIFY_PHASE13 = process.env.RUN_VERIFY_PHASE13 !== "0";
const VERIFY_GATE_QUIET_MS = Number.parseInt(process.env.VERIFY_GATE_QUIET_MS || "", 10) || 45_000;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix no-undef globals for this Node+Playwright script to unblock lint.

ESLint is flagging runtime globals used here (process, console, fetch, setTimeout, plus browser-context symbols used in callbacks). If lint is required in CI, this script currently won’t pass.

Suggested minimal file-level fix
+/* eslint-env node, browser */
 /**
  * Phase 21 UAT — World Echo (SOLO-02/03) Playwright automation.
  *

Also applies to: 79-79, 95-95, 114-114, 327-327, 337-337, 394-394, 407-407, 448-448

🧰 Tools
🪛 ESLint

[error] 49-49: 'process' is not defined.

(no-undef)


[error] 50-50: 'process' is not defined.

(no-undef)


[error] 53-53: 'process' is not defined.

(no-undef)


[error] 54-54: 'process' is not defined.

(no-undef)


[error] 55-55: 'process' is not defined.

(no-undef)


[error] 56-56: 'process' is not defined.

(no-undef)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/uat-phase21-playwright.mjs` around lines 49 - 56, The script uses
Node.js and browser runtime globals like process, console, fetch, and setTimeout
without declaring them, causing ESLint no-undef violations throughout the file.
Add a file-level ESLint global declaration comment at the very top of the
scripts/uat-phase21-playwright.mjs file (before any code) to declare all these
globals, including process, console, fetch, setTimeout, and any other
browser-context symbols used in callbacks. This minimal change will resolve the
lint failures without requiring additional ESLint configuration changes.

Source: Linters/SAST tools

Comment thread scripts/uat-phase21-playwright.mjs Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/verify-phase21.mjs`:
- Line 8: The `URLSearchParams` used on line 89 is not imported, causing an
ESLint `no-undef` error. Add `URLSearchParams` to the import statement on line 8
where `fileURLToPath` is imported from `node:url` so that both symbols are
available in the file.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 87a3f475-ffc7-4e61-a4b1-36898f87a7a7

📥 Commits

Reviewing files that changed from the base of the PR and between 54529ce and 2dafb8c.

📒 Files selected for processing (15)
  • docs/ISSUE-LOG.md
  • eslint.config.js
  • packages/shared/src/speakIntent.test.ts
  • packages/shared/src/speakIntent.ts
  • scripts/benchmark-speak-browser.mjs
  • scripts/lib/e2e-memory-helpers.mjs
  • scripts/lib/playtest-findings-patch.mjs
  • scripts/lib/speak-browser-round.mjs
  • scripts/lib/speak-browser-stack.mjs
  • scripts/lib/uat-phase21-helpers.mjs
  • scripts/uat-phase21-playwright.mjs
  • scripts/verify-phase20.mjs
  • scripts/verify-phase21.mjs
  • workers/agent-worker/src/main.py
  • workers/agent-worker/tests/test_llm_social_memory.py
💤 Files with no reviewable changes (1)
  • workers/agent-worker/src/main.py
🚧 Files skipped from review as they are similar to previous changes (12)
  • eslint.config.js
  • packages/shared/src/speakIntent.ts
  • workers/agent-worker/tests/test_llm_social_memory.py
  • scripts/lib/speak-browser-stack.mjs
  • scripts/verify-phase20.mjs
  • scripts/lib/playtest-findings-patch.mjs
  • scripts/lib/speak-browser-round.mjs
  • scripts/lib/e2e-memory-helpers.mjs
  • scripts/uat-phase21-playwright.mjs
  • scripts/benchmark-speak-browser.mjs
  • scripts/lib/uat-phase21-helpers.mjs
  • docs/ISSUE-LOG.md

* Replaces Phase 15 journal-quest-strip gate with lore-discover-toast + drawer「已发现」.
*/
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix ESLint no-undef for URLSearchParams.

Line 89 uses URLSearchParams, and the current lint run flags it as undefined.

Suggested surgical diff
-import { fileURLToPath } from "node:url";
+import { fileURLToPath, URLSearchParams } from "node:url";

Also applies to: 89-89

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/verify-phase21.mjs` at line 8, The `URLSearchParams` used on line 89
is not imported, causing an ESLint `no-undef` error. Add `URLSearchParams` to
the import statement on line 8 where `fileURLToPath` is imported from `node:url`
so that both symbols are available in the file.

Source: Linters/SAST tools

moyunzero and others added 3 commits June 22, 2026 14:46
… fixes

Ship DiscoveredLorePanel in the shell drawer, remove JournalQuestStrip,
and add verify:phase21 for chunk lore, drawer history, and reload recall.

Harden worker memory for E2E: disable httpx system proxy on localhost,
persist player lines before emit done, retry recent-memory fetches, and
fall back to recency when embed recall misses after reload.

Fixes ISSUE-055

Co-authored-by: Cursor <cursoragent@cursor.com>
Extract shared lore/explore helpers, add uat:phase21:playwright with worker quiet period, and fix verify:phase13 canvas focus after immersive shell changes.

Co-authored-by: Cursor <cursoragent@cursor.com>
Align speakIntent relay patterns with worker, fix overlay selectors and P21-06 NPC grid checks, tighten verify health/banner polling, and document ISSUE-053 guardrail #77.

Co-authored-by: Cursor <cursoragent@cursor.com>
@moyunzero moyunzero force-pushed the feat/phase21-world-echo branch from 2dafb8c to 7e0311a Compare June 22, 2026 06:48
Co-authored-by: Cursor <cursoragent@cursor.com>
@moyunzero moyunzero merged commit c4c460c into main Jun 22, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant