fix: preserve complete consultation replies and recover retained history

This commit is contained in:
鲨鱼辣椒
2026-09-28 17:38:31 +08:00
parent dda2f0a3be
commit d530abe580
14 changed files with 643 additions and 38 deletions

View File

@@ -12,15 +12,22 @@
## Scope
- Diagnose the screenshot showing only a short prose fragment followed by a legacy parse-failure notice and raw structured output.
- Diagnose and repair the screenshot showing only a short prose fragment followed by a legacy parse-failure notice and raw structured output.
- After diagnosis, user authorized the three repair paths: conservative full-prose recovery, independent auxiliary-field fallback plus explicit manual retry, and passive recovery of retained history.
## Intent And Constraints
- Read-only investigation requested. No product edits, history changes, app restart, paid question, cloud access or deployment.
- Initial investigation was read-only. The later implementation is explicitly authorized; keep local raw evidence and avoid automatic model replay, live app restart, cloud edits, or deployment. The earlier main merge/push request covered the completed Agent refresh feature.
- Latest user constraint: no subagents. Remaining code review, changes and verification are performed by the primary agent; earlier partial subagent work was reviewed locally.
- Ownership and planning gates passed in this task checkout. Reused unchanged canonical context from the prior turn; read all current peer task scopes. Current trial Main 9adab45 / Renderer cb48f60 belongs to the separate reply-cleanup task and is distinct from main 4495345. Inspected committed code only; peer working files remain untouched.
## Outcome
- The three approved repair paths are implemented and locally verified: conservative full-prose recovery, explicit incomplete-reply handling/manual retry, and passive retained-history recovery. The running client remains unchanged.
- Implementation and final validation are recorded below; the following investigation notes preserve how the original diagnosis was established.
### Initial Diagnosis
- The screenshot notice maps to the legacy structured-response parse fallback. The UI retains unparsedResponse separately from the recovered prose, so content in the raw box was received even though normal presentation failed.
- Independently reproduced an exact-shape failure: an unescaped ASCII quote after the opening prose causes JSON parsing to fail, while stringToken/recoverReply accepts only the prefix as a complete reply. The old parser returns the same short-prefix + screenshot notice combination. The newer shared/teacher-reply fields recovery in committed 9adab45 still accepts the same premature prefix.
- This is a demonstrated mechanism, not a confirmed trace of the screenshot request. The exact question/response was not found in the known trial/installed-app project teacher histories. The screenshot shows only the tail of the raw output, so its precise triggering bytes and provider termination reason remain unknown.
@@ -28,6 +35,10 @@
## Verification
- Final implementation checks: 329 unit checks, 17 headless UI cases, typecheck, scoped ESLint and production compilation passed. Native Electron case added and enumerated but not executed. The inherited dependency-document drift finding remains disclosed below.
### Initial Investigation Verification
- Read old shared/teacher-discussion.ts stringToken/recoverReply/parser fallback, UI diagnostics rendering, service/discussion persistence and cloud-runner final-output handling.
- Independent agent performed an in-memory execution of both committed parsers with malformed quoted prose and reproduced the truncated prefix. No tests or product files added/modified.
- Inspected only relevant local app runtime descriptors/project catalogs and teacher conversation records to seek the screenshot request; no matching request found. No credentials read or emitted.
@@ -35,11 +46,11 @@
## Follow-ups
- If repair is requested, fix conservative recovery in the current reply-only implementation, preserving exact raw diagnostics; do not restore retired discussion components. Verify the exact request raw prefix/provider finish reason before assigning a unique cause to this screenshot.
- The requested repair is complete; integrate and update the running client as a separate next step. Provider byte-level correlation remains unavailable; retain the screenshot-level evidence boundary.
## Promotion Candidates
- None. This read-only diagnosis adds evidence and a repair direction, not an accepted product behavior change.
- See Repair Follow-ups And Promotion Candidates below. Initial diagnosis introduced no accepted behavior change; the later repair was approved by the user.
## Agent Log Screenshot Follow-up
@@ -47,4 +58,30 @@
- User supplied an Agent log screenshot containing the visible prefix `{"reply":"它早就不是"刚搭好架子"那种阶段了——` followed by a substantial prose answer and legacy structure payload. The earlier client screenshot shows exactly the prose prefix 它早就不是, the legacy fallback notice, and the later tool payload in the raw-content box.
- Independent reviewer replayed the visible prefix in memory against the old discussion parser and committed 9adab45 reply parser. Both cut at precisely the same quote. Together the screenshots locate the observed short-body symptom to malformed-format handling/recovery, strongly contradicting a stream that delivered only the first few characters.
- Distinguish upstream malformed structured output from downstream unsafe recovery: model-produced internal quotes need valid encoding, and the client must not accept an ambiguous prefix as complete prose. This is parsing before rendering, rather than visual clipping.
- Evidence remains screenshot-level, not a request-correlated byte capture. A log renderer could alter escape display, so do not claim the full transport was byte-for-byte audited or all possible transport issues were excluded. No corrective code has been applied.
- Evidence remains screenshot-level, not a request-correlated byte capture. A log renderer could alter escape display, so do not claim the full transport was byte-for-byte audited or all possible transport issues were excluded. No corrective code had been applied at this diagnostic stage; the approved implementation is recorded below.
## Approved Repair Implementation
- Same-task ownership and planning gates passed on resume; registry remains this task / feature / owned worktree and branch. Merged the committed current reply-only dependency `40c247a` through `dda2f0a` so the fix applies to the current consultation implementation. No peer working files or canonical documents were edited; protocol-boundary and welcome/child-language changes were not imported.
- Prose extraction now requires a credible field boundary. Internal unescaped quotes and literal control characters are repaired only inside the prose field and only when the entire repaired envelope parses. Ambiguous boundaries, duplicate bodies and missing body endings fail visibly; missing content is never guessed. Ordinary Markdown, escaped code and Unicode remain intact.
- Complete prose survives malformed or truncated auxiliary data. Replies without trustworthy complete prose get `replyIncomplete`; UI shows an explicit incomplete state and a manual retry, never the half-sentence as a finished answer. Raw diagnostics stay folded and inert, including when escaping was successfully repaired. Technical output instructions require proper JSON string escaping without changing the published persona.
- Manual retry reuses original text, intent, references, source, topic and published version; an accepted completed turn gets a new request identity, uncertain resends reuse their identity. Click guarding prevents duplicate sends. Both current text/references and edits made while awaiting the send response survive. Project-only history does not substitute a different source or send the `project` sentinel as a conversation id. Disabled/legacy Agents and check-ins cannot be retried as ordinary questions.
- Store reads repair eligible completed history from retained original content locally. Valid unrelated prose is not replaced by a different recovered answer. Failed/cancelled/interrupted/running replies are never promoted to success. The projection preserves raw data, opaque legacy fields, accounting, request status and topic metadata; no model call or repair-only disk write occurs. In mixed history, restart recovery is persisted before applying the display projection. Incomplete assistant content and suggestions are excluded from both compiled model context and read_conversation while the user's original question remains available.
## Implementation Verification
- Focused parser, history and Renderer unit suites: 153/153 passed.
- Related service/cloud/model/read-tools/guidance/retired-route/activity suites: 176/176 passed (329 unit checks total).
- Typecheck and scoped ESLint passed. Production Renderer/Main/Preload/worker compilation passed (`build:vite`); build artifacts remain ignored.
- Headless Chrome layout/interaction suite: 17/17 passed, covering 319px and 508px widths, complete screenshot-shaped prose, incomplete manual retry, original input retention, no automatic send, draft retention and legacy-history behavior. Personally inspected narrow-panel recovered/incomplete screenshots. Initial browser run could not launch because the Playwright bundled binary was absent; reran successfully using the already-installed Chrome channel.
- Added an Electron integration case for click-only retry and preservation of both main and consultation drafts. Native Electron case was not run because the existing fixture shows/focuses an app window; keep the user's no-focus workflow intact. Browser interaction and backend history tests passed independently; do not claim a native smoke or a real-provider end-to-end run.
- Current live client was not replaced/restarted; code is ready for integration and a subsequent app update. Tests use deterministic local fixtures and do not consume paid model calls or modify real conversation history.
## Documentation Boundary Note
- Project-document structure check passed. The task-aware drift checker reports the already-committed cleanup task record as foreign relative to the original `4495345` base because this task merged the required reply-only dependency. That record is byte-identical to dependency `40c247a`; no owned-work edits touch it. Do not rewrite or delete the peer record, falsify the recorded base, or claim this inherited checker finding passed. All implementation-era document edits are limited to this task record and product README.
## Repair Follow-ups And Promotion Candidates
- Integrate the tested repair branch, then update the user's running client when requested. Exact historical screenshot request was not located locally; fixture tests reproduce the visible triggering bytes, not a provider trace.
- Candidate for the canonical current-state/consultation architecture at integration: preserve trustworthy full prose independently of auxiliary data; flag ambiguous incomplete prose explicitly; user-triggered retries preserve original scope and draft; retained raw data permits local history repair without replay. Evidence: focused unit and headless UI checks above. Future impact: any new reply format/history migration must retain the same safety properties. No change to published persona, model billing boundary or retired component policy; no additional product decision required beyond the user's approved repair.