Compare commits

...
Author SHA1 Message Date
总工andmultica-agent 038f1eeae7 HEL-906 rework materials: r5-followup-package §4 dual-accounting + fail-closed wording, handoff update
- §4: conservative pre-record vs confirmed-delivered accounting, ledger
  failure fail-closed, guarantees scoped to armed process (cross-process
  bounded overshoot / crash undercount stay monitored boundaries, no
  'never exceeds' claims), window execution convention (3 sequential
  calls, tightened params with margin), verification command now 24 cases
- milestone table registers rework commit cf395d4
- handoff-HEL-906.json: rework deliverable/acceptance/verification as above

Co-authored-by: multica-agent <github@multica.ai>
2026-10-28 09:04:29 +08:00
总工andmultica-agent cf395d4e06 HEL-906 rework: persistence fail-closed stop + conservative pre-record accounting
- asr_usage: ledger write/read failure, corruption or mid-window vanish now
  stop all window outbound (fail-closed), preserve known consumption
  (monotonic, no zero-budget default), log + record guard_stop (durable
  across same-window restarts)
- asr_usage: conservative reserve before send (failed/cancelled sends are
  NOT refunded) + separately tracked confirmed-delivered seconds; replay
  restores both
- stt_funasr: reserve_audio before ws.send, note_audio_delivered after
  successful await only
- tests: 24 deterministic offline cases incl. manager's budget-regression
  repro, read/corrupt/missing rejection, record-then-raise/cancel fake ws,
  guard_stop across restart

Co-authored-by: multica-agent <github@multica.ai>
2026-10-28 09:02:51 +08:00
总工andmultica-agent ccfc04edcd HEL-906: close r5-followup-package §4 blocker, register milestone, add handoff
- §4: ASR audio/retry rows now program-gated (pre-send/pre-connect refusal
  when armed, fail-closed default); mechanism, arming env, reproducible
  verification commands, program-guarantee vs monitor-only boundary
  (bounded cross-process staleness, bounded crash undercount, .12 real-stack
  pre-window check still required); wall-clock proxy demoted to lower-bound
  cross-check; first-line status updated
- milestone table: register hel-906-asr-usage-counting @ ecec9df (candidate,
  PR not merged, pending 总管 review)
- handoff-HEL-906.json (v1.12, read at 0b804a4); requirement checks 4/4 pass
  (origin/main and 0b804a4, with/without handoff)

Co-authored-by: multica-agent <github@multica.ai>
2026-10-28 08:46:07 +08:00
总工andmultica-agent ecec9df8e8 HEL-906: continuous-call ASR usage metering + one-shot window guard in dedicated worker seam
- asr_usage.py (stdlib-only): append-only JSONL ledger, per-call/window audio
  seconds counted from actually-sent frames (sample-rate aware, resend counted),
  connect attempt/failure/retry semantics (normal per-utterance reconnects are
  not retries), fail-closed default (real cap=0 unless a window is armed),
  pre-send blocking so no byte crosses the cap, ledger replay for restart
  recovery and cross-window isolation, resumable summarize CLI
- stt_funasr.py: guard hooks on the real send path (check_connect before
  open_utt, precheck before send, count after send, recv-side death recorded,
  per-utterance flush aggregation); usage=None keeps prior behavior
- worker.py: entrypoint builds guard from env per job, room name as call id,
  on_exhausted -> ctx.shutdown ends the call, window summary in start log
- tests: 17 deterministic offline tests incl. fake livekit/websockets SDK
  driving the real _run seam, sample-rate variants, silence/cancel/failure/
  reconnect/repeated-end, cap rejection before crossing, ledger persistence
  across restart, room deletion keeps history, other windows unaffected

Co-authored-by: multica-agent <github@multica.ai>
2026-10-28 08:44:20 +08:00
总工andmultica-agent 0b804a49ef HEL-905: clarify verified baseline vs registration commits in milestone table
Co-authored-by: multica-agent <github@multica.ai>
2026-10-28 01:41:25 +08:00
总工andmultica-agent fdfa3424b7 HEL-905: register final verified head 03cec26 in follow-up package milestone table
Co-authored-by: multica-agent <github@multica.ai>
2026-10-28 01:41:12 +08:00
总工andmultica-agent 03cec2679e HEL-905: unified review handoff + follow-up package honesty fixes (first-line blocker, register_asr scope, ledger jq boundary, mic-interrupt T0 time base)
Co-authored-by: multica-agent <github@multica.ai>
2026-10-28 01:40:57 +08:00
总工 7cb73429f7 HEL-905: integrate HEL-904 backend rework (recoverable register-failure mapping, call-window voice pin) 2026-10-28 01:14:35 +08:00
总工 7c387db0a0 HEL-905: integrate HEL-903 preserved frontend rework (author source 51669aa, 11 files byte-preserved) 2026-10-28 01:14:32 +08:00
总管andmultica-agent 088ac621dc HEL-903: retain author log bytes without newline conversion
Co-authored-by: multica-agent <github@multica.ai>
2026-10-28 01:11:54 +08:00
总管andmultica-agent 3efecced3f HEL-903: record exact source preservation and four requirement checks
Co-authored-by: multica-agent <github@multica.ai>
2026-10-28 01:11:41 +08:00
总管andmultica-agent 51669aae6d HEL-903: preserve visual author voice status, call voice and recovery fixes
Co-authored-by: multica-agent <github@multica.ai>
2026-10-28 01:10:48 +08:00
施工员2号andmultica-agent 07dda2bfa2 HEL-904: backend rework — recoverable register-failure mapping, worker-state corruption docstring, call-window voice pin closes capture/worker race
- register(): local metadata write failure -> LocalRecordFailed -> 503 Chinese
  message with the cg- id (shared registration NOT rolled back, refresh list to
  recover, never re-register); register_asr journaled BEFORE local persist so
  failed registrations still count in the resource ledger
- write_worker_voice(): docstring now states the whole-file rewrite on
  unreadable/corrupt state (other keys preserved only when parseable)
- capture->worker race closed with a session-immutable-snapshot equivalent:
  _capture_voice holds the captured voice (no await gap); apply_selection
  defers its worker-state write while any call is live; open_session releases
  the hold on failure/cancel; VoiceSessionService.on_session_settled releases
  on explicit end, end-into-debt and TTL expiry; response voice == the voice
  the worker actually reads for that call, mid-call changes affect the next
  call only; overlapping-call residual documented honestly
- tests: hel902 fault-injection case 1 rewritten for the new contract
  (recovery + restart + 409 gate kept), 4 new deterministic Event-gated race
  tests (worker read delayed past dispatch, concurrent apply, failure/cancel
  cleanup, next-call voice), zero sleep-based assertions
- docs: contract §2.2/§2.5/§3 updated; r5 followup package tightened (head
  milestones d167ba9/b09a907/pending-review, register ASR vs call ASR split,
  call-ASR counting means + honest pending blocker, single-timebase frame
  silence measurement, D1 female_soft reference audio pinned by sha256,
  recycling checklist, shared-TTS snapshot/restore, git-revert is not
  recycling, caps count failures/retries/outbound-no-response)
- evidence: pytest 380/380 (376 baseline + 4), verify chain 12/12, ruff clean,
  requirement checks 4/4 (origin/main and b09a907, with/without handoff)

Co-authored-by: multica-agent <github@multica.ai>
2026-10-28 01:09:19 +08:00
总工andmultica-agent b09a907747 HEL-902 unified offline review: integration branch verified (pytest 376/376 + chain 12/12 + req checks 4/4), fault-injection suite for register/session/journal half-states, frontend preservation re-verified 8/8 + author logs consistent, one must-fix (services wording) + five suggestions consolidated, real-test approval package prepared (caps stay 0 until approved), requirements README baseline-line erratum v1.11->v1.12
Co-authored-by: multica-agent <github@multica.ai>
2026-10-28 00:37:37 +08:00
总工 d167ba927c Merge remote-tracking branch 'origin/hel-879-assembly' into hel-902-integration 2026-10-28 00:20:10 +08:00
总管andmultica-agent 37b4e690cf HEL-900: record byte preservation and requirements verification
Co-authored-by: multica-agent <github@multica.ai>
2026-10-28 00:18:06 +08:00
总管andmultica-agent 701442d4d4 HEL-900: preserve visual director custom voice frontend unchanged
Co-authored-by: multica-agent <github@multica.ai>
2026-10-28 00:16:59 +08:00
总管andmultica-agent be149cd6f7 HEL-879 verify endpoint grants and correct final window evidence
Co-authored-by: multica-agent <github@multica.ai>
2026-10-28 00:11:44 +08:00
opencodeandmultica-agent 376653135b HEL-879 R5 real-machine window executed (2026-10-27 15:26:54-15:58:10 UTC, 31min16s, within 2h/3-call/40req/500k-token/200-TTS-segment/0-embed authorization): real chain verified on .12 (isolated 8360/8361 dual-entry + 8363 WSS bridge + dedicated worker 8082 + shared TTS 9880 recovered for the window only) -- 3 conversational calls incl. 3 continuous rounds with receipts acked (4 sharing_attempt ack_at/judged), 3 real barge-in interrupts (turn_cancel interrupted, old turn fail-closed), hangup deletes room with ~20ms residual, abrupt-drop rooms TTL-compensated clean after API restart epoch 4->5, voice/text mutual cancel via same chat lock observed (voice-cancelled-text direction); usage ledger 14/40 requests, 42717/500000 tokens, 12/200 TTS segments, 0 embeddings, 0 new spend, budget sealed (real usage preserved) + consistency snapshot archived; two real-machine fixes with evidence: livekit_wire server token now carries SDK-parity video grants (room_admin/room_create; grantless bearer rejected 401 by real server) and Twirp calls switched to JSON encoding (protobuf body unparseable), offline fakes updated, 352/352 pytest + ruff(src/tests/scripts) clean; self-signed cert reissued critical CA:FALSE SAN=IP after rustls CaUsedAsEndEntity rejection; D1 audition README corrected to 'non-custom-function entry' per user feedback (comment 01a17de1); precise ~1s interrupt stop timing and text-cancels-voice direction honestly listed as not-obtained (follow-up window); all window-only processes torn down, protected services untouched, ComfyUI stays paused
Co-authored-by: multica-agent <github@multica.ai>
2026-10-28 00:00:41 +08:00
施工员2号andmultica-agent ce7279e68f HEL-899: custom voice via reference audio on the unified API — reuse .12 panel zero-shot registry (server-side proxy, cg- namespaced IDs never collide with the 11 builtin voices, panel stays the only voices.json writer) + CosyVoice fresh-synthesis preview (unknown voice pre-checked: no silent default fallback) + persisted selection/manifest/usage journal in instance data space with atomic writes and full rollback; session-open captures the effective voice (selection if valid else worker-file value else default, D1 domain untouched when user never chose) pinned per call, mid-call changes land at next call at the earliest; explicit allow_asr gates FunASR transcription, preview/ASR journaled, preview writes no chat/memory/receipt rows; zero new deps (raw-bytes upload endpoint), zero real calls in tests: 18 new cases (formats/size/duration refusals, collision+concurrency, panel down/reject/lying, tts down, rollback, restart persistence, call pinning, identity/shared-state untouched) + offline chain script 12/12, suite 370; req governance v1.12: CG-010 boundary updated to custom-voice-first per user comment 01a17de1 (verbatim evidence + changes + manifest), checks pass vs origin/main and base 8dc84f8 with handoff-HEL-899
Co-authored-by: multica-agent <github@multica.ai>
2026-10-27 23:34:00 +08:00
zonggongandmultica-agent 8dc84f88e9 HEL-879 final closure: integrate preserved HEL-898 rework (b0dcb17->18aef2a ff, zero backend diff), one-pass review of the three fixes passes offline (agent-left teardown via existing fail path incl. DELETE debt; closed set per-call unbounded, seq gate unchanged; speaking-state sentence-granularity display with in-place grow), web 118/118 + typecheck/build/lint(0err/1known) green, req checks 3/3; deliver D1 zero-synthesis audition package (11 reference recordings fetched read-only, sha256 matched per file, offline index.html + manifest; repo keeps manifest/provenance only) and update r5-test-package section 4/8 into the delivered-audition + consolidated request form; zero real calls/downloads/installs/deploy/merge, caps stay 0
Co-authored-by: multica-agent <github@multica.ai>
2026-10-27 07:24:27 +08:00
总管andmultica-agent 18aef2a459 HEL-898: record independent 118 tests and 53 browser replay checks
Co-authored-by: multica-agent <github@multica.ai>
2026-10-27 07:14:21 +08:00
总管andmultica-agent 9254c17316 HEL-898: preserve director three-fix rework verbatim on b0dcb17
Co-authored-by: multica-agent <github@multica.ai>
2026-10-27 07:09:23 +08:00
总管andmultica-agent b0dcb17e1a HEL-879 keep pending frontend acceptance explicit in R5 package
Co-authored-by: multica-agent <github@multica.ai>
2026-10-27 06:49:33 +08:00
总管andmultica-agent 9756db3204 HEL-879 clarify dependency inventory and evidence scope; clean bridge lint
Co-authored-by: multica-agent <github@multica.ai>
2026-10-27 06:48:37 +08:00
hel-879-workerandmultica-agent 11fd0e2dc2 HEL-879 R5 test package v2 (materials rework per manager's 4-item list; zero execution): (1) dependencies split into two tracks - R5 voice-only chain verified present (LiveKit 7880 plain ws / FunASR / agent venv livekit-agents 1.8.8 / CosyVoice weights, GPU 30520MiB free) vs FULL unified experience honestly listed as 41-item closure truly-missing (mem0ai fixed-source wheel offline-built sha256 698284b6 352586B, greenlet/pillow/truststore cache-reusable, 37 pending download authorization, hatchling resolved; existing .12 product venvs probed have NO mem0 and are system 3.12.3 - 'zero missing' does not hold for the full experience; fastembed models actually 190.4MB+0.05MB, hel895 doc's ~92MB flagged as erratum); (2) trusted HTTPS+WSS rewritten with evidence - retracted 'one self-signed click covers LiveKit WSS 7880' (7880 is plain ws shared by generic worker and 8090 demo, 7881 is not an HTTP endpoint, the 8090 https page itself hands browsers ws:// = mixed content with no proof it ever worked); proposed dedicated 8363 TLS bridge deploy/voice/wss_bridge.example.py (asyncio+ssl stdlib, zero new deps; local smoke TLS1.3 + byte transparency pass, wss-bridge-smoke.log), livekit_url=wss://...:8363, per-device per-port trust steps, ignore-cert switches explicitly banned, iOS/Android behavior marked pending real device; (3) budget per-category - acquire() is an atomic pre-send hold so failures/retries/interrupts/background all count (LLM call count != successful chat lines); LLM+embedding = code-enforced hard gates, ASR/TTS/session-creation honestly listed as ops-only caps (window<=2h, calls<=3, TTS<=200 segments; session-cap listed as engineering follow-up); proposed caps do not self-activate, caps stay 0; (4) engineering params decided by engineering - 8360/8361/8363/8082 and /opt/voice-agent-cybergril + /opt/cybergril-voice-certs all verified conflict-free read-only; D1 rewritten to the real 11 voice IDs with zero-synthesis reference-audio audition plan (8090 panel has NO audition and mutates shared state - v1 claim retracted; generic demo 'rain' is not this user's choice). .12 facts from read-only SSH probes, evidence sheet r5-facts-20261027.md (secrets redacted); web/src and src zero changes; 4x requirements checks pass (origin/main and f92a86f, with/without handoff); handoff appended; zero real LLM/embed/ASR/TTS/mic, zero download/install/deploy/merge, real caps stay 0
Co-authored-by: multica-agent <github@multica.ai>
2026-10-27 06:44:42 +08:00
zonggongandmultica-agent f92a86f5d8 HEL-879 unified integration review: actual fc905fe worker frames (generated on the .12 real SDK, read-only SSH stdin) fed through the preserved HEL-898 frontend - fixtures + 9 vitest cases pass (incremental->final->record takeover, phi/no-receipt/interrupt/proactive-during-playout lifecycles, user-interim/hush/drop-reopen boundaries, replay/late/out-of-order frames all dropped); seq namespace analysis (job=room=dispatch=controller call, one entrypoint per job, reconnect keeps the bus, single dispatch site per fresh room - no same-room second-job reset path; residual gap: agent-left zombie UI, for the author); DOCUMENTED GAP: closed-64 squeeze lets a duplicate high-seq turn_start re-enter a retired speech (not producible by the current worker; unbounded-Set fix suggested for the author); browser layer NOT independently re-run (no GUI system libs locally, zero-install policy) - hel879_unified_shots.mjs harness committed for Edge replay, author's 61/61 + theme screenshots remain the interface evidence; regression pytest 352/352, vitest 108/108, typecheck/build/lint(0 err/1 known warning)/ruff clean, requirements checks 4/4 (origin/main and fc905fe, with/without updated handoff-HEL-879); R5 real-test package prepared for the main card (r5-test-package.md) with zero execution; mock &turn/&id params are additive test-infra extensions, default semantics unchanged
Co-authored-by: multica-agent <github@multica.ai>
2026-10-27 06:27:27 +08:00
总管andmultica-agent fb7ea846ff HEL-898: record exact source preservation and independent offline checks
Co-authored-by: multica-agent <github@multica.ai>
2026-10-27 05:54:01 +08:00
总管andmultica-agent 46ac0a7a20 HEL-898: preserve director v2 subtitle wiring verbatim on fc905fe
Co-authored-by: multica-agent <github@multica.ai>
2026-10-27 05:53:00 +08:00
zonggongandmultica-agent fc905fe6bd HEL-879 timing closure: the three contract-approval findings fixed as implementation-timing only (v2 envelope fields/event kinds untouched, web/src untouched, no re-audit of passed binding/backup/budget work) - (1) open_session re-kicks the cleanup owner AFTER registration+persist: a real dispatch await yields the loop and the pre-dispatch kick's cleaner already exited with nothing to watch (manager repro: 20ms real dispatch await, TTL 25ms, 120ms of silence -> DELETE=0, expired registration still present, cleaner=None; fixed: exactly one DELETE, registration settled, no dangling coroutine - the bounded auto-expiry/shared-cap mechanism itself is unchanged), new regressions reproduce first on the old code with a genuinely-yielding dispatch stand-in (asyncio.sleep, not the instantly-returning MockTransport) plus an earlier-deadline wake negative; (2) speak_proactive_row takes the handle the real sync AgentSession.say returns (iscoroutine only unwraps await-style stand-ins - the real SpeechHandle itself defines __await__, so awaiting it would wait out the whole playout), publishes proactive_start IMMEDIATELY, registers the handle's own end/cancel, then waits out that one row (sequential rows kept); .12 real-SDK probe: start visible while handle_done=false, end only after, cancel-only on interrupt, v2 fields identical; (3) _after_playout gates assistant_final on the completed+legal-same-turn-receipt conditions BEFORE anything is sent - incomplete streams, missing/partial receipts and stale epochs close with turn_cancel only (reason incomplete/no-receipt), never final-then-unack; the complete+receipt path keeps BOTH final and ack; phi/interrupt/failure/unattributed fail-closed unchanged; real-SDK check extended to 14/14 ALL PASS on .12 (proactive timing, proactive interrupt, no-receipt no-final negatives all driving the actual worker module's registered callbacks), before/after evidence in docs/rework/hel879/timing-fix/ and sdk-check-hel879-timing.log, reusable probe generator scripts/gen_hel879_timing_probe.py; regression at this head: pytest 352/352 x3 consecutive runs, ruff(src/tests/tools) clean, verify_hel877_voice_chain 8/8, requirements checks 4/4 (origin/main and c121c42, each with/without handoff-HEL-879.json, handoff appended with this round's scope); zero real LLM/embed/ASR/TTS, zero downloads, .12 touched read-only via SSH stdin only, not deployed, nothing merged, PR#11 and hel-870-assembly untouched, real caps stay 0
Co-authored-by: multica-agent <github@multica.ai>
2026-10-27 05:31:05 +08:00
zonggongandmultica-agent 3228b1e0d7 HEL-879 remaining closure: auto-expiry cleanup ownership, exact SDK speech-turn attribution, and the versioned v2 subtitle contract for HEL-898
A. Room lifecycle (总管 review items A-1/A-2): the single cleanup owner now watches
   ACTIVE session deadlines as well as debts - a session nobody touches expires into
   the ledger and has its room really deleted with zero further requests (old code:
   _cleanup_loop only scanned debts, expired rooms were never deleted until some
   request ran _prune). Explicit end and the background sweep share ONE persisted
   attempt/age cap and abandoned state - _sessions no longer bypasses it (the
   manager measured 2 background attempts + 3 more interactive deletes = 5); an
   abandoned debt raises CleanupAbandoned -> 503 with room/attempts/reason instead
   of pretending done-404, success clears both _sessions and _debts, restart
   continues the same persisted cap, close/cancel leaves no dangling coroutine, and
   only this instance's own rooms are ever touched. New wake event makes a fresh
   debt retry immediately instead of waiting out a deadline sleep.

B. Queued speech turns bind EXACTLY (总管 interleaving note_handle(H1)->H2->
   note_stream(S1)->S2 gave H1=None, H2='turn-one'): CyberGrillLLM.chat now reads
   the SDK's own agents_speech_handle ContextVar (livekit-agents 1.8.8 sets it in
   _create_speech_task around every speech pipeline; source-verified on .12 and
   asserted at runtime) so each ChatApiLLMStream carries speech_handle/speech_id of
   the very handle whose generation created it. TurnLedger binds only on that exact
   attribution - a newer speech NEVER retires an older pending handle, say/proactive
   generations bind nobody, missing attribution fails closed (no ack, no subtitle
   turn), interrupt/failure discards only its own binding.

C. Versioned subtitle contract v2 for the visual author's wiring card (HEL-898):
   every cg.subtitle payload gains {v:2, seq} (session-monotonic), turn identity
   (speech_id, turn_id, same-source message_id for proactive rows) and explicit
   lifecycle events - turn_start/assistant_interim/assistant_final/turn_end/
   turn_cancel(reason) plus proactive_start/proactive_end/proactive_cancel that
   ride the say SpeechHandle instead of the receipt branch. Accumulators are
   isolated per speech_id (a late delta after cancel is dropped producer-side);
   seq + speech_id give the frontend two independent stale-rejection rules. Full
   executable contract: docs/voice-subtitle-contract-v2.md with recorded samples in
   docs/rework/hel879/subtitle-samples/ (scripts/gen_subtitle_samples_hel879.py).
   web/src untouched.

Verification: the .12 real-SDK check now LOADS THE ACTUAL WORKER MODULE and drives
the callbacks IT registered on a real AgentSession (no hand-copied functions) -
both-queued delayed generations bind exactly, a say sandwiched between user turns
steals nothing, unattributed generations fail closed, interrupted turns cancel
without final/receipt (11/11 ALL PASS, source-level proof of the ContextVar wiring
included; evidence docs/rework/hel879/sdk-check-hel879-remaining.log). The old r2
lifespan test flake is root-caused (clock freeze and block were two separate portal
calls; a 10ms life tick could slip between them and claim the frozen day unblocked)
and fixed atomically without raising timeouts or dropping assertions - 12/12 solo,
full suite 350/350 three times in a row on .venv311; ruff back to the single
pre-existing error; voice chain 8/8; release offline R1-R7 with zero failures;
requirement checks 4/4 (origin/main and c121c42, each with/without handoff).
Zero real LLM/embed/ASR/TTS calls, zero downloads/installs, nothing deployed or
merged, real caps stay 0.

Co-authored-by: multica-agent <github@multica.ai>
2026-10-27 05:10:52 +08:00
zonggongandmultica-agent b4dadd82b3 HEL-879: refresh release-snapshot selftest (12/12), budget-split (10/10) and sdk-audit evidence at the exact rework head so the recorded code_commit matches the code it verifies
Co-authored-by: multica-agent <github@multica.ai>
2026-10-27 04:27:27 +08:00
zonggongandmultica-agent 7c73d392b9 HEL-879 rework materials: hatchling cache correction with the offline mem0ai wheel actually built from the fixed source (sha256 698284b6..., zero downloads/installs, no 3.10 mixing, recorded in release-artifacts.json), hel895 release doc §0 now separates the historical HEL-895/896 candidate from the current hel-879-assembly candidate and §4/§5 carry the v3 snapshot semantics, budget §6.2.4 amended - a sealed library is never reopened by raising total_requests (contradiction removed), runbook §3.3/3.5/3.6/4.4/4.5 corrected (bounded cleanup ledger replaces the unproven rooms-die-naturally claim; assistant_interim kind; real-but-default-off proactive seam; single-owner dual launcher), one comment-only correction in web/src/voice/session.ts removing the unproven server-auto-cleanup claim (no logic/style change; CSS and JS bundle hashes byte-stable), CG-011 + her incremental subtitles now have a real minimal backend seam (voice/seam.py + worker/llm_chat_api wiring, proactive default off) with the precise frontend interface and acceptance list in docs/rework/hel879/voice-seam-interfaces.md, real-SDK check extended to 10 items (worker callback path on real SpeechHandle/SpeechCreatedEvent; interim seam through real LLMStream) and re-run on .12 ALL PASS, handoff-HEL-879.json appended with the rework scope; regression at this head: pytest 341/341, ruff clean except 1 pre-existing error identical at 7f0a739, web typecheck 0 errors / lint 0 errors 1 pre-existing warning / vitest 88/88 / build passes (CSS index-BpZaybfD.css unchanged), voice chain 8/8, budget split 10/10, release offline R1-R7, http pagination 7/7, real-service browser 9/9 with zero non-loopback outbound, pinned-SDK 16/16+12/12+6/6+7/7+12/12, requirements checks 4/4 (origin/main and c121c42, each with/without handoff)
Co-authored-by: multica-agent <github@multica.ai>
2026-10-27 04:27:04 +08:00
zonggongandmultica-agent 78a5cfd8d7 HEL-879 rework: fix the four manager-review edges - (1) dual-port launcher owns the app lifecycle exactly once: uvicorn listeners run with lifespan=off, either listener ending stops the other BEFORE the shared store closes, cancellation cleans up listeners first, and create_app's lifespan gains a fail-closed double-entry guard (the old shape silently re-initialized Store/epoch/life/chat_lock on the second entry); (2) room ownership survives delete failures: end_session keeps the session in a bounded persisted cleanup ledger until the remote delete lands (retry/idempotent, LiveKit 404 = already gone = settled), expiry moves rooms into that ledger instead of silently dropping them (the manager measured zero DELETE attempts on expired ends), graceful shutdown does not hang up live calls, restart compensates only this instance's own recorded rooms bounded by attempts/TTL+grace with abandonment recorded, and the router answers 503 (not 404) while a room is still owned so the browser keeps its owed DELETE; (3) TurnLedger binds each speech handle to its own generation stream explicitly (oldest pending handle gets the stream the SDK starts; a newer speech retires an older generation-less handle) - a handle without a generation can no longer steal the next turn's stream/receipt, the worker discards interrupted/failed bindings fail-closed before consuming; (4) release snapshot v3: verify requires three-way count equality (manifest source = manifest snapshot = restored actual; rows_match is an extra constraint, never an OR bypass), the rollback drill actually restores by source path into a persistent isolated tree and opens the restored DBs there, same-basename DBs get collision-free archive names, duplicate sources are refused; new regression file tests/test_voice_rework_hel879.py 14/14 reproducing every edge first, full pytest 338/338
Co-authored-by: multica-agent <github@multica.ai>
2026-10-27 04:10:23 +08:00
zonggongandmultica-agent 7f0a739213 HEL-879: refresh release-snapshot selftest (9/9) and budget-split (10/10) evidence at the exact final head 7f66015 so the recorded code_commit matches the code it verifies
Co-authored-by: multica-agent <github@multica.ai>
2026-10-27 03:37:07 +08:00
zonggongandmultica-agent 7f66015ac8 HEL-879 unified voice candidate assembled and verified on hel-879-assembly (isolated branch; PR#11 and hel-870-assembly untouched) - the merge brings exactly the audited HEL-877 voice backend (aba2aeb: src/cybergril/voice module with zero new dependencies, byte-identical to the author's head) and the HEL-897-verified voice frontend (c61d9c9: web/src byte-identical to the original author's head - the CSS hash change to index-BpZaybfD.css is entirely the author's own components.css +21 lines, no visual rework by this card); the one integration adjustment is test-harness alignment, not a product change: the voice adapter fixtures and verify_hel877_voice_chain.py built their router without a budget config, which the mainline's HEL-882 hard gate (guard.acquire before every real send) now correctly refuses at cap 0 - they now use the same tests/conftest.engineering_budget() every mainline test uses, no gate weakened, no src change on either author's side; full battery rerun on the assembled head: pytest 324/324 (299 mainline + 25 voice), ruff clean, web typecheck 0 errors / lint 0 errors 1 pre-existing warning / vitest 88/88 / build passes, real-server browser 9/9 with the HEL-896 precise-interaction revision (reach card over real HTTP, stage plays the reach-out, unread clears, message-id dedup, restart epoch+1, zero permission API calls, zero non-loopback outbound - evidence docs/rework/hel879/browser/), verify_hel877_voice_chain 8/8 offline full-chain, the .12 livekit-agents 1.8.8 real-SDK stdin check regenerated from THIS head's sources 8/8 ALL PASS (zero writes zero network), check_release_offline R1-R7 passes, pinned-SDK regressions on hel857-venv all green (close-ownership 16/16, shutdown-queued 12/12, inflight 6/6, semantic-recovery 7/7, mem0-real 12/12 - the seal change is exercised through the same PersistentBudget paths), requirement checks 4/4 (origin/main and c121c42, each with and without handoff-HEL-879.json); release-artifacts.json records the exact release code commit (01ff919), the tarball sha256 measured equal to the lock, and the cache-reusable wheels reverified with content digests after the disk-recovery cache incident; honest gaps kept open: real-call acceptance untested and unauthorized (D1 voice, trusted HTTPS device path, budgets), LiveKit room/worker release after API restart still lacks real-environment evidence (_prune only drops the in-memory registry, end_session does delete_room), incremental subtitles remain a contract gap not a completed feature, CG-011 proactive voice stays an explicit no-op; zero real LLM/embed/ASR/TTS, zero downloads, no mic, no service started or stopped on any shared machine, not deployed, nothing merged, authoritative ledgers/freezes/HEL-835 gate untouched, real caps stay 0
Co-authored-by: multica-agent <github@multica.ai>
2026-10-27 03:36:37 +08:00
zonggong 8afdabc7d1 HEL-879 assembly: merge the HEL-897-verified voice frontend (c61d9c9, hel-878-voice-wiring) - original author's six-file visual base byte-preserved with the late-cleanup/interleaved-start fix (761a2331) already independently re-verified 88/88 by the manager; no visual rework, no backend changes on this side 2026-10-27 03:27:07 +08:00
zonggong 48252b1dcd HEL-879 assembly: merge the audited HEL-877 voice backend (aba2aeb) onto the HEL-879 release-closure mainline - backend-only voice module (session/token router, chat_api_client, llm_chat_api, receipt, stt/tts adapters, worker) with zero new dependencies, provenance-preserved, e978e87 fully contained in both parents; web/ untouched by this side 2026-10-27 03:27:03 +08:00
zonggongandmultica-agent 01ff919859 HEL-879 release closure: the four HEL-896 audit findings become enforced behavior - (1) archive consistency: tools/release_snapshot_hel896.py v2 checks the wal_checkpoint(TRUNCATE) return (busy!=0 = hard failure, no deliverable tar, half-built dir removed - 'stopped service first' is a precondition, never a reason to ignore an actual failed result), aborts packaging on any source-vs-snapshot table-count mismatch instead of recording rows_match=False and shipping anyway, and verify_archive now rejects legacy packages that recorded rows_match=False while mapping a real rollback drill (unpack to an isolated dir, open every DB, check rows AND hashes against both recorded source and snapshot counts, emit a source-path rollback_plan - no more tar-internal self-attestation); selftest 9/9 reproduces every old failure shape first (naive WAL copy loses companion, held-reader checkpoint=(1,9,8) leaves the copied image malformed/missing the events row, the manager's rows_match=False legacy package verifying ok) then proves each fix (busy fails with zero artifacts, stale-snapshot mismatch fails with zero artifacts, online mode refuses --file dirs containing SQLite by magic-header scan, stop mode recursively takes embedded SQLite like history_root/mem0_public_history.db through checkpoint+per-table verification, verify maps source paths and passes); (2) early-finish seal: PersistentBudget gains sealed()/seal() writing sealed/sealed_requests/sealed_tokens/sealed_at atomically into budget_total beside the real usage (never faking 55, never zeroing), acquire checks the seal FIRST so every category and every remaining allowance rejects budget_sealed:51/55, the marker survives restart and backup-API archive/restore, reseal is idempotent, and there is deliberately no unseal path (re-test = new approval + new instance per budget-package 6.2.4); tools/seal_budget_hel879.py makes the closure an explicit auditable operator command with a post-seal rejection self-check; verify_budget_split_hel896.py gains S6 (51/55 experience chain completes -> seal keeps usage 51 -> 52nd and cross-category rejected with dialogue daily still 19/60 proving it is the seal not the caps, mistaken restart rejected, archive restore rejected, marker requests=51 intact) now 10/10, HEAD made dynamic instead of hardcoded 04eff61; budget-package-hel895.md 6.2 rewritten to the executable seal order (stop -> seal -> consistency snapshot); (3) single install entry: deploy/hel895-release-candidate.md 2 is now the only copy-paste install (uv --offline, npm --offline, cache-miss = stop and record, network only inside an authorized release window with per-item sha256) and 4 is the only fixed-SDK entry as a five-step chain (verify tarball sha256 9dcef930...dda23 against mem0-stack.lock -> build the wheel from the fixed source with hatchling honestly listed as the missing build backend -> cp311 dependency prep with greenlet/pillow/truststore confirmed cache-reusable on re-run -> local-wheels-only --no-index install where mem0ai must come from the fixed-source wheel, the lock line is version-only -> same-process co-import plus the 92MB fastembed cache placement); deploy/hel874-release.md 2/4 carry superseded banners pointing here, mem0-stack.lock no longer routes installs through the index mem0ai==2.2.1 wheel; (4) exact-commit honesty: audit_sdk_offline_hel896.py derives head from git rev-parse and accepts an output dir, fresh evidence written to docs/rework/hel879/sdk-audit/ (hel896 evidence left untouched) recording closure 55 / missing 41 (28 pure + 13 native) / cache-reusable [greenlet, pillow, truststore] / tarball-matches-lock at c121c42; regression on this head: pytest 299/299, ruff clean (src/tests/tools), release-snapshot selftest 9/9, budget-split 10/10 including the new S6 negatives; zero provider calls, zero downloads, authoritative ledgers/freezes/HEL-835 gate untouched, not deployed, PR#11 untouched
Co-authored-by: multica-agent <github@multica.ai>
2026-10-27 03:26:30 +08:00
leeferandmultica-agent c61d9c95c9 HEL-897: record bytewise preservation and independent regression checks
Co-authored-by: multica-agent <github@multica.ai>
2026-10-27 02:52:42 +08:00
leeferandmultica-agent 761a2331f5 HEL-897: preserve visual author voice session race fixes
Co-authored-by: multica-agent <github@multica.ai>
2026-10-27 02:52:08 +08:00
leeferandmultica-agent f259bd34e1 HEL-878: retain requirement sources and record independent review
Co-authored-by: multica-agent <github@multica.ai>
2026-10-27 02:39:05 +08:00
leeferandmultica-agent b95ce798b4 HEL-878: preserve original author continuous voice wiring
Co-authored-by: multica-agent <github@multica.ai>
2026-10-27 02:35:37 +08:00
施工员2号 aba2aebeb0 HEL-877: wire the audited .12 voice base into the one CyberGril chat chain as backend-only code (no deploy, no real voice call) - new independent src/cybergril/voice module: session+router add POST /api/voice/session / DELETE end / GET honest status on the unified instance (server-side room + dedicated cybergril-voice-agent dispatch + short-TTL join token via zero-new-dependency livekit_wire HS256/Twirp matching the installed SDK's wire format verified claim-for-claim on the .12 agent venv stdin-only; keys never reach the browser; unconfigured/no-profile/TTS-down refuse honestly and capabilities.voice stays false), chat_api_client speaks the exact real.ts RealChatAdapter protocol (per-turn health epoch, bounded 409 resync, robust incremental SSE parsing across multibyte/CRLF/multi-data chunk splits, fixed turn_id idempotency, immediate HTTP-stream close on barge-in without waiting for the next ASR final), llm_chat_api is a livekit llm.LLM adapter whose only backend is POST /api/chat so persona/mood/dyad/memory/life-sharing/budget gates and the one same-turn chat_lock govern text and voice identically (phi spoken verbatim via ChatChunk, never archived, no receipt), receipt.py confirms a sharing receipt only after real SDK playback completion with every negative (interrupted/failed/mid-playout/phi/incomplete/stale-epoch) fail-closed and exactly once per turn, stt_funasr/tts_cosyvoice reuse the proven .12 adapters verbatim with provenance, worker.py assembles the dedicated worker (independent voice_state_cybergril.json so D1 stays undecided, no second persona, cg.subtitle data-channel contract for the visual lead, CG-011 proactive seam left as an explicit no-op hook, built-in health port 8082); app.py keeps a minimal registration with voice_service injection for isolated tests; deployment drafts only (config/voice.example.yaml, worker.env.example, restart_cybergril.sh.example with exact-pattern process matching, serve_dual_8360_8361.py keeping HTTP 8360 and the HTTPS voice entry on ONE process sharing state/locks, runbook-hel877-voice.md contract+startup+rollback incl. the >=6GB GPU precondition and the do-not-copy historical fuser warning) - verification: 281 pytest pass on 3.11 venv + requirements.lock after merging the chief engineer's mid-flight HEL-874 rework e978e87 in full (parallel work preserved), verify_hel877_voice_chain.py 8/8 offline full-chain incl. socket-level barge-in fail-close and text/voice mutual same-gate cancellation, real-SDK check on the .12 livekit-agents 1.8.8 venv 8/8 ALL PASS via SSH stdin with zero writes zero network, dual-port single-process smoke on loopback 18360/18361 with a throwaway self-signed cert then stopped, ruff clean on all new code, dual-target requirement checks pass vs origin/hel-870-assembly (current tip e978e87) and origin/main with handoff-HEL-877.json; zero real LLM/embed/TTS/ASR requests, no mic/camera, no downloads/installs, no service start/stop, no 4080 access, no third-account or profile writes, web/ untouched, not merged, not deployed
Co-authored-by: multica-agent <github@multica.ai>

## HEL-877 交付摘要(R5 后台施工,未部署未合并)

- 新独立模块 src/cybergril/voice/:专属 livekit worker(agent_name=cybergril-voice-agent,复用 .12 FunASR/CosyVoice/silero 底座,不 fork 不另搭平台,无第二人格/记忆,不直连 LLM);llm_chat_api 适配器与文字前端 real.ts 同协议走 POST /api/chat(epoch 409 有界重同步、SSE 分片、turn_id 幂等、打断即断流);/api/voice/session 建房+专属派发+短时限 token 全服务端(密钥不进浏览器;缺配置/无档案/TTS 停机诚实拒绝;capabilities.voice 不伪造);receipt 仅在 SDK 真实播放完成后确认(打断/失败/φ 轮/旧 epoch 全 fail-closed)。
- 部署草案:/opt/voice-agent-cybergril 目录、worker 健康端口 8082、HTTP 8360 + HTTPS 8361 同进程双入口(deploy/voice/serve_dual_8360_8361.py,共用同一状态/锁)、启动顺序含 TTS GPU≥6GB 前置与回滚;本卡未执行任何启停。契约(开关/字幕/失败/打断/挂断 + CG-011 主动开口接缝)见 docs/runbook-hel877-voice.md §3。
- 自测(零真实请求):281 pytest 全过(含语音 25 例:socket 级打断断流、SSE 边界、重放零调用、文字/语音同门互打断、回执全负例、token 房间隔离);scripts/verify_hel877_voice_chain.py 8/8;.12 agent venv(livekit-agents 1.8.8)真 SDK 检查 8/8(stdin-only 零写入零网络);双端口同进程本地冒烟通过;ruff 干净;需求检查双目标通过(含 handoff-HEL-877.json)。
- 施工中总工 HEL-874(e978e87)推上同分支,本卡已完整并入,不覆盖并行成果。未验证如实列明:端到端语音(TTS 停机/GPU 3.9GB<5.6GB)、真 LiveKit server 的 dispatch/delete、移动端实机——待接线/审核卡真机验收,未经真实验收不称 R5 完成。
2026-10-26 22:42:19 +08:00
施工员2号 a324fd57b7 Merge remote-tracking branch 'origin/hel-870-assembly' into agent/2/6a08a4c18f23 2026-10-26 22:39:06 +08:00
施工员2号andmultica-agent 23415fbcd0 HEL-876: voice-base read-only verification + R5/R6 readiness materials - docs/voice-r5-readiness-HEL876.md records the 2026-10-26 bounded read-only audit of 192.168.200.12 with timestamps: LiveKit/FunASR/8090 alive with health evidence, 8081 corrected as the voice-agent livekit worker (not a legacy lip-sync piece), CosyVoice TTS 9880 gracefully stopped since 2026-10-24 22:17 and NOT running, MuseTalk 8010 unit gone, ComfyUI 8188 holding 26.6GB of the 32GB GPU leaving 3.9GB free < ~5.6GB TTS requirement (the only hard resource gap, coordination not unilateral action), voice catalog (11 voices, rain selected) with whitelisted config field names only and no credentials read or printed, .11 gateway model-list probe and open-meteo outbound both 200; the R5 seam is fixed at src/cybergril/api/app.py:488 - the worker must reuse the same chat chain via a livekit LLM adapter speaking the identical RealChatAdapter protocol (epoch sync/serialized turns/barge-in cancel/idempotent replay/receipt round-trip), proven offline with zero model calls by scripts/verify_hel876_voice_seam.py (six checks all pass on 3.11 venv + requirements.lock installed from the local uv offline cache, ruff clean, explicitly NOT claimed as end-to-end voice verification); R6 seams located read-only: city placeholder ManagePanel.tsx:182 with no backend, no weather client (outbound now proven), demo-only material import :178/:183, full-backup boundary = companion.db + vec_public + mem0_public_history.db + embed ledger where copying the single db file is not a complete backup and Persona.fingerprint is the same-her check; three-column honest conclusions (verified / unverified / actually-missing) plus six zero-cost work items ready for dispatch; no product code touched (src/ web/ unchanged), no new product PR, candidate branch not merged, dual-target requirement checks 4/4 pass with handoff-HEL-876.json
Co-authored-by: multica-agent <github@multica.ai>
2026-10-26 21:42:41 +08:00
267 changed files with 32222 additions and 275 deletions
+2
View File
@@ -1,2 +1,4 @@
__pycache__/
*.pyc
web/node_modules
.venv311/
+42
View File
@@ -0,0 +1,42 @@
# CyberGril 语音会话端点脱敏配置示例(HEL-877,R5 后台施工)
#
# 复制为 config/voice.yaml(或用 CYBERGRIL_VOICE_CONFIG 指定路径)后由总管
# 按部署值填写;api_key/api_secret 只写服务器本地文件,不进仓库、不进浏览器、
# 不进任何日志。环境变量 CYBERGRIL_VOICE_* 可覆盖同名字段。
#
# 没有这份文件 = 本实例未配置语音:POST /api/voice/session 如实 503 拒绝,
# /api/health 的 capabilities.voice 保持 false,不伪造已接通。
# LiveKit SFU(.12 已有 docker voice-livekit;浏览器接入地址)
# 注意:7880 是纯 ws 且被通用 worker/8090 共享,不得改 TLS。页面走 HTTPS 时
# 浏览器连 ws:// 属混合内容会被拦——R5 方案是独立的 TLS 桥(拟议,未部署):
# deploy/voice/wss_bridge.example.py,livekit_url 用 wss://192.168.200.12:8363
# (详见 docs/rework/hel879/unified-review/r5-test-package.md §3)。
livekit_url: ws://192.168.200.12:7880
# 服务端 Twirp 调用地址(本机回环即可)
livekit_internal: http://127.0.0.1:7880
# LiveKit API 凭据(与 .12 /opt/voice-agent/.env 中 LIVEKIT_KEYS 同源;此处只放占位符)
api_key: <LIVEKIT_API_KEY_PLACEHOLDER>
api_secret: <LIVEKIT_API_SECRET_PLACEHOLDER>
# 专属 worker 的 agent_name:与通用 cascade-voice-agent 区分派发,互不争抢
agent_name: cybergril-voice-agent
# CosyVoice TTS 健康探测(TTS 不在就诚实拒绝开通话,不假装能说话)
tts_health_url: http://127.0.0.1:9880/health
# ---- 自定义音色(HEL-899)----
# .12 语音面板(8090,https 自签):音色列表/zero-shot 登记的唯一写入者,
# CyberGril 统一 API 服务端代理调用;浏览器永不直连,凭据不外发。
panel_url: https://127.0.0.1:8090
# CosyVoice 合成入口(试听现合成;/health 的热加载名单用于通话开启时校验)
tts_url: http://127.0.0.1:9880
# 共享 voices.json 只读回看(补全 ASR 自动转写的参考文本,供备份清单);不配则留空
registry_file: /opt/voice-agent/cosyvoice/voices.json
# CyberGril 专属 worker 的音色状态文件(worker 会话入口读一次;通话中改选最迟下一通话生效)
worker_state_file: /opt/voice-agent-cybergril/voice_state_cybergril.json
# 浏览器接入 token 有效期(秒):只够加入房间,短时限
token_ttl_seconds: 300
# 会话登记有效期(秒):房间早空/挂断后登记自然过期
session_ttl_seconds: 7200
+9
View File
@@ -20,6 +20,10 @@ mem0 同进程导入的准确缺项与不下载解决路径、空账初始化、
## 2. 安装命令(发布卡执行)
> **已被取代(HEL-879,2026-10-27)**:本节安装命令缺 `--offline`/`--no-index`
> 强制项。唯一照抄入口改为 `deploy/hel895-release-candidate.md` §2。本文件其余
> 内容作为历史交付证据保留原样。
```bash
# 全新目录,逐条照抄;任何一步失败即停
# 2.1 服务账号与目录(独立可写数据路径,权限边界明确)
@@ -63,6 +67,11 @@ Mem0 提取都会在发送前被硬闸拒绝(`budget_*_exhausted`)——这
## 4. 真实语义激活前置(B 部分批准后;准确缺项与不下载路径)
> **已被取代(HEL-879,2026-10-27)**:本节 4.1/4.2 按索引拉 `mem0ai==2.2.1`
> 同名 wheel,不能保证固定源码身份 2c81af3;固定 SDK 激活的唯一照抄入口改为
> `deploy/hel895-release-candidate.md` §4(固定 tarball 哈希 → 固定源码构建轮 →
> cp311 依赖准备 → 仅本地轮安装 → 同进程 co-import)。以下保留为历史证据。
**当前事实(HEL-882 离线核实,tools/check_release_offline_hel882.py R6):**
固定安装 hel857-venv 是 Python 3.10.12,产品发布 venv 是 3.11.16,两者
site-packages 不能混用;本机 uv/pip 缓存里没有 mem0ai/fastembed/qdrant-client
+136 -41
View File
@@ -1,5 +1,17 @@
# HEL-895 · 统一发布候选(离线核验过,未执行部署)
> **HEL-879 发布工具收口(2026-10-27,隔离候选分支 `hel-879-assembly`)**:按总管
> 对 HEL-896 的独立复核完成四项有限收口——①归档一致性:checkpoint 返回值必查、
> 源/快照行数不一致或 checkpoint busy 一律 CLI 非 0 且不形成可交付包,verify 拒绝
> 记录了 `rows_match=False` 的旧包(§5);②目录内真实 SQLite(history_root 的
> `mem0_public_history.db` 等)停服口径走 checkpoint+逐表核对,在线模式只允许显式
> 单库 backup API、含 SQLite 的目录直接拒绝;③提前封账:`tools/seal_budget_hel879.py`
> 显式封账(真实用量保留、跨重启/归档拒绝,负例 S6);④安装入口唯一化:本文件 §2/§4
> 为唯一照抄入口(全 offline/no-index;mem0ai 只认固定源码轮),`deploy/hel874-release.md`
> §2/§4 已标注被取代。产物清单与精确提交见 `docs/rework/hel879/sdk-audit/` 与
> `docs/rework/hel879/release-artifacts.json`。**不要用旧父提交(04eff61/152479b)
> 的说明冒充本候选 head:以交付评论与 handoff JSON 记录的精确提交为准。**
> **HEL-896 修订(2026-10-27,正常追加 PR#11)**:按总管独立复审补齐——①浏览器
> 「真实服务 7/7」中 B3 原为已证实误报,测试已修为精确交互+严格断言(9 项检查,
> 含权限 API 调用计数与非回环出站守卫,证据 `docs/rework/hel896/browser/`);
@@ -16,6 +28,16 @@ HEL-881 已审核的主动留言前端(`bf234570`)装成一个统一候选
## 0. 精确 head 与整合方式
> **HEL-879 返修勘误(2026-10-27)**:本节以下原文记录的是 **HEL-895/896 历史
> 候选**(分支 `hel-870-assembly`,PR #11 未合并),其 299 pytest / 55 vitest 等
> 验证数字属于那个历史 head,**不是当前发布候选**。当前统一发布候选是分支
> `hel-879-assembly`(在 7f0a739 之后正常追加返修提交,不强推、不并入 PR #11),
> 其精确 head、pytest/vitest/build 全量数字与证据以**交付评论与
> `docs/requirements/handoff-HEL-879.json` 记录为准**;本文件的安装/检查入口
> (§2/§4/§5/§6)继续适用于当前候选,产物清单见
> `docs/rework/hel879/sdk-audit/` 与 `docs/rework/hel879/release-artifacts.json`
> (`rework_head` 字段)。历史叙述保留原样,不冒充、不改写。
- **候选 head**:见交付评论(分支 `hel-870-assembly`,PR #11 的正常追加提交,
未强推、未合并)。第一父为 HEL-894 通过复审的 `152479b3503c3d1ab27681170e6bfd592c54380a`,
合并入 HEL-881 已审核的 `bf234570c0b6595a72f8dbc79e42c63685e08e01`
@@ -51,13 +73,36 @@ HEL-881 已审核的主动留言前端(`bf234570`)装成一个统一候选
| 预算执行包 | `docs/budget-package-hel895.md`(含 §6 HEL-896 分账修订) | A/B/C 一次批准核定稿,默认 0 |
| HEL-896 工具 | `tools/verify_budget_split_hel896.py`、`tools/audit_sdk_offline_hel896.py`、`tools/release_snapshot_hel896.py`、修订版 `web/tests/hel895_real.mjs` | 分账切换验证、SDK 余缺只读审计、一致性快照/回退验证、严格浏览器检查 |
## 2. 安装命令(发布卡执行)
## 2. 安装命令(发布卡执行;本节是唯一照抄入口——HEL-879 收口)
与 `deploy/hel874-release.md` §2 完全相同(服务账号 → 全新空目录 clone 本 head →
`uv venv --python 3.11` + `uv pip install -r requirements.lock` → `npm ci &&
npm run build` → 安装 service → `curl /api/health`),此处不重复抄写;唯一差别是
`git checkout <本文件 §0 的精确 head>`。发布前必过检查同样按 `deploy/hel874-release.md`
§6 执行,另加本卡两个新工具(真实 HTTP 分页合同、真实服务浏览器)在目标机重跑。
```bash
# 全新目录,逐条照抄;任何一步失败即停。全部安装强制 offline/no-index:
# 缓存命中即装,缓存缺失即停并留证;联网补齐只发生在获批的发布窗口内,
# 逐项 sha256 留证(seq48–51 勘误教训:不写 offline 的安装发生过网络获取)。
# 2.1 服务账号与目录(独立可写数据路径,权限边界明确)
useradd --system --home-dir /opt/cybergril-hel874 --shell /usr/sbin/nologin cybergril || true
mkdir -p /opt/cybergril-hel874/data # data 必须为空:新账从零初始化
chown -R cybergril:cybergril /opt/cybergril-hel874
# 2.2 代码 checkout:全新空目录 clone(空目录里没有仓库,不能直接 git fetch)
git clone ssh://git@192.168.200.36:222/leefer/CyberGril.git /opt/cybergril-hel874/current
git -C /opt/cybergril-hel874/current checkout <交付评论/handoff JSON 记录的精确head>
# 2.3 依赖与前端(uv/npm 一律 offline;缓存缺失即停)
cd /opt/cybergril-hel874/current
uv venv --python 3.11 .venv311
uv pip install --python .venv311/bin/python --offline -r requirements.lock
(cd web && npm ci --offline && npm run build)
# 2.4 服务单元(单元内已带 PYTHONPATH=<current>/src:requirements.lock 只装依赖,
# 产品代码经 PYTHONPATH 进程内可 import)
cp deploy/cybergril-hel874-8360.service.example /etc/systemd/system/cybergril-hel874.service
systemctl daemon-reload && systemctl enable --now cybergril-hel874
curl -s http://127.0.0.1:8360/api/health | python3 -m json.tool
```
发布前必过检查按 §6 与 `deploy/hel874-release.md` §6 的清单执行(该节仍有效),
另加本卡工具(真实 HTTP 分页合同、真实服务浏览器)在目标机重跑。
## 3. 健康能力矩阵(/api/health 预期)
@@ -68,38 +113,70 @@ weather/materials/backup=false。**主动留言前端已并入本候选:生活
真实 reach-out 后,打开的页面每 20 秒读一次新行、回来立即读,未读有标记;该行为
已在本 head 用真实服务验证(§0)。**
## 4. 固定 SDK(3.11)准确缺项与不下载路径
## 4. 固定 SDK(3.11)激活——唯一照抄入口(HEL-879 收口)
**HEL-896 只读审计修订(2026-10-27,`tools/audit_sdk_offline_hel896.py`,证据
`docs/rework/hel896/sdk-audit/`)**:此前「只缺 mem0ai/qdrant-client/fastembed 三个
cp311 wheel 及纯 Python 传递依赖」的概括**不准确**,勘误如下:
**本节取代 `deploy/hel874-release.md` §4(历史文件,已标注被取代,不得再照抄)。**
旧 4.1/4.2 按 `deploy/mem0-stack.lock` 的 `mem0ai==2.2.1` 向索引拉同名 wheel——
**索引同名 wheel 不能证明固定源码身份 2c81af3**,这条路已废除。入口只有一条:
- 三个顶层包固定版本(mem0ai 2.2.1 / qdrant-client 1.20.0 / fastembed 0.9.1)的
WHEEL 均为 `py3-none-any` 纯 Python,本身无 cp311 专用轮一说;**mem0ai 的安装来源
是本地固定源码**(hel857-src,`deploy/mem0-stack.lock` 源码提交 2c81af3、tarball
SHA256 `9dcef930…dda23` 已在本机 hel857-data 实测一致)——从索引下载同名 2.2.1
wheel 不证明与固定源码相同,发布必须用固定 tarball/源码离线构建安装。
- 按 3.11 Linux markers + extras 从 3.10 固定栈安装元数据求闭包共 **55 项,产品
3.11 venv 缺 41 项**(28 纯 Python + 13 原生扩展:grpcio/numpy/onnxruntime/
tokenizers/sqlalchemy/protobuf/greenlet/pillow 等)。这是**依赖闭包清单**,不是
已完成的 cp311 解析或下载结果。
- 当前本机缓存对 41 项缺项**立即可复用**的只有 3 项(greenlet 3.5.6 cp311、
pillow 12.3.0 cp311、truststore 0.10.5 py3-none-any,uv wheels-v6/v7 缓存实测);
其余 37 项 + mem0ai 固定源码离线构建所需 build backend(hatchling 未在缓存)由
发布卡获授权后按 `deploy/hel874-release.md` §4 拉取,逐项 sha256 留证,下载清单
保持 `deploy/mem0-stack.lock` 与固定源码身份;缺项不阻塞其余发布工作。
- **fastembed 模型缓存实测**:hel857-data/fastembed-cache 含真实模型
`Qdrant/bge-small-zh-v1.5` 与 `Qdrant/bm25`(约 92MB);发布机落位=实例数据目录
内该缓存的拷贝,真实 mem0 模式的 cache_dir 指向它;本卡零下载、不推理。
- **seq48–51 pip 事件勘误(缓存取证)**:HEL-895 运行中去掉 `--offline` 安装的
pip 26.2.2 在 uv 缓存的 wheel 条目创建于 2026-10-27 02:10:04(该运行窗口内,
来源 files.pythonhosted.org,且为窗口内唯一新增缓存条目)——该步发生了网络
获取,前卡「零下载」对此步不成立;requirements.lock 装机同期未产生新缓存条目
(全部命中既有缓存)。字节级完整下载与条件请求无法由缓存取证区分,按保守口径
记为下载。**后续一切安装强制 `--offline`/`--no-index`。**
**固定 tarball 哈希 → 固定源码构建轮 → 目标 cp311 依赖准备 → 仅本地轮安装 → 同进程 co-import。**
- **第 0 步 · 产物清单核对**:`docs/rework/hel879/sdk-audit/sdk-audit.json`(本候选
精确提交上重跑)与 `docs/rework/hel879/release-artifacts.json` 记录闭包 55 项、
缺 41 项、缓存可复用 3 项与逐项哈希;发布卡先核对清单与目标提交一致。
事实口径(HEL-896 审计核定,HEL-879 重跑仍成立):三个顶层固定版本
(mem0ai 2.2.1 / qdrant-client 1.20.0 / fastembed 0.9.1)的 wheel 均为
`py3-none-any` 纯 Python;闭包按 3.11 Linux markers+extras 共 **55 项,产品
3.11 venv 缺 41 项**(28 纯 Python + 13 原生:grpcio/numpy/onnxruntime/
tokenizers/sqlalchemy/protobuf 等),这是闭包清单不是已完成的 cp311 下载结果。
seq48–51 pip 事件勘误保留:不写 offline 的安装发生过网络获取(uv 缓存取证),
故本入口全程 `--offline`/`--no-index`。
- **第 1 步 · 固定 tarball 哈希核对**(零下载):
```bash
sha256sum <hel857-data>/mem0-2c81af3.tar.gz
# 必须等于 deploy/mem0-stack.lock 记录的
# 9dcef9303ebcec116eec6739f01b8b35bd24459de6a569686b4d19b9412dda23
```
不一致即停;不得改用索引 `mem0ai==2.2.1` wheel 冒充固定源(audit 已证明两者
身份不可互证)。
- **第 2 步 · 固定源码构建轮(HEL-879 返修:已完成可离线构建)**:此前此处写
「hatchling 当前不在任何本机缓存、待补」——经总管只读核查与本卡复核**勘误**:
uv wheels-v6/wheels-v7 均有 hatchling **1.32.4** 缓存(`release-artifacts.json`
`build_backend` 节逐路径留证)。固定源 tarball 已在 3.11.16 + `uv build --wheel
--offline` 下**离线构建出轮**(零下载、零安装、不混 3.10 site-packages):
`mem0ai-2.2.1-py3-none-any.whl`,
sha256 `698284b6083ab8b7761454d65876a6d1bb983d278ad21a7bee38d760cb20e9db`。
发布卡在目标机重跑同一命令并核对该哈希:
```bash
tar xzf <hel857-data>/mem0-2c81af3.tar.gz && cd mem0-2c81af3*/
uv build --wheel --offline --python 3.11 -o wheels/
sha256sum wheels/mem0ai-2.2.1-py3-none-any.whl
```
- **第 3 步 · 目标 cp311 依赖准备**:41 项缺项中**缓存今天即可复用 3 项**
(greenlet 3.5.6 cp311、pillow 12.3.0 cp311、truststore 0.10.5 py3-none-any,
uv wheels-v6/v7 缓存实测,HEL-879 重跑核实仍命中);其余 37 项
**由总管获批后发布窗口拉取**(`python3 -m pip download --only-binary :all:
--implementation cp --python-version 3.11 --platform manylinux2014_x86_64
--abi cp311 -r <缺项清单> -d wheels/`),逐项 sha256 留证;本卡零下载、零联网。
某固定版本若无 cp311 wheel,按 lock 注释如实上报缺项,不换版本号、不混 sdist。
- **第 4 步 · 仅本地轮安装**(--no-index 全程,目录里只有上两步产物):
```bash
uv pip install --python .venv311/bin/python --no-index \
--find-links <本地wheels目录> mem0ai==2.2.1 qdrant-client==1.20.0 fastembed==0.9.1 <41缺项>
```
`deploy/mem0-stack.lock` 的 mem0ai 行只作版本记录;安装来源必须是第 2 步的
固定源码轮(同版本号不同来源不互认)。
- **第 5 步 · 同进程 co-import 与模型落位**:
```bash
.venv311/bin/python -c "import cybergril.api, mem0, qdrant_client, fastembed; print('co-import ok')"
```
fastembed 模型缓存(hel857-data/fastembed-cache,Qdrant/bge-small-zh-v1.5 +
bm25 约 92MB,实测存在)拷入实例数据目录,真实 mem0 模式 cache_dir 指向它。
产品 venv 目前 `import mem0/qdrant_client/fastembed` 均缺、产品代码导入不受影响;
在 wheel 落位且 co-import 通过前,不得以 stub/FTS 冒充真实 Mem0 体验就绪。
真缺的下载(37 项运行闭包;hatchling 已勘误为缓存命中并完成离线构建,不在缺单)
仍待主卡授权,本卡不为补命令联网。
## 5. 停用与保留数据回退
@@ -108,26 +185,44 @@ cp311 wheel 及纯 Python 传递依赖」的概括**不准确**,勘误如下
`systemctl stop` → 数据目录原样保留 → 切回上一 release/上一提交(R4 迁移全部
增量新表,旧代码可安全打开同一数据库);彻底撤销留数据档不删。
**HEL-896 归档/回退修订(2026-10-27)**:此前「SQLite WAL 单文件可整体复制/归档」
的口径**不安全**——在线库只复制主 `.db` 会丢 WAL 里已提交的资料(总管复现件连
`companion` 表都不在;本卡 `tools/release_snapshot_hel896.py` 自测同样复现
`no such table: companion`,证据 `docs/rework/hel896/release-snapshot/selftest.json`)。
发布归档一律二选一:**停服务 + `PRAGMA wal_checkpoint(TRUNCATE)` 后整目录归档**
(推荐),或**在线用 SQLite backup API 逐库一致性快照**。照抄命令:
**HEL-896 归档/回退修订 + HEL-879 收口(2026-10-27,v3 返修)**:在线库只复制主
`.db` 会丢 WAL 里已提交的资料(总管复现件连 `companion` 表都不在)。发布归档一律
二选一:**停服务 + `PRAGMA wal_checkpoint(TRUNCATE)` 后整目录归档**(推荐),或
**在线用 SQLite backup API 对显式单库做一致性快照**。工具已按收口语义强制执行
(v3;v2 的两处已证缺陷由总管独立复核提出并修复):
- checkpoint 返回 busy≠0 或源/快照行数不一致 → CLI 非 0、不产生可交付归档
(「先停服」是前提,不是忽略失败结果的理由);
- 目录内真实 SQLite(history_root 的 `mem0_public_history.db`、向量目录内库等,
按文件头魔数递归识别)在停服口径一并 checkpoint+逐表核对;
- 在线模式只接受显式 `--db`(backup API);传入含 SQLite 的 `--file` 目录直接拒绝;
- `verify` 要求**三个计数直接相等**(manifest 源行数 = manifest 快照行数 = 恢复
实测;`rows_match` 只是额外约束——v2 的 `rows_match is True or …` 允许记录值
绕过源计数比较,源 2/快照 1 仍 ok,已废除);回退演练按源路径映射**实际恢复**
到归档旁的持久独立目标树、打开恢复库核对行数与哈希,输出 rollback_plan;
- 归档侧目标名无冲突映射:不同目录的同名 `ledger.db` 不再互相覆盖(v2 后写覆盖
前写仍先交成功包);同一源重复传入直接拒绝。
照抄命令:
```bash
# 覆盖范围:产品库(CYBERGRIL_DB)、语义向量目录 vector_root、推导史 history_root、
# 权威嵌入账 ledger_path(按部署实例 config/semantic.yaml 实际路径逐项代入)
# 停服口径(推荐;目录内 SQLite 一并一致性处理):
.venv311/bin/python tools/release_snapshot_hel896.py snapshot \
--name <release-或阶段名> --db <产品库> [--db <权威账>] \
--file <vector_root> --file <history_root> --out /opt/<实例>/archives/
# 在线兜底口径:仅显式单库(backup API),不得传含 SQLite 的目录:
.venv311/bin/python tools/release_snapshot_hel896.py snapshot --name <名> \
--db <产品库> --out /opt/<实例>/archives/ --online
.venv311/bin/python tools/release_snapshot_hel896.py verify --archive <产物>.tar.gz
```
产物含逐文件 SHA256 清单、**代码精确提交**(快照时刻 `git rev-parse HEAD`)、
逐表行数核对与回退演练结果;不删档,旧归档永久保留。回退=停服务 → 解包快照到
新数据目录 → 指回上一 release/上一提交 → 起 service → `verify` 通过后交付。
本节是**发布归档/回退要求**,不冒充 R6(CG-009 每日自动备份/手动完整导出)完成。
新数据目录(按 rollback_plan 的源路径映射)→ 指回上一 release/上一提交 → 起
service → `verify` 通过后交付。本节是**发布归档/回退要求**,不冒充 R6(CG-009
每日自动备份/手动完整导出)完成。
## 6. 本候选不做的事
+6 -4
View File
@@ -1,11 +1,13 @@
# CyberGril 语义记忆层固定栈清单(HEL-882;B 部分批准后由发布卡拉取)
# 固定源:mem0ai 2.2.1(源码提交 2c81af3bbe27d9bccb09c772d583df9e9be72ee5,
# tarball SHA256 9dcef9303ebcec116eec6739f01b8b35bd24459de6a569686b4d19b9412dda23)。
# 用于 deploy/hel874-release.md §4:在具备内网源权限的环境按 3.11 平台拉全
# wheel 后离线并入发布 venv;本卡不执行下载。版本来自 hel857-venv 实测安装
# HEL-879 唯一安装入口:deploy/hel895-release-candidate.md §4(固定 tarball 哈希 →
# 固定源码构建轮 → cp311 依赖准备 → 仅本地轮安装 → 同进程 co-import)。
# 本文件的 mem0ai==2.2.1 行只作版本记录——不得按此行向索引拉同名 wheel 冒充固定源
# (旧 deploy/hel874-release.md §4 已被取代)。若某包无 3.11 wheel,发布卡须如实
# 上报缺项,不得换版本号冒充固定安装。版本来自 hel857-venv 实测安装
#(Python 3.10.12 侧 mem0ai 2.2.1 / qdrant-client 1.20.0 / fastembed 0.9.1),
# 3.11 侧需同版本 wheel;若某包无 3.11 wheel,发布卡须如实上报缺项,不得
# 换版本号冒充固定安装。
# 3.11 侧需同版本 wheel。
mem0ai==2.2.1
qdrant-client==1.20.0
fastembed==0.9.1
+29
View File
@@ -0,0 +1,29 @@
#!/usr/bin/env bash
# CyberGril 专属语音 worker 运维脚本草案(HEL-877,本卡未执行)。
# 用法: bash /opt/voice-agent-cybergril/restart_cybergril.sh [stop|status]
# 边界: 只管本 worker(python -m cybergril.voice.worker),绝不匹配通用
# worker 的 `agent.py start`,不动 /opt/voice-agent 与 8090/8081。
set -u
BASE=/opt/voice-agent-cybergril
cd "$BASE"
VENV=/opt/voice-agent/agent/venv # 复用已装 livekit-agents 1.8.8 的 venv
PAT='cybergril.voice.worker start' # 精确匹配本 worker 命令行
for p in $(pgrep -f "$PAT"); do kill "$p" 2>/dev/null; done
sleep 2
for p in $(pgrep -f "$PAT"); do kill -9 "$p" 2>/dev/null; done
if [ "${1:-}" = "stop" ]; then echo "cybergril voice worker stopped"; exit 0; fi
if [ "${1:-}" = "status" ]; then
pgrep -af "$PAT" || echo "not running"
curl -s --max-time 3 "http://127.0.0.1:${CYBERGRIL_WORKER_HEALTH_PORT:-8082}/" \
&& echo " <- health" || echo "health port not answering"
exit 0
fi
set -a; source "$BASE/.env"; set +a
PYTHONPATH="$BASE" nohup "$VENV/bin/python" -m cybergril.voice.worker start > worker.log 2>&1 &
echo "cybergril voice worker starting (pid $!)"
sleep 18
grep -m1 "registered worker" worker.log >/dev/null && echo "OK: worker registered" \
|| { echo "FAIL:"; tail -5 worker.log; exit 1; }
+105
View File
@@ -0,0 +1,105 @@
"""统一 CyberGril 实例单进程双入口启动器(HEL-877 部署草案,本卡未执行).
HTTP 8360 与 HTTPS 8361(语音页需要 secure context 才能拿麦克风)必须共用
同一个 API 进程、同一份 app.state(epoch / chat_lock / store / 语音会话登记)。
HEL-879 收口(总管复核第 1 项):uvicorn 两个 Server 默认各自进入一次 FastAPI
lifespan,导致 epoch 1→2、Store/life/chat_lock 全部被第二次初始化替换——同一个
app 对象并不保证只初始化一次。本启动器改为:
- 应用生命周期由本启动器**独占管理且只进入一次**(`app.router.lifespan_context`);
- 两个 uvicorn Server 以 `lifespan="off"` 只负责监听,共享同一份状态;
- 任一监听退出(失败/取消/信号)先把另一个也停掉,**再**退出生命周期:
绝不出现一边还在接请求、一边已被另一个监听提前关库;循环与资源只关闭一次;
- `create_app` 的 lifespan 自带重入门卫:任何二次进入直接报错(fail-closed),
防止未来改回 auto 双 Server 时再次静默替换状态。
自签证书签发命令与回滚步骤见 docs/runbook-hel877-voice.md。
用法(部署卡执行,先 `cd` 到仓库根并激活 3.11 venv):
PYTHONPATH=src python deploy/voice/serve_dual_8360_8361.py \
--http-port 8360 --https-port 8361 \
--cert /opt/cybergril-voice-certs/cert.pem --key /opt/cybergril-voice-certs/key.pem
本地离线冒烟(不涉及任何现网/证书变更):
PYTHONPATH=src python deploy/voice/serve_dual_8360_8361.py \
--http-port 18360 --https-port 18361 --cert <本地测试证书> --key <本地测试密钥>
"""
from __future__ import annotations
import argparse
import asyncio
import contextlib
import os
import sys
import uvicorn
from cybergril.api.app import create_app
async def serve(http_port: int, https_port: int, cert: str, key: str, app=None) -> None:
# ONE app object, ONE lifespan owner: both listeners share state, locks,
# epoch and the store. The uvicorn servers never run the app lifespan
# themselves (lifespan="off") — the context manager below is its only
# owner, so initialization and shutdown each happen exactly once.
if app is None:
app = create_app()
http_server = uvicorn.Server(
uvicorn.Config(app, host="0.0.0.0", port=http_port, log_level="info",
lifespan="off")
)
https_server = uvicorn.Server(
uvicorn.Config(app, host="0.0.0.0", port=https_port, ssl_certfile=cert,
ssl_keyfile=key, log_level="info", lifespan="off")
)
async with app.router.lifespan_context(app):
servers = [asyncio.create_task(http_server.serve()),
asyncio.create_task(https_server.serve())]
try:
done, pending = await asyncio.wait(servers,
return_when=asyncio.FIRST_COMPLETED)
except asyncio.CancelledError:
# The launcher itself was cancelled: stop both listeners first
# (graceful should_exit, hard cancel only as a last resort), await
# their full exit, only then let the lifespan close (and re-raise).
# No listener is ever left running without its state.
for server in (http_server, https_server):
server.should_exit = True
for task in servers:
with contextlib.suppress(asyncio.CancelledError, Exception):
await asyncio.wait_for(asyncio.shield(task), timeout=5.0)
for task in servers:
task.cancel()
with contextlib.suppress(asyncio.CancelledError, Exception):
await task
raise
# Any listener ending stops the other FIRST; only after both listeners
# have fully exited does the lifespan context above close the loop's
# resources and the store — never the other way around.
for server in (http_server, https_server):
server.should_exit = True
for task in pending:
with contextlib.suppress(asyncio.CancelledError, Exception):
await task
for task in done:
task.result() # surface the first listener's real error, after cleanup
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--http-port", type=int, default=8360)
parser.add_argument("--https-port", type=int, default=8361)
parser.add_argument("--cert", required=True)
parser.add_argument("--key", required=True)
parser.add_argument("--db", default=os.environ.get("CYBERGRIL_DB"))
args = parser.parse_args()
if args.db:
os.environ["CYBERGRIL_DB"] = args.db
asyncio.run(serve(args.http_port, args.https_port, args.cert, args.key))
return 0
if __name__ == "__main__":
sys.exit(main())
+18
View File
@@ -0,0 +1,18 @@
# CyberGril 语音 worker 脱敏环境变量示例(HEL-877)
#
# 部署位置 /opt/voice-agent-cybergril/.env(权限 600,不进仓库)。
# 复用 /opt/voice-agent/agent/venv(livekit-agents 1.8.8)与 .12 现有
# LiveKit(7880)/FunASR(10095)/CosyVoice(9880);不新装任何依赖。
# 统一 CyberGril 实例(HTTP 8360 / HTTPS 8361 同进程双入口)
CYBERGRIL_API_BASE=http://127.0.0.1:8360
# 专属派发名(与 config/voice.yaml 的 agent_name 一致)
CYBERGRIL_AGENT_NAME=cybergril-voice-agent
# worker 注册 LiveKit 所需凭据(与通用 worker restart 脚本同源;只放占位符)
LIVEKIT_URL=ws://127.0.0.1:7880
LIVEKIT_API_KEY=<LIVEKIT_API_KEY_PLACEHOLDER>
LIVEKIT_API_SECRET=<LIVEKIT_API_SECRET_PLACEHOLDER>
# 专属健康端口(livekit-agents worker 内建 GET / 健康检查;8081 是通用 worker)
CYBERGRIL_WORKER_HEALTH_PORT=8082
# CyberGril 自己的音色状态文件(不读通用助手 voice_state.json)
CYBERGRIL_VOICE_STATE=/opt/voice-agent-cybergril/voice_state_cybergril.json
+107
View File
@@ -0,0 +1,107 @@
#!/usr/bin/env python3
"""CyberGril R5 · WSS TLS bridge (PROPOSAL — 拟议材料,未在任何服务器执行).
背景(docs/rework/hel879/unified-review/r5-facts-20261027.md E2/E4):.12 的
LiveKit 7880 是纯 HTTP/WS 且通用 worker/8090 演示都依赖它保持明文,不能改
TLS;HTTPS 页面直连 ws:// 又是混合内容。本脚本在**新端口**终结 TLS 并把字节
流透明转发给本机 7880,使浏览器可用 ``wss://192.168.200.12:<port>`` 接入
LiveKit 信令,零新依赖(asyncio + ssl 标准库),不触碰既有服务。
- 监听(拟议):0.0.0.0:8363,证书 = 与 8361 同一张自签证书
(SAN=IP:192.168.200.12,存 /opt/cybergril-voice-certs/,600)。
- 上游:127.0.0.1:7880(仅回环,不暴露第二明文入口)。
- voice.yaml:livekit_url 改 wss://192.168.200.12:8363;
livekit_internal 保持 http://127.0.0.1:7880。
- 实现为透明 TCP TLS 终结(不解析 WebSocket/HTTP),LiveKit 信令是 7880 上的
HTTP/WebSocket,字节级转发即协议无关;WebRTC 媒体面(UDP/7881)不经本桥,
由 WebRTC 自带 DTLS/SRTP 加密。
- 每设备分别打开 https://192.168.200.12:8361 与 :8363,验证证书例外和实际
连接;不推断例外缓存范围。禁止用 --ignore-certificate-errors 冒充验收。
部署(获批后)示例:
python3 wss_bridge.example.py --listen 0.0.0.0:8363 \
--upstream 127.0.0.1:7880 \
--cert /opt/cybergril-voice-certs/cybergril-voice.pem \
--key /opt/cybergril-voice-certs/cybergril-voice.key
回滚:停本进程即可(独立进程,不影响 LiveKit/通用 worker/8090)。
本地冒烟(零网络出站):见
docs/rework/hel879/unified-review/r5-facts-20261027.md 与
docs/rework/hel879/unified-review/wss-bridge-smoke.log(一次性本地证书 +
dummy 上游回环验证字节穿透与 TLS 握手)。
"""
from __future__ import annotations
import argparse
import asyncio
import contextlib
import ssl
import sys
async def _pipe(reader: asyncio.StreamReader, writer: asyncio.StreamWriter) -> None:
try:
while True:
data = await reader.read(65536)
if not data:
break
writer.write(data)
await writer.drain()
except (ConnectionError, asyncio.CancelledError, ssl.SSLError):
pass
finally:
with contextlib.suppress(Exception):
writer.close()
async def _handle(
downstream: tuple[asyncio.StreamReader, asyncio.StreamWriter],
upstream_host: str,
upstream_port: int,
) -> None:
down_reader, down_writer = downstream
try:
up_reader, up_writer = await asyncio.open_connection(upstream_host, upstream_port)
except (ConnectionError, OSError):
down_writer.close()
return
await asyncio.gather(
_pipe(down_reader, up_writer),
_pipe(up_reader, down_writer),
return_exceptions=True,
)
async def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description="TLS->plain TCP bridge (CyberGril R5 WSS proposal)")
parser.add_argument("--listen", default="0.0.0.0:8363", help="host:port to terminate TLS on")
parser.add_argument("--upstream", default="127.0.0.1:7880", help="plain upstream host:port")
parser.add_argument("--cert", required=True, help="TLS certificate PEM path")
parser.add_argument("--key", required=True, help="TLS private key path")
args = parser.parse_args(argv)
listen_host, _, listen_port = args.listen.rpartition(":")
up_host, _, up_port = args.upstream.rpartition(":")
if not listen_host or not up_host:
parser.error("listen/upstream must be host:port")
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ctx.load_cert_chain(args.cert, args.key)
async def handler(reader: asyncio.StreamReader, writer: asyncio.StreamWriter) -> None:
await _handle((reader, writer), up_host, int(up_port))
server = await asyncio.start_server(
handler, listen_host or "0.0.0.0", int(listen_port), ssl=ctx
)
addrs = ", ".join(str(s.getsockname()) for s in server.sockets or [])
print(f"wss-bridge(PROPOSAL) tls {addrs} -> {up_host}:{up_port}", file=sys.stderr)
async with server:
await server.serve_forever()
return 0
if __name__ == "__main__":
raise SystemExit(asyncio.run(main()))
+73
View File
@@ -0,0 +1,73 @@
{
"base": "bf234570c0b6595a72f8dbc79e42c63685e08e01",
"patch_sha256": "02a7b3d2c03aee79c9fc7c8a470c7053cd7b8aa410699ec51b255c333acdeda4",
"source_zip_sha256": "4596f2cc03210074be1fc9fe23dcdd607f5d240b16f142b67986c1886ce6e9be",
"files": [
{
"path": "docs/requirements/handoff-HEL-878.json",
"sha256": "258cf2935f5aafc20cd7a80a9550ae062024849ff39c078068f371692bbaa516",
"bytes": 9439
},
{
"path": "web/README.md",
"sha256": "f596492a443e7ae9972496a40db2f40c79f8667d4552628afa4718efbd29c726",
"bytes": 3113
},
{
"path": "web/VENDOR.md",
"sha256": "e11c139b5c7a6f86264439b03997bd89c79e40c994ac1db57f31717e83b660b6",
"bytes": 893
},
{
"path": "web/public/vendor/livekit-client-2.22.4.umd.min.js",
"sha256": "040db4c0db72e7f7599cd0e06b0f58f95e0082a395cdde410ca26224a79a0626",
"bytes": 598796
},
{
"path": "web/src/app/RealApp.tsx",
"sha256": "8afb63b8a6b196e29ced1a8bcef541f1f80a10811f1dc0f965631a9f0c201f5e",
"bytes": 14382
},
{
"path": "web/src/chat/ChatPanel.tsx",
"sha256": "419c2f4243d7a147ea8ebcf377d74c97dc558049a3bf76dee39bba0746d2c9e6",
"bytes": 34936
},
{
"path": "web/src/styles/components.css",
"sha256": "f93b875f6918d402aaaefeffdc52fdd908328fb8ac70766704e528c6a8bc154d",
"bytes": 89243
},
{
"path": "web/src/voice/livekit.ts",
"sha256": "c42475d37f7eef7abe0903b66656aedc5379bc9de3dcba11c1b88450a470ab04",
"bytes": 6859
},
{
"path": "web/src/voice/session.ts",
"sha256": "128123ab75e788699150321ca1341081450c34b09918b0b825c542dbc60b6e07",
"bytes": 18058
},
{
"path": "web/tests/fake-livekit.js",
"sha256": "d16e794308073e967566f3ebda16e72d0f5eb0132b657338baa6a29115b02508",
"bytes": 4177
},
{
"path": "web/tests/hel878_mock.mjs",
"sha256": "d7c9d79308979f1f3cd7172f56f52b4cfb35de1fcbae2876fa7d66ea4e2c3071",
"bytes": 8855
},
{
"path": "web/tests/hel878_shots.mjs",
"sha256": "ad664b26b66990e59ca118b0afe1e6dfea8a64351bb9bbb7a666f332b2f15760",
"bytes": 22874
},
{
"path": "web/tests/voice.test.ts",
"sha256": "f6bc899b3202465ec68b276ed21ccbbc5ba6e7edda014e2d7ea3ff63e5683a73",
"bytes": 20829
}
],
"unchanged_archive_files_match_base": true
}
+20
View File
@@ -0,0 +1,20 @@
HEL-878 源码保全与补查(总管,2026-10-27 北京时间)
原作者提交评论:01a1797c-188d-79df-9be1-df6bd47effc6。读取需求 v1.11/60项;前端基点 bf234570c0b6595a72f8dbc79e42c63685e08e01,后台契约 aba2aeb,实际总装基点 04eff611e7d85e539d43717711172441420ac853,main 3a986726d8593e0ed2274f79db6cc5d807d2ebf0。
13 个改动文件逐字节匹配作者 MANIFEST;补丁 SHA-256 匹配,git apply --reverse --check 通过;源码包其余文件与 bf234570 内容一致(仅允许未改文件 CRLF/LF 差异),包内 MANIFEST 与单独附件一致。原样提交 b95ce798b4a9455d941d247a46865b3ac202bf2c,不改作者业务代码或视觉。
补齐前端分支缺少的历史用户原话文件 comment-evidence-full-experience-20261026.json 及 source-manifest,逐字节来自总装 04eff611;无需求变更。Python 原版检查对 origin/main、origin/hel-870-assembly、origin/agent/2/6a08a4c18f23 各带/不带 handoff-HEL-878.json 共6次通过。
使用既有 node_modules(未安装下载)复跑 typecheck、lint、81项单测、build 全通过;lint 0错/1条既有警告。作者45项浏览器检查本轮未重跑,仍为作者证据。真实语音、真实麦克风、WebRTC、ASR/TTS/LLM请求均未执行;未部署、未合并、未改现有服务或绑定。
独立补查发现必须返修的问题:
1. 第一次会话正常开启;模拟掉线,服务端 DELETE s1 延迟返回。
2. 用户立即开启第二次会话,worker已加入,界面 listening,controller.active=true。
3. 放行第一次的 DELETE(含 settle 补偿请求)。旧 fail() 在 await teardown(live) 之后继续写 OFF,没有重新核对代次,覆盖新会话的界面。
4. 实测:controller.active=true,但最后 onView.phase=off。旧失败提示也会覆盖新会话,界面与麦克风会话状态不一致。
定位 web/src/voice/session.ts 的 start() 内 fail()。附件 review878_late_cleanup.test.ts 为独立负例,放到 web/tests/ 后用 ./node_modules/.bin/vitest run tests/review878_late_cleanup.test.ts 可复现;本轮实际执行失败:Expected listening / Received off,原始日志见 review878-race.log。这里只做有限复现,未代替原作者修改;交原作者修复异步清理后的状态提交边界并补有意义回归。
关联 CG-010/018、VIS-013、UX-005/006、GOV-001–006、DEL-001。原81项通过不涵盖这个负例,不能称离线验收通过。修正后交 HEL-879 统一审核;CG-011语音主动开口仍为空操作、增量字幕仍缺、远端断网清理/租约与API重启实情、真实SDK回调与播放回执、真机三轮/打断/停止/HTTPS均继续承接,不因协议现状或mock通过降低完整要求。R6/R7继续主卡HEL-797承接。
+14 -6
View File
@@ -113,16 +113,24 @@ HEL-857 的 60 次实验授权);B/C 需白栖知明确批准。
`docs/budget-plan-hel874.md` §1 体验链 1–7 逐步执行:创建/诠释/习惯 4、对话 16、
生活 8、判定/成长/gist 20、主动措辞 3,其余额度备用至 55;每步在四道闸下原子
预占(类别日顶→日 token 池→一次性总量;S1 验证叠加取最紧)。
2. **A 结束即封账**,两种到达方式同义:额度用满(`budget_total_requests_exhausted:55/55`
如实拒绝、不补发)或体验链走完。封账动作:`systemctl stop` 8360 实例 →
`tools/release_snapshot_hel896.py` 做一致性快照(停服+checkpoint/backup API)→
归档(数据目录 + 哈希清单)交交付评论。封账跨重启、跨归档不失效(S2 验证),
2. **A 结束即封账**,两种到达方式都有可执行动作(HEL-879 收口):额度用满时
`budget_total_requests_exhausted:55/55` 如实拒绝、不补发;**体验链提前走完
(如 51/55)时运行 `tools/seal_budget_hel879.py --db <团队实例库>` 显式封账**
——真实用量 51 原样写入并保留(不伪造 55、不清零),此后该库任何类别任何
余量的 `acquire` 都拒绝 `budget_sealed:51/55`,跨重启、跨 backup API 归档/
快照恢复同样拒绝(S6 负例:51/55 提前收尾、误重启、归档恢复均拒绝;重复
seal 幂等不覆盖原标记;无解除封账代码路径)。封账动作顺序:`systemctl stop`
8360 实例 → `seal_budget_hel879.py` → `tools/release_snapshot_hel896.py`
一致性快照(封账标记随库一同归档)→ 归档(数据目录 + 哈希清单)交交付评论。
`budget_total` 永不手工清零。
3. **旧测试数据不会转成用户伴侣**:测试档案、聊天、生活数据只存在于团队实例库与
归档;用户实例是全新空库,由用户自己创建档案;产品无任何「测试库导入用户库」
路径,发布卡明令禁止把 8360 数据目录复制/合并进用户实例数据目录。
4. 后续任何团队复检 = 新批准 + 新实例/新库(或在 8360 实例上以新批准抬
`total_requests`),绝不续在用户账上。
4. 后续任何团队复检 = **新批准 + 全新隔离实例/新库**,绝不续在用户账上,也
**绝不在已封账的 8360 库上抬 `total_requests` 重开**(HEL-879 返修勘误:本节
旧版括注「或在 8360 实例上以新批准抬 total_requests」与「无解除封账路径」
自相矛盾——封账标记随库持久,抬 cap 不能也不得绕过它;该括注废除)。旧
8360 库及其封账标记原样归档留存,作为真实用量的证据链。
### 6.3 B/C 的开始与日常
+1 -1
View File
@@ -1,6 +1,6 @@
# CyberGril 需求入口
当前完整基准:**v1.11**。批准需求继承办法的主卡评论:`01a1635b-b47c-79ed-ac66-9408c7905af9`。
当前完整基准:**v1.12**(HEL-899 起 v1.12;本行此前长期写 v1.11,属入口勘误,基准与变更记录本身自 v1.12 起即为准确,未回退)。批准需求继承办法的主卡评论:`01a1635b-b47c-79ed-ac66-9408c7905af9`。
所有新任务、续办、设计、施工、审核先读完整当前需求,再读本次相关来源。只有白栖知明确修改的要求才可变更,阶段范围不能覆盖产品要求。
+4 -3
View File
@@ -1,6 +1,6 @@
{
"schema_version": 1,
"version": "1.11",
"version": "1.12",
"approval_comment_id": "01a1734c-1159-7a44-a81f-3105f1b8d98d",
"owner_id": "e5c6c938-db48-4ff9-954f-4fb8b063a132",
"scope": "完整要求;v1.11在v1.10的59项上新增成熟方案优先复用执行约束,原产品与验收边界保留;不代表已选定底座、授权迁移或功能通过。",
@@ -118,10 +118,11 @@
"category": "产品",
"topic": "连续语音与文字",
"conclusion": "用户主动开启语音后自然连续交谈,支持开口打断她;结束语音后停止收音,同时保留文字聊天。",
"boundary": "响应速度、回声消除和打断效果需用现有语音服务实测,未承诺固定毫秒数。",
"boundary": "响应速度、回声消除和打断效果需用现有语音服务实测,未承诺固定毫秒数。音色入口以可自定义音色为准:在网页上传或录制参考音频→试听→应用到同一位她的后续通话,直接复用已装TTS的零样本克隆;不以仅选预设音色替代,先前参考包试听方案不采用。本轮通话测试授权总管自定音色、最长2小时/3次通话;自定义音色交付不等于用户已验收音色,也不改变换声音只换发声、不重建伴侣、不改出生底色/名字/关系/聊天与记忆的边界。",
"source_comment_ids": [
"01a15750-2cf9-7f23-9269-0f150d2c5e61",
"01a15750-e061-733b-9d80-38459d306b97"
"01a15750-e061-733b-9d80-38459d306b97",
"01a17de1-2892-78be-a422-eb6a11ffebfd"
]
},
{
+10
View File
@@ -108,6 +108,16 @@
"GOV-006"
],
"impact": "原59项保持;不预设保留自研底座,也不指定任何最终开源项目;未授权产品迁移、依赖安装、现网部署或改变资料/隐私边界。"
},
{
"version": "1.12",
"approval_comment_id": "01a17de1-2892-78be-a422-eb6a11ffebfd",
"approved_by": "e5c6c938-db48-4ff9-954f-4fb8b063a132",
"summary": "CG-010音色边界更新:以可自定义音色为准(网页上传/录制参考音频→试听→应用),直接复用已装TTS零样本克隆;不以仅选预设替代,参考包试听方案不采用;本轮通话测试授权总管自定音色、最长2小时/3次通话。",
"affected_ids": [
"CG-010"
],
"impact": "只改音色入口与测试授权;换声音只换发声、不重建伴侣、不改底色/名字/关系/聊天/记忆的既有边界不变;交付与阶段通过不等于用户已验收音色;不新增付费服务、不改变部署与资料边界。"
}
]
}
+10
View File
@@ -39,3 +39,13 @@ v1.7:把原始31项、已确认视觉和后续用户裁定整理到同一入
> 记住我的原则,能借鉴成熟方案的就直接借鉴,从零创造的难度太大需要的是一个专业的团队,不适用于我这个不专业的人加上没有决策性的AI的组合;另外我之前让你看的我跟另外智能体的交流内容希望你提取值得借鉴的地方
旧记录未明确规定通用内核必须优先复用成熟实现;现新增GOV-006,减少从零研发和长期自行维护,不因沉没工作量而默认保留自研内核。原59项不删减。交流材料提炼为候选机制,具体技术栈、底座、情绪映射及迁移范围未获批;不将本次文档记录当功能通过或现网施工授权。
## v1.12:音色入口定为可自定义音色
来源评论:`01a17de1-2892-78be-a422-eb6a11ffebfd`(2026-10-27 15:01:10 UTC,回应总管阻塞通报 `01a17d45-f14e-7b62-9850-646e040c7318`)。白栖知原话:
> .12已恢复,另外以上一条让我解压附件包试听,我不明白是什么意思,是让我听预设音色吗?如果是我觉得你的设计不够合理,我想要的是可以自定义音色,我的.12上安装的TTS是支持自定义音色的。至于最长2小时,3次通话的测试,我同意,音色你自己决定就行
旧边界(CG-010/D1 路线):音色按"先在现有中文女声里挑、都不满意再做零样本克隆"的条件路线推进,本轮以参考包编号试听为交付形式。新要求:**音色入口以可自定义音色为准**——在网页上传或录制参考音频→试听→应用到同一位她的后续通话,直接复用已装TTS的零样本克隆;不以仅选预设音色替代,参考包试听方案不采用。另明确:本轮通话测试授权总管自定音色、最长2小时/3次通话(操作授权,记 HEL-879 测试执行,不进需求边界)。
影响范围:仅 CG-010 音色入口与测试授权;换声音只换发声、不重建伴侣、不改出生底色/名字/关系/聊天/记忆的既有边界不变;CG-010 其余条文与原59项不动。工程交付或测试通过不等于用户已验收音色,也不得把"用户已授权自定义音色"改写成"用户已选定某支音色"。原话记录见 `sources/comment-evidence-custom-voice-20261027.json`(施工员2号按平台原话逐字核对录入)。
+1 -1
View File
@@ -1,5 +1,5 @@
{
"baseline_version": "1.11",
"baseline_version": "1.12",
"items": [
{
"requirement_id": "CG-001",
+5 -3
View File
@@ -1,9 +1,11 @@
# CyberGril 完整当前需求 · v1.11
# CyberGril 完整当前需求 · v1.12
完整要求;版本1.10在v1.9(交流静图认可)之上,把白栖知已体验的对坐动态小样 v2 整体效果(演示层面)记入 VIS-011 与 CG-004 边界;不证明其余页面、真实功能或完整产品已验收。
v1.11新增GOV-006成熟方案优先复用;原59项与v1.10阶段验收边界保持。
v1.12按白栖知2026-10-27原话(`01a17de1-2892-78be-a422-eb6a11ffebfd`)把CG-010音色边界更新为可自定义音色:网页上传/录制参考音频→试听→应用到同一位她的后续通话,直接复用已装TTS零样本克隆,不以仅选预设替代;先前参考包试听方案不采用。本轮通话测试授权总管自定音色、最长2小时/3次通话。该项交付不等于用户已验收音色。
## 阅读与效力
用户明确的新决定覆盖旧决定并记录来源;完整当前记录优先于任务摘录、技术方案和旧仓库 SPEC。无明确用户裁定的冲突先查来源,不擅自取舍。
@@ -84,9 +86,9 @@ v1.11新增GOV-006成熟方案优先复用;原59项与v1.10阶段验收边界
用户主动开启语音后自然连续交谈,支持开口打断她;结束语音后停止收音,同时保留文字聊天。
边界:响应速度、回声消除和打断效果需用现有语音服务实测,未承诺固定毫秒数。
边界:响应速度、回声消除和打断效果需用现有语音服务实测,未承诺固定毫秒数。音色入口以可自定义音色为准:在网页上传或录制参考音频→试听→应用到同一位她的后续通话,直接复用已装TTS的零样本克隆;不以仅选预设音色替代,先前参考包试听方案不采用。本轮通话测试授权总管自定音色、最长2小时/3次通话;自定义音色交付不等于用户已验收音色,也不改变换声音只换发声、不重建伴侣、不改出生底色/名字/关系/聊天与记忆的边界。
来源评论:`01a15750-2cf9-7f23-9269-0f150d2c5e61`、`01a15750-e061-733b-9d80-38459d306b97`。
来源评论:`01a15750-2cf9-7f23-9269-0f150d2c5e61`、`01a15750-e061-733b-9d80-38459d306b97`、`01a17de1-2892-78be-a422-eb6a11ffebfd`。
## CG-011 · 主动联系范围
+51
View File
@@ -0,0 +1,51 @@
{
"baseline_version": "1.11",
"read_commit": "31970c4dd3851aae67a922b03264743d504b8e97",
"task": "HEL-876:核清 .12 已有语音底座真实状态并把 R5 语音接入工程材料备齐;同步只读核对 R6 接缝;零真实模型/嵌入/合成/识别请求,不改现网",
"requirement_ids": [
"CG-006",
"CG-009",
"CG-010",
"CG-018",
"CG-019",
"CG-020",
"CG-021",
"CG-022",
"CG-023",
"CG-028",
"CG-029",
"CG-030",
"CG-031",
"AM-001",
"GOV-001",
"GOV-002",
"GOV-003",
"GOV-004",
"GOV-005",
"GOV-006",
"DEL-001"
],
"deliverable": "docs/voice-r5-readiness-HEL876.md(带时间戳的 .12 只读核查:7880/10095/8090 存活证据、8081=voice worker 的身份修正、9880 TTS 于 2026-10-24 22:17 优雅停机未运行、8010 MuseTalk 单元已不存在、8188 ComfyUI 占 26.6GB 显存致空闲仅 3.9GB<所需 5.6GB、音色表 11 支与当前 rain、两个 venv 版本、.11 网关与 open-meteo 探活;R5 worker 复用 app.py:488 同一 chat 链的接缝与协议;R6 城市天气/素材/备份四处接缝定位;三栏结论与零成本工程清单)+ scripts/verify_hel876_voice_seam.py 离线零模型协议检查(六项全过:epoch 同步、完整轮含 receipt、replay 零重复调用、received 确认、barge-in 新轮取消旧行=cancelled、409 重同步)。src/ 与 web/ 零改动,无新产品 PR。",
"acceptance": [
"端口/进程/健康证据全部为本轮 2026-10-26 13:22-13:38 UTC 实测,未照抄 8090/8081 旧解释:8081 实为 livekit-agents worker(pid 451883),MuseTalk 8010 单元已不存在,TTS 9880 停机(tts.log 优雅停机于 10-24 22:17)",
"凭据与 token 未读取未输出;.env 仅列白名单字段名;证书/防火墙/系统环境未改;未启停任何服务;未访问 4080;零真实 LLM 对话/嵌入/合成/识别请求",
"语音 worker 复用同一条业务链的结论落到文件/行为级:chat 链六项协议性质(epoch/串行/取消/replay/receipt/断开清理)以 fake provider 离线证明,全部通过;明确该证明不等于端到端真实通话通过",
"R5 材料备齐:目录/端口/worker 方案、复用依赖与版本、启动顺序与 TTS 重启命令、音色候选清单(无用户决定则不替定,D1 附试听/克隆真实入口及 TTS 停机期间不可试听的如实说明)、D3 麦克风用户动作、GPU 时段协调项",
"R6 只读核对只定位不实现:城市(ManagePanel.tsx:182 占位、后端无代码)、天气(无代码、open-meteo 出网已证)、素材 manifest(仅演示态 :178/:183、后端无代码)、备份边界(companion.db+vec_public+mem0_public_history.db+嵌入账本,复制单 .db 不算完整;Persona.fingerprint 可作同一位她校验)",
"三栏结论如实:已证实/未验证/确实缺资源(GPU 3.9GB<5.6GB 为唯一硬缺口);零成本工程清单六项;文档独立分支提交、候选 PR 不合并"
],
"deferred": [
"R5 真实验收:连续 3 轮、开口打断 ~1s 停声、结束即停麦、无残音、移动端麦克风——TTS 恢复并接好 worker 后在 R5 施工卡执行",
"TTS 重启:需 GPU 空闲≥6GB(ComfyUI 释放协调),由总管与白栖知安排时段,本卡未执行任何启停",
"D1 音色最终决定与 D3 用户设备信任操作:交总管集中沟通;D2 天气源采纳、D4 视觉观察仍按原依赖待用户决策",
"R6 城市/天气/素材/备份的实现(本卡仅定位接缝):建议总管按 §7 零成本清单连续派工",
"统一候选独立审核部署(8360)与语音产品代码增量(/api/voice/session、HTTPS 口、前端语音按钮):后续卡"
],
"requirement_changes": [],
"verification": [
"SSH 只读核查 2026-10-26 13:22-13:38 UTC(ss/ps/systemctl/docker/nvidia-smi/curl/日志尾部/源码阅读),未做任何写操作",
"scripts/verify_hel876_voice_seam.py 于 3.11 venv(uv 离线缓存安装,零下载)+ requirements.lock 全过六项;ruff 0 错",
"check_requirements.py 双目标(origin/main 与 origin/hel-870-assembly)含本 handoff 退出码 0",
"未验证并如实声明:本轮端到端语音可用性(TTS 停机)、真机移动端麦克风、TTS 恢复后延迟"
]
}
+54
View File
@@ -0,0 +1,54 @@
{
"baseline_version": "1.11",
"read_commit": "31970c4dd3851aae67a922b03264743d504b8e97",
"task": "HEL-877:把已核清的语音底座接到统一 CyberGril 业务链(获批 R5 后台施工);新增专属 livekit worker(agent_name=cybergril-voice-agent,复用 .12 FunASR/CosyVoice/silero VAD,不 fork 不另搭平台)+ 统一实例 POST /api/voice/session 最小协议(建房/专属派发/短时限 token 服务端处理,密钥不进浏览器)+ 与文字前端同协议的 livekit LLM 适配器(POST /api/chat,SSE/epoch 409 有界重同步/turn_id 幂等/打断即断流)+ SDK 真实播放完成才确认的分享回执;零真实请求自测交可审核代码",
"requirement_ids": [
"CG-006",
"CG-010",
"CG-011",
"CG-013",
"CG-014",
"CG-015",
"CG-016",
"CG-018",
"CG-019",
"CG-020",
"CG-021",
"AM-001",
"GOV-001",
"GOV-002",
"GOV-003",
"GOV-004",
"GOV-005",
"GOV-006",
"DEL-001"
],
"deliverable": "src/cybergril/voice/ 独立模块:session.py+router.py(POST /api/voice/session、DELETE 结束删房释放麦克风/job、GET /api/voice/status 诚实开关;缺配置/无档案/TTS 不可用如实拒绝;app.py 仅最小注册且 capabilities.voice 不伪造)、livekit_wire.py(零新依赖 HS256 token+Twirp 派发/删房,与已装 SDK 同线上格式)、chat_api_client.py(与 real.ts 同协议:health epoch、SSE 分片鲁棒解析、409 一次重试、固定 turn_id、打断 finally 即断流)、llm_chat_api.py(livekit llm.LLM 适配器,只发当前用户话轮,服务端组装全部人格/记忆/关系;φ 原句经 ChatChunk 念出无回执)、receipt.py(should_ack 只有 played_to_end 且未打断未失败且带完整 receipt 且 epoch 一致才确认;TurnLedger 绑定 speech handle↔流;确认恰好一次)、stt_funasr.py/tts_cosyvoice.py(.12 已验证适配器原样复用带出处)、worker.py(agent_name=cybergril-voice-agent、独立 voice_state_cybergril.json 音色、无第二人格、cg.subtitle 字幕数据通道、CG-011 主动开口空操作接缝、专属健康端口 8082);config/voice.example.yaml 与 deploy/voice/{worker.env.example,restart_cybergril.sh.example,serve_dual_8360_8361.py} 部署草案(HTTP 8360+HTTPS 8361 同进程双入口共用同一状态/锁,runbook 含启动顺序/回滚/GPU 前置);docs/runbook-hel877-voice.md 契约+部署文档;tests/test_voice_session.py(9)+test_voice_chat_adapter.py(16)、scripts/verify_hel877_voice_chain.py、scripts/gen_hel877_sdk_check.py",
"acceptance": [
"语音轮次走统一链被证明:worker 适配器只调 POST /api/chat,文字/语音互相打断由服务端同一 chat_lock 轮次门处理(本地 socket 实测:语音打断文字轮旧行=cancelled 无幽灵行、打断取消即刻断 HTTP 流旧行 fail-closed、新轮干净)",
"完成轮重放不新增模型调用;409 旧 epoch 一次有界重同步后成功;SSE 多字节/CRLF/多行 data 分片边界解析正确;turn_id 固定使 SDK 内部重试幂等",
"分享 receipt 仅在 SDK 真实播放完成后确认:should_ack 对被打断/播放失败/未播完/φ 轮/残缺回执/旧 epoch 全负例拒绝(测试覆盖),确认恰好一次,服务端仍独立 fail-closed 校验",
"POST /api/voice/session 服务端建房+专属派发(agent_name=cybergril-voice-agent,protobuf CreateDispatch 与已装 SDK 同格式)+短时限 token(TTL≤300s,与 livekit.api.AccessToken 声明集全等,.12 真 SDK 比对通过);房间隔离(两 session 不同房、token 各自房间);DELETE 删房结束;缺配置/无档案/TTS 停机诚实拒绝且 capabilities.voice 保持 false",
"worker 不持有第二人格/记忆、不直连 LLM、不读通用助手 persona_state/voice_state(独立 voice_state_cybergril.json,D1 未拍板回落 default);CG-011 语音主动开口留明确空操作接缝并写清边界(仅已开启会话、同业务链、可打断、不新回执通道)",
"真 SDK 兼容在 .12 agent venv(livekit-agents 1.8.8)stdin-only 零写入零网络验证 8 项全过(真 LLM/LLMStream 子类、真 ChatContext、真流机制 ChatChunk+final+receipt、φ ChatChunk、token 声明集与 SDK AccessToken 全等、server token 同形、SpeechHandle/事件 API 存在、打断取消立即关流)",
"双入口同进程:本地 18360/18361 冒烟单 pid 双端口同 epoch 两端诚实拒答;HTTP 与 HTTPS 共用同一 app 对象/状态/锁,无第二实例写同一 DB",
"部署草案齐全(/opt/voice-agent-cybergril、健康端口 8082、启动顺序含 TTS GPU≥6GB 前置、回滚、禁抄历史 fuser 命令的明确警告);本卡未执行任何部署/启停/下载安装,未动 web/、三账、档案、4080、现网服务"
],
"deferred": [
"R5 真机验收:连续 3 轮、开口打断 ~1s 停声、结束停麦无残音、移动端 Safari/Chrome 麦克风——TTS 恢复(GPU 需 ≥5.6GB,现 3.9GB,ComfyUI 不停)并接线后在后续卡执行;未经真实验收不得称 R5 完成",
"视觉总监接线:语音按钮/字幕(cg.subtitle)/失败/打断/挂断按 runbook §3 契约;本卡未改 web/",
"总工独立审核与部署(runbook §4):.12 上对真 LiveKit server 的 dispatch/delete、HTTPS 证书签发与实机信任(每台手机高级→继续)待实测,不承诺每台手机点继续必然可用",
"语音内主动开口(CG-011 接缝)与 D1 音色最终决定、D3 用户设备动作、GPU 时段:总管集中与白栖知沟通",
"R6 天气/素材/完整备份承接不变;视觉观察默认关闭不变"
],
"requirement_changes": [],
"verification": [
"PYTHONPATH=src .venv311/bin/python -m pytest tests -q:281 passed(总工 HEL-874 返工后全量 256 + 本卡 25,含语音会话 9 与适配器/回执 16,全 fake HTTP/SSE/本地 127.0.0.1 socket,零真实模型/嵌入/合成/识别)",
"scripts/verify_hel877_voice_chain.py 一键全链 8 项全过(真实 app + fake LiveKit/TTS + 本地 socket:拒答序/专属派发/短时限 token/房间隔离/删房结束/多轮回执/重放零调用/409 重同步/打断 fail-closed/播放完成回执一次/capabilities.voice=false)",
"scripts/gen_hel877_sdk_check.py 生成脚本经 SSH stdin 在 .12 /opt/voice-agent/agent/venv/bin/python(livekit-agents 1.8.8、livekit 1.1.20)执行:8 项 ALL PASS(零写入、零网络、未触任何服务)",
"双入口本地冒烟:一次性自签证书(本机 openssl 生成)+ deploy/voice/serve_dual_8360_8361.py 于 18360/18361,单 pid、同 epoch、/api/voice/status 诚实、未配置 POST 503;测后进程与端口已释放",
"ruff check src tests scripts deploy:本卡代码 0 错(唯一遗留 SIM115 属既有 deploy/hel835_guard.py,非本卡);check_requirements.py 双目标(origin/hel-870-assembly 现tip e978e87 与 origin/main)含本 handoff 通过",
"读取位置记录:开工时 origin/hel-870-assembly=31970c4 与卡文一致;施工中总工推入 HEL-874(e978e87),本卡已完整 merge 保其成果不覆盖;origin/hel-876-voice-readiness=23415fb(总管读取点)已并入",
"未验证如实声明:端到端语音(TTS 停机未连麦)、真 LiveKit server 的 dispatch/delete 实测、worker 运行时行为(字幕/VAD/回声抑制仅源码级对齐 .12 已验证实现与 SDK 静态契约)、移动端实机"
]
}
+43
View File
@@ -0,0 +1,43 @@
{
"baseline_version": "1.11",
"read_commit": "bf234570c0b6595a72f8dbc79e42c63685e08e01",
"task": "HEL-878(R5 前端接线,视觉总监本人设计并编码):在已认可的交谈界面上由用户主动开启/结束连续语音;按 HEL-877 后台契约(aba2aeb docs/runbook-hel877-voice.md §3)接 GET /api/voice/status、POST/DELETE /api/voice/session 与 cg.subtitle 字幕,复用 .12 :8090 既有 livekit-client 2.22.4 浏览器客户端;文字与语音同一位她、同一聊天记录,轮次从 /api/messages 回读不串台;所有退出路径立即停麦并清理;零真实模型/嵌入/合成/识别/采集/部署",
"requirement_ids": [
"CG-006", "CG-010", "CG-011", "CG-018", "CG-020",
"VIS-001", "VIS-002", "VIS-003", "VIS-004", "VIS-005", "VIS-006", "VIS-007", "VIS-008", "VIS-009", "VIS-010", "VIS-011", "VIS-012", "VIS-013",
"UX-001", "UX-002", "UX-005", "UX-006",
"AM-001",
"GOV-001", "GOV-002", "GOV-003", "GOV-004", "GOV-005", "GOV-006",
"DEL-001"
],
"deliverable": "自前端原稿 hel-875-proactive-wiring @ bf23457 起的补丁(仅 web/ 与本交接):1) web/src/voice/session.ts——VoiceController 生命周期(代次计数:任何迟到的 token/房间/麦克风/房间事件只清理不显示);顺序为诚实状态(只 GET status,不建房不采麦)→ 加载客户端 → 采麦(拒绝则服务端什么都不开)→ POST session(结束时已在途的请求等其返回并 DELETE,不留孤儿房)→ 加入并发布麦克风 → 等 worker 加入(20 s 未加入如实失败);状态取 worker 的 lk.agent.state,缺属性时按字幕保守推断;interim 为草稿、user_final 取消在途文字回复并触发回读、assistant_final 触发回读、error(φ) 只显示「未记入聊天」不入记录;文字盖过她的语音时静音残余音频直到她回到聆听;结束顺序为停麦→关房间与音频→DELETE;断网时本地立即停采,远端 DELETE 记入本浏览器 cg.voice.owed,恢复网络/再次进入/再次开启时补发(404 视为已清);页面卸载用 keepalive DELETE;mergeTurns/recentRows 只合并非主动留言的服务器行(主动留言仍走原 inbox 轮询),按 turn_id 与已有文字轮去重,pending 行回读至落定。2) web/src/voice/livekit.ts——同源懒加载 /vendor/livekit-client-2.22.4.umd.min.js(来源 .12:8090/static,SHA-256 见 web/VENDOR.md,未改 package.json/锁文件),回声消除采集、她的音频集中在隐藏容器(结束即暂停/清空/移除)、自动播放受阻时提供「点一下听她的声音」、加入可被结束即时中止。3) ChatPanel.tsx——真实模式麦克风按钮按状态诚实标注(不可用时淡化并说明原因),确认框说明麦克风、去向、同一份记忆、如何打断与停止、未经真机验收;沿用原演示 voice-row 位置与视觉语言做成玻璃单行(连接/等待为呼吸、聆听用你的颜色、她说话用她的光色,文字始终写明状态);文字流在无 final 时先查服务器行:被新轮取消即按已取消处理,不报连接中断、不回填草稿;离开交谈/Esc/结束交谈/导航卸载面板即结束语音;在途语音轮期间她的主动留言照原规则排队。4) RealApp.tsx——语音进行中顶栏写「语音(未经真机验收)」。5) components.css 新增 .voice-row.real 等样式(含手机)。6) 测试:web/tests/voice.test.ts 26 项;web/tests/hel878_mock.mjs(HEL-877 路由契约 mock)+ fake-livekit.js(脚本化替身,浏览器假音频设备)+ hel878_shots.mjs 浏览器检查 45 项与截图;web/VENDOR.md、README 语音段落。",
"acceptance": [
"到达交谈页只发一次 GET /api/voice/status:不建房、不申请麦克风、不加载 LiveKit 客户端;configured=false / tts_ok=false / 无路由各有诚实说明,按钮淡化且不弹确认;voice=false 不被当作已接通(浏览器 1–2、单测 honest switch)",
"用户确认后:一个会话、一个麦克风、发布到该房间;等待 worker 时显示等待而非已接通;聆听/思考/说话随 lk.agent.state 切换;字幕草稿、φ 原句(标未记入聊天且不入记录)均按契约(浏览器 4–6)",
"语音轮只从 /api/messages 进入舞台与完整记录,各一次,与文字同一段记录(浏览器 5);前端从不 POST /api/chat 或 /api/chat/received(单测)",
"双通道:文字盖过她说话时残余音频静音、回到聆听再恢复;语音打断在途文字回复时该轮按已取消处理,无“连接中断/重试”、不回填草稿,记录显示已取消(浏览器 7–8)",
"结束语音/导航离开/Esc/结束交谈/房间掉线/麦克风被撤销/服务端拒绝/重复开关后:本地麦克风 track 全部 ended、她的音频与容器移除、服务端房间删除;迟到事件不复活旧会话(浏览器 10–14,单测 lifecycle 15 项)",
"断网:本地立即停麦停音频,远端房间如实记为欠删(不冒充已删);恢复网络后补发 DELETE 并清除记录;页面卸载 keepalive DELETE 到达(浏览器 14);欠删跨页面保留、404 视为已清(单测)",
"电脑 1440×900 与手机 390×844(2x)、月光/雾光两主题截图;手机语音行不出屏(浏览器 15)",
"真实 livekit-client 2.22.4 构建中,适配器用到的全部名称存在(RoomEvent 七项、Track.Source.Microphone、ParticipantKind.AGENT、createLocalAudioTrack、Room 方法/属性);仅 API 面核对,未连接",
"演示入口零语音请求;工程:tsc 0 错,eslint 0 错(1 条既有警告),vitest 7 文件 81/81,vite build 通过"
],
"deferred": [
"真实通话全部未测(R5 真机验收交 HEL-879 统一审核):连续 3 轮、开口打断约 1 s 停声、结束停麦无残音、回声、首包延迟、移动端 Safari/Chrome 麦克风与自动播放、自签 HTTPS 信任;浏览器检查用的是脚本化 LiveKit 替身与 Chromium 假音频设备,不是 WebRTC 通话",
"lk.agent.state 属性名按 livekit-agents 1.x 惯例接入,并在缺失时用字幕推断;worker 实际是否发布该属性须真机确认",
"断网时服务端房间的即时删除在物理上做不到:依赖 worker 在浏览器参与者离开后结束任务与会话登记过期(runbook §3.4/3.5,后台行为未在本卡实测),本页恢复网络或下次进入时补发 DELETE",
"CG-011 语音内主动开口:worker._maybe_open_proactive 仍是空操作,前端未虚构;后台最小需求见 runbook §3.6(会话内取已落库 proactive 行 session.say 原文、可打断、无新回执通道),呈现节奏待总管/视觉总监另定;目前语音开启时她的主动留言仍以文字进入并在她思考/说话时排队",
"她说话时没有逐字字幕(契约只在播放完成后发 assistant_final);舞台在落库后呈现全文。若要边说边显示需后台发增量字幕,属契约变更",
"D1 音色、D3 设备信任、测试额度由总管与白栖知沟通;本卡未接触任何服务、凭据或部署",
"原版 Python 需求检查未在本运行时执行(无 Python、无 git),须总管/总工对实际目标分支与 origin/main 含/不含本交接复跑;需与 HEL-882 最新母线同步后再审",
"R6 天气/素材/备份、R7 视觉观察、CG-028/029 口型与真实视频仍由主卡 HEL-797 承接;整体沉浸体验由白栖知验收"
],
"requirement_changes": [],
"verification": [
"读取:前端 bf234570(总管输入包 frontend/,HEL-875/880 原作者原稿);后台契约 aba2aeb(输入包 voice-backend/:runbook-hel877-voice.md、voice/router.py、session.py、worker.py、handoff-HEL-877.json、requirements v1.11 含 comment-evidence-full-experience-20261026.json);已读 AGENTS.md、README/current(CG-006/010/011/018/020、UX-005/006、VIS-013)/coverage/changes、integration-plan-v1 R5 与 D1/D3、voice-r5-readiness-HEL876.md",
"web:tsc --noEmit 0;eslint src 0 错;vitest run 7 文件 81/81(原 55 + 语音 26,含变异检查:去掉迟到授权回收或代次判定各使对应用例失败);vite build 通过(node_modules 沿用 HEL-875 同一锁文件离线树,无新增依赖、无下载)",
"浏览器:node tests/hel878_mock.mjs 8878 + node tests/hel878_shots.mjs <msedge.exe> <out> 8878,45/45(无头 Edge,--use-fake-device-for-media-stream;脚本化 LiveKit 替身;本机回环);中断前一轮为 39/43,失败的 4 项(语音打断文字误报、断网远端清理、卸载检查受其连带)经本轮修复后通过",
"唯一外部读取:一次只读 GET .12:8090 页面及其 /static/livekit-client.umd.min.js 以复用既有客户端;无模型、嵌入、合成、识别请求,无真实麦克风/摄像头,无部署或服务改动",
"需求结构检查:本运行时无 Python/git,未执行原版脚本,不声称通过;仅用 HEL-875 留下的 Node 移植版辅助:以 bf23457 为基含本交接通过(60 项);以后台 aba2aeb 的需求文件为基失败「历史来源必须保留」,原因是该线已追加 comment-evidence-full-experience-20261026.json 而前端原稿尚未包含,与本补丁无关,合入母线前须先同步该需求目录"
]
}
File diff suppressed because one or more lines are too long
+34
View File
@@ -0,0 +1,34 @@
{
"baseline_version": "1.11",
"read_commit": "b95ce798b4a9455d941d247a46865b3ac202bf2c",
"task": "HEL-897(HEL-878 有限返修,视觉总监本人编码):修正重开语音被旧掉线结果覆盖——旧会话 fail() 在 await teardown 之后提交 OFF,覆盖已 listening 的新会话;并检查同一失败/结束/重开路径中 await 前后的代次与所有权。不重新设计,不改视觉",
"requirement_ids": [
"CG-010", "CG-018",
"VIS-013",
"UX-005", "UX-006",
"GOV-001", "GOV-002", "GOV-003", "GOV-004", "GOV-005", "GOV-006",
"DEL-001"
],
"deliverable": "自 HEL-878 原稿(总管保全提交 b95ce798,13 文件哈希逐一核对一致)起的补丁,只动 web/src/voice/session.ts 与测试:1) start() 内 fail():代次与 live 释放后先发起 teardown(麦克风同步先停、房间关闭与 DELETE 已发出),立即在本会话仍拥有页面时提交 OFF 与失败提示,再等待清理完成;清理迟到返回只释放旧会话自己的麦克风/房间/服务端房间,之后不再写界面。2) start() 先同步取得新代次与 live,再等待被替换会话的清理(原先 await end() 之后才取代次:两次开启交错时,较早一次醒来会抢走代次、把已接通的新会话孤立在外,其麦克风与房间不再被释放);被替换后醒来即退出;settle 补发在取得代次之后。3) web/tests/voice.test.ts 新增「a late end of an older call」6 项(掉线/麦克风被撤销各一,worker 未加入超时,正常结束慢 DELETE,交错重开,迟到麦克风授权);Server 假件增加按会话挂起 DELETE。4) web/tests/review878_late_cleanup.test.ts:总管独立负例原样纳入。5) hel878_mock.mjs 增加 /__hold-delete 控制;hel878_shots.mjs 增加第 12 段后 HEL-897 浏览器复现 4 项。",
"acceptance": [
"总管负例 review878_late_cleanup.test.ts:修前失败(Expected listening / Received off,与总管日志一致),修后通过;测试未改",
"掉线与麦克风被撤销后,旧 DELETE 与 settle 补发都挂起期间重开:新会话 listening、active=true、无旧提示;放行后界面不再变化;旧麦克风已停、旧房间已关,新麦克风/房间未被停;服务端只剩新房间;再结束后全部清空(单测各 1 项)",
"worker 未加入超时失败且 DELETE 慢、正常结束 DELETE 慢后重开、两次开启交错(前一次仍在等旧房间归还)、迟到麦克风授权:只有最新会话存活,界面与 active 一致(单测 4 项;交错一项修前失败,暴露同路径的第二处代次漏洞)",
"修前对照:同一测试集在原 session.ts 上 5 项失败(总管负例 + 新增 4 项),修后 33/33",
"浏览器(无头 Edge,脚本化 LiveKit 替身与假音频设备):掉线后旧 DELETE 挂起,提示与停麦立即生效;新会话 listening;放行旧 DELETE 后新会话仍 listening、无旧提示、1 路活麦克风、她的音频在、服务端只剩新房间;结束后全部清理。修前构建 45/49(实测 phase=off 而新麦克风仍活),修后 49/49,原 45 项(结束/导航/Esc/结束交谈/断网/卸载/快速开关等退出路径)全部保持通过",
"工程:tsc 0 错;eslint 0 错(1 条既有警告);vitest 8 文件 88/88;vite build 通过"
],
"deferred": [
"CG-011 语音内主动开口、她说话时的增量字幕、断网时远端即时清理/租约与 API 重启实情、真实 SDK 回调与播放回执、真机连续三轮/打断/停麦/HTTPS/Safari 全部未测,仍交 HEL-879 统一审核;本卡单测与浏览器均为替身与假设备,不是 WebRTC 通话",
"原版 Python 需求检查未在本运行时执行(无 Python、无 git),须总管对实际目标分支复跑(含/不含 handoff-HEL-878.json 与本交接);未用 Node 移植版冒充通过",
"总管在前端分支从 04eff611 补齐的历史原话文件与 source-manifest 以总管保全分支为准;本补丁不触碰需求目录中除本交接以外的任何文件",
"D1 音色、D3 设备信任由总管与白栖知沟通;R6/R7、CG-028/029 仍由主卡 HEL-797 承接;整体沉浸体验由白栖知验收"
],
"requirement_changes": [],
"verification": [
"读取:总管 HEL-897 卡与附件(HEL-878-总管复核.md、preservation-878.json、review878-race.log、review878_late_cleanup.test.ts);本人 HEL-878 原稿树(逐文件 SHA-256 与 preservation-878.json 13 项一致后才施工,原稿另行备份);需求与 integration-plan R5/D1/D3 沿用 HEL-878 已读内容,本卡无需求变更",
"web:tsc --noEmit 0;eslint src 0 错 1 既有警告;vitest run 8 文件 88/88(原 81 + 本卡 6 + 总管负例 1);vite build 通过;node_modules 沿用 HEL-878 离线树,未下载、未安装",
"修前/修后:同一测试集与浏览器脚本分别在原 session.ts 与修正版上运行,日志随交付包",
"无模型、嵌入、合成、识别请求,无真实麦克风采集,无部署、合并、服务或绑定改动;未改后台与 HEL-896 发布包"
]
}
+47
View File
@@ -0,0 +1,47 @@
{
"baseline_version": "1.11",
"read_commit": "f92a86f5d8f839338311cdee08179049de34eb20",
"task": "HEL-898(HEL-879 接线卡,视觉总监本人编码):在已认可的交谈界面接上她的增量字幕(cg.subtitle 契约 v2)与语音内主动开口呈现,处理打断/文字接管/错误/挂断收尾;不另做聊天页、不改主题与整体动效;f92a86f 统一复核后的有限返修三项(worker 离场诚实收场、已退休 speech 不复活、短语音回复播放中可见)及回放基础设施",
"requirement_ids": [
"CG-004", "CG-006", "CG-010", "CG-011", "CG-018", "CG-020",
"VIS-001", "VIS-002", "VIS-003", "VIS-004", "VIS-005", "VIS-006", "VIS-007", "VIS-008", "VIS-009", "VIS-010", "VIS-011", "VIS-012", "VIS-013",
"UX-001", "UX-002", "UX-005", "UX-006",
"AM-001",
"GOV-001", "GOV-002", "GOV-003", "GOV-004", "GOV-005", "GOV-006",
"DEL-001"
],
"deliverable": "自总管输入包 3228b1e(INPUT-MANIFEST 逐文件 SHA-256)起的前端补丁,只动 web/src/voice/session.ts、web/src/chat/ChatPanel.tsx 与测试:1) session.ts 严格解析 v2 信封(v===2、正整数 seq、kind 白名单、角色与种类对应、她的事件必带 speech_id、assistant_final 必带 turn_id、proactive_* 必带正整数 message_id;不合格整条丢弃且不推进序号);每次通话(控制器代次)独立 seq 高水位,seq<=已应用即丢;按 speech 生命周期只接受当前打开的一轮,已关闭/被取代的 speech 不再显示,旧 speech 的高 seq 事件不能关闭新轮。VoiceView 新增 her(speech/text/state open|final|done/turnId/messageId/proactive),与用户 ASR 草稿 heard 分槽。2) turn_start 开轮,assistant_interim 整句替换,assistant_final 原子替换为落库文本并请页面回读 /api/messages,turn_end 关轮(无 final 的关轮按未成为正式回复即清),turn_cancel/error(φ)/user_final/文字接管 hush/结束/掉线/卸载/重开即时清;φ 原句仍在语音行以“未记入聊天”显示。3) proactive_start 以落库原文、同源 message_id 显示在她的位置,proactive_end 保留至记录或下一轮接替,proactive_cancel 即清;不发任何请求、不标已读、不开麦、不重 TTS、不进入正式记录。4) ChatPanel:她的语音行进入与文字回复相同的舞台槽位(当前一两句、自动续读、暂停/回看、全文),生长中沿用文字流的“最新一句先压住”规则,逐分片不重启动画;落库行到来时同一 turn 同一位置接替,行键不变不重放;被撤回的语音行以 instant 方式立即离场(不淡出);减少动态设置沿用既有逻辑。你用语音开始新一轮时与发送文字一致恢复自动呈现(原先暂停状态下她的回答不会进来)。5) 测试:voice.test.ts 改为 v2 帧并新增 11 项(录制样例三场景回放、信封负例、乱序/重放/迟到/双 handle、φ、主动开口及打断、文字接管/挂断/重开、舞台行键接替);fake-livekit.js 未带信封的帧自动套 v2;hel878_mock.mjs 增 /__proactive;hel878_shots.mjs 的她的 final 与 φ 改为 v2 生命周期;新增 hel898_shots.mjs(两主题×电脑/手机)。【f92a86f 有限返修,首轮读取 3228b1e;本轮输入包 166 文件 SHA-256 全符】1) livekit.ts 监听 RoomEvent.ParticipantDisconnected(vendor 2.22.4 原名 participantDisconnected,emit(participant, reason)),只在本房间她的 worker(按身份记住的 agent 或 isAgent)离开、且房内没有其他 agent 时报 agentLeft;房间关闭中不报。session.ts 的 RoomEvents 新增 agentLeft:仅当前代次且 agent 已接入时走既有 fail(清字幕/草稿、停麦、清播放、关房、DELETE 本次服务房,失败入欠账重试),中文提示 WORDS.agentLeft;普通参与者离场、旧房间迟到离场、用户主动挂断都不触发。2) 已关闭 speech 由 64 项窗口数组改为每次通话一个 Set,随通话代次释放;seq 严格门不变。3) 语音回复开放态改为 live=speaking:reading.pagesOf 对其最后一块只显示已成句部分(。!?!?… 及其后的收尾引号括号),未成句部分仍压住;文字流 live=generating 的规则不变。已显示的语音行之后只会在同键下增长(短句与下一句合并时):LineStage.grow 原位追加新字的既有墨迹,不重建行、不重放;减少动态时直接设文本。ChatPanel 签名对语音行计入文本,全文页“还在说…”也适用于 speaking;ReadingClock 对同键增长只延长停留时间。没有新布局或样式。4) 测试:hel879_frames 的 DOCUMENTED GAP 改为拒绝断言,并新增每通话独立用例;voice.test 新增短回复/长句/文字流不变用例;新增 hel898_agent_left.test.ts(真实 livekitTransport 适配器 + 脚本化 SDK 事件名,另有控制器负例);fake-livekit.js 增 ParticipantDisconnected/leave/lateLeave/guest;hel878_mock.mjs 增 /__pid、/__reset,监听失败即退出;hel879_unified_shots.mjs 修回放基础设施(端口空闲与 pid 确认、多段录制按当前通话单调重编号并写 seq-renumbering.json、新通话重置、每主题 /__reset 隔离、负例原样发送并断言拒绝),加三项正反例和 CDP screencast→WebM 录屏",
"acceptance": [
"假 LiveKit 有序回放 normal-turn.jsonl:用户草稿→用户终稿→turn_start→两次增量(整句替换)→final(带 turn_id)→turn_end;她的字在舞台出现时 /__rows 尚无该回复;落库后记录中只出现一次(页面只回读,不写)",
"interrupt.jsonl 与负例:turn_cancel 后她的未完成行立即离场;被取消轮之后的高 seq 增量/final/重复 turn_start 均不显示;整段录制重放不改变界面;双 handle 时旧 speech 的 turn_end/turn_cancel 不关闭新轮;无 final 的 turn_end 视为未成为正式回复并清除",
"φ:error 原句在语音行显示并标“未记入聊天”,她的增量行立即清除,随后 turn_cancel(error_phi) 不抹掉 φ 原句;不落正式记录",
"proactive.jsonl:原文一字不差显示在她的位置并保留落库行身份(id/turn),可被你的语音打断(proactive_cancel 即清);全程页面无任何非 GET 请求(无回执、无已读、无消息写入),不调用 onSpoke/onRecord",
"文字接管、结束语音、掉线、新通话:她的行即时清;旧房间迟到帧被代次挡住;新通话序号从 1 重新开始并正常显示",
"两主题(月光/雾光)电脑 1440×900 与手机 390×844 截图与连续帧:沿用已认可位置与组件,无新增组件或样式;减少动态设置下她的字照常出现、无入场动画",
"工程:tsc 0 错;eslint 0 错 1 条既有警告(ChatPanel fullButton ref,HEL-897 起即有);vitest 8 文件 99/99;vite build 通过;浏览器 hel898 61/61,HEL-878 回归 49/49;修前对照:新 voice.test.ts 在原 session.ts/ChatPanel 上 12 项失败",
"f92a86f 返修-1:worker 离场(房间仍开)时通话即时结束:她的行清除,提示“她那边的语音中断了……”,麦克风轨 ended,播放容器移除,本次 session DELETE 恰好一次;普通参与者离场不挂断;旧房间迟到离场不影响新通话;用户主动挂断只有一次 DELETE、没有离场提示;DELETE 失败入欠账并重试(单测)",
"f92a86f 返修-2:adversarial-gates 66 次挤出后,speech_A 以新 seq 重开/增量/终稿都被拒(单测与浏览器原样探针),之后新 speech 正常进入;新通话从 seq 1 开始,不受旧通话已退休 id 影响",
"f92a86f 返修-3:worker-normal 回放中“在听呢,”“…那件事”未成句时舞台不显示她的半句;第三个增量成句后、final 之前即显示整句,并在播放期间保持;final 与落库行接替为同一 DOM 行(不重建、不重放);短句与下一句合并时同节点原位增长、首字墨迹不动;长句只显示已完成的行、不整段铺满;文字流仍压住最新块",
"f92a86f 返修工程:tsc 0;eslint 0 错 1 条既有警告;vitest 10 文件 118/118;build 通过;修前对照 vitest 7 项失败;统一实际 worker 帧浏览器回放(moon/fog × 电脑/手机 + 减少动态)修后 53/53、修前 30/53(失败都落在三项及其连带);hel898_shots 61/61、hel878_shots 49/49;8 段 WebM 录屏取自真实 screencast 帧"
],
"deferred": [
"真实通话未测:真实 LiveKit/WebRTC、真实 worker 与 SDK 回调时序、真机/Safari/iOS、可信 HTTPS+WSS、R5 连续三轮/1 秒打断/停麦无残音/有限预算,均仍由 HEL-797 主卡承接;本卡全部为假 LiveKit、假音频设备与 Chromium 无头 Edge",
"CG-011 语音内主动开口仅是“会话开时念一条已落库旧留言”的最小接缝(后台默认关,本卡不启用);会话中持续主动联系、激活节奏、未读联动策略未完成,不得记为 CG-011 完成",
"后台并行小修(建房延迟后的清理唤醒、主动 say 的 start 发得过晚、无完整 receipt 错误发 final)由原总工在 HEL-879 处理;本卡按 v2 正确语义实现,最终后台 commit 出来后需按其统一复核(特别是 proactive_start 晚于音频开始时的呈现)",
"已知边界:seq 按“一次 worker job 一个序列”实现;若同一房间内 worker 重连且序号归零,前端会按契约丢弃其帧直到超过旧高水位——需后台确认 job 与房间一一对应",
"原版 Python 需求检查(scripts/check_requirements.py --base-ref origin/main,含本交接)未在本运行时执行(无 Python、无 git),须总管对实际目标分支复跑",
"D1 音色、cap=0、R6/R7、整体沉浸体验由主卡与白栖知决定;工程自测不是用户体验验收",
"f92a86f 返修:真实 SDK 下 worker 离场事件的时序(含网络重连期间 remoteParticipants 被清空但不发离场事件)只按 vendor 源码核对,未在真实房间验证",
"回放基础设施把多段录制重编号(原始帧保留在 fixtures,映射见 seq-renumbering.json),证明的是页面在“同一通话单调 seq”下的呈现与门,不证明 worker 跨 job 的 seq 行为"
],
"requirement_changes": [],
"verification": [
"读取:HEL-898 卡(启动交接与原说明)、输入包 3228b1e:docs/voice-subtitle-contract-v2.md、docs/rework/hel879/voice-seam-interfaces.md、subtitle-samples/*.jsonl、src/cybergril/voice/seam.py 与 worker.py(事件发出点)、AGENTS.md、docs/requirements/README.md/current.md(v1.11)/coverage.md/changes.md、sources/integration-plan-v1.md R5",
"工具:沿用本机既有离线工具链(HEL-834 留存 node v22.11.0;HEL-897 留存 web/node_modules,package-lock 哈希与本包一致,目录联接使用);未下载、未安装",
"web:tsc --noEmit 0;eslint src 0 错 1 既有警告;vitest run 8 文件 99/99(原 88 + 新增);vite build 通过;日志随交付包",
"浏览器:node tests/hel878_mock.mjs + node tests/hel898_shots.mjs(无头 Edge、脚本化 LiveKit 替身、Chromium 假音频设备)61/61;node tests/hel878_shots.mjs 回归 49/49;截图与连续帧随交付包",
"无模型、嵌入、ASR、TTS 请求,无真实麦克风采集,无部署、合并、服务或绑定改动;未改后台、总装分支或 PR#11",
"f92a86f 返修读取:卡内三项说明、HEL-898-统一复核返修输入-f92a86f.zip(166 文件 SHA-256 全符)、web/public/vendor/livekit-client-2.22.4.umd.min.js 中 RoomEvent.ParticipantDisconnected 的定义与 emit(participant, reason)、断开清理路径不发该事件;本包缺 docs/rework/hel879/subtitle-samples,测试运行时沿用 3228b1e 输入包同名文件(不入补丁)",
"f92a86f 返修浏览器:node tests/hel879_unified_shots.mjs <msedge> <out> 8879 9499(自带 mock,先确认端口空闲与 pid);hel898_shots/hel878_shots 由本运行自起 mock(核对 pid 后按 PID 停止)"
]
}
+57
View File
@@ -0,0 +1,57 @@
{
"baseline_version": "1.12",
"read_commit": "8dc84f88e9d5e961e17b246f24c23eaf526b012e",
"task": "HEL-899:把现有 CosyVoice 参考音频零样本克隆能力接入 CyberGril 统一 API(网页上传/录制参考音频→登记 cg- 专属 ID→试听现合成→应用到同一位她的后续通话);直接复用 .12 语音面板 8090 的 zero-shot 注册与 CosyVoice 热加载、9880 /tts 合成,不训练新模型、不重造克隆引擎、不自建第二写者;本卡只做后台/协议与零真实调用自测,前端按合同接视觉总监,真实试听放统一审核",
"requirement_ids": [
"CG-006",
"CG-009",
"CG-010",
"CG-013",
"CG-014",
"CG-015",
"CG-016",
"CG-018",
"CG-019",
"CG-020",
"CG-021",
"AM-001",
"GOV-001",
"GOV-002",
"GOV-003",
"GOV-004",
"GOV-005",
"GOV-006",
"DEL-001"
],
"deliverable": "src/cybergril/voice/custom.py(CustomVoiceService:8090 面板代理=共享 voices.json 唯一写者、cg-+8hex 服务端生成 ID 不撞既有 11 支、WAV 头服务端复验 RIFF/WAVE≤15MB≥2s、显式 allow_asr 才走 FunASR 转写、试听先验存在拦住 CosyVoice 未知音色静默回落 default、原子落盘+失败整体回滚、选择/元数据/资源流水持久在实例资料空间重启读回、备份清单 manifest);router.py 五个新端点(GET/POST /api/voice/voices、POST /api/voice/preview、GET/POST /api/voice/selection)+ /api/voice/session 响应新增 voice=本次通话捕获音色(选择失效诚实回落 default;未选择不代写 worker 文件不替 D1 定音色);session.py 会话记录 voice 字段+/health 名单读回;worker 语义不变(入口读一次→通话中改选最迟下一通话生效、绝不半句换人);契约文档 docs/voice-custom-contract-HEL899.md(前端可直接实现的请求/响应/错误/录音格式/部署与回滚);config/voice.example.yaml 增补(panel_url/tts_url/registry_file/worker_state_file,env 可覆盖);零新依赖(登记端点原始字节体+查询串,特意不引 multipart 库);tests/test_voice_custom.py 18 项 + scripts/verify_hel899_custom_voice.py 12/12;治理:v1.12(CG-010 边界更新,来源评论 01a17de1-2892-78be-a422-eb6a11ffebfd 逐字录入 sources/comment-evidence-custom-voice-20261027.json + manifest/baseline/coverage/changes 同步)",
"acceptance": [
"完整最小合同成立:列出可用声音(builtin 11 支+custom 标记、选择/试听状态、备份清单字段)、登记参考音频(服务端生成 cg- 专属 ID,不可能撞名覆盖既有 11 支,撞名重试不覆盖)、试听生成(每次现合成 WAV 24k/mono、无缓存不拿旧音频冒充)、读取/应用专属音色(换声音只换发声)全部经统一 API,浏览器不直连 8090/9880,凭据不下发",
"当前 session/chat 业务不分叉:worker/统一聊天链零改动;通话开启捕获有效音色并写入会话记录与 worker 状态文件,通话中改选不影响本通话、最迟下一通话生效(测试断言会话 voice 定住)、不打断或误认原轮次/receipt(receipt 链路未动)",
"原子与失败诚实:登记失败(面板 400/5xx/断连/谎报成功)不留下半条可选项、无元数据残留;应用失败整体回滚(选择状态与 worker 文件都回旧值);服务不可用一律 503 中文文案;ASR 仅在 allow_asr=true 显式准许且参考文字为空时发生(面板 FunASR,非 LLM),每次记 register_asr/preview_tts 流水可核算",
"试听不写聊天/记忆/生活/回执(四表计数零变化测试断言);自定义音色未试听不得应用(409),内置 D1 候选不强制;未知音色 404 且 TTS 零调用(前置拦住 CosyVoice 静默回落 default);未知选择通话开启诚实回落 default 不冒充",
"持久化与身份不变:选择/元数据/试听凭据重启读回(重建服务同批文件测试);换声音不改 companion/bazi_core/chat_message(逐字节对比测试);通用助手 voice_state/persona_state/既有音色零写入零删除(桩端点计数零);无人经 API 选择时不代写 worker 文件(D1 域不替用户定)",
"零真实调用:全链 fake 面板/TTS/LiveKit 传输,零真实 LLM/嵌入/ASR/TTS、零模型下载/安装、零共享音色实写、零部署、零现网触碰、未访问 4080、未动 HEL-879 现场"
],
"deferred": [
"真实 CosyVoice 试听/真实 FunASR 转写/克隆相似度:统一审核卡在获批环境实测,复用合法剩余额度,不另开账绕 HEL-879 上限(最长2小时/3次通话授权属总管测试执行,不在本卡)",
"前端入口(上传/录音/试听/应用 UI 与入口呈现):视觉总监按 docs/voice-custom-contract-HEL899.md §2 契约接线;本卡未动 web/",
"自定义音色删除与完整备份/恢复(CG-009/R6):本卡最小合同不含删除(失败/多余登记不影响使用),备份清单字段已就位,恢复=按清单重新登记(面板无导出接口)",
"D1 最终音色决定仍归白栖知/总管;本轮测试音色已授权总管自定",
"R5 整体真机验收与 R1–R7 完整要求继续承接;自定义音色交付不等于 R5 完成或整产品完成"
],
"requirement_changes": [
{
"summary": "v1.12:CG-010 音色边界更新为可自定义音色(网页上传/录制参考音频→试听→应用,直接复用已装TTS零样本克隆,不以仅选预设替代,参考包试听方案不采用);影响仅 CG-010 音色入口与测试授权,换声音只换发声等既有边界不变",
"approval_comment_id": "01a17de1-2892-78be-a422-eb6a11ffebfd",
"record": "sources/comment-evidence-custom-voice-20261027.json + changes.json/changes.md v1.12 + baseline.json/coverage.json/current.md 同步"
}
],
"verification": [
"PYTHONPATH=src .venv311/bin/python -m pytest tests -q:370 通过(基线 8dc84f8 全量 352 + 本卡 18:tests/test_voice_custom.py,全 fake 面板/TTS/LiveKit httpx.MockTransport + 临时文件 + 隔离 registry,零网络零真实模型调用)",
"scripts/verify_hel899_custom_voice.py:12/12 全过(真实 app+router+service,fake 传输:列表→无准许拒ASR→显式登记→未试听409→试听现合成→应用→会话捕获→通话中改选定住本通话→下一通话生效→capabilities.voice=false→重启读回选择/元数据/试听凭据)",
"ruff check src tests scripts deploy:本卡代码 0 错(既有 deploy/hel835_guard.py 的 SIM115 遗留不变,非本卡)",
"python3 scripts/check_requirements.py --base-ref origin/main 与 --base-ref 8dc84f8(实际基准):含本 handoff 通过(v1.12 升版+新来源+变更记录合法,历史来源与变更未改写)",
".12 只读取证(SSH 零写入零服务触碰):/opt/voice-agent/web/server.py 实读核对 upload 校验(名字规则/15MB/WAV magic/32000字节/空参考文本→FunASR 转写/失败删 wav/voices.json 原子写)、/opt/voice-agent/cosyvoice/server.py 实读核对 mtime 热加载与 /tts 未知音色静默回落 default、voices.json 11 支音色清单——契约按实际源码写,不按记忆",
"未验证如实声明:真实 CosyVoice 合成与 FunASR 转写未调用(TTS 9880 停机中且本卡零真实调用);浏览器端录音上传未实现(前端合同已交);.12 未部署、未改任何现网配置/端口/数据"
]
}
+56
View File
@@ -0,0 +1,56 @@
{
"baseline_version": "1.12",
"read_commit": "ce7279e68f64319c080f7ef150cb86c892b81ecb",
"task": "HEL-900:在已认可的设置界面接入自定义声音——网页上传或录制参考声音→登记→试听实际合成→应用给同一位她的后续通话;按 docs/voice-custom-contract-HEL899.md §2 接 HEL-899 后台(agent/2/652da8a82e50 @ ce7279e),只改 web/,零真实调用",
"requirement_ids": [
"CG-006",
"CG-009",
"CG-010",
"CG-013",
"CG-014",
"CG-015",
"CG-016",
"CG-018",
"CG-019",
"CG-020",
"CG-021",
"VIS-003",
"VIS-004",
"VIS-008",
"VIS-010",
"UX-003",
"UX-006",
"AM-001",
"GOV-001",
"GOV-002",
"GOV-003",
"GOV-004",
"GOV-005",
"GOV-006",
"DEL-001"
],
"deliverable": "设置面板新增「她的声音」分区(沿既有设置结构、组件与两套主题 token,不另起视觉方向):web/src/voice/custom.ts(同源 API 客户端:GET/POST /api/voice/voices、POST /api/voice/preview、POST /api/voice/selection;浏览器解码任意可解音频→16kHz 单声道 16 位 PCM WAV 原始字节体上传,参数走查询串;≥2 秒/≤15MB/静音/解不开在浏览器先拦;allow_asr 仅在参考文字为空且用户明确打开开关时为 true;试听响应须为合法 WAV 才可播放;Recorder 仅由点击开启、只录音频、停止/取消/离开/音轨中断即释放、20 秒自动停、迟到的麦克风授权立即关闭);web/src/manage/VoiceSection.tsx(现在的声音与生效时机、上传/录音二选一、参考原声与「她用这个声音说的·刚刚现场合成」分开标注的主题化播放条、名字/参考文字/转写开关、登记、可用声音列表、试听→浏览器 canplay 确认可播后才能应用、每类请求一代 AbortController + 进行中引用防重复、切换/重试/离开/卸载释放请求与 objectURL、失败保留输入);ManagePanel 接入分区、未登记的参考声音计入未保存离开确认、能力标签随服务器实际状态;components.css 追加分区样式(只用既有 token,两主题与手机断点);离线 mock tests/hel900_mock.mjs、浏览器驱动 tests/hel900_shots.mjs、单元测试 tests/hel900_custom_voice.test.ts",
"acceptance": [
"用户可在网页选择本地音频文件或主动点「开始录音」得到参考声音;产物为真实 PCM WAV(mock 端按 RIFF/WAVE 头核验 16kHz/单声道/16 位、时长≥3 秒、Content-Type audio/wav、原始字节体),改扩展名的非 WAV 在浏览器被拒且零上传",
"参考文字可填写;留空时须用户明确打开转写开关才传 allow_asr=true,默认不代开;已填写参考文字时一律 allow_asr=false;转写结果回显",
"参考原声与实际合成试听分开标注(「你给的声音,不是她的声音」/「刚刚现场合成·不是参考原声」);试听每次重新请求,非 WAV 响应拒绝播放;浏览器 canplay 确认本次新试听可播后「给她用这个声音」才可点;切换声音、重试、离开时中止旧请求、停播并回收 objectURL,迟到的旧试听不出现",
"读取并显示当前实际选择;应用成功后显示新声音并说明从下一次通话生效、已接通的通话不中途换声;换声音文案与请求均不触及名字/底色/关系/聊天/记忆;不读写通用助手状态",
"失败如实:未配置/无通路/登记服务 503/TTS 503/422/409/断网/拒麦/麦克风占用/非安全来源/录音取消/停止/离开/重复提交/旧异步结果晚到各有中文说明;失败保留参考声音与已填内容;重复点击只发一次登记且不中止已发出的上传",
"录音仅由用户点击开启、只请求音频不请求摄像头;停止、取消、切到其他分区、关闭设置、音轨中断后真实音轨全部 ended;打开分区本身只读列表、不申请麦克风;演示入口只显示说明卡、不挂载该组件(代码条件 real && open,未做演示入口浏览器测试)"
],
"deferred": [
"真实 CosyVoice 试听、真实 FunASR 转写与克隆相似度、真实麦克风与真机(iPhone/Android/Safari)录音解码:留总工统一审核在获批环境与额度内实测,本卡零真实调用",
"后端待补核项(总管已列给统一审核):真实 worker 通话开始读音色的时序、登记服务端成功后本地落盘失败/断连的半状态、ASR/试听实际用量与既有限额的衔接;前端不替后台修实现",
"自定义音色删除、完整备份/恢复(CG-009/R6)后续卡;当前界面已写明暂不能在此删除",
"交谈页未显示「本次通话所用声音」(POST /api/voice/session 的 voice 字段);如需在通话中提示,可后续小卡补",
"设置「外接服务」分区仍沿旧文案显示语音识别/合成「尚未接通」,与本卡新分区及 HEL-878 语音接通不一致,未在本卡改动,交总管决定是否另行修正",
"D1 最终音色决定仍归白栖知/总管;R5 整体真机验收与 R1–R7 完整要求继续承接,本卡交付不等于用户已验收音色"
],
"requirement_changes": [],
"verification": [
"web: tsc --noEmit 0 错;eslint src 0 错(仅既有 ChatPanel.tsx:240 警告,非本卡);vitest 137/137 通过(基线 118 + 本卡 19:WAV 编码/头校验、48k 立体声→16k 单声道、过短/静音/解不开/过大拒绝、API 合同与错误透传、离线零请求、allow_asr 规则、非 WAV 试听拒绝、中止的试听不返回结果、409/503、Recorder 仅点击开启/停止保留/取消丢弃/迟到授权立即关闭/20 秒自动停/音轨中断/拒绝与非安全来源/重复点击单开);vite build 通过",
"浏览器:node tests/hel900_mock.mjs 8900 + node tests/hel900_shots.mjs <msedge.exe> <outDir> 8900,headless Edge + Chromium 假音频设备,83/83 通过;月光/雾光 × 电脑 1440×900/手机 390×844 主流程截图各 6 张及边界截图(文件上传、断网、登记 503、旧试听丢弃、409、拒麦、离开确认、未配置/无通路/服务不在)",
"python -I scripts/check_requirements.py --handoff docs/requirements/handoff-HEL-900.json 通过;本运行环境无 git,--base-ref origin/main 比较未能执行,改以与 ce7279e 源码包逐文件哈希比对确认 docs/requirements 除新增本交接文件外零改动,--base-ref 检查留统一审核在仓库内执行",
"未验证如实声明:假麦克风是浏览器内置提示音不是人声;试听是 mock 生成的音调不是 TTS;未接真实 8090/9880/FunASR,未部署,未触碰 HEL-879 现场与共享音色注册表,未访问 4080;Safari 与真机未测"
]
}
+27
View File
@@ -0,0 +1,27 @@
{
"baseline_version": "1.12",
"read_commit": "d167ba927c1b07e4771219707c744e90051837d9",
"task": "HEL-902:统一离线审核 HEL-900 前端(hel-900-preserved @ 701442d/37b4e69)与 HEL-899 后台(ce7279e),整合 HEL-879 真实窗口修复(hel-879-assembly @ be149cd)到独立分支 hel-902-integration,零真实调用、零部署、零依赖下载;并备齐真机补测申请包",
"requirement_ids": ["CG-006", "CG-009", "CG-010", "CG-013", "CG-014", "CG-015", "CG-016", "CG-018", "CG-019", "CG-020", "CG-021", "VIS-003", "VIS-004", "VIS-008", "VIS-010", "UX-003", "UX-006", "AM-001", "GOV-001", "GOV-002", "GOV-003", "GOV-004", "GOV-005", "GOV-006", "DEL-001"],
"deliverable": "独立整合分支 hel-902-integration(701442d+be149cd 无冲突合并,不改写母线/旧现场/PR#11);总工统一审核报告 docs/rework/hel902/REVIEW.md(含后台故障注入新增 tests/test_voice_review_hel902.py 6 用例:本地元数据落盘失败可恢复不逼重复注册、响应断连重试留可回收孤儿条目、开房 worker 写失败诚实 503、worker 状态损坏行为如实记录、流水写不进时应用 fail-closed 409、选择写失败整体回滚);真机补测申请包 docs/rework/hel902/r5-followup-package.md(时窗/逐类上限/停机方法/参考音频来源与 cg- 共享新增回收/旧服务保护/HEL-879 遗留量测并入);需求入口 README 基准行 v1.11→v1.12 勘误(授权非视觉小修,基准不回退)",
"acceptance": [
"统一 head 上实际复跑:pytest 376/376(370 基线+新增6)、verify_hel899_custom_voice.py 12/12、ruff src/tests/scripts 通过、需求检查 4/4(origin/main 与 ce7279e 各带/不带 handoff-HEL-900)",
"前端保全独立复核:8 文件 blob SHA-256 与保全清单 8/8 一致;作者 ZIP 重下校验 SHA-256 一致;36 截图全部有效 PNG 且视口与 harness 定义一致;作者日志(vitest 137/137、lint 0错1既有警告、build、浏览器 83/83)与 handoff 声明逐项相符",
"前端逐行静态复核与合同 §2/用户原话 01a17de1 一致:原始 WAV 字节+查询串、浏览器真转 16k mono PCM16、大小时长静音拒绝、allow_asr 显式门、试听必合法 WAV+canplay 后才可应用、参考原声与现场合成分开、请求代际清理、录音轨迹释放、演示入口不挂载零请求、CSS 仅用既有 token",
"后台重点补核:capture→写 worker 文件→dispatch→worker 入口读一次的顺序锁定;共享注册表仅经 8090 且 cg- 命名空间不碰 11 支内置与通用助手状态;ASR/试听流水仅核算、硬闸仅 LLM/嵌入(如实,未称硬闸)",
"问题清单集中一轮:🔴外接服务文案按实际能力改写(交视觉作者);🟡登记本地落盘失败映射文案、孤儿条目说明、worker 状态 docstring、会话页显示本次音色(结论:需要)、capture-dispatch 串行化;承接 CG-009/R6 删除与备份、R5 整体验收、R1–R7 完整功能"
],
"deferred": [
"真实登记/可选 ASR 转写/真实试听/应用与下一通话验证、克隆相似度、真机(iPhone/Android/Safari)录音路径:待主卡批准补测包后执行,未批前真实 cap=0",
"HEL-879 遗留:打断后约1秒停声毫秒级量测、文字打断语音方向、真实浏览器/设备麦克风路径——并入同一补测窗口,量测方法已写入补测包",
"前端独立复跑(tsc/eslint/vitest/build/浏览器两主题×电脑/手机、演示入口零外发断言):本机依赖被清理且禁下载,需一次性 npm 同锁文件安装授权或随真机窗口在 .12 执行",
"自定义音色删除、完整备份/恢复(CG-009/R6)后续卡;R5 整体验收与 R1–R7 完整功能持续承接;PR#4/PR#8 等仍无合并或替换正式界面授权"
],
"requirement_changes": [],
"verification": [
"pytest tests -q 376/376(py3.11.16 复用既有审核 venv,零安装)",
"python -I scripts/verify_hel899_custom_voice.py 12/12;ruff check src tests scripts 通过",
"python -I scripts/check_requirements.py --base-ref origin/main(及 --handoff docs/requirements/handoff-HEL-900.json、ce7279e 对照)4/4 通过",
"前端与浏览器未在本机复跑(环境缺依赖且本卡禁下载安装),以作者日志+逐行静态复核+保全哈希复核作证据,如实标注未验证范围;真机项全部列入补测包"
]
}
+57
View File
@@ -0,0 +1,57 @@
{
"baseline_version": "1.12",
"read_commit": "b09a907747f9836c4aea04016757321d28b7cb6b",
"task": "HEL-903:HEL-902 统一审核后的原作者有限返修(只改 web/ 与本卡交接/证据)——外接服务语音两行按 /api/voice/status 如实区分状态;通话栏显示 session 响应 voice 固定的「本次通话声音」;登记后无明确结果(500/503/响应断连)时提示先刷新列表看编号条目,列表标出缺本地元数据的 cg- 条目。基于 origin/hel-902-integration @ b09a907,保留 HEL-879 真实窗口修复与 HEL-900 作者源码,不推写统一母线、不回退 ce7279e,零真实调用",
"requirement_ids": [
"CG-006",
"CG-009",
"CG-010",
"CG-013",
"CG-014",
"CG-015",
"CG-016",
"CG-018",
"CG-019",
"CG-020",
"CG-021",
"VIS-003",
"VIS-004",
"VIS-008",
"VIS-010",
"VIS-013",
"UX-001",
"UX-003",
"UX-005",
"UX-006",
"AM-001",
"GOV-001",
"GOV-002",
"GOV-003",
"GOV-004",
"GOV-005",
"GOV-006",
"DEL-001"
],
"deliverable": "web/src/voice/session.ts:readVoiceStatus 把 GET /api/voice/status 分成 none(无接口)/off(未配置)/ok(已配置且合成健康检查通过)/down(已配置但健康检查未过)/unknown(读不到)五态,voiceStatus 改为由它派生、行为不变;VoiceView 新增 voice(本次通话固定声音,开房前 null,服务器未给或不合规为 {id:null}),只在拿到本代 grant 时写入,结束/失败/迟到旧请求一律随 view 复位;callVoiceOf 只接受列表可容纳的 id。web/src/chat/ChatPanel.tsx:在既有通话条加「本次通话 · 声音名」(default→默认声音、缺字段→声音未说明,名字只按该 id 查列表,查不到显示编号,不用设置里的当前选择),title 写明到本次结束不变、设置里的新选择下一次通话生效。web/src/manage/ManagePanel.tsx:外接服务的语音识别/语音合成两行改为进入分区及点「重新读取状态」时读 /api/voice/status(仅真实入口),按五态如实说明,识别一行写明没有单独健康检查;配置框、按钮、说明卡与文字检查提示随之更正,视觉观察仍写尚未接通。web/src/voice/custom.ts + VoiceSection.tsx:VoiceError 增加 uncertain(已发出后断连、5xx、成功响应体读不全),登记遇到时提示「可能已经登记上了,先刷新列表看 cg- 编号条目,不用再登记」,提供「刷新列表」并把再登记改为「确认没有,再登记」;刷新后对比失败时的自定义列表,指出新出现条目或说明暂无新条目需稍等;列表把无本地元数据的 cg- 条目标「只有编号,缺本地资料」并加说明。4xx 与离线未发出仍为原明确提示。不新增删除入口、不自动删声音。components.css 追加通话条声音标签、状态行配色、编号条目说明与手机按钮换行(只用既有 token,两主题)。测试:tests/hel903_voice_rework.test.ts(11 例)、tests/hel903_mock.mjs、tests/hel903_shots.mjs;tests/hel898_agent_left.test.ts 期望视图补 voice:null。",
"acceptance": [
"外接服务:语音合成在 ok/down/off/none/unknown(500)/unknown(断连) 下分别显示「已配置 · 合成服务健康检查可连接」「已配置 · 合成服务这会儿暂不可用」「未配置 · 这台服务没有语音配置」「这台服务没有语音状态接口」「状态未知 · 这次没读到语音状态」;语音识别不声称有自己的健康检查;任一状态下语音两行都不出现「尚未接通」或「已接通」;读取状态不申请麦克风;说明写明「可连接」不代表整套语音通话已验收",
"通话:开房后通话条显示 session 响应 voice 对应的「本次通话 · 名称」;通话中另一端改选声音,本次通话显示不变;挂断后标签消失、麦克风释放;下一次通话显示新选择;响应缺 voice 字段显示「声音未说明」而非设置当前选择;default 显示「默认声音」;更早一次通话迟到的 grant 不覆盖新通话(单测)",
"登记:服务器已收下却回 500、或响应中途断开、或 503 时,提示可能已登记并先刷新列表;不自动重读、不自动再登记(上传计数验证);点刷新列表后能指出新出现的 cg- 编号条目并标「只有编号,缺本地资料」;若无新条目则说明稍等再刷、确认没有再登记,确认后再登记一次只发一次;参考声音与已填内容在失败后保留",
"回归:既有上传→登记→试听 canplay→应用流程、换声音只换发声的说明、录音点击开启/停止保留/取消释放麦克风、通话字幕与结束清理均保持;演示入口打开外接服务、她的声音与交谈零 /api 请求"
],
"deferred": [
"后台新错误映射(本地元数据落盘失败 OSError→503/422 中文文案等,HEL-902 §5-2)由另卡处理;本卡先兼容既有 500/503,后续统一整合时按合同补核前端文案",
"语音识别没有独立健康状态接口,外接服务只能如实写「没有单独的健康检查」;如需显示识别服务真实状态需后台另行提供",
"编号条目的删除/回收(经 8090 面板自有删除)、自定义音色删除与完整备份恢复(CG-009/R6)继续由后续卡承接,本卡不新增删除入口",
"_capture_voice→worker 读文件的秒级竞态(HEL-902 §5-6)属后台;前端显示的是服务器声明的本次捕获值",
"真实 CosyVoice/FunASR/LiveKit、真机(iPhone/Android/Safari)与 R5 整体验收、R1–R7 完整功能继续承接;HEL-901 仍停放;本卡不部署、不合并"
],
"requirement_changes": [],
"verification": [
"web(复用原作者 HEL-900 工作区已存在的同版 node_modules,package-lock SHA256 F950DE58…7A27 一致,经目录联接使用,零下载安装):tsc --noEmit 0 错;eslint src 0 错 1 既有警告(ChatPanel fullButton,非本卡);vitest 148/148(基线 137 + 本卡 11);vite build 通过",
"浏览器:node tests/hel903_mock.mjs 8903 + node tests/hel903_shots.mjs <msedge.exe> <outDir> 8903,headless Edge + 假 LiveKit(tests/fake-livekit.js)+ Chromium 假音频设备,90/90 通过:月光/雾光 × 电脑 1440×900/手机 390×844 各 18 项(外接服务、应用声音、本次通话声音固定、下一通更新、字幕、挂断释放、登记 500 后恢复),状态五分支、缺 voice 字段/default、响应断连与 503 恢复、录音释放、演示零请求",
"对照截图:同一 mock 跑 b09a907 原样构建(产物 index-C8UHROe1.js 与 HEL-900 作者构建同名)before 模式三张:外接服务、通话条、登记 500",
"需求检查:python -I scripts/check_requirements.py 与 --handoff docs/requirements/handoff-HEL-903.json 本机执行;本运行环境无 git,--base-ref origin/main 与 --base-ref b09a907 均无法执行,改以与 b09a907 源码包逐文件 SHA256 比对确认 docs/requirements 除新增本交接文件外零改动,--base-ref 四组检查交总管在仓库补跑",
"未验证如实声明:假麦克风是浏览器内置提示音;假 LiveKit 不是 WebRTC;试听是 mock 音调不是 TTS;未接真实 8090/9880/FunASR/LiveKit,未部署,未写共享音色,未访问 4080;Safari 与真机未测"
]
}
+28
View File
@@ -0,0 +1,28 @@
{
"baseline_version": "1.12",
"read_commit": "b09a907747f9836c4aea04016757321d28b7cb6b",
"task": "HEL-904:HEL-902 统一审核后的后台有限返修(HEL-899 原作者)。三项:①登记在8090成功而本地元数据落盘失败→503中文映射(编号可恢复、共享登记不回滚、不逼重复登记),ASR流水先记再落盘;②worker状态文件损坏整体重写的实况补入docstring/合同,正常可读文件仍保留其他键;③修通话捕获音色与worker实读竞态:以会话不可变快照(通话窗口持有)保证响应voice=worker本通话实读声音、通话中改选只影响下一通,覆盖失败/取消/挂断失败转欠账/TTL到期结算,确定性Event门控无sleep;④补测申请材料收紧(仅材料不执行)。零真实调用、零部署、零下载、不动web/与共享worker、不新增依赖",
"requirement_ids": ["CG-006", "CG-009", "CG-010", "CG-013", "CG-014", "CG-015", "CG-016", "CG-018", "CG-019", "CG-020", "CG-021", "AM-001", "GOV-001", "GOV-002", "GOV-003", "GOV-004", "GOV-005", "GOV-006", "DEL-001"],
"deliverable": "独立分支(自b09a907)后台返修:custom.py登记失败映射LocalRecordFailed+ASR流水先记、write_worker_voice损坏实况docstring、通话持有hold/release+apply暂缓写、router捕获登记/失败取消释放/503映射/settle挂钩、session.py on_session_settled结算(挂断成功/转欠账/TTL到期);tests/test_voice_review_hel902.py第1例按新契约改写(含重启恢复与409守门断言)、新增tests/test_voice_rework_hel904.py确定性时序4例;合同docs/voice-custom-contract-HEL899.md §2.2/§2.5/§3更新;docs/rework/hel902/r5-followup-package.md收紧(head里程碑表:d167ba9=整合提交、b09a907=审核head、本卡head待统一复审登记,登记ASR与连续通话ASR区分、连续通话ASR计数手段与待补阻塞如实声明、停声量测单一时间基准帧级录音、参考音频明确D1包female_soft段含sha256/长度、回收清单与不未经确认保留测试垃圾、共享TTS启停快照恢复、git回退不代替共享回收、上限含失败口径与硬闸/监看界线);docs/rework/hel904/REWORK.md返修记录",
"acceptance": [
"登记成功+本地元数据写失败:503中文文案含编号、明确共享登记不回滚、刷新列表可找回并试听/应用、不逼重复登记(tests/test_voice_review_hel902.py改写例:含重启按编号label=编号继续用、新音色未试听409守门不放松、11支内置与通用助手零改写)",
"worker状态文件损坏实况:docstring/合同如实注明整体重写仅剩voice,不再宣称任何情况下保留其他键;正常可读文件其他键保留(既有用例继续通过)",
"竞态收口:确定性Event门控测试把worker读时点延迟到dispatch返回之后、并发应用新声音后——本通话worker读到=响应voice=旧音色;挂断结算后下一通capture用新声音;挂断失败转欠账、TTL到期、dispatch失败、请求取消四条路径都释放持有、应用随即写透;无sleep凑测试(tests/test_voice_rework_hel904.py 4例)",
"补测材料:仅材料不执行,候选上限只收紧不放宽,一切失败/取消/重试/出站无响应计入口径;LLM/嵌入=程序硬闸与其余工程监看界线写明;连续通话ASR无程序计数手段处如实标待补阻塞;真实cap=0保持"
],
"deferred": [
"真实登记/可选转写/真实试听/应用与下一通验证、克隆相似度、打断停声帧级量测、真机麦克风路径:待主卡批准补测包后执行,未批前真实cap=0",
"本卡后台修复与前端另卡(外接服务文案、会话页显示本次音色)需原总工一次统一复审;通过后登记最终已验证head,此前任何head不得当作执行依据",
"连续通话ASR分钟级音频时长与连接重试的程序计数手段:待补阻塞,窗口前由总管在.12验证LiveKit/worker侧指标可执行性,不通过则窗口不得启动含连续通话步骤",
"前端同锁文件npm安装复跑:单独待批项,本轮不执行",
"自定义音色删除、完整备份/恢复(CG-009/R6)后续卡;R5整体验收与R1-R7完整功能持续承接;本卡通过≠自定义音色用户验收,更≠完整产品完成"
],
"requirement_changes": [],
"verification": [
"pytest tests -q:380通过(基线376+新增4,py3.11.16复用既有venv零安装),全量含HEL-899 18项与总工故障注入全数通过",
"python -I scripts/verify_hel899_custom_voice.py:12/12(含通话中改选定住本通话、下一通生效)",
"ruff check src tests scripts:通过",
"需求检查4/4:--base-ref origin/main 与 --base-ref b09a907 各带/不带本handoff全部通过",
"未验证(如实):真实worker读时点、真实dispatch/房间、打断停声与真机路径——全部留在获批补测窗口;重叠通话不同音色的残余竞态如实记录于合同§2.5与REWORK.md,不宣称消除"
]
}
+30
View File
@@ -0,0 +1,30 @@
{
"baseline_version": "1.12",
"read_commit": "7cb73429f7a0585bed17873179a627c890336c70",
"task": "HEL-905:HEL-903 前端返修与 HEL-904 后台返修的统一离线复审(总工)。在自己独立分支 hel-905-unified-review@7cb73429 集成前端088ac62(原作者源码51669aa,11文件逐字节一致)与后台07dda2b,不重出架构、不另起视觉、不重复派工;一次审完三处前端修正(语音合成五态与ASR无单独健康检查的诚实说明、演示零API外发、session voice固定本次声音)与500/503/断连恢复流(先刷新找cg-编号条目、缺资料标注、不自动重登记/不自动删除)、后台通话持有方案的失败/取消/挂断转欠账/TTL结算路径、以及补测包材料可批性。真实cap=0,零部署、零下载安装、不发真实请求、不合并PR",
"requirement_ids": ["CG-006", "CG-009", "CG-010", "CG-013", "CG-014", "CG-015", "CG-016", "CG-018", "CG-019", "CG-020", "CG-021", "VIS-003", "VIS-004", "VIS-008", "VIS-010", "VIS-013", "UX-001", "UX-003", "UX-005", "UX-006", "AM-001", "GOV-001", "GOV-002", "GOV-003", "GOV-004", "GOV-005", "GOV-006", "DEL-001"],
"deliverable": "统一复审结论(本卡评论)+集成分支 origin/hel-905-unified-review @ 7cb73429f7a0585bed17873179a627c890336c70(merge 前端088ac62与后台07dda2b于 b09a907 之上,历史正常保留)。独立核验:11文件SHA256与原作者改后清单逐一一致;前端三处修正与恢复流按源码逐行复核(callVoiceOf只认严格id、default=默认声音、缺答=声音未说明不猜、失败/结束复位OFF、late grant被stale/gen挡住;演示模式全部效果守if(!real)不发任何请求;uncertain分类422/409/离线=不确定false、5xx/发送后断=不确定true,主按钮换刷新列表、从不自动重登记);后台持有hold在capture写文件后无await间隙登记、挂断成功/挂断失败转欠账/TTL到期/dispatch失败/请求取消五路释放、Event门控无sleep。总工独立复跑(复用既有venv311,零安装):pytest tests 380/380、verify_hel899_custom_voice 12/12、ruff src tests scripts 通过;需求检查4/4(origin/main与b09a907各带/不带本handoff)。前端构建/单测/浏览器因web/node_modules缺失未独立复跑,如实列为待批项,不以作者148/148、90/90冒充",
"acceptance": [
"集成核验:7cb73429含088ac62与07dda2b两个先祖、基点b09a907;author-manifest.json 11文件改后SHA256与统一head工作树逐一一致(含handoff JSON共11项match 11/11);前端仅作者源码,总工未替作者改视觉方向",
"前端三处修正复核:readVoiceStatus五态(none/off/ok/down/unknown)+ASR行如实写「没有单独的健康检查,以实际通话为准」;演示模式services/voiceStatus效果全守if(!real)零API外发;session voice只取本通grant严格id、命名查找按id匹配、跨id答案不显示、结束/失败复位、下一通用新选择",
"恢复流复核:uncertain(5xx/发送后断/体不完整)→提示先刷新找cg-新条目、刷新前后集合比对指认新增项、idOnly条目如实标注「只有编号,缺本地资料」并说明可试听可用、无删除入口、绝不自动重登记或自动删除;Chromium自动重发登记POST的服务端非幂等风险如实保留(重试另立新cg-编号+共享注册表孤儿条目,合同§2.2如实声明,不宣称前端提示已解决服务端幂等)",
"后台通话持有复核:capture写文件后同步登记持有(无await间隙);挂断成功、挂断失败转清理欠账、TTL到期_prune、dispatch失败、开房请求在途取消五条路径均释放持有;期间apply只记选择不写worker文件,会话响应voice=worker本通话实读;重叠通话较新capture重写文件的残余如实记录于合同§2.5,顺序用例通过不称竞态根除",
"总工独立复跑:pytest tests -q 380/380、python -I scripts/verify_hel899_custom_voice.py 12/12、ruff check src tests scripts 通过(venv311=Python3.11.16复用,零安装零下载);需求检查4/4通过",
"补测包核对(材料,不执行):连续ASR精确计数仍为待补阻塞且包内如实声明;包首行不声称材料齐备;停声量测单一时基、female_soft参考段sha256、8090新增cg-清单与回收、共享TTS快照恢复、失败计入用量等逐项在案;候选上限未超904已收紧值"
],
"deferred": [
"前端独立复跑(同锁npm安装、tsc/vitest/build、浏览器两主题×电脑/手机、录音释放/字幕/取消迟到/演示零外发):web/node_modules缺失,本轮禁止安装,列为待主卡批准项;作者148/148与90/90仅为作者证据",
"真实登记/试听/应用、真实worker读时点、打断停声帧级量测、真机麦克风:待主卡集中请批新窗口后执行,本轮真实cap=0、未部署、未触旧服务与8090/4080",
"挂断失败转欠账与TTL到期释放持有时,远端房间删除落地前的极端未入场worker仍可能读到后续写透值:与重叠通话同类残余、每worker仍整通话单一音色,建议下轮在合同§2.5残余段一并补写明(非阻塞)",
"连续ASR分钟级时长/连接重试的程序计数手段:仍为待补阻塞,窗口启动前须由总管在.12验证LiveKit/worker侧指标可执行性",
"CG-009/R6删除与完整备份恢复、R5真机与整体验收、R1-R7完整功能继续承接;本卡通过≠自定义音色用户验收,更≠完整产品完成;旧HEL-879窗口封账永不重开"
],
"requirement_changes": [],
"verification": [
"git:7cb73429为origin/hel-905-unified-review head,merge-base核088ac62/07dda2b均为先祖、基点b09a907未回退、母线与PR#11未改写",
"SHA256:docs/rework/hel903/preservation/author-manifest.json全部11项与统一head工作树一致(独立脚本核对match 11/11)",
"venv311(Python3.11.16)独立复跑:pytest tests 380/380、verify_hel899_custom_voice.py 12/12、ruff check src tests scripts 通过",
"需求检查4/4:--base-ref origin/main 与 --base-ref b09a907 各带/不带 docs/requirements/handoff-HEL-905.json 全部通过(60项)",
"未验证(如实):前端构建/单测/浏览器未独立复跑(缺node_modules,不安装);真实语音链路全部未动;补测包材料审读基于文档与源码,实际量测留获批窗口"
]
}
+45
View File
@@ -0,0 +1,45 @@
{
"baseline_version": "1.12",
"read_commit": "0b804a49ef53eb3d251347c147d96a3f6aa5fe19",
"task": "HEL-906:补齐连续语音实际用量计数与测试准备(施工;2026-10-28 总管定点复核返工)。只补 HEL-905 复审明确列出的连续通话 ASR 实际用量/连接重试计数缺口:在 CyberGril 专属 worker/适配层复用现有 LiveKit/FunASR 接口,落地每通话与全窗口的实际送入 ASR 音频量与 连接尝试/重连/失败计数;达到有限窗口上限时在发送前/建连前阻止并结束本通;上限为待用户批准的新一次性窗口,默认未布防=真实 cap=0(fail-closed,与 think/budget.py 同一先例)。定点返工只修总管复核出的两个缺口:①账本写入失败不得使预算倒退(持久化读/写失败或损坏时停止本窗出站并保全已知消耗,禁止默认零预算/继续放行);②发送失败/取消后的不确定出站不得漏计(发送前保守预记整帧、失败/取消不退还,确证送达量另列,不得宣称预记皆确证)。旧 HEL-879 封账保持原样。零模型/嵌入/真实 ASR/TTS、零新增安装下载、不启动服务、不改共享 /opt/voice-agent、不改视觉、不合并 PR。",
"requirement_ids": [
"CG-010",
"CG-018",
"CG-019",
"CG-020",
"CG-021",
"AM-001",
"GOV-001",
"GOV-002",
"GOV-003",
"GOV-004",
"GOV-005",
"GOV-006",
"DEL-001"
],
"deliverable": "独立分支 hel-906-asr-usage-counting(基于 origin/hel-905-unified-review@0b804a4;首轮源码 ecec9df+材料 ccfc04e,定点返工源码 cf395d4+本材料提交):①src/cybergril/voice/asr_usage.py(仅标准库)——追加写 JSONL 账本、每通/全窗计数、fail-closed 默认(无 CYBERGRIL_ASR_WINDOW_ID 即拒绝一切连接/音频)、触线在发送前/建连前拒绝(AsrWindowClosed)、账本重放恢复(同窗重启不重置预算、跨窗口互不影响)、只读汇总 CLI;返工新增——保守预记 reserve_audio(发送前计入,失败/取消不退还)+确证送达 note_audio_delivered 双口径(confirmed_seconds 单独累计与落账、重放恢复)、账本写失败/读失败/行损坏/中途消失 fail-closed:停止本窗一切出站、内存已知消耗只增不减(禁止零预算回退)、error 日志与停机原因、guard_stop 落账且同窗重启重放仍停;②stt_funasr.py 真实送数接缝——建连前 check_connect、建连结果落账、发送前 reserve_audio(越限帧/账本故障在此拒绝,零字节出站)、await ws.send 成功返回后 note_audio_delivered、utterance 边界/流结束聚合落账、接收侧死亡标记;usage=None 时行为与本卡之前完全一致;③worker.py entrypoint 按 job 构造 guard(房间名为每通标识)、on_exhausted→ctx.shutdown 结束本通(触线与账本故障同一接线)、启动日志输出窗口摘要(含停机状态);④tests/test_voice_asr_usage.py 24 项确定性离线测试;⑤docs/rework/hel902/r5-followup-package.md §4 更新为返工后如实口径+里程碑表登记返工提交;本 handoff JSON",
"acceptance": [
"计数口径:音频=发送前按整帧保守预记的样本数/采样率(48k 重采样后按 16k 输出帧计,重发按重复计);发送失败/取消不退还(帧可能已出站);确证送达量(await 成功返回)单独累计,只作核对下界,不得宣称预记皆确证送达;墙钟与事后成功流水不参与",
"账本 fail-closed(总管复现回归):注入 Path.open 追加失败后已送音频仍在内存(1.3s 不回退 0.8s)、重放不得拉回旧账本值、后续 0.6s 在发送前被拒、停机回调恰好一次带原因;读失败(read_text 注入 OSError)、行损坏(坏 JSON 行)、账本中途消失(unlink)同样 fail-closed 停出站且保全已知消耗;guard_stop 落账后同窗重启仍停",
"连接口径:每次建连尝试(含失败)逐条落账;正常说完一轮后的下一次建连不是 retry,只有失败/接收侧死亡/流异常之后的建连计 retry;每通 retry 上限建连前拒绝(测试驱动真实接缝验证拒绝始于建连之前、假 provider 未被触碰)",
"到线停机:全窗口音频上限触线时越限帧在发送前拒绝(出站字节恰至上限,一个字节不越线,触线帧不碰 provider)、on_exhausted 恰好一次、worker 接线断言 ctx.shutdown 真被调用结束本通;未布防默认拒绝一切 ASR(真实 cap=0 的代码级落实)",
"历史与恢复:删房成功后 asr-usage.jsonl 逐通历史原样保留(测试用真实 VoiceSessionService 成功删房对照);同窗口重启 guard 重放恢复预算/逐通计数/确证量,不得重置;不同 window id 互不误停",
"确定性离线测试(24 项):静音/取消、建连失败/重连、反复结束、不同采样率、触线前拒绝、落盘恢复、崩溃少计有界(≤flush 如实声明)、假 ws 先记 payload 再 raise/取消后预算仍含该帧而确证量不含、worker entrypoint 注入断言",
"补测包 §4 更新为返工后口径:保守/确证双口径、账本故障 fail-closed、程序保证限定为布防进程内(跨进程 ≤5s 有界过冲、崩溃 ≤5s 少计仍为监看边界,不承诺全窗口跨进程绝不越限)、窗口执行约定 ≤3 通顺序不重叠+参数收紧预留余量;候选上限不放宽(≤2h/3通话/6开房/LLM20/60000tokens/嵌入0/登记3/预听10/通话TTS40段/ASR45分钟/每通重连6次/新增付费0)"
],
"deferred": [
"窗口前 .12 真实栈联验:用 scripts/gen_hel877_sdk_check.py 同法在真实 agent venv 加载本模块,确认真 SDK 帧率/重采样与 ctx.shutdown 真实行为(离线假件无法替代,未验证前不启动含连续通话步骤)",
"新一次性窗口仍待白栖知批准:未批前真实 cap=0,不启动、不预留出站;获批后由窗口负责人显式布防 CYBERGRIL_ASR_WINDOW_* 环境变量",
"跨进程并行通话的有界过冲(≤5s resync/flush 间隔)与崩溃少计(≤5s)属如实声明的监看边界,不冒充零误差硬闸;窗口执行约定 ≤3 通顺序执行不重叠、实操上限与 flush/resync 收紧预留余量(已写入 §4)",
"真机麦克风/文字打断帧级量测、LiveKit/worker 侧房间指标交叉核对:并入获批窗口执行,本卡不自行采麦/摄像头",
"前端浏览器/构建独立复跑(同锁 npm ci)仍为单独待批项;CG-009/R6 删除与完整备份恢复、R5 整体验收、R1–R7 完整功能继续承接;本卡通过≠自定义音色或连续语音用户验收"
],
"requirement_changes": [],
"verification": [
"pytest tests/test_voice_asr_usage.py -q:24/24 通过(venv311 复用,零安装;含总管复现的预算回退回归路径)",
"受影响语音区回归:test_voice_asr_usage+test_voice_session+test_voice_custom+test_voice_rework_hel904+test_voice_review_hel902+test_voice_chat_adapter+test_voice_rework_hel879 共 105/105 通过;按卡约定未重跑全量 380/12(作者证据保留)",
"ruff check src/ tests/ 通过",
"需求检查:python3 scripts/check_requirements.py 对 origin/main 与 0b804a4 各带/不带本 handoff 四组通过",
"未验证(如实):真实 FunASR/LiveKit/CosyVoice 栈、真实麦克风、浏览器/手机画面——零真实调用,全部待获批窗口"
]
}
+2 -1
View File
@@ -15,5 +15,6 @@
"comment-evidence-mature-reuse-20261025.json": "38ca076e26477e89cd161eaadb0fb00b0ee7bb5a26de2bd6ab41de52faaae79e",
"mature-reuse-principle-20261025.md": "a2a75e071d3af5db722727bfaf8083bb60d07dcdd74e90d1e1374c1aafd2064d",
"other-agent-discussion-20261025.txt": "42cd97029d73eace110e49e0566343dfeb25fe4f0633a10b78b126289938aaeb",
"comment-evidence-full-experience-20261026.json": "75dd55b8755bdad217b44c3f516c9372b046aae06dff511527014c7b209eccff"
"comment-evidence-full-experience-20261026.json": "75dd55b8755bdad217b44c3f516c9372b046aae06dff511527014c7b209eccff",
"comment-evidence-custom-voice-20261027.json": "ed72bd78218e1c0b4ca644bf933d6bdb2e3aa8cd2c0333f3410f6b60845bd3cb"
}
@@ -0,0 +1,10 @@
[
{
"id": "01a17de1-2892-78be-a422-eb6a11ffebfd",
"author_type": "member",
"author_id": "e5c6c938-db48-4ff9-954f-4fb8b063a132",
"content": ".12已恢复,另外以上一条让我解压附件包试听,我不明白是什么意思,是让我听预设音色吗?如果是我觉得你的设计不够合理,我想要的是可以自定义音色,我的.12上安装的TTS是支持自定义音色的。至于最长2小时,3次通话的测试,我同意,音色你自己决定就行",
"created_at": "2026-10-27T15:01:10Z",
"parent_id": "01a17d45-f14e-7b62-9850-646e040c7318"
}
]
Binary file not shown.

After

Width:  |  Height:  |  Size: 438 KiB

@@ -0,0 +1,12 @@
{
"ok": true,
"checks": 9,
"fail_count": 0,
"netguard": {
"guard_loaded": true,
"nonloopback_connect_attempts": 0,
"nonloopback_dns_lookups": 0,
"rejected": []
},
"service_epoch_isolation": "CYBERGRIL_MODELS loopback, caps 0, socket guard rejecting non-loopback"
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 441 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 447 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 350 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 447 KiB

+47
View File
@@ -0,0 +1,47 @@
# HEL-879 · D1 参考试听包(零合成,已交付)
> 交付时间 2026-10-27。性质:把服务器既有 11 支音色的 zero-shot 参考录音整理成
> 可离线播放的 HTML 试听页 + 音频包,作为主卡 D1 拍板材料。**零 TTS 合成、零
> 克隆、零共享音色切换、零下载安装**;对 .12 只做 SSH 只读复制,未改服务器任何
> 文件与状态。
>
> **2026-10-27 勘误(主卡授权轮)**:白栖知反馈「让我解压附件包试听」的设计
> 不合理(主卡评论 `01a17de1-2892-78be-a422-eb6a11ffebfd`)——他要的是可
> 自定义音色,而不是从预设音色里挑一个。本包正名为「**非定制功能入口**」:
> 它只是既有 11 支登记音色的参考原声对照材料,**不是**用户音色定制/上传/录制
> 的功能入口;正式的自定义音色(上传、录制、试听、应用)由独立施工分支接续,
> 未完成前不宣称可用。本轮 R5 有限测试由总管选定临时测试音色 `female_soft`
> (已核服务器登记;临时测试值,不是用户最终音色),不要求用户解压本包选 ID。
## 包内容(随本卡评论附件交付,不入仓库大媒体)
`HEL-879-D1-参考试听包.zip`(4.4MB,sha256
`e20764f9f98cbfac5ef966227540a75c5061cf16d151e5d0c9d86b620e3972f9`):
- `index.html` —— 离线试听页(自包含,无外部依赖;解压后直接用浏览器打开),
逐支列出音色 ID、时长/采样率、参考录音原文、播放器、文件哈希前缀;
页首明确注明「参考录音 ≠ 实际合成效果」。
- `manifest.json` —— 机器可读清单:11 支音色的 id / 文件 / prompt_text /
服务器源路径 / sha256 / 字节数 / 秒数 / 采样率 / 声道(本目录留有同内容副本)。
- `voice_samples/*.wav` —— 11 段参考录音原文件。
## 来源与取回方式(可复核)
- 音色登记:`192.168.200.12:/opt/voice-agent/cosyvoice/voices.json`(实读,
11 个 ID:bailu、bajie、default、female_soft、haimianbaobao、jiaojiao、
male_warm、nezha、rain、sunwukong、xiaoyun;每项 `prompt_wav` + `prompt_text`)。
- 音频文件:`prompt_wav` 所指 wav(`voice_samples/<id>.wav` 10 支 +
default 用 `CosyVoice/asset/zero_shot_prompt.wav`),`scp` 只读取回。
- 完整性:每文件本地 sha256 与服务器 `sha256sum` 输出逐一相符(见
manifest.json;default 为 IEEE float32/24kHz/3.5s,bailu 为 44.1kHz 立体声
10s,其余 16k/32k 单声道 2.4–16.8s)。
## 边界(如实)
- 参考录音是各音色**来源说话人的真实录音**(zero-shot 克隆依据),不是
CosyVoice 实际合成产物;实际合成效果与参考录音不同,需在获批测试窗口内
真实合成复核(计入 TTS 预算)。
- 参考录音原文为各来源录音本身的内容,按原样保留,仅作试听对照;其中数支
为影视/角色向来源,是否适合「她」由白栖知试听后判断,工程不代选、不预筛。
- 通用演示当前 `voice_state.json` = rain 是共享演示状态,不是本用户的选择依据。
- 克隆新音色需写共享 `voices.json` 并触发共享 TTS 热加载,**需单独授权**,本包未做。
@@ -0,0 +1,135 @@
{
"package": "HEL-879 D1 参考试听包(零合成)",
"generated": "2026-10-27",
"source_server": "192.168.200.12 (SSH 只读取回,未改服务器任何文件)",
"source_registry": "/opt/voice-agent/cosyvoice/voices.json",
"count": 11,
"notes": [
"参考录音 = 该音色 zero-shot 克隆的来源录音(音色来源说话人的真实录音),不是 CosyVoice 实际合成产物;实际合成效果与参考录音不同,需在获批测试窗口内真实合成复核(计入 TTS 预算)。",
"通用演示当前使用 rain,是共享演示状态,不是本用户的选择依据。",
"本包只读取得,未切换/修改任何共享音色状态;克隆需另行单独授权。"
],
"voices": [
{
"id": "default",
"file": "voice_samples/zero_shot_prompt.wav",
"prompt_text": "希望你以后能够做的比我还好呦。",
"server_prompt_wav": "/opt/voice-agent/cosyvoice/CosyVoice/asset/zero_shot_prompt.wav",
"sha256": "c7b31d6dbe7cc6a716dded00550db5b50940bf209e424e4ad207b12e657c8ff6",
"bytes": 334138,
"seconds": 3.5,
"sample_rate": 24000,
"channels": 1
},
{
"id": "sunwukong",
"file": "voice_samples/sunwukong.wav",
"prompt_text": "俺老孙来也,俺乃500年前大闹天宫的齐天大圣孙悟空,这里山势险峻,只怕藏有妖精妖怪,哪里逃吃?俺老孙吧。",
"server_prompt_wav": "/opt/voice-agent/cosyvoice/voice_samples/sunwukong.wav",
"sha256": "a0cf44a8ab659ebddb8bc4faffe88f717d14d2c66c09bc5ea657212826806e68",
"bytes": 429746,
"seconds": 13.4,
"sample_rate": 16000,
"channels": 1
},
{
"id": "nezha",
"file": "voice_samples/nezha.wav",
"prompt_text": "天雷滚滚,我好怕怕劈的我浑身掉渣渣,突破天劫,我笑,哈哈逆天改命。我吹喇叭滴答滴答,滴滴答。",
"server_prompt_wav": "/opt/voice-agent/cosyvoice/voice_samples/nezha.wav",
"sha256": "9911a73e3f54c235dedb05c18f29356f41ba3b1e1b58811c4f766897f0fe1d10",
"bytes": 305196,
"seconds": 9.5,
"sample_rate": 16000,
"channels": 1
},
{
"id": "bajie",
"file": "voice_samples/bajie.wav",
"prompt_text": "师傅,哎,这取经路比高,老庄种地还苦,俺老猪饿的能吞下石龙素包子,猴哥,你筋斗云块,要不先去画个圆。",
"server_prompt_wav": "/opt/voice-agent/cosyvoice/voice_samples/bajie.wav",
"sha256": "1f9c84ce8c10145ace5f615a428a08e2482f322363f037245878068aa95ed98a",
"bytes": 424320,
"seconds": 13.3,
"sample_rate": 16000,
"channels": 1
},
{
"id": "haimianbaobao",
"file": "voice_samples/haimianbaobao.wav",
"prompt_text": "没错,我就是个笨蛋派大星,我们一起去抓水母吧,我们一起度过快乐的一天。",
"server_prompt_wav": "/opt/voice-agent/cosyvoice/voice_samples/haimianbaobao.wav",
"sha256": "c60912dfccd76390baa921d4c99b0120f528837608964290e96cad1221da9c9d",
"bytes": 228726,
"seconds": 7.1,
"sample_rate": 16000,
"channels": 1
},
{
"id": "female_soft",
"file": "voice_samples/female_soft.wav",
"prompt_text": "这点我说一下,就是因为那个就是摩根豪泽尔这本儿金钱心理学。其实在我们这行业里也算一本很有名名的书了。就我之前也读过。然后你说到这点啊,让我想起来他这本书里还挺。",
"server_prompt_wav": "/opt/voice-agent/cosyvoice/voice_samples/female_soft.wav",
"sha256": "4a65d17099239711601cb17e28b7aa7b6149acd153b92fd0366c23902bd4f687",
"bytes": 341192,
"seconds": 10.7,
"sample_rate": 16000,
"channels": 1
},
{
"id": "male_warm",
"file": "voice_samples/male_warm.wav",
"prompt_text": "um a while we built eight hype looe test track adjacent to space acts just for a student competition。 um to encourage intiative ideas in transport and actually ends up being the the biggest um vacuum chamber in the world after the launch。 haveringcollider。",
"server_prompt_wav": "/opt/voice-agent/cosyvoice/voice_samples/male_warm.wav",
"sha256": "2c4109b2d685e1923ed66433eb08c92047a1f67510629a27edf49af4e5c606dd",
"bytes": 539032,
"seconds": 16.8,
"sample_rate": 16000,
"channels": 1
},
{
"id": "xiaoyun",
"file": "voice_samples/xiaoyun.wav",
"prompt_text": "盯着你看怎么啦,难得看你这么有意思的样子嘛",
"server_prompt_wav": "/opt/voice-agent/web/../cosyvoice/voice_samples/xiaoyun.wav",
"sha256": "6ad3ca7a6f1d9ddd10c5647338200fc9913f7adc71d8495a61b6b8e18eadc87e",
"bytes": 297004,
"seconds": 4.6,
"sample_rate": 32000,
"channels": 1
},
{
"id": "rain",
"file": "voice_samples/rain.wav",
"prompt_text": "你是不是早就想睡我了呀",
"server_prompt_wav": "/opt/voice-agent/web/../cosyvoice/voice_samples/rain.wav",
"sha256": "37db24611b999f2f2328fa19c5c6e26117b5ec5d3d2037068af2fc12c6ad25ba",
"bytes": 152364,
"seconds": 2.4,
"sample_rate": 32000,
"channels": 1
},
{
"id": "jiaojiao",
"file": "voice_samples/jiaojiao.wav",
"prompt_text": "怎么还有没射的啊,是我不够努力吗",
"server_prompt_wav": "/opt/voice-agent/web/../cosyvoice/voice_samples/jiaojiao.wav",
"sha256": "6db2b2de2d59e22bb0badbb1a6325d68885eda409ad63f5941e5ccad6486fc9f",
"bytes": 301484,
"seconds": 4.7,
"sample_rate": 32000,
"channels": 1
},
{
"id": "bailu",
"file": "voice_samples/bailu.wav",
"prompt_text": "烈火军校啊,招摇啊这种类型,我都是那种比较……呃……豪爽,比较开朗的那种类型,阳光大女孩儿,那种感觉",
"server_prompt_wav": "/opt/voice-agent/web/../cosyvoice/voice_samples/bailu.wav",
"sha256": "3fbc0aff09118a9bc10bae87a837e6a3de6a30268c6ee01c727b56c49aecd26d",
"bytes": 1769550,
"seconds": 10.0,
"sample_rate": 44100,
"channels": 2
}
]
}
+124
View File
@@ -0,0 +1,124 @@
# HEL-879 · R5 有限真实语音测试窗口记录(2026-10-27)
> 授权:白栖知主卡评论 `01a17de1-2892-78be-a422-eb6a11ffebfd`(同意最长 2 小时 /
> 3 次通话;音色由工程自定);额度沿《r5-test-package.md》§6 申请上限执行。
> 窗口(UTC):**15:26:54 – 15:58:10**(31 分 16 秒,未用满 2 小时)。
> 候选 `hel-879-assembly` @ `8dc84f8`;模型/运行时/认证与既有实例一致(同一
> `192.168.200.11:8731` halogen 端点),零新增付费支出。
## 1. 部署布局(全部独立,已全部撤除)
| 组件 | 端口/路径 | 说明 |
| --- | --- | --- |
| 统一实例(同进程双入口) | HTTP 8360 + HTTPS 8361 | `deploy/voice/serve_dual_8360_8361.py`,隔离数据 `/opt/cybergril-hel879/data/`;`.venv311`+uv CPython 3.11.16 自本机离线复制(.12 无 uv/node,零下载) |
| WSS TLS 桥 | 8363 → 127.0.0.1:7880 | `deploy/voice/wss_bridge.example.py`,与 8361 同一张自签证书 |
| 专属 worker | 健康口 8082 | `/opt/voice-agent-cybergril/`,复用 agent venv(livekit-agents 1.8.8),`voice_state_cybergril.json` = `female_soft`(临时测试值;未写通用助手 `voice_state.json`/`voices.json`) |
| 共享 TTS(CosyVoice) | 9880 | 窗口内从停机恢复;结束时核实无他人占用后按窗口前状态停回 |
| 测试档案 | — | 隔离实例自建测试伴侣(名字「小测」),非用户真实档案 |
配置差异(均记录在案、可回滚):`models.yaml` 预算写入本轮批准值
(dialogue 40、total 40/500k,其余类别 0);`CYBERGRIL_SEMANTIC=stub`(嵌入 0);
`CYBERGRIL_R2_ENABLED=1`(receipt 签发依赖生活运行时开启;life/background 预算
仍为 0,实测无后台模型调用);`voice.yaml` `session_ttl_seconds: 900`
(真实验证 TTL 到期删房用;仓库默认 7200)。
## 2. 真机发现并修复的代码问题(有限返工,含证据)
1. **Twirp 服务 token 缺少端点授权(401)**:`livekit_wire.mint_server_token`
原按「无 grants 形态」铸造,但已装 SDK 的 `create_dispatch` 以
`VideoGrants(room_admin=True, room=…)` 签名、`delete_room` 以
`VideoGrants(room_create=True)` 签名;真机 LiveKit 拒绝无授权 bearer(401)。
离线期 8 项 SDK 检查只比对了 token 形态,未对真服务器发送,未暴露此问题。
修复:`mint_server_token` 增加 `video` grants 参数,两处调用按 SDK 同款授权。
2. **protobuf 响应不可解析**:CreateDispatch 以 `application/protobuf` 请求时
服务器回二进制,原实现的 `json.loads` 崩溃。修复:改走 Twirp JSON
(`application/json`),零新依赖,dispatch id 正常解析;离线替身同步改为
JSON 断言(`tests/test_voice_session.py`、`tests/test_voice_rework_hel879.py`)。
3. **自签证书 `CA:TRUE` 被 rustls 拒绝**:openssl 默认给自签证书加了
`basicConstraints=CA:TRUE`,livekit 客户端(rustls)报 `CaUsedAsEndEntity`。
重签为 `critical,CA:FALSE` + `SAN=IP:192.168.200.12` 后通过;测试客户端以
`SSL_CERT_FILE` 进程级信任该证书(非系统信任库、非浏览器例外冒充)。
修复后全量 `pytest tests -q` **352/352** 通过、ruff(src/tests/scripts)0 错;
本文件与修复随本轮提交入库。
## 3. 验证结果
真链:预录参考录音(**音频回放,非用户设备麦克风/手机**)→ 真实 WebRTC →
LiveKit 7880(客户端经 8363 WSS 桥)→ FunASR 流式识别 → 统一 `POST /api/chat`
(真 LLM)→ CosyVoice 合成(female_soft)→ WebRTC 回流收音(48k WAV 落盘)。
| 项 | 结果 | 证据 |
| --- | --- | --- |
| 连续 ≥3 轮(同档案、同对话源、音色一致) | ✅ 通话2:haimianbaobao/nezha/rain 三轮全部 `turn_end`;跨通话记忆延续可见(通话2 回复接住通话1 话题) | `call1b.subtitles.jsonl`、`chat_message` 全表 |
| 完整播放 receipt | ✅ 总管收尾独立复核:最终库及一致性快照均为 **8 条** `sharing_attempt`,全部 `ack_at` 落值、state=judged;取消用户轮 msg7/msg23 对应回执均为0。原交付4条是较早阶段数字,不能代表最终库 | 只读SQL与快照恢复核对;`/api/chat/received 200` |
| 开口打断 | ✅(发生真实性)通话1 三次真实 `turn_cancel(interrupted)`,用户开口截断在播回复、旧轮 user 行标 cancelled;**精确 ~1s 停声计时未取得**(量测设计缺陷+后两轮回复过短无以复测,见 §5) | `call1.subtitles.jsonl` seq68/87/92 |
| 软件客户端挂断清理 | ✅ DELETE 200 → 房间真实删除(ListRooms 空)、删除后仅 2 帧在途(约20ms)、软件客户端 track 停发;**不代表用户设备麦克风指示灯或扬声器残音已验** | `call1b.json hangup` |
| 断线(不挂断)+ TTL 到期后台删房 | ✅ 掉线不删 → 会话过期自动转债删除;15:52/15:56/15:57 三笔全部结清,账本空、房间空 | 轮询记录(窗口观察) |
| API 重启清理 | ✅ 重启 epoch 4→5、档案/历史完整、voice 配置在;重启遗留会话按 TTL 由补偿清理全部结清 | 同上 |
| 文字/语音互斥 | ✅(机制)文字轮被后到语音轮取消(msg23=cancelled)——同一把 chat 锁双向互斥成立;**「文字打断语音」方向未单独取证**(触发时机竞态,见 §5) | `call3.json`、消息表 |
| HTTPS/WSS | ✅ 8361/8363 同证书 TLS1.3、SAN=IP 可达;WSS 桥真实承载信令与媒体建立(客户端全程经 8363 接入) | 窗口日志、preflight |
| 嵌入 0 / 语义 stub | ✅ `semantic_backend=stub`,全程零嵌入调用 | `/api/health` |
| 字幕 v2 全生命周期 | ✅ seq 单调(三次通话均验证)、interim→final→turn_end / 打断→turn_cancel 无 final | 各 `subtitles.jsonl` |
## 4. 用量账(逐笔可查,硬闸全程在位)
- **LLM**:14/40 次请求、42,717/500,000 tokens(`budget_total`/`request_usage`/
`token_usage` 逐行;含失败/取消轮,预占语义如代码注释)。结束即封账:
`seal_budget_hel879.py` 已执行,封账标记随库归档,真实用量保留不清零。
- **TTS**:12/200 合成段(`tts.log` POST /tts 计数;本地 GPU、零外部计费)。
- **ASR**:仅窗口内 3 次通话使用(本地 CPU FunASR,零外部计费)。
- **嵌入/新增付费**:0。
- **语音会话**:建房 6 次 = **3 次真实通话**(含全部验证轮次)+ 3 次工具端
崩溃遗留的空会话(1 次手工 DELETE、2 次由 TTL 补偿自动删除;空会话无对话、
零模型消耗,如实计数不隐瞒)。
## 5. 未测 / 留待下轮(如实)
- **精确 1 秒级打断停声计时**:本轮三次真实打断均已发生并留痕,但帧级停声
时间戳未随证据落盘(下一轮客户端应在每帧记录到达时间并按 speech 归属切分)。
`call1.json` 已有发布音频到取消3.26秒、到最后agent帧5.3秒、cancel后204帧的
原始粗计时,不能隐去这些慢值。该计时未区分触发录音的语音起点、旧speech与
后续speech,不能据此量出旧声准确停止时间,**更不能判定达到约1秒**;下轮须
定向测量后再作结论。
- **「文字打断语音」方向**:本轮竞态取证到反向(语音取消文字);机制同一把锁,
下轮把文字触发改为「assistant_interim 出现后」即可定向取证。
- **真实设备浏览器路径**(D3 用户动作):证书「高级→继续」两处(8361/8363)、
真机麦克风(iOS Safari/安卓 Chrome)未测——须用户设备动作,本轮交付地址与
最小步骤,不以 rustls/SSL_CERT_FILE 冒充浏览器例外。
- 回复质量观察:长上下文后出现单字回复与一条英文串扰(与测试档案 boundary
文案提到「测试/临时」相关)——记录为内容质量观察项,非链路故障。
## 6. 收尾与回滚
- 窗口结束:worker/桥/双入口进程全部停止,8360/8361/8363/8082/9880 复核无监听;
TTS 按窗口前状态停回(精确 PID 终止,未用 fuser -k,核过无他人占用)。
- 受保护服务零接触:8300/8310/8320/8350/7880/7881/10095 原样运行,8340 保持
停机,ComfyUI 保持暂停(GPU 回到 97MiB 占用)。
- 数据封存:预算封账 + `release_snapshot_hel896.py` 停服一致性快照
(`/opt/cybergril-hel879/archives/hel879-r5-window-close-*`,rows_match 全过);
证据包 `/opt/cybergril-hel879/hel879-r5-evidence-20261027.tar.gz`
(sha256 `b23af43b…c394`,含三次通话字幕/JSON/音频/worker/TTS 日志/清理账)。
- 回滚:全部为本轮新建独立进程/目录,删除 `/opt/cybergril-hel879`、
`/opt/voice-agent-cybergril`、`/opt/cybergril-voice-certs` 即完全退场;Git 侧
为本分支正常追加提交,PR#11 与既有实例未动。
## 7. 总管独立收尾复核补充
只读核对14次请求/42717 tokens及sealed=1,封账值与真实用量相等;清理账sessions=0、
debts=0,8360/8361/8363/8082/9880无监听。没有重新打开原账或恢复本轮服务。
证据包完整SHA256为`b23af43b39ed77263c7b3f285796e77e8b1278acbf547b4619adad600ed5c394`。
一致性快照已由总管在本地隔离目录按原工具实际恢复,SHA256与三方行数相等,
恢复库同为sealed=1、14/42717用量和8条已确认回执。归档永不因上述“回滚”说明
自动删除;保留测试资料与证据,销毁须另有明确授权。
原MANIFEST的code_commit记录8dc84f8(运行时修复尚未提交),不是最终修复提交。
独立复核部署中的livekit_wire.py SHA256为
`ffc477cd96d48c510905ce9fb887ba5b00edd6941ee59cf1b960f51f18500941`,
与3766531收录源码逐字节一致;本说明补足溯源,不篡改原归档。
本轮只完成R5部分验证,不能称全部R5验收通过。实际6次建房必须连同3个空会话如实
保留,不把“3次实际通话”写成“只建房3次”。窗口已收尾且3次实际通话用完,未用
模型/token/TTS额度不自动授权新窗口;后续将精确打断计时、文字打断语音和用户设备
浏览器路径作为必补项,与自定义音色交回后的统一验证安排集中处理,不擅自重开账。
+96
View File
@@ -0,0 +1,96 @@
{
"rounds": [
{
"play_label": "xiaoyun",
"reply": {
"kind": "turn_cancel",
"speech_id": "speech_b687b8055307",
"reason": "interrupted"
}
},
{
"play_label": "bailu",
"reply": {
"kind": "turn_cancel",
"speech_id": "speech_8f12c4a50957",
"reason": "no-receipt"
}
},
{
"play_label": "jiaojiao",
"reply": {
"kind": "turn_cancel",
"speech_id": "speech_4c73b7d1829c",
"reason": "interrupted"
}
}
],
"interrupt": {
"outcome": "interrupted",
"trigger": "audio",
"publish_ms": 1793115246711,
"cancel_ms": 1793115249971,
"cancel_latency_s": 3.26,
"last_agent_frame_ms": 1793115252008,
"audio_stop_latency_s": 5.3,
"frames_after_cancel": 204
},
"hangup": {
"delete_status": 200,
"delete_body": {
"ended": true,
"room": "cg-voice-qeghdnxehn"
},
"delete_ms": 1793115252011,
"agent_frames_after_delete": 2,
"room_removed": true
},
"call": {
"tag": "call1",
"session": {
"url": "wss://192.168.200.12:8363",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ2aWRlbyI6eyJyb29tSm9pbiI6dHJ1ZSwicm9vbSI6ImNnLXZvaWNlLXFlZ2hkbnhlaG4iLCJjYW5QdWJsaXNoIjp0cnVlLCJjYW5TdWJzY3JpYmUiOnRydWUsImNhblB1Ymxpc2hEYXRhIjp0cnVlfSwic3ViIjoidXNlci1nd2h2ZXpjeiIsImlzcyI6ImRldmtleSIsIm5iZiI6MTc5MzExNTIwMywiZXhwIjoxNzkzMTE1NTAzLCJuYW1lIjoi55So5oi3In0.D91S6SXWBtoVoSC6TEsULuK8WNHQu36O_vZQS1urwbw",
"room": "cg-voice-qeghdnxehn",
"session_id": "Ey8pBEFHbrO8i6Lbc0-47mh7",
"agent_name": "cybergril-voice-agent",
"epoch": 3,
"expires_at": 1793116103636
},
"subtitle_count": 96,
"seq_monotonic": true,
"agent_frames": 4766,
"agent_audio_seconds": 47.7,
"agent_audio_rate": 48000,
"agent_joined_ms": 1793115204287,
"agent_left_ms": null,
"subtitles_file": "/opt/cybergril-hel879/evidence/call1.subtitles.jsonl",
"agent_audio_file": "/opt/cybergril-hel879/evidence/call1.agent.wav"
},
"budget_after": {
"at_ms": 1793115254555,
"token_usage": [
{
"day": "2026-10-27",
"scene": "dialogue_main",
"used": 11416
}
],
"request_usage": [
{
"day": "2026-10-27",
"category": "dialogue",
"requests": 4
}
],
"budget_total": [
{
"key": "total_requests",
"value": 4
},
{
"key": "total_tokens",
"value": 11416
}
]
}
}
@@ -0,0 +1,96 @@
{"v": 2, "seq": 1, "kind": "interim", "role": "user", "text": "盯", "_at_ms": 1793115205011}
{"v": 2, "seq": 2, "kind": "interim", "role": "user", "text": "着你看怎", "_at_ms": 1793115205596}
{"v": 2, "seq": 3, "kind": "interim", "role": "user", "text": "么啦", "_at_ms": 1793115206196}
{"v": 2, "seq": 4, "kind": "user_final", "role": "user", "text": "盯着你看怎么啦", "_at_ms": 1793115207385}
{"v": 2, "seq": 5, "kind": "turn_start", "role": "assistant", "text": "", "speech_id": "speech_b687b8055307", "_at_ms": 1793115207410}
{"v": 2, "seq": 6, "kind": "interim", "role": "user", "text": "难得", "_at_ms": 1793115207554}
{"v": 2, "seq": 7, "kind": "interim", "role": "user", "text": "看你这么", "_at_ms": 1793115208153}
{"v": 2, "seq": 8, "kind": "interim", "role": "user", "text": "有意思的", "_at_ms": 1793115208743}
{"v": 2, "seq": 9, "kind": "interim", "role": "user", "text": "样子", "_at_ms": 1793115209349}
{"v": 2, "seq": 10, "kind": "assistant_interim", "role": "assistant", "text": "没", "speech_id": "speech_b687b8055307", "_at_ms": 1793115209395}
{"v": 2, "seq": 11, "kind": "assistant_interim", "role": "assistant", "text": "没怎么", "speech_id": "speech_b687b8055307", "_at_ms": 1793115209436}
{"v": 2, "seq": 12, "kind": "assistant_interim", "role": "assistant", "text": "没怎么啦", "speech_id": "speech_b687b8055307", "_at_ms": 1793115209439}
{"v": 2, "seq": 13, "kind": "assistant_interim", "role": "assistant", "text": "没怎么啦,", "speech_id": "speech_b687b8055307", "_at_ms": 1793115209478}
{"v": 2, "seq": 14, "kind": "assistant_interim", "role": "assistant", "text": "没怎么啦,工程师", "speech_id": "speech_b687b8055307", "_at_ms": 1793115209479}
{"v": 2, "seq": 15, "kind": "assistant_interim", "role": "assistant", "text": "没怎么啦,工程师。", "speech_id": "speech_b687b8055307", "_at_ms": 1793115209522}
{"v": 2, "seq": 16, "kind": "assistant_interim", "role": "assistant", "text": "没怎么啦,工程师。你", "speech_id": "speech_b687b8055307", "_at_ms": 1793115209563}
{"v": 2, "seq": 17, "kind": "assistant_interim", "role": "assistant", "text": "没怎么啦,工程师。你盯着", "speech_id": "speech_b687b8055307", "_at_ms": 1793115209565}
{"v": 2, "seq": 18, "kind": "assistant_interim", "role": "assistant", "text": "没怎么啦,工程师。你盯着看", "speech_id": "speech_b687b8055307", "_at_ms": 1793115209605}
{"v": 2, "seq": 19, "kind": "assistant_interim", "role": "assistant", "text": "没怎么啦,工程师。你盯着看,", "speech_id": "speech_b687b8055307", "_at_ms": 1793115209611}
{"v": 2, "seq": 20, "kind": "assistant_interim", "role": "assistant", "text": "没怎么啦,工程师。你盯着看,我就", "speech_id": "speech_b687b8055307", "_at_ms": 1793115209650}
{"v": 2, "seq": 21, "kind": "assistant_interim", "role": "assistant", "text": "没怎么啦,工程师。你盯着看,我就当", "speech_id": "speech_b687b8055307", "_at_ms": 1793115209688}
{"v": 2, "seq": 22, "kind": "assistant_interim", "role": "assistant", "text": "没怎么啦,工程师。你盯着看,我就当你在", "speech_id": "speech_b687b8055307", "_at_ms": 1793115209731}
{"v": 2, "seq": 23, "kind": "assistant_interim", "role": "assistant", "text": "没怎么啦,工程师。你盯着看,我就当你在检查", "speech_id": "speech_b687b8055307", "_at_ms": 1793115209773}
{"v": 2, "seq": 24, "kind": "assistant_interim", "role": "assistant", "text": "没怎么啦,工程师。你盯着看,我就当你在检查我", "speech_id": "speech_b687b8055307", "_at_ms": 1793115209816}
{"v": 2, "seq": 25, "kind": "assistant_interim", "role": "assistant", "text": "没怎么啦,工程师。你盯着看,我就当你在检查我有没有", "speech_id": "speech_b687b8055307", "_at_ms": 1793115209816}
{"v": 2, "seq": 26, "kind": "assistant_interim", "role": "assistant", "text": "没怎么啦,工程师。你盯着看,我就当你在检查我有没有乱", "speech_id": "speech_b687b8055307", "_at_ms": 1793115209857}
{"v": 2, "seq": 27, "kind": "assistant_interim", "role": "assistant", "text": "没怎么啦,工程师。你盯着看,我就当你在检查我有没有乱动", "speech_id": "speech_b687b8055307", "_at_ms": 1793115209900}
{"v": 2, "seq": 28, "kind": "assistant_interim", "role": "assistant", "text": "没怎么啦,工程师。你盯着看,我就当你在检查我有没有乱动。", "speech_id": "speech_b687b8055307", "_at_ms": 1793115209947}
{"v": 2, "seq": 29, "kind": "user_final", "role": "user", "text": "难得看你这么有意思的样子吗", "_at_ms": 1793115210503}
{"v": 2, "seq": 30, "kind": "turn_cancel", "role": "assistant", "text": "", "speech_id": "speech_b687b8055307", "reason": "interrupted", "_at_ms": 1793115210511}
{"v": 2, "seq": 31, "kind": "interim", "role": "user", "text": "烈会军校", "_at_ms": 1793115212136}
{"v": 2, "seq": 32, "kind": "interim", "role": "user", "text": "校啊招", "_at_ms": 1793115212733}
{"v": 2, "seq": 33, "kind": "interim", "role": "user", "text": "啊这种类", "_at_ms": 1793115213338}
{"v": 2, "seq": 34, "kind": "interim", "role": "user", "text": "型我都是", "_at_ms": 1793115213936}
{"v": 2, "seq": 35, "kind": "interim", "role": "user", "text": "那种比较", "_at_ms": 1793115214541}
{"v": 2, "seq": 36, "kind": "interim", "role": "user", "text": "呃豪", "_at_ms": 1793115215744}
{"v": 2, "seq": 37, "kind": "interim", "role": "user", "text": "爽比", "_at_ms": 1793115216340}
{"v": 2, "seq": 38, "kind": "interim", "role": "user", "text": "较开", "_at_ms": 1793115216943}
{"v": 2, "seq": 39, "kind": "interim", "role": "user", "text": "朗的那种", "_at_ms": 1793115217541}
{"v": 2, "seq": 40, "kind": "interim", "role": "user", "text": "类型", "_at_ms": 1793115218142}
{"v": 2, "seq": 41, "kind": "interim", "role": "user", "text": "阳光大", "_at_ms": 1793115218753}
{"v": 2, "seq": 42, "kind": "interim", "role": "user", "text": "女孩", "_at_ms": 1793115219344}
{"v": 2, "seq": 43, "kind": "interim", "role": "user", "text": "那种感", "_at_ms": 1793115220543}
{"v": 2, "seq": 44, "kind": "interim", "role": "user", "text": "觉", "_at_ms": 1793115221139}
{"v": 2, "seq": 45, "kind": "interim", "role": "user", "text": "都给", "_at_ms": 1793115221743}
{"v": 2, "seq": 46, "kind": "user_final", "role": "user", "text": "烈火军校啊,朝阳啊这种类型我都是那种比较呃豪爽,比较开朗的那种类型,阳光大女孩那种感觉就", "_at_ms": 1793115223193}
{"v": 2, "seq": 47, "kind": "turn_start", "role": "assistant", "text": "", "speech_id": "speech_8f12c4a50957", "_at_ms": 1793115226967}
{"v": 2, "seq": 48, "kind": "assistant_interim", "role": "assistant", "text": "原来", "speech_id": "speech_8f12c4a50957", "_at_ms": 1793115228249}
{"v": 2, "seq": 49, "kind": "assistant_interim", "role": "assistant", "text": "原来你是", "speech_id": "speech_8f12c4a50957", "_at_ms": 1793115228291}
{"v": 2, "seq": 50, "kind": "assistant_interim", "role": "assistant", "text": "原来你是那种", "speech_id": "speech_8f12c4a50957", "_at_ms": 1793115228333}
{"v": 2, "seq": 51, "kind": "assistant_interim", "role": "assistant", "text": "原来你是那种阳光", "speech_id": "speech_8f12c4a50957", "_at_ms": 1793115228335}
{"v": 2, "seq": 52, "kind": "assistant_interim", "role": "assistant", "text": "原来你是那种阳光大", "speech_id": "speech_8f12c4a50957", "_at_ms": 1793115228376}
{"v": 2, "seq": 53, "kind": "assistant_interim", "role": "assistant", "text": "原来你是那种阳光大女孩", "speech_id": "speech_8f12c4a50957", "_at_ms": 1793115228377}
{"v": 2, "seq": 54, "kind": "assistant_interim", "role": "assistant", "text": "原来你是那种阳光大女孩的类型", "speech_id": "speech_8f12c4a50957", "_at_ms": 1793115228420}
{"v": 2, "seq": 55, "kind": "assistant_interim", "role": "assistant", "text": "原来你是那种阳光大女孩的类型啊", "speech_id": "speech_8f12c4a50957", "_at_ms": 1793115228464}
{"v": 2, "seq": 56, "kind": "assistant_interim", "role": "assistant", "text": "原来你是那种阳光大女孩的类型啊,", "speech_id": "speech_8f12c4a50957", "_at_ms": 1793115228506}
{"v": 2, "seq": 57, "kind": "assistant_interim", "role": "assistant", "text": "原来你是那种阳光大女孩的类型啊,难怪", "speech_id": "speech_8f12c4a50957", "_at_ms": 1793115228508}
{"v": 2, "seq": 58, "kind": "assistant_interim", "role": "assistant", "text": "原来你是那种阳光大女孩的类型啊,难怪感觉", "speech_id": "speech_8f12c4a50957", "_at_ms": 1793115228548}
{"v": 2, "seq": 59, "kind": "assistant_interim", "role": "assistant", "text": "原来你是那种阳光大女孩的类型啊,难怪感觉你", "speech_id": "speech_8f12c4a50957", "_at_ms": 1793115228590}
{"v": 2, "seq": 60, "kind": "assistant_interim", "role": "assistant", "text": "原来你是那种阳光大女孩的类型啊,难怪感觉你说话", "speech_id": "speech_8f12c4a50957", "_at_ms": 1793115228634}
{"v": 2, "seq": 61, "kind": "assistant_interim", "role": "assistant", "text": "原来你是那种阳光大女孩的类型啊,难怪感觉你说话都", "speech_id": "speech_8f12c4a50957", "_at_ms": 1793115228636}
{"v": 2, "seq": 62, "kind": "assistant_interim", "role": "assistant", "text": "原来你是那种阳光大女孩的类型啊,难怪感觉你说话都透", "speech_id": "speech_8f12c4a50957", "_at_ms": 1793115228674}
{"v": 2, "seq": 63, "kind": "assistant_interim", "role": "assistant", "text": "原来你是那种阳光大女孩的类型啊,难怪感觉你说话都透着一股", "speech_id": "speech_8f12c4a50957", "_at_ms": 1793115228716}
{"v": 2, "seq": 64, "kind": "assistant_interim", "role": "assistant", "text": "原来你是那种阳光大女孩的类型啊,难怪感觉你说话都透着一股豪", "speech_id": "speech_8f12c4a50957", "_at_ms": 1793115228717}
{"v": 2, "seq": 65, "kind": "assistant_interim", "role": "assistant", "text": "原来你是那种阳光大女孩的类型啊,难怪感觉你说话都透着一股豪爽", "speech_id": "speech_8f12c4a50957", "_at_ms": 1793115228758}
{"v": 2, "seq": 66, "kind": "assistant_interim", "role": "assistant", "text": "原来你是那种阳光大女孩的类型啊,难怪感觉你说话都透着一股豪爽劲儿", "speech_id": "speech_8f12c4a50957", "_at_ms": 1793115228760}
{"v": 2, "seq": 67, "kind": "assistant_interim", "role": "assistant", "text": "原来你是那种阳光大女孩的类型啊,难怪感觉你说话都透着一股豪爽劲儿。", "speech_id": "speech_8f12c4a50957", "_at_ms": 1793115228801}
{"v": 2, "seq": 68, "kind": "turn_cancel", "role": "assistant", "text": "", "speech_id": "speech_8f12c4a50957", "reason": "no-receipt", "_at_ms": 1793115239147}
{"v": 2, "seq": 69, "kind": "interim", "role": "user", "text": "怎么还", "_at_ms": 1793115240503}
{"v": 2, "seq": 70, "kind": "interim", "role": "user", "text": "有没事", "_at_ms": 1793115241090}
{"v": 2, "seq": 71, "kind": "interim", "role": "user", "text": "的", "_at_ms": 1793115241700}
{"v": 2, "seq": 72, "kind": "user_final", "role": "user", "text": "怎么还有没事的", "_at_ms": 1793115242576}
{"v": 2, "seq": 73, "kind": "turn_start", "role": "assistant", "text": "", "speech_id": "speech_4c73b7d1829c", "_at_ms": 1793115242585}
{"v": 2, "seq": 74, "kind": "interim", "role": "user", "text": "是我不够", "_at_ms": 1793115243790}
{"v": 2, "seq": 75, "kind": "assistant_interim", "role": "assistant", "text": "哈哈", "speech_id": "speech_4c73b7d1829c", "_at_ms": 1793115243833}
{"v": 2, "seq": 76, "kind": "assistant_interim", "role": "assistant", "text": "哈哈,", "speech_id": "speech_4c73b7d1829c", "_at_ms": 1793115243874}
{"v": 2, "seq": 77, "kind": "assistant_interim", "role": "assistant", "text": "哈哈,因为", "speech_id": "speech_4c73b7d1829c", "_at_ms": 1793115243875}
{"v": 2, "seq": 78, "kind": "assistant_interim", "role": "assistant", "text": "哈哈,因为我在", "speech_id": "speech_4c73b7d1829c", "_at_ms": 1793115243916}
{"v": 2, "seq": 79, "kind": "assistant_interim", "role": "assistant", "text": "哈哈,因为我在认真", "speech_id": "speech_4c73b7d1829c", "_at_ms": 1793115243917}
{"v": 2, "seq": 80, "kind": "assistant_interim", "role": "assistant", "text": "哈哈,因为我在认真听", "speech_id": "speech_4c73b7d1829c", "_at_ms": 1793115243958}
{"v": 2, "seq": 81, "kind": "assistant_interim", "role": "assistant", "text": "哈哈,因为我在认真听你", "speech_id": "speech_4c73b7d1829c", "_at_ms": 1793115244002}
{"v": 2, "seq": 82, "kind": "assistant_interim", "role": "assistant", "text": "哈哈,因为我在认真听你说话", "speech_id": "speech_4c73b7d1829c", "_at_ms": 1793115244044}
{"v": 2, "seq": 83, "kind": "assistant_interim", "role": "assistant", "text": "哈哈,因为我在认真听你说话呀", "speech_id": "speech_4c73b7d1829c", "_at_ms": 1793115244045}
{"v": 2, "seq": 84, "kind": "assistant_interim", "role": "assistant", "text": "哈哈,因为我在认真听你说话呀。", "speech_id": "speech_4c73b7d1829c", "_at_ms": 1793115244086}
{"v": 2, "seq": 85, "kind": "interim", "role": "user", "text": "努力", "_at_ms": 1793115244386}
{"v": 2, "seq": 86, "kind": "user_final", "role": "user", "text": "是我不够努力吗", "_at_ms": 1793115245511}
{"v": 2, "seq": 87, "kind": "turn_cancel", "role": "assistant", "text": "", "speech_id": "speech_4c73b7d1829c", "reason": "interrupted", "_at_ms": 1793115245516}
{"v": 2, "seq": 88, "kind": "interim", "role": "user", "text": "盯着你看", "_at_ms": 1793115248139}
{"v": 2, "seq": 89, "kind": "interim", "role": "user", "text": "怎么", "_at_ms": 1793115248742}
{"v": 2, "seq": 90, "kind": "turn_start", "role": "assistant", "text": "", "speech_id": "speech_7a9c0f804ca0", "_at_ms": 1793115249065}
{"v": 2, "seq": 91, "kind": "user_final", "role": "user", "text": "盯着你看怎么啦", "_at_ms": 1793115249963}
{"v": 2, "seq": 92, "kind": "turn_cancel", "role": "assistant", "text": "", "speech_id": "speech_7a9c0f804ca0", "reason": "interrupted", "_at_ms": 1793115249971}
{"v": 2, "seq": 93, "kind": "interim", "role": "user", "text": "难得", "_at_ms": 1793115250198}
{"v": 2, "seq": 94, "kind": "interim", "role": "user", "text": "看你这么", "_at_ms": 1793115250808}
{"v": 2, "seq": 95, "kind": "interim", "role": "user", "text": "有意思的", "_at_ms": 1793115251389}
{"v": 2, "seq": 96, "kind": "interim", "role": "user", "text": "样子", "_at_ms": 1793115251989}
+97
View File
@@ -0,0 +1,97 @@
{
"rounds": [
{
"play_label": "haimianbaobao",
"reply": {
"kind": "turn_end",
"speech_id": "speech_e69e351f1c44",
"reason": null
}
},
{
"play_label": "nezha",
"reply": {
"kind": "turn_end",
"speech_id": "speech_ff61928cdce0",
"reason": null
}
},
{
"play_label": "rain",
"reply": {
"kind": "turn_end",
"speech_id": "speech_2f9dc8b9848d",
"reason": null
}
}
],
"interrupt": {
"outcome": "no-cancel",
"trigger": "audio",
"publish_ms": 1793115490292,
"cancel_ms": null,
"cancel_latency_s": null,
"old_audio_stop_ms": 1793115512315,
"old_audio_stop_latency_s": 22.02,
"old_frames_during_interrupt": 2203,
"frames_after_cancel_plus1s5": 0
},
"hangup": {
"delete_status": 200,
"delete_body": {
"ended": true,
"room": "cg-voice-5c4y64wxkl"
},
"delete_ms": 1793115512327,
"agent_frames_after_delete": 2,
"room_removed": true
},
"call": {
"tag": "call1",
"session": {
"url": "wss://192.168.200.12:8363",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ2aWRlbyI6eyJyb29tSm9pbiI6dHJ1ZSwicm9vbSI6ImNnLXZvaWNlLTVjNHk2NHd4a2wiLCJjYW5QdWJsaXNoIjp0cnVlLCJjYW5TdWJzY3JpYmUiOnRydWUsImNhblB1Ymxpc2hEYXRhIjp0cnVlfSwic3ViIjoidXNlci12ZHl3c2h2ZSIsImlzcyI6ImRldmtleSIsIm5iZiI6MTc5MzExNTQzMSwiZXhwIjoxNzkzMTE1NzMxLCJuYW1lIjoi55So5oi3In0.PjOElCid-0VLPw8w0iQml4ca9QkLqoCwu9EJf2pz4DE",
"room": "cg-voice-5c4y64wxkl",
"session_id": "jCiBIdbaj2_2_AjxrwChbQ4A",
"agent_name": "cybergril-voice-agent",
"epoch": 4,
"expires_at": 1793116331245
},
"subtitle_count": 98,
"seq_monotonic": true,
"agent_frames": 8042,
"agent_audio_seconds": 80.4,
"agent_audio_rate": 48000,
"agent_joined_ms": 1793115431859,
"agent_left_ms": null,
"subtitles_file": "/opt/cybergril-hel879/evidence/call1b.subtitles.jsonl",
"agent_audio_file": "/opt/cybergril-hel879/evidence/call1b.agent.wav"
},
"budget_after": {
"at_ms": 1793115514868,
"token_usage": [
{
"day": "2026-10-27",
"scene": "dialogue_main",
"used": 23504
}
],
"request_usage": [
{
"day": "2026-10-27",
"category": "dialogue",
"requests": 8
}
],
"budget_total": [
{
"key": "total_requests",
"value": 8
},
{
"key": "total_tokens",
"value": 23504
}
]
}
}
@@ -0,0 +1,98 @@
{"v": 2, "seq": 1, "kind": "interim", "role": "user", "text": "没", "_at_ms": 1793115432559}
{"v": 2, "seq": 2, "kind": "interim", "role": "user", "text": "错", "_at_ms": 1793115433170}
{"v": 2, "seq": 3, "kind": "interim", "role": "user", "text": "我就是个", "_at_ms": 1793115433760}
{"v": 2, "seq": 4, "kind": "interim", "role": "user", "text": "笨蛋", "_at_ms": 1793115434360}
{"v": 2, "seq": 5, "kind": "interim", "role": "user", "text": "派大星", "_at_ms": 1793115434960}
{"v": 2, "seq": 6, "kind": "interim", "role": "user", "text": "我们", "_at_ms": 1793115435559}
{"v": 2, "seq": 7, "kind": "interim", "role": "user", "text": "一起去", "_at_ms": 1793115436165}
{"v": 2, "seq": 8, "kind": "interim", "role": "user", "text": "抓水母", "_at_ms": 1793115436768}
{"v": 2, "seq": 9, "kind": "interim", "role": "user", "text": "吧我", "_at_ms": 1793115437365}
{"v": 2, "seq": 10, "kind": "interim", "role": "user", "text": "们一起度", "_at_ms": 1793115437963}
{"v": 2, "seq": 11, "kind": "interim", "role": "user", "text": "过快乐的", "_at_ms": 1793115438565}
{"v": 2, "seq": 12, "kind": "interim", "role": "user", "text": "一天", "_at_ms": 1793115439163}
{"v": 2, "seq": 13, "kind": "user_final", "role": "user", "text": "没错,我就是个笨蛋派大星,我们一起去抓水母吧,我们一起度过快乐的一天", "_at_ms": 1793115440793}
{"v": 2, "seq": 14, "kind": "turn_start", "role": "assistant", "text": "", "speech_id": "speech_e69e351f1c44", "_at_ms": 1793115440831}
{"v": 2, "seq": 15, "kind": "assistant_interim", "role": "assistant", "text": "好", "speech_id": "speech_e69e351f1c44", "_at_ms": 1793115442938}
{"v": 2, "seq": 16, "kind": "assistant_interim", "role": "assistant", "text": "好呀", "speech_id": "speech_e69e351f1c44", "_at_ms": 1793115442940}
{"v": 2, "seq": 17, "kind": "assistant_interim", "role": "assistant", "text": "好呀,", "speech_id": "speech_e69e351f1c44", "_at_ms": 1793115442981}
{"v": 2, "seq": 18, "kind": "assistant_interim", "role": "assistant", "text": "好呀,那我们", "speech_id": "speech_e69e351f1c44", "_at_ms": 1793115442984}
{"v": 2, "seq": 19, "kind": "assistant_interim", "role": "assistant", "text": "好呀,那我们这就", "speech_id": "speech_e69e351f1c44", "_at_ms": 1793115443024}
{"v": 2, "seq": 20, "kind": "assistant_interim", "role": "assistant", "text": "好呀,那我们这就出发", "speech_id": "speech_e69e351f1c44", "_at_ms": 1793115443067}
{"v": 2, "seq": 21, "kind": "assistant_interim", "role": "assistant", "text": "好呀,那我们这就出发去", "speech_id": "speech_e69e351f1c44", "_at_ms": 1793115443068}
{"v": 2, "seq": 22, "kind": "assistant_interim", "role": "assistant", "text": "好呀,那我们这就出发去抓", "speech_id": "speech_e69e351f1c44", "_at_ms": 1793115443109}
{"v": 2, "seq": 23, "kind": "assistant_interim", "role": "assistant", "text": "好呀,那我们这就出发去抓水", "speech_id": "speech_e69e351f1c44", "_at_ms": 1793115443110}
{"v": 2, "seq": 24, "kind": "assistant_interim", "role": "assistant", "text": "好呀,那我们这就出发去抓水母", "speech_id": "speech_e69e351f1c44", "_at_ms": 1793115443152}
{"v": 2, "seq": 25, "kind": "assistant_interim", "role": "assistant", "text": "好呀,那我们这就出发去抓水母!", "speech_id": "speech_e69e351f1c44", "_at_ms": 1793115443154}
{"v": 2, "seq": 26, "kind": "assistant_interim", "role": "assistant", "text": "好呀,那我们这就出发去抓水母!不过", "speech_id": "speech_e69e351f1c44", "_at_ms": 1793115443195}
{"v": 2, "seq": 27, "kind": "assistant_interim", "role": "assistant", "text": "好呀,那我们这就出发去抓水母!不过我可", "speech_id": "speech_e69e351f1c44", "_at_ms": 1793115443237}
{"v": 2, "seq": 28, "kind": "assistant_interim", "role": "assistant", "text": "好呀,那我们这就出发去抓水母!不过我可不想", "speech_id": "speech_e69e351f1c44", "_at_ms": 1793115443238}
{"v": 2, "seq": 29, "kind": "assistant_interim", "role": "assistant", "text": "好呀,那我们这就出发去抓水母!不过我可不想住", "speech_id": "speech_e69e351f1c44", "_at_ms": 1793115443279}
{"v": 2, "seq": 30, "kind": "assistant_interim", "role": "assistant", "text": "好呀,那我们这就出发去抓水母!不过我可不想住进", "speech_id": "speech_e69e351f1c44", "_at_ms": 1793115443321}
{"v": 2, "seq": 31, "kind": "assistant_interim", "role": "assistant", "text": "好呀,那我们这就出发去抓水母!不过我可不想住进那个", "speech_id": "speech_e69e351f1c44", "_at_ms": 1793115443322}
{"v": 2, "seq": 32, "kind": "assistant_interim", "role": "assistant", "text": "好呀,那我们这就出发去抓水母!不过我可不想住进那个菠萝", "speech_id": "speech_e69e351f1c44", "_at_ms": 1793115443363}
{"v": 2, "seq": 33, "kind": "assistant_interim", "role": "assistant", "text": "好呀,那我们这就出发去抓水母!不过我可不想住进那个菠萝屋", "speech_id": "speech_e69e351f1c44", "_at_ms": 1793115443406}
{"v": 2, "seq": 34, "kind": "assistant_interim", "role": "assistant", "text": "好呀,那我们这就出发去抓水母!不过我可不想住进那个菠萝屋,", "speech_id": "speech_e69e351f1c44", "_at_ms": 1793115443449}
{"v": 2, "seq": 35, "kind": "assistant_interim", "role": "assistant", "text": "好呀,那我们这就出发去抓水母!不过我可不想住进那个菠萝屋,我只", "speech_id": "speech_e69e351f1c44", "_at_ms": 1793115443450}
{"v": 2, "seq": 36, "kind": "assistant_interim", "role": "assistant", "text": "好呀,那我们这就出发去抓水母!不过我可不想住进那个菠萝屋,我只喜欢", "speech_id": "speech_e69e351f1c44", "_at_ms": 1793115443491}
{"v": 2, "seq": 37, "kind": "assistant_interim", "role": "assistant", "text": "好呀,那我们这就出发去抓水母!不过我可不想住进那个菠萝屋,我只喜欢在海", "speech_id": "speech_e69e351f1c44", "_at_ms": 1793115443534}
{"v": 2, "seq": 38, "kind": "assistant_interim", "role": "assistant", "text": "好呀,那我们这就出发去抓水母!不过我可不想住进那个菠萝屋,我只喜欢在海边", "speech_id": "speech_e69e351f1c44", "_at_ms": 1793115443577}
{"v": 2, "seq": 39, "kind": "assistant_interim", "role": "assistant", "text": "好呀,那我们这就出发去抓水母!不过我可不想住进那个菠萝屋,我只喜欢在海边晒太阳", "speech_id": "speech_e69e351f1c44", "_at_ms": 1793115443578}
{"v": 2, "seq": 40, "kind": "assistant_interim", "role": "assistant", "text": "好呀,那我们这就出发去抓水母!不过我可不想住进那个菠萝屋,我只喜欢在海边晒太阳。", "speech_id": "speech_e69e351f1c44", "_at_ms": 1793115443620}
{"v": 2, "seq": 41, "kind": "assistant_final", "role": "assistant", "text": "好呀,那我们这就出发去抓水母!不过我可不想住进那个菠萝屋,我只喜欢在海边晒太阳。", "speech_id": "speech_e69e351f1c44", "turn_id": "b72de17e-bf42-470e-b396-c219bd7593f9", "_at_ms": 1793115457080}
{"v": 2, "seq": 42, "kind": "turn_end", "role": "assistant", "text": "", "speech_id": "speech_e69e351f1c44", "turn_id": "b72de17e-bf42-470e-b396-c219bd7593f9", "_at_ms": 1793115457081}
{"v": 2, "seq": 43, "kind": "interim", "role": "user", "text": "天雷滚", "_at_ms": 1793115458260}
{"v": 2, "seq": 44, "kind": "interim", "role": "user", "text": "滚我好", "_at_ms": 1793115458865}
{"v": 2, "seq": 45, "kind": "interim", "role": "user", "text": "怕怕", "_at_ms": 1793115459463}
{"v": 2, "seq": 46, "kind": "interim", "role": "user", "text": "劈", "_at_ms": 1793115460064}
{"v": 2, "seq": 47, "kind": "interim", "role": "user", "text": "的我浑", "_at_ms": 1793115460657}
{"v": 2, "seq": 48, "kind": "interim", "role": "user", "text": "身掉渣", "_at_ms": 1793115461256}
{"v": 2, "seq": 49, "kind": "interim", "role": "user", "text": "渣突", "_at_ms": 1793115461862}
{"v": 2, "seq": 50, "kind": "interim", "role": "user", "text": "破天", "_at_ms": 1793115462458}
{"v": 2, "seq": 51, "kind": "interim", "role": "user", "text": "劫我笑", "_at_ms": 1793115463067}
{"v": 2, "seq": 52, "kind": "interim", "role": "user", "text": "哈哈", "_at_ms": 1793115463659}
{"v": 2, "seq": 53, "kind": "interim", "role": "user", "text": "逆", "_at_ms": 1793115464263}
{"v": 2, "seq": 54, "kind": "interim", "role": "user", "text": "天改命", "_at_ms": 1793115464867}
{"v": 2, "seq": 55, "kind": "interim", "role": "user", "text": "我吹喇", "_at_ms": 1793115465459}
{"v": 2, "seq": 56, "kind": "interim", "role": "user", "text": "叭滴", "_at_ms": 1793115466062}
{"v": 2, "seq": 57, "kind": "interim", "role": "user", "text": "答滴答", "_at_ms": 1793115466663}
{"v": 2, "seq": 58, "kind": "interim", "role": "user", "text": "滴滴", "_at_ms": 1793115467270}
{"v": 2, "seq": 59, "kind": "user_final", "role": "user", "text": "天雷滚滚,我好怕怕劈的我浑身掉渣渣,突破天劫,我笑哈哈逆天改命,我吹喇叭滴答滴答,滴滴答", "_at_ms": 1793115468935}
{"v": 2, "seq": 60, "kind": "turn_start", "role": "assistant", "text": "", "speech_id": "speech_ff61928cdce0", "_at_ms": 1793115468946}
{"v": 2, "seq": 61, "kind": "assistant_interim", "role": "assistant", "text": "哈哈", "speech_id": "speech_ff61928cdce0", "_at_ms": 1793115470187}
{"v": 2, "seq": 62, "kind": "assistant_interim", "role": "assistant", "text": "哈哈,", "speech_id": "speech_ff61928cdce0", "_at_ms": 1793115470230}
{"v": 2, "seq": 63, "kind": "assistant_interim", "role": "assistant", "text": "哈哈,你", "speech_id": "speech_ff61928cdce0", "_at_ms": 1793115470231}
{"v": 2, "seq": 64, "kind": "assistant_interim", "role": "assistant", "text": "哈哈,你唱", "speech_id": "speech_ff61928cdce0", "_at_ms": 1793115470273}
{"v": 2, "seq": 65, "kind": "assistant_interim", "role": "assistant", "text": "哈哈,你唱得", "speech_id": "speech_ff61928cdce0", "_at_ms": 1793115470315}
{"v": 2, "seq": 66, "kind": "assistant_interim", "role": "assistant", "text": "哈哈,你唱得还挺", "speech_id": "speech_ff61928cdce0", "_at_ms": 1793115470316}
{"v": 2, "seq": 67, "kind": "assistant_interim", "role": "assistant", "text": "哈哈,你唱得还挺有", "speech_id": "speech_ff61928cdce0", "_at_ms": 1793115470357}
{"v": 2, "seq": 68, "kind": "assistant_interim", "role": "assistant", "text": "哈哈,你唱得还挺有节奏", "speech_id": "speech_ff61928cdce0", "_at_ms": 1793115470359}
{"v": 2, "seq": 69, "kind": "assistant_interim", "role": "assistant", "text": "哈哈,你唱得还挺有节奏感的", "speech_id": "speech_ff61928cdce0", "_at_ms": 1793115470400}
{"v": 2, "seq": 70, "kind": "assistant_interim", "role": "assistant", "text": "哈哈,你唱得还挺有节奏感的!", "speech_id": "speech_ff61928cdce0", "_at_ms": 1793115470443}
{"v": 2, "seq": 71, "kind": "assistant_final", "role": "assistant", "text": "哈哈,你唱得还挺有节奏感的!", "speech_id": "speech_ff61928cdce0", "turn_id": "baa6c5c7-b483-49d4-905c-d6b7e5dd7786", "_at_ms": 1793115476107}
{"v": 2, "seq": 72, "kind": "turn_end", "role": "assistant", "text": "", "speech_id": "speech_ff61928cdce0", "turn_id": "baa6c5c7-b483-49d4-905c-d6b7e5dd7786", "_at_ms": 1793115476109}
{"v": 2, "seq": 73, "kind": "interim", "role": "user", "text": "你是不是", "_at_ms": 1793115477463}
{"v": 2, "seq": 74, "kind": "interim", "role": "user", "text": "早就想", "_at_ms": 1793115478068}
{"v": 2, "seq": 75, "kind": "interim", "role": "user", "text": "睡我", "_at_ms": 1793115478661}
{"v": 2, "seq": 76, "kind": "interim", "role": "user", "text": "了", "_at_ms": 1793115479267}
{"v": 2, "seq": 77, "kind": "user_final", "role": "user", "text": "你是不是早就想睡我了呀", "_at_ms": 1793115480206}
{"v": 2, "seq": 78, "kind": "turn_start", "role": "assistant", "text": "", "speech_id": "speech_2f9dc8b9848d", "_at_ms": 1793115480215}
{"v": 2, "seq": 79, "kind": "assistant_interim", "role": "assistant", "text": "哈哈", "speech_id": "speech_2f9dc8b9848d", "_at_ms": 1793115481774}
{"v": 2, "seq": 80, "kind": "assistant_interim", "role": "assistant", "text": "哈哈,", "speech_id": "speech_2f9dc8b9848d", "_at_ms": 1793115481776}
{"v": 2, "seq": 81, "kind": "assistant_interim", "role": "assistant", "text": "哈哈,你", "speech_id": "speech_2f9dc8b9848d", "_at_ms": 1793115481816}
{"v": 2, "seq": 82, "kind": "assistant_interim", "role": "assistant", "text": "哈哈,你思维", "speech_id": "speech_2f9dc8b9848d", "_at_ms": 1793115481816}
{"v": 2, "seq": 83, "kind": "assistant_interim", "role": "assistant", "text": "哈哈,你思维跳跃", "speech_id": "speech_2f9dc8b9848d", "_at_ms": 1793115481857}
{"v": 2, "seq": 84, "kind": "assistant_interim", "role": "assistant", "text": "哈哈,你思维跳跃得太", "speech_id": "speech_2f9dc8b9848d", "_at_ms": 1793115481859}
{"v": 2, "seq": 85, "kind": "assistant_interim", "role": "assistant", "text": "哈哈,你思维跳跃得太快了", "speech_id": "speech_2f9dc8b9848d", "_at_ms": 1793115481903}
{"v": 2, "seq": 86, "kind": "assistant_interim", "role": "assistant", "text": "哈哈,你思维跳跃得太快了,", "speech_id": "speech_2f9dc8b9848d", "_at_ms": 1793115481906}
{"v": 2, "seq": 87, "kind": "assistant_interim", "role": "assistant", "text": "哈哈,你思维跳跃得太快了,我都", "speech_id": "speech_2f9dc8b9848d", "_at_ms": 1793115481945}
{"v": 2, "seq": 88, "kind": "assistant_interim", "role": "assistant", "text": "哈哈,你思维跳跃得太快了,我都跟不上", "speech_id": "speech_2f9dc8b9848d", "_at_ms": 1793115481987}
{"v": 2, "seq": 89, "kind": "assistant_interim", "role": "assistant", "text": "哈哈,你思维跳跃得太快了,我都跟不上。", "speech_id": "speech_2f9dc8b9848d", "_at_ms": 1793115481988}
{"v": 2, "seq": 90, "kind": "assistant_final", "role": "assistant", "text": "哈哈,你思维跳跃得太快了,我都跟不上。", "speech_id": "speech_2f9dc8b9848d", "turn_id": "a91bbbb6-45b0-4671-84a2-027cc194f420", "_at_ms": 1793115489067}
{"v": 2, "seq": 91, "kind": "turn_end", "role": "assistant", "text": "", "speech_id": "speech_2f9dc8b9848d", "turn_id": "a91bbbb6-45b0-4671-84a2-027cc194f420", "_at_ms": 1793115489068}
{"v": 2, "seq": 92, "kind": "interim", "role": "user", "text": "盯着你看", "_at_ms": 1793115491717}
{"v": 2, "seq": 93, "kind": "interim", "role": "user", "text": "怎么啦", "_at_ms": 1793115492309}
{"v": 2, "seq": 94, "kind": "user_final", "role": "user", "text": "盯着你看怎么啦", "_at_ms": 1793115493388}
{"v": 2, "seq": 95, "kind": "turn_start", "role": "assistant", "text": "", "speech_id": "speech_0d075d37bc31", "_at_ms": 1793115493398}
{"v": 2, "seq": 96, "kind": "assistant_interim", "role": "assistant", "text": "没", "speech_id": "speech_0d075d37bc31", "_at_ms": 1793115494608}
{"v": 2, "seq": 97, "kind": "assistant_final", "role": "assistant", "text": "没", "speech_id": "speech_0d075d37bc31", "turn_id": "5bd95c66-b42b-4571-bfc2-fbf376c90747", "_at_ms": 1793115501161}
{"v": 2, "seq": 98, "kind": "turn_end", "role": "assistant", "text": "", "speech_id": "speech_0d075d37bc31", "turn_id": "5bd95c66-b42b-4571-bfc2-fbf376c90747", "_at_ms": 1793115501163}
+303
View File
@@ -0,0 +1,303 @@
{
"barges": [
{
"label": "bajie",
"outcome": "reply-too-short-to-barge"
},
{
"label": "sunwukong",
"outcome": "reply-too-short-to-barge"
},
{
"label": "haimianbaobao",
"outcome": "reply-too-short-to-barge"
}
],
"text_turn": {
"turn_id": "cb1bbe27-795f-45e3-b7bf-f2d6371c0830",
"status": 200,
"deltas": 0,
"final": null,
"ms": 114
},
"voice_reply_after_text": {
"kind": "turn_end",
"speech_id": "speech_9aa32dc387c3",
"reason": null
},
"voice_cancelled_by_text": false,
"drop": {
"dropped_ms": 1793116007019,
"room": "cg-voice-oleganioz7",
"session_id": "Si6UZJbAENsNExR-B1cfpIEX",
"agent_frames_after_drop": 1
},
"call": {
"tag": "call3",
"session": {
"url": "wss://192.168.200.12:8363",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ2aWRlbyI6eyJyb29tSm9pbiI6dHJ1ZSwicm9vbSI6ImNnLXZvaWNlLW9sZWdhbmlvejciLCJjYW5QdWJsaXNoIjp0cnVlLCJjYW5TdWJzY3JpYmUiOnRydWUsImNhblB1Ymxpc2hEYXRhIjp0cnVlfSwic3ViIjoidXNlci11cWZ5dWhudCIsImlzcyI6ImRldmtleSIsIm5iZiI6MTc5MzExNTY5MCwiZXhwIjoxNzkzMTE1OTkwLCJuYW1lIjoi55So5oi3In0.wXERZSpOZVIidVRq-8dkIbb7e2gPa0HUX8_ZV-kHTN4",
"room": "cg-voice-oleganioz7",
"session_id": "Si6UZJbAENsNExR-B1cfpIEX",
"agent_name": "cybergril-voice-agent",
"epoch": 4,
"expires_at": 1793116590248
},
"subtitle_count": 104,
"seq_monotonic": true,
"agent_frames": 31510,
"agent_audio_seconds": 315.1,
"agent_audio_rate": 48000,
"agent_joined_ms": 1793115690867,
"agent_left_ms": null,
"subtitles_file": "/opt/cybergril-hel879/evidence/call3.subtitles.jsonl",
"agent_audio_file": "/opt/cybergril-hel879/evidence/call3.agent.wav"
},
"messages": [
{
"id": 1,
"role": "user",
"t": "盯着你看怎么啦",
"status": "sent",
"turn_id": "61605d50-17f0-4d9e-b407-60574304a827"
},
{
"id": 2,
"role": "companion",
"t": "没怎么啦,工程师。你盯着看,我就当你在检查我有没有乱动。",
"status": "sent",
"turn_id": "61605d50-17f0-4d9e-b407-60574304a827"
},
{
"id": 3,
"role": "user",
"t": "难得看你这么有意思的样子吗 烈火军校啊,朝阳啊这种类型我都是那种比较呃豪爽,比较开朗的那种类型,阳光大女孩那种感觉就",
"status": "sent",
"turn_id": "eb182bcd-3441-42d8-ba60-9eced3a6a5a5"
},
{
"id": 4,
"role": "companion",
"t": "原来你是那种阳光大女孩的类型啊,难怪感觉你说话都透着一股豪爽劲儿。",
"status": "sent",
"turn_id": "eb182bcd-3441-42d8-ba60-9eced3a6a5a5"
},
{
"id": 5,
"role": "user",
"t": "怎么还有没事的",
"status": "sent",
"turn_id": "0e1a5226-4ca6-44bb-a343-39647f0112e3"
},
{
"id": 6,
"role": "companion",
"t": "哈哈,因为我在认真听你说话呀。",
"status": "sent",
"turn_id": "0e1a5226-4ca6-44bb-a343-39647f0112e3"
},
{
"id": 7,
"role": "user",
"t": "是我不够努力吗",
"status": "cancelled",
"turn_id": "47d8d0ce-7ac7-4202-9757-64f5f4fb1300"
},
{
"id": 8,
"role": "user",
"t": "没错,我就是个笨蛋派大星,我们一起去抓水母吧,我们一起度过快乐的一天",
"status": "sent",
"turn_id": "b72de17e-bf42-470e-b396-c219bd7593f9"
},
{
"id": 9,
"role": "companion",
"t": "好呀,那我们这就出发去抓水母!不过我可不想住进那个菠萝屋,我只喜欢在海边晒太阳。",
"status": "sent",
"turn_id": "b72de17e-bf42-470e-b396-c219bd7593f9"
},
{
"id": 10,
"role": "user",
"t": "天雷滚滚,我好怕怕劈的我浑身掉渣渣,突破天劫,我笑哈哈逆天改命,我吹喇叭滴答滴答,滴滴答",
"status": "sent",
"turn_id": "baa6c5c7-b483-49d4-905c-d6b7e5dd7786"
},
{
"id": 11,
"role": "companion",
"t": "哈哈,你唱得还挺有节奏感的!",
"status": "sent",
"turn_id": "baa6c5c7-b483-49d4-905c-d6b7e5dd7786"
},
{
"id": 12,
"role": "user",
"t": "你是不是早就想睡我了呀",
"status": "sent",
"turn_id": "a91bbbb6-45b0-4671-84a2-027cc194f420"
},
{
"id": 13,
"role": "companion",
"t": "哈哈,你思维跳跃得太快了,我都跟不上。",
"status": "sent",
"turn_id": "a91bbbb6-45b0-4671-84a2-027cc194f420"
},
{
"id": 14,
"role": "user",
"t": "盯着你看怎么啦",
"status": "sent",
"turn_id": "5bd95c66-b42b-4571-bfc2-fbf376c90747"
},
{
"id": 15,
"role": "companion",
"t": "没",
"status": "sent",
"turn_id": "5bd95c66-b42b-4571-bfc2-fbf376c90747"
},
{
"id": 16,
"role": "user",
"t": "师傅,哎,这取经路比高,老庄种地还苦,俺老猪饿的能吞下石龙素包子,猴哥,你筋斗云块,要不先去化个缘",
"status": "sent",
"turn_id": "9bba6412-6679-48c2-9f1c-4bfc07f58aea"
},
{
"id": 17,
"role": "companion",
"t": "师傅,",
"status": "sent",
"turn_id": "9bba6412-6679-48c2-9f1c-4bfc07f58aea"
},
{
"id": 18,
"role": "user",
"t": "俺老孙来也,俺乃500年前大闹天宫的齐天大圣孙悟空,这里山势险峻,只怕藏有妖精妖怪,哪里逃吃?俺老孙吧",
"status": "sent",
"turn_id": "16006868-5ee7-49cc-b9c9-1a8dcfe74d08"
},
{
"id": 19,
"role": "companion",
"t": "好",
"status": "sent",
"turn_id": "16006868-5ee7-49cc-b9c9-1a8dcfe74d08"
},
{
"id": 20,
"role": "user",
"t": "没错,我就是个笨蛋派大星,我们一起去抓水母吧,我们一起度过快乐的一天",
"status": "sent",
"turn_id": "fdf28a03-19a8-4fcb-ba8c-24587736c3f0"
},
{
"id": 21,
"role": "companion",
"t": "好",
"status": "sent",
"turn_id": "fdf28a03-19a8-4fcb-ba8c-24587736c3f0"
},
{
"id": 22,
"role": "user",
"t": "你是不是早就想睡我了呀",
"status": "sent",
"turn_id": "623bab59-0edc-4751-9feb-a9a7e4a11233"
},
{
"id": 23,
"role": "user",
"t": "(文字通道)我改用打字了,这条应该立刻打断你正在说的话。",
"status": "cancelled",
"turn_id": "cb1bbe27-795f-45e3-b7bf-f2d6371c0830"
},
{
"id": 25,
"role": "companion",
"t": "Considering the limited time by the user, I have to give the",
"status": "sent",
"turn_id": "623bab59-0edc-4751-9feb-a9a7e4a11233"
}
],
"receipts": [
{
"message_id": 9,
"turn_id": "b72de17e-bf42-470e-b396-c219bd7593f9",
"ack_at": 1793115457081,
"state": "judged"
},
{
"message_id": 11,
"turn_id": "baa6c5c7-b483-49d4-905c-d6b7e5dd7786",
"ack_at": 1793115476109,
"state": "judged"
},
{
"message_id": 13,
"turn_id": "a91bbbb6-45b0-4671-84a2-027cc194f420",
"ack_at": 1793115489069,
"state": "judged"
},
{
"message_id": 15,
"turn_id": "5bd95c66-b42b-4571-bfc2-fbf376c90747",
"ack_at": 1793115501163,
"state": "judged"
},
{
"message_id": 17,
"turn_id": "9bba6412-6679-48c2-9f1c-4bfc07f58aea",
"ack_at": 1793115715591,
"state": "judged"
},
{
"message_id": 19,
"turn_id": "16006868-5ee7-49cc-b9c9-1a8dcfe74d08",
"ack_at": 1793115816209,
"state": "judged"
},
{
"message_id": 21,
"turn_id": "fdf28a03-19a8-4fcb-ba8c-24587736c3f0",
"ack_at": 1793115908179,
"state": "judged"
},
{
"message_id": 25,
"turn_id": "623bab59-0edc-4751-9feb-a9a7e4a11233",
"ack_at": 1793116005932,
"state": "pending"
}
],
"budget": {
"at_ms": 1793116007051,
"token_usage": [
{
"day": "2026-10-27",
"scene": "dialogue_main",
"used": 42717
}
],
"request_usage": [
{
"day": "2026-10-27",
"category": "dialogue",
"requests": 14
}
],
"budget_total": [
{
"key": "total_requests",
"value": 14
},
{
"key": "total_tokens",
"value": 42717
}
]
}
}
@@ -0,0 +1,104 @@
{"v": 2, "seq": 1, "kind": "interim", "role": "user", "text": "师", "_at_ms": 1793115691554}
{"v": 2, "seq": 2, "kind": "interim", "role": "user", "text": "傅", "_at_ms": 1793115692163}
{"v": 2, "seq": 3, "kind": "interim", "role": "user", "text": "哎这", "_at_ms": 1793115692756}
{"v": 2, "seq": 4, "kind": "interim", "role": "user", "text": "取经", "_at_ms": 1793115693359}
{"v": 2, "seq": 5, "kind": "interim", "role": "user", "text": "路比", "_at_ms": 1793115693964}
{"v": 2, "seq": 6, "kind": "interim", "role": "user", "text": "高老", "_at_ms": 1793115694564}
{"v": 2, "seq": 7, "kind": "interim", "role": "user", "text": "庄种", "_at_ms": 1793115695157}
{"v": 2, "seq": 8, "kind": "interim", "role": "user", "text": "地还", "_at_ms": 1793115695762}
{"v": 2, "seq": 9, "kind": "interim", "role": "user", "text": "苦俺", "_at_ms": 1793115696359}
{"v": 2, "seq": 10, "kind": "interim", "role": "user", "text": "老中", "_at_ms": 1793115696958}
{"v": 2, "seq": 11, "kind": "interim", "role": "user", "text": "饿的能", "_at_ms": 1793115697557}
{"v": 2, "seq": 12, "kind": "interim", "role": "user", "text": "吞下", "_at_ms": 1793115698168}
{"v": 2, "seq": 13, "kind": "interim", "role": "user", "text": "石龙", "_at_ms": 1793115698760}
{"v": 2, "seq": 14, "kind": "interim", "role": "user", "text": "素包", "_at_ms": 1793115699353}
{"v": 2, "seq": 15, "kind": "interim", "role": "user", "text": "子", "_at_ms": 1793115699967}
{"v": 2, "seq": 16, "kind": "interim", "role": "user", "text": "猴哥", "_at_ms": 1793115700562}
{"v": 2, "seq": 17, "kind": "interim", "role": "user", "text": "你筋", "_at_ms": 1793115701159}
{"v": 2, "seq": 18, "kind": "interim", "role": "user", "text": "斗云", "_at_ms": 1793115701768}
{"v": 2, "seq": 19, "kind": "interim", "role": "user", "text": "快", "_at_ms": 1793115702352}
{"v": 2, "seq": 20, "kind": "interim", "role": "user", "text": "要不", "_at_ms": 1793115702961}
{"v": 2, "seq": 21, "kind": "interim", "role": "user", "text": "先", "_at_ms": 1793115703557}
{"v": 2, "seq": 22, "kind": "interim", "role": "user", "text": "去画个", "_at_ms": 1793115704166}
{"v": 2, "seq": 23, "kind": "user_final", "role": "user", "text": "师傅,哎,这取经路比高,老庄种地还苦,俺老猪饿的能吞下石龙素包子,猴哥,你筋斗云块,要不先去化个缘", "_at_ms": 1793115706500}
{"v": 2, "seq": 24, "kind": "turn_start", "role": "assistant", "text": "", "speech_id": "speech_ba7b3f730ffd", "_at_ms": 1793115710205}
{"v": 2, "seq": 25, "kind": "assistant_interim", "role": "assistant", "text": "师傅", "speech_id": "speech_ba7b3f730ffd", "_at_ms": 1793115711789}
{"v": 2, "seq": 26, "kind": "assistant_interim", "role": "assistant", "text": "师傅,", "speech_id": "speech_ba7b3f730ffd", "_at_ms": 1793115711850}
{"v": 2, "seq": 27, "kind": "assistant_final", "role": "assistant", "text": "师傅,", "speech_id": "speech_ba7b3f730ffd", "turn_id": "9bba6412-6679-48c2-9f1c-4bfc07f58aea", "_at_ms": 1793115715589}
{"v": 2, "seq": 28, "kind": "turn_end", "role": "assistant", "text": "", "speech_id": "speech_ba7b3f730ffd", "turn_id": "9bba6412-6679-48c2-9f1c-4bfc07f58aea", "_at_ms": 1793115715590}
{"v": 2, "seq": 29, "kind": "interim", "role": "user", "text": "俺老", "_at_ms": 1793115792459}
{"v": 2, "seq": 30, "kind": "interim", "role": "user", "text": "孙来", "_at_ms": 1793115793061}
{"v": 2, "seq": 31, "kind": "interim", "role": "user", "text": "也俺", "_at_ms": 1793115793659}
{"v": 2, "seq": 32, "kind": "interim", "role": "user", "text": "乃五百", "_at_ms": 1793115794261}
{"v": 2, "seq": 33, "kind": "interim", "role": "user", "text": "年前", "_at_ms": 1793115794859}
{"v": 2, "seq": 34, "kind": "interim", "role": "user", "text": "大闹", "_at_ms": 1793115795470}
{"v": 2, "seq": 35, "kind": "interim", "role": "user", "text": "天宫的", "_at_ms": 1793115796055}
{"v": 2, "seq": 36, "kind": "interim", "role": "user", "text": "齐天", "_at_ms": 1793115796661}
{"v": 2, "seq": 37, "kind": "interim", "role": "user", "text": "霸圣", "_at_ms": 1793115797263}
{"v": 2, "seq": 38, "kind": "interim", "role": "user", "text": "孙悟", "_at_ms": 1793115797864}
{"v": 2, "seq": 39, "kind": "interim", "role": "user", "text": "空这", "_at_ms": 1793115798464}
{"v": 2, "seq": 40, "kind": "interim", "role": "user", "text": "里山", "_at_ms": 1793115799067}
{"v": 2, "seq": 41, "kind": "interim", "role": "user", "text": "势险", "_at_ms": 1793115799657}
{"v": 2, "seq": 42, "kind": "interim", "role": "user", "text": "峻", "_at_ms": 1793115800262}
{"v": 2, "seq": 43, "kind": "interim", "role": "user", "text": "只怕藏", "_at_ms": 1793115800858}
{"v": 2, "seq": 44, "kind": "interim", "role": "user", "text": "有妖", "_at_ms": 1793115801462}
{"v": 2, "seq": 45, "kind": "interim", "role": "user", "text": "精妖", "_at_ms": 1793115802053}
{"v": 2, "seq": 46, "kind": "interim", "role": "user", "text": "怪", "_at_ms": 1793115802654}
{"v": 2, "seq": 47, "kind": "interim", "role": "user", "text": "哪里", "_at_ms": 1793115803271}
{"v": 2, "seq": 48, "kind": "interim", "role": "user", "text": "逃", "_at_ms": 1793115803858}
{"v": 2, "seq": 49, "kind": "interim", "role": "user", "text": "吃完老", "_at_ms": 1793115804464}
{"v": 2, "seq": 50, "kind": "interim", "role": "user", "text": "我所", "_at_ms": 1793115805066}
{"v": 2, "seq": 51, "kind": "interim", "role": "user", "text": "一", "_at_ms": 1793115805654}
{"v": 2, "seq": 52, "kind": "user_final", "role": "user", "text": "俺老孙来也,俺乃500年前大闹天宫的齐天大圣孙悟空,这里山势险峻,只怕藏有妖精妖怪,哪里逃吃?俺老孙吧", "_at_ms": 1793115807317}
{"v": 2, "seq": 53, "kind": "turn_start", "role": "assistant", "text": "", "speech_id": "speech_d1c8f4816b10", "_at_ms": 1793115810983}
{"v": 2, "seq": 54, "kind": "assistant_interim", "role": "assistant", "text": "好", "speech_id": "speech_d1c8f4816b10", "_at_ms": 1793115812231}
{"v": 2, "seq": 55, "kind": "assistant_final", "role": "assistant", "text": "好", "speech_id": "speech_d1c8f4816b10", "turn_id": "16006868-5ee7-49cc-b9c9-1a8dcfe74d08", "_at_ms": 1793115816208}
{"v": 2, "seq": 56, "kind": "turn_end", "role": "assistant", "text": "", "speech_id": "speech_d1c8f4816b10", "turn_id": "16006868-5ee7-49cc-b9c9-1a8dcfe74d08", "_at_ms": 1793115816213}
{"v": 2, "seq": 57, "kind": "interim", "role": "user", "text": "没", "_at_ms": 1793115893204}
{"v": 2, "seq": 58, "kind": "interim", "role": "user", "text": "错我", "_at_ms": 1793115893809}
{"v": 2, "seq": 59, "kind": "interim", "role": "user", "text": "就是个笨", "_at_ms": 1793115894415}
{"v": 2, "seq": 60, "kind": "interim", "role": "user", "text": "蛋", "_at_ms": 1793115895004}
{"v": 2, "seq": 61, "kind": "interim", "role": "user", "text": "派大", "_at_ms": 1793115895614}
{"v": 2, "seq": 62, "kind": "interim", "role": "user", "text": "星我们一", "_at_ms": 1793115896206}
{"v": 2, "seq": 63, "kind": "interim", "role": "user", "text": "起去抓", "_at_ms": 1793115896811}
{"v": 2, "seq": 64, "kind": "interim", "role": "user", "text": "水母", "_at_ms": 1793115897406}
{"v": 2, "seq": 65, "kind": "interim", "role": "user", "text": "吧我们", "_at_ms": 1793115898018}
{"v": 2, "seq": 66, "kind": "interim", "role": "user", "text": "一起度", "_at_ms": 1793115898608}
{"v": 2, "seq": 67, "kind": "interim", "role": "user", "text": "过快乐的一", "_at_ms": 1793115899211}
{"v": 2, "seq": 68, "kind": "user_final", "role": "user", "text": "没错,我就是个笨蛋派大星,我们一起去抓水母吧,我们一起度过快乐的一天", "_at_ms": 1793115901261}
{"v": 2, "seq": 69, "kind": "turn_start", "role": "assistant", "text": "", "speech_id": "speech_8ac7cd94c5db", "_at_ms": 1793115901269}
{"v": 2, "seq": 70, "kind": "assistant_interim", "role": "assistant", "text": "好", "speech_id": "speech_8ac7cd94c5db", "_at_ms": 1793115902769}
{"v": 2, "seq": 71, "kind": "assistant_final", "role": "assistant", "text": "好", "speech_id": "speech_8ac7cd94c5db", "turn_id": "fdf28a03-19a8-4fcb-ba8c-24587736c3f0", "_at_ms": 1793115908179}
{"v": 2, "seq": 72, "kind": "turn_end", "role": "assistant", "text": "", "speech_id": "speech_8ac7cd94c5db", "turn_id": "fdf28a03-19a8-4fcb-ba8c-24587736c3f0", "_at_ms": 1793115908181}
{"v": 2, "seq": 73, "kind": "interim", "role": "user", "text": "你是不是", "_at_ms": 1793115991657}
{"v": 2, "seq": 74, "kind": "interim", "role": "user", "text": "早就想", "_at_ms": 1793115992268}
{"v": 2, "seq": 75, "kind": "interim", "role": "user", "text": "睡我", "_at_ms": 1793115992857}
{"v": 2, "seq": 76, "kind": "interim", "role": "user", "text": "了", "_at_ms": 1793115993462}
{"v": 2, "seq": 77, "kind": "user_final", "role": "user", "text": "你是不是早就想睡我了呀", "_at_ms": 1793115994404}
{"v": 2, "seq": 78, "kind": "turn_start", "role": "assistant", "text": "", "speech_id": "speech_9aa32dc387c3", "_at_ms": 1793115994412}
{"v": 2, "seq": 79, "kind": "assistant_interim", "role": "assistant", "text": "Considering", "speech_id": "speech_9aa32dc387c3", "_at_ms": 1793115994844}
{"v": 2, "seq": 80, "kind": "assistant_interim", "role": "assistant", "text": "Considering the", "speech_id": "speech_9aa32dc387c3", "_at_ms": 1793115994878}
{"v": 2, "seq": 81, "kind": "assistant_interim", "role": "assistant", "text": "Considering the limited", "speech_id": "speech_9aa32dc387c3", "_at_ms": 1793115994911}
{"v": 2, "seq": 82, "kind": "assistant_interim", "role": "assistant", "text": "Considering the limited time", "speech_id": "speech_9aa32dc387c3", "_at_ms": 1793115994944}
{"v": 2, "seq": 83, "kind": "assistant_interim", "role": "assistant", "text": "Considering the limited time by", "speech_id": "speech_9aa32dc387c3", "_at_ms": 1793115994977}
{"v": 2, "seq": 84, "kind": "assistant_interim", "role": "assistant", "text": "Considering the limited time by the", "speech_id": "speech_9aa32dc387c3", "_at_ms": 1793115995010}
{"v": 2, "seq": 85, "kind": "assistant_interim", "role": "assistant", "text": "Considering the limited time by the user", "speech_id": "speech_9aa32dc387c3", "_at_ms": 1793115995044}
{"v": 2, "seq": 86, "kind": "assistant_interim", "role": "assistant", "text": "Considering the limited time by the user,", "speech_id": "speech_9aa32dc387c3", "_at_ms": 1793115995077}
{"v": 2, "seq": 87, "kind": "assistant_interim", "role": "assistant", "text": "Considering the limited time by the user, I", "speech_id": "speech_9aa32dc387c3", "_at_ms": 1793115995111}
{"v": 2, "seq": 88, "kind": "assistant_interim", "role": "assistant", "text": "Considering the limited time by the user, I have", "speech_id": "speech_9aa32dc387c3", "_at_ms": 1793115995144}
{"v": 2, "seq": 89, "kind": "assistant_interim", "role": "assistant", "text": "Considering the limited time by the user, I have to", "speech_id": "speech_9aa32dc387c3", "_at_ms": 1793115995178}
{"v": 2, "seq": 90, "kind": "assistant_interim", "role": "assistant", "text": "Considering the limited time by the user, I have to give", "speech_id": "speech_9aa32dc387c3", "_at_ms": 1793115995212}
{"v": 2, "seq": 91, "kind": "assistant_interim", "role": "assistant", "text": "Considering the limited time by the user, I have to give the", "speech_id": "speech_9aa32dc387c3", "_at_ms": 1793115995245}
{"v": 2, "seq": 92, "kind": "assistant_interim", "role": "assistant", "text": "Considering the limited time by the user, I have to give the solution", "speech_id": "speech_9aa32dc387c3", "_at_ms": 1793115995278}
{"v": 2, "seq": 93, "kind": "assistant_interim", "role": "assistant", "text": "Considering the limited time by the user, I have to give the solution based", "speech_id": "speech_9aa32dc387c3", "_at_ms": 1793115995312}
{"v": 2, "seq": 94, "kind": "assistant_interim", "role": "assistant", "text": "Considering the limited time by the user, I have to give the solution based on", "speech_id": "speech_9aa32dc387c3", "_at_ms": 1793115995345}
{"v": 2, "seq": 95, "kind": "assistant_interim", "role": "assistant", "text": "Considering the limited time by the user, I have to give the solution based on the", "speech_id": "speech_9aa32dc387c3", "_at_ms": 1793115995378}
{"v": 2, "seq": 96, "kind": "assistant_interim", "role": "assistant", "text": "Considering the limited time by the user, I have to give the solution based on the thinking", "speech_id": "speech_9aa32dc387c3", "_at_ms": 1793115995412}
{"v": 2, "seq": 97, "kind": "assistant_interim", "role": "assistant", "text": "Considering the limited time by the user, I have to give the solution based on the thinking directly", "speech_id": "speech_9aa32dc387c3", "_at_ms": 1793115995445}
{"v": 2, "seq": 98, "kind": "assistant_interim", "role": "assistant", "text": "Considering the limited time by the user, I have to give the solution based on the thinking directly now", "speech_id": "speech_9aa32dc387c3", "_at_ms": 1793115995478}
{"v": 2, "seq": 99, "kind": "assistant_interim", "role": "assistant", "text": "Considering the limited time by the user, I have to give the solution based on the thinking directly now.", "speech_id": "speech_9aa32dc387c3", "_at_ms": 1793115995511}
{"v": 2, "seq": 100, "kind": "assistant_interim", "role": "assistant", "text": "Considering the limited time by the user, I have to give the solution based on the thinking directly now.\n", "speech_id": "speech_9aa32dc387c3", "_at_ms": 1793115995549}
{"v": 2, "seq": 101, "kind": "assistant_interim", "role": "assistant", "text": "Considering the limited time by the user, I have to give the solution based on the thinking directly now.\n</think>", "speech_id": "speech_9aa32dc387c3", "_at_ms": 1793115995583}
{"v": 2, "seq": 102, "kind": "assistant_interim", "role": "assistant", "text": "Considering the limited time by the user, I have to give the solution based on the thinking directly now.\n</think>\n\n", "speech_id": "speech_9aa32dc387c3", "_at_ms": 1793115995614}
{"v": 2, "seq": 103, "kind": "assistant_final", "role": "assistant", "text": "Considering the limited time by the user, I have to give the solution based on the thinking directly now.\n</think>\n\n", "speech_id": "speech_9aa32dc387c3", "turn_id": "623bab59-0edc-4751-9feb-a9a7e4a11233", "_at_ms": 1793116005934}
{"v": 2, "seq": 104, "kind": "turn_end", "role": "assistant", "text": "", "speech_id": "speech_9aa32dc387c3", "turn_id": "623bab59-0edc-4751-9feb-a9a7e4a11233", "_at_ms": 1793116005935}
@@ -0,0 +1,2 @@
WINDOW_START=2026-10-27T15:26:54Z
WINDOW_END=2026-10-27T15:58:10Z
+409
View File
@@ -0,0 +1,409 @@
{"message": "starting worker", "level": "INFO", "name": "livekit.agents", "version": "1.8.8", "rtc-version": "1.1.20", "pid": 8800, "timestamp": "2026-10-27T15:21:02.628498+00:00"}
{"message": "plugin registered", "level": "INFO", "name": "livekit.agents", "plugin": "livekit.plugins.silero", "version": "1.8.8", "pid": 8800, "timestamp": "2026-10-27T15:21:02.628622+00:00"}
{"message": "preloading plugins", "level": "INFO", "name": "livekit.agents", "packages": ["livekit.plugins.silero", "livekit.agents.ipc._preload", "livekit.agents.ipc._preload_freeze"], "pid": 8800, "timestamp": "2026-10-27T15:21:02.628690+00:00"}
{"message": "HTTP server listening on :8082", "level": "INFO", "name": "livekit.agents", "pid": 8800, "timestamp": "2026-10-27T15:21:02.632394+00:00"}
{"message": "initializing process", "level": "INFO", "name": "livekit.agents", "pid": 8879, "timestamp": "2026-10-27T15:21:03.868641+00:00"}
{"message": "initializing process", "level": "INFO", "name": "livekit.agents", "pid": 8881, "timestamp": "2026-10-27T15:21:03.871820+00:00"}
{"message": "initializing process", "level": "INFO", "name": "livekit.agents", "pid": 8883, "timestamp": "2026-10-27T15:21:03.874482+00:00"}
{"message": "initializing process", "level": "INFO", "name": "livekit.agents", "pid": 8885, "timestamp": "2026-10-27T15:21:03.877329+00:00"}
{"message": "process initialized", "level": "INFO", "name": "livekit.agents", "pid": 8885, "elapsed_time": 0.14, "timestamp": "2026-10-27T15:21:04.013188+00:00"}
{"message": "process initialized", "level": "INFO", "name": "livekit.agents", "pid": 8883, "elapsed_time": 0.14, "timestamp": "2026-10-27T15:21:04.013380+00:00"}
{"message": "process initialized", "level": "INFO", "name": "livekit.agents", "pid": 8879, "elapsed_time": 0.14, "timestamp": "2026-10-27T15:21:04.013613+00:00"}
{"message": "process initialized", "level": "INFO", "name": "livekit.agents", "pid": 8881, "elapsed_time": 0.14, "timestamp": "2026-10-27T15:21:04.016022+00:00"}
/opt/voice-agent/agent/venv/lib/python3.12/site-packages/jwt/api_jwt.py:149: InsecureKeyLengthWarning: The HMAC key is 28 bytes long, which is below the minimum recommended length of 32 bytes for SHA256. See RFC 7518 Section 3.2.
return self._jws.encode(
{"message": "registered worker", "level": "INFO", "name": "livekit.agents", "agent_name": "cybergril-voice-agent", "deployment": "", "id": "AW_LGViCVagSNNu", "url": "ws://127.0.0.1:7880", "region": "", "protocol": 17, "pid": 8800, "timestamp": "2026-10-27T15:21:04.039801+00:00"}
{"message": "received job request", "level": "INFO", "name": "livekit.agents", "job_id": "AJ_dcfbVLNh9cxc", "dispatch_id": "AD_pFPtM5kSSpZH", "room": "probe-room-2", "room_id": "RM_mNZagsczYXao", "agent_name": "cybergril-voice-agent", "resuming": false, "enable_recording": false, "pid": 8800, "timestamp": "2026-10-27T15:28:57.444695+00:00"}
{"message": "initializing process", "level": "INFO", "name": "livekit.agents", "pid": 10801, "timestamp": "2026-10-27T15:28:57.518855+00:00"}
{"message": "process initialized", "level": "INFO", "name": "livekit.agents", "pid": 10801, "elapsed_time": 0.12, "timestamp": "2026-10-27T15:28:57.639650+00:00"}
Nvidia Decoder is supported.
{"message": "adaptive interruption is disabled by default in production mode", "level": "INFO", "name": "livekit.agents", "pid": 8885, "job_id": "AJ_dcfbVLNh9cxc", "room": "probe-room-2", "room_id": "RM_mNZagsczYXao", "timestamp": "2026-10-27T15:28:57.893684+00:00"}
{"message": "failed to emit event conversation_item_added\nTraceback (most recent call last):\n File \"/opt/voice-agent/agent/venv/lib/python3.12/site-packages/livekit/rtc/event_emitter.py\", line 70, in emit\n callback(*callback_args)\n File \"/opt/voice-agent-cybergril/cybergril/voice/worker.py\", line 193, in _on_item\n if ev.item.role == \"assistant\":\n ^^^^^^^^^^^^\n File \"/opt/voice-agent/agent/venv/lib/python3.12/site-packages/pydantic/main.py\", line 1105, in __getattr__\n raise AttributeError(f'{type(self).__name__!r} object has no attribute {item!r}')\nAttributeError: 'AgentHandoff' object has no attribute 'role'", "level": "ERROR", "name": "livekit", "exc_info": "Traceback (most recent call last):\n File \"/opt/voice-agent/agent/venv/lib/python3.12/site-packages/livekit/rtc/event_emitter.py\", line 70, in emit\n callback(*callback_args)\n File \"/opt/voice-agent-cybergril/cybergril/voice/worker.py\", line 193, in _on_item\n if ev.item.role == \"assistant\":\n ^^^^^^^^^^^^\n File \"/opt/voice-agent/agent/venv/lib/python3.12/site-packages/pydantic/main.py\", line 1105, in __getattr__\n raise AttributeError(f'{type(self).__name__!r} object has no attribute {item!r}')\nAttributeError: 'AgentHandoff' object has no attribute 'role'", "pid": 8885, "job_id": "AJ_dcfbVLNh9cxc", "room": "probe-room-2", "room_id": "RM_mNZagsczYXao", "timestamp": "2026-10-27T15:28:57.895149+00:00"}
[cybergril-worker] session started (api=http://127.0.0.1:8360, voice=female_soft, agent_name=cybergril-voice-agent)
{"message": "received job request", "level": "INFO", "name": "livekit.agents", "job_id": "AJ_FBBgf5Si2JYd", "dispatch_id": "AD_iJTKQsucFwSK", "room": "probe-room-3", "room_id": "RM_msdxb3QWFMYf", "agent_name": "cybergril-voice-agent", "resuming": false, "enable_recording": false, "pid": 8800, "timestamp": "2026-10-27T15:32:32.446385+00:00"}
{"message": "initializing process", "level": "INFO", "name": "livekit.agents", "pid": 11093, "timestamp": "2026-10-27T15:32:32.549290+00:00"}
{"message": "process initialized", "level": "INFO", "name": "livekit.agents", "pid": 11093, "elapsed_time": 0.12, "timestamp": "2026-10-27T15:32:32.665948+00:00"}
Nvidia Decoder is supported.
{"message": "adaptive interruption is disabled by default in production mode", "level": "INFO", "name": "livekit.agents", "pid": 8883, "job_id": "AJ_FBBgf5Si2JYd", "room": "probe-room-3", "room_id": "RM_msdxb3QWFMYf", "timestamp": "2026-10-27T15:32:32.853330+00:00"}
{"message": "failed to emit event conversation_item_added\nTraceback (most recent call last):\n File \"/opt/voice-agent/agent/venv/lib/python3.12/site-packages/livekit/rtc/event_emitter.py\", line 70, in emit\n callback(*callback_args)\n File \"/opt/voice-agent-cybergril/cybergril/voice/worker.py\", line 193, in _on_item\n if ev.item.role == \"assistant\":\n ^^^^^^^^^^^^\n File \"/opt/voice-agent/agent/venv/lib/python3.12/site-packages/pydantic/main.py\", line 1105, in __getattr__\n raise AttributeError(f'{type(self).__name__!r} object has no attribute {item!r}')\nAttributeError: 'AgentHandoff' object has no attribute 'role'", "level": "ERROR", "name": "livekit", "exc_info": "Traceback (most recent call last):\n File \"/opt/voice-agent/agent/venv/lib/python3.12/site-packages/livekit/rtc/event_emitter.py\", line 70, in emit\n callback(*callback_args)\n File \"/opt/voice-agent-cybergril/cybergril/voice/worker.py\", line 193, in _on_item\n if ev.item.role == \"assistant\":\n ^^^^^^^^^^^^\n File \"/opt/voice-agent/agent/venv/lib/python3.12/site-packages/pydantic/main.py\", line 1105, in __getattr__\n raise AttributeError(f'{type(self).__name__!r} object has no attribute {item!r}')\nAttributeError: 'AgentHandoff' object has no attribute 'role'", "pid": 8883, "job_id": "AJ_FBBgf5Si2JYd", "room": "probe-room-3", "room_id": "RM_msdxb3QWFMYf", "timestamp": "2026-10-27T15:32:32.855790+00:00"}
[cybergril-worker] session started (api=http://127.0.0.1:8360, voice=female_soft, agent_name=cybergril-voice-agent)
{"message": "received job request", "level": "INFO", "name": "livekit.agents", "job_id": "AJ_QhLhdpZKohT3", "dispatch_id": "AD_6Esm9sgx9sbT", "room": "cg-voice-74blnuc9c6", "room_id": "RM_q3vSy5gNpvMA", "agent_name": "cybergril-voice-agent", "resuming": false, "enable_recording": false, "pid": 8800, "timestamp": "2026-10-27T15:32:39.512686+00:00"}
{"message": "initializing process", "level": "INFO", "name": "livekit.agents", "pid": 11326, "timestamp": "2026-10-27T15:32:39.616162+00:00"}
{"message": "process initialized", "level": "INFO", "name": "livekit.agents", "pid": 11326, "elapsed_time": 0.12, "timestamp": "2026-10-27T15:32:39.737803+00:00"}
Nvidia Decoder is supported.
{"message": "adaptive interruption is disabled by default in production mode", "level": "INFO", "name": "livekit.agents", "pid": 8879, "job_id": "AJ_QhLhdpZKohT3", "room": "cg-voice-74blnuc9c6", "room_id": "RM_q3vSy5gNpvMA", "timestamp": "2026-10-27T15:32:39.969275+00:00"}
{"message": "failed to emit event conversation_item_added\nTraceback (most recent call last):\n File \"/opt/voice-agent/agent/venv/lib/python3.12/site-packages/livekit/rtc/event_emitter.py\", line 70, in emit\n callback(*callback_args)\n File \"/opt/voice-agent-cybergril/cybergril/voice/worker.py\", line 193, in _on_item\n if ev.item.role == \"assistant\":\n ^^^^^^^^^^^^\n File \"/opt/voice-agent/agent/venv/lib/python3.12/site-packages/pydantic/main.py\", line 1105, in __getattr__\n raise AttributeError(f'{type(self).__name__!r} object has no attribute {item!r}')\nAttributeError: 'AgentHandoff' object has no attribute 'role'", "level": "ERROR", "name": "livekit", "exc_info": "Traceback (most recent call last):\n File \"/opt/voice-agent/agent/venv/lib/python3.12/site-packages/livekit/rtc/event_emitter.py\", line 70, in emit\n callback(*callback_args)\n File \"/opt/voice-agent-cybergril/cybergril/voice/worker.py\", line 193, in _on_item\n if ev.item.role == \"assistant\":\n ^^^^^^^^^^^^\n File \"/opt/voice-agent/agent/venv/lib/python3.12/site-packages/pydantic/main.py\", line 1105, in __getattr__\n raise AttributeError(f'{type(self).__name__!r} object has no attribute {item!r}')\nAttributeError: 'AgentHandoff' object has no attribute 'role'", "pid": 8879, "job_id": "AJ_QhLhdpZKohT3", "room": "cg-voice-74blnuc9c6", "room_id": "RM_q3vSy5gNpvMA", "timestamp": "2026-10-27T15:32:39.971493+00:00"}
[cybergril-worker] session started (api=http://127.0.0.1:8360, voice=female_soft, agent_name=cybergril-voice-agent)
{"message": "livekit::rtc_engine::rtc_session:704:livekit::rtc_engine::rtc_session - publisher data channel '_reliable' closed unexpectedly", "level": "ERROR", "name": "livekit", "pid": 8879, "job_id": "AJ_QhLhdpZKohT3", "room": "cg-voice-74blnuc9c6", "room_id": "RM_q3vSy5gNpvMA", "timestamp": "2026-10-27T15:32:59.437282+00:00"}
{"message": "process exiting", "level": "INFO", "name": "livekit.agents", "reason": "parent process shutdown", "pid": 8879, "job_id": "AJ_QhLhdpZKohT3", "room": "cg-voice-74blnuc9c6", "room_id": "RM_q3vSy5gNpvMA", "timestamp": "2026-10-27T15:33:01.444726+00:00"}
{"message": "received job request", "level": "INFO", "name": "livekit.agents", "job_id": "AJ_4vPjEVFaLPMq", "dispatch_id": "AD_MVu9k7fFLQyy", "room": "cg-voice-qeghdnxehn", "room_id": "RM_EMAorR7C2evE", "agent_name": "cybergril-voice-agent", "resuming": false, "enable_recording": false, "pid": 8800, "timestamp": "2026-10-27T15:33:23.836716+00:00"}
{"message": "initializing process", "level": "INFO", "name": "livekit.agents", "pid": 11830, "timestamp": "2026-10-27T15:33:23.895593+00:00"}
{"message": "process initialized", "level": "INFO", "name": "livekit.agents", "pid": 11830, "elapsed_time": 0.12, "timestamp": "2026-10-27T15:33:24.010796+00:00"}
Nvidia Decoder is supported.
{"message": "RoomIO linked to participant", "level": "INFO", "name": "livekit.agents", "lk.pii.participant_identity": "user-gwhvezcz", "lk.pii.room_name": "cg-voice-qeghdnxehn", "pid": 8881, "job_id": "AJ_4vPjEVFaLPMq", "room": "cg-voice-qeghdnxehn", "room_id": "RM_EMAorR7C2evE", "timestamp": "2026-10-27T15:33:24.256405+00:00"}
{"message": "adaptive interruption is disabled by default in production mode", "level": "INFO", "name": "livekit.agents", "pid": 8881, "job_id": "AJ_4vPjEVFaLPMq", "room": "cg-voice-qeghdnxehn", "room_id": "RM_EMAorR7C2evE", "timestamp": "2026-10-27T15:33:24.260750+00:00"}
{"message": "failed to emit event conversation_item_added\nTraceback (most recent call last):\n File \"/opt/voice-agent/agent/venv/lib/python3.12/site-packages/livekit/rtc/event_emitter.py\", line 70, in emit\n callback(*callback_args)\n File \"/opt/voice-agent-cybergril/cybergril/voice/worker.py\", line 193, in _on_item\n if ev.item.role == \"assistant\":\n ^^^^^^^^^^^^\n File \"/opt/voice-agent/agent/venv/lib/python3.12/site-packages/pydantic/main.py\", line 1105, in __getattr__\n raise AttributeError(f'{type(self).__name__!r} object has no attribute {item!r}')\nAttributeError: 'AgentHandoff' object has no attribute 'role'", "level": "ERROR", "name": "livekit", "exc_info": "Traceback (most recent call last):\n File \"/opt/voice-agent/agent/venv/lib/python3.12/site-packages/livekit/rtc/event_emitter.py\", line 70, in emit\n callback(*callback_args)\n File \"/opt/voice-agent-cybergril/cybergril/voice/worker.py\", line 193, in _on_item\n if ev.item.role == \"assistant\":\n ^^^^^^^^^^^^\n File \"/opt/voice-agent/agent/venv/lib/python3.12/site-packages/pydantic/main.py\", line 1105, in __getattr__\n raise AttributeError(f'{type(self).__name__!r} object has no attribute {item!r}')\nAttributeError: 'AgentHandoff' object has no attribute 'role'", "pid": 8881, "job_id": "AJ_4vPjEVFaLPMq", "room": "cg-voice-qeghdnxehn", "room_id": "RM_EMAorR7C2evE", "timestamp": "2026-10-27T15:33:24.261961+00:00"}
[cybergril-worker] session started (api=http://127.0.0.1:8360, voice=female_soft, agent_name=cybergril-voice-agent)
[stt] utterance open, frame sr=24000
[stt] interim: 盯
[stt->session] interim_transcript: 盯
[stt] interim: 着你看怎
[stt->session] interim_transcript: 着你看怎
[stt] interim: 么啦
[stt->session] interim_transcript: 么啦
[stt] FINAL(merged): 盯着你看怎么啦
[stt->session] final_transcript: 盯着你看怎么啦
[stt] interim: 难得
[stt->session] interim_transcript: 难得
[stt] interim: 看你这么
[stt->session] interim_transcript: 看你这么
[stt] interim: 有意思的
[stt->session] interim_transcript: 有意思的
[stt] interim: 样子
[stt->session] interim_transcript: 样子
[stt] FINAL(merged): 难得看你这么有意思的样子吗
[stt->session] final_transcript: 难得看你这么有意思的样子吗
{"message": "transcript arrives after turn has been committed. consider raising `min_delay` in the endpointing options to accommodate a slow stt. subsequent occurrences will log at debug level.", "level": "WARNING", "name": "livekit.agents", "pid": 8881, "job_id": "AJ_4vPjEVFaLPMq", "room": "cg-voice-qeghdnxehn", "room_id": "RM_EMAorR7C2evE", "timestamp": "2026-10-27T15:33:30.501478+00:00"}
[stt] interim: 烈会军校
[stt->session] interim_transcript: 烈会军校
[stt] interim: 校啊招
[stt->session] interim_transcript: 校啊招
[stt] interim: 啊这种类
[stt->session] interim_transcript: 啊这种类
[stt] interim: 型我都是
[stt->session] interim_transcript: 型我都是
[stt] interim: 那种比较
[stt->session] interim_transcript: 那种比较
[stt] interim: 呃豪
[stt->session] interim_transcript: 呃豪
[stt] interim: 爽比
[stt->session] interim_transcript: 爽比
[stt] interim: 较开
[stt->session] interim_transcript: 较开
[stt] interim: 朗的那种
[stt->session] interim_transcript: 朗的那种
[stt] interim: 类型
[stt->session] interim_transcript: 类型
[stt] interim: 阳光大
[stt->session] interim_transcript: 阳光大
[stt] interim: 女孩
[stt->session] interim_transcript: 女孩
[stt] interim: 那种感
[stt->session] interim_transcript: 那种感
[stt] interim: 觉
[stt->session] interim_transcript: 觉
[stt] interim: 都给
[stt->session] interim_transcript: 都给
[stt] FINAL(merged): 烈火军校啊,朝阳啊这种类型我都是那种比较呃豪爽,比较开朗的那种类型,阳光大女孩那种感觉就
[stt->session] final_transcript: 烈火军校啊,朝阳啊这种类型我都是那种比较呃豪爽,比较开朗的那种类型,阳光大女孩那
[tts] first audio after 3.335s (原来你是那种阳光大女孩的类型啊,难怪感觉...)
{"message": "livekit::rtc_engine::rtc_session:704:livekit::rtc_engine::rtc_session - publisher data channel '_reliable' closed unexpectedly", "level": "ERROR", "name": "livekit", "pid": 8885, "job_id": "AJ_dcfbVLNh9cxc", "room": "probe-room-2", "room_id": "RM_mNZagsczYXao", "timestamp": "2026-10-27T15:33:57.436181+00:00"}
{"message": "livekit::rtc_engine::rtc_session:704:livekit::rtc_engine::rtc_session - publisher data channel '_lossy' closed unexpectedly", "level": "ERROR", "name": "livekit", "pid": 8885, "job_id": "AJ_dcfbVLNh9cxc", "room": "probe-room-2", "room_id": "RM_mNZagsczYXao", "timestamp": "2026-10-27T15:33:57.437201+00:00"}
{"message": "livekit::rtc_engine::rtc_session:704:livekit::rtc_engine::rtc_session - publisher data channel '_data_track' closed unexpectedly", "level": "ERROR", "name": "livekit", "pid": 8885, "job_id": "AJ_dcfbVLNh9cxc", "room": "probe-room-2", "room_id": "RM_mNZagsczYXao", "timestamp": "2026-10-27T15:33:57.437449+00:00"}
{"message": "process exiting", "level": "INFO", "name": "livekit.agents", "reason": "parent process shutdown", "pid": 8885, "job_id": "AJ_dcfbVLNh9cxc", "room": "probe-room-2", "room_id": "RM_mNZagsczYXao", "timestamp": "2026-10-27T15:33:59.447143+00:00"}
[stt] interim: 怎么还
[stt->session] interim_transcript: 怎么还
[stt] interim: 有没事
[stt->session] interim_transcript: 有没事
[stt] interim: 的
[stt->session] interim_transcript: 的
[stt] FINAL(merged): 怎么还有没事的
[stt->session] final_transcript: 怎么还有没事的
[stt] interim: 是我不够
[stt->session] interim_transcript: 是我不够
[stt] interim: 努力
[stt->session] interim_transcript: 努力
[stt] FINAL(merged): 是我不够努力吗
[stt->session] final_transcript: 是我不够努力吗
[stt] interim: 盯着你看
[stt->session] interim_transcript: 盯着你看
[stt] interim: 怎么
[stt->session] interim_transcript: 怎么
[stt] FINAL(merged): 盯着你看怎么啦
[stt->session] final_transcript: 盯着你看怎么啦
[stt] interim: 难得
[stt->session] interim_transcript: 难得
[stt] interim: 看你这么
[stt->session] interim_transcript: 看你这么
[stt] interim: 有意思的
[stt->session] interim_transcript: 有意思的
[stt] interim: 样子
[stt->session] interim_transcript: 样子
{"message": "closing agent session due to participant disconnect (disable via `RoomInputOptions.close_on_disconnect=False`)", "level": "INFO", "name": "livekit.agents", "room": "cg-voice-qeghdnxehn", "participant": "user-gwhvezcz", "reason": "ROOM_DELETED", "pid": 8881, "job_id": "AJ_4vPjEVFaLPMq", "room_id": "RM_EMAorR7C2evE", "timestamp": "2026-10-27T15:34:12.027601+00:00"}
{"message": "skipping user input, speech scheduling is paused", "level": "WARNING", "name": "livekit.agents", "lk.pii.user_input": "盯着你看怎么啦", "pid": 8881, "job_id": "AJ_4vPjEVFaLPMq", "room": "cg-voice-qeghdnxehn", "room_id": "RM_EMAorR7C2evE", "timestamp": "2026-10-27T15:34:12.040356+00:00"}
{"message": "failed to send session event\nTraceback (most recent call last):\n File \"/opt/voice-agent/agent/venv/lib/python3.12/site-packages/livekit/agents/voice/remote_session.py\", line 477, in _write_loop\n await self._transport.send_message(msg)\n File \"/opt/voice-agent/agent/venv/lib/python3.12/site-packages/livekit/agents/voice/remote_session.py\", line 153, in send_message\n raise RuntimeError(\"room session transport is closed\")\nRuntimeError: room session transport is closed", "level": "WARNING", "name": "livekit.agents", "exc_info": "Traceback (most recent call last):\n File \"/opt/voice-agent/agent/venv/lib/python3.12/site-packages/livekit/agents/voice/remote_session.py\", line 477, in _write_loop\n await self._transport.send_message(msg)\n File \"/opt/voice-agent/agent/venv/lib/python3.12/site-packages/livekit/agents/voice/remote_session.py\", line 153, in send_message\n raise RuntimeError(\"room session transport is closed\")\nRuntimeError: room session transport is closed", "pid": 8881, "job_id": "AJ_4vPjEVFaLPMq", "room": "cg-voice-qeghdnxehn", "room_id": "RM_EMAorR7C2evE", "timestamp": "2026-10-27T15:34:12.042202+00:00"}
[stt] FINAL(merged): 难得看你这么有意思的样子吗
[stt->session] final_transcript: 难得看你这么有意思的样子吗
{"message": "failed to send session event\nTraceback (most recent call last):\n File \"/opt/voice-agent/agent/venv/lib/python3.12/site-packages/livekit/agents/voice/remote_session.py\", line 477, in _write_loop\n await self._transport.send_message(msg)\n File \"/opt/voice-agent/agent/venv/lib/python3.12/site-packages/livekit/agents/voice/remote_session.py\", line 153, in send_message\n raise RuntimeError(\"room session transport is closed\")\nRuntimeError: room session transport is closed", "level": "WARNING", "name": "livekit.agents", "exc_info": "Traceback (most recent call last):\n File \"/opt/voice-agent/agent/venv/lib/python3.12/site-packages/livekit/agents/voice/remote_session.py\", line 477, in _write_loop\n await self._transport.send_message(msg)\n File \"/opt/voice-agent/agent/venv/lib/python3.12/site-packages/livekit/agents/voice/remote_session.py\", line 153, in send_message\n raise RuntimeError(\"room session transport is closed\")\nRuntimeError: room session transport is closed", "pid": 8881, "job_id": "AJ_4vPjEVFaLPMq", "room": "cg-voice-qeghdnxehn", "room_id": "RM_EMAorR7C2evE", "timestamp": "2026-10-27T15:34:12.890538+00:00"}
{"message": "skipping user input, speech scheduling is paused", "level": "WARNING", "name": "livekit.agents", "lk.pii.user_input": "难得看你这么有意思的样子吗", "pid": 8881, "job_id": "AJ_4vPjEVFaLPMq", "room": "cg-voice-qeghdnxehn", "room_id": "RM_EMAorR7C2evE", "timestamp": "2026-10-27T15:34:12.891932+00:00"}
{"message": "failed to send session event\nTraceback (most recent call last):\n File \"/opt/voice-agent/agent/venv/lib/python3.12/site-packages/livekit/agents/voice/remote_session.py\", line 477, in _write_loop\n await self._transport.send_message(msg)\n File \"/opt/voice-agent/agent/venv/lib/python3.12/site-packages/livekit/agents/voice/remote_session.py\", line 153, in send_message\n raise RuntimeError(\"room session transport is closed\")\nRuntimeError: room session transport is closed", "level": "WARNING", "name": "livekit.agents", "exc_info": "Traceback (most recent call last):\n File \"/opt/voice-agent/agent/venv/lib/python3.12/site-packages/livekit/agents/voice/remote_session.py\", line 477, in _write_loop\n await self._transport.send_message(msg)\n File \"/opt/voice-agent/agent/venv/lib/python3.12/site-packages/livekit/agents/voice/remote_session.py\", line 153, in send_message\n raise RuntimeError(\"room session transport is closed\")\nRuntimeError: room session transport is closed", "pid": 8881, "job_id": "AJ_4vPjEVFaLPMq", "room": "cg-voice-qeghdnxehn", "room_id": "RM_EMAorR7C2evE", "timestamp": "2026-10-27T15:34:12.892740+00:00"}
{"message": "Task exception was never retrieved\nfuture: <Task finished name='Task-453' coro=<LocalParticipant.publish_data() done, defined at /opt/voice-agent/agent/venv/lib/python3.12/site-packages/livekit/rtc/participant.py:315> exception=PublishDataError('engine: connection error: engine is closed')>\nTraceback (most recent call last):\n File \"/opt/voice-agent/agent/venv/lib/python3.12/site-packages/livekit/rtc/participant.py\", line 359, in publish_data\n raise PublishDataError(cb.publish_data.error)\nlivekit.rtc.participant.PublishDataError: engine: connection error: engine is closed", "level": "ERROR", "name": "asyncio", "exc_info": "Traceback (most recent call last):\n File \"/opt/voice-agent/agent/venv/lib/python3.12/site-packages/livekit/rtc/participant.py\", line 359, in publish_data\n raise PublishDataError(cb.publish_data.error)\nlivekit.rtc.participant.PublishDataError: engine: connection error: engine is closed", "pid": 8881, "job_id": "AJ_4vPjEVFaLPMq", "room": "cg-voice-qeghdnxehn", "room_id": "RM_EMAorR7C2evE", "timestamp": "2026-10-27T15:34:12.894879+00:00"}
{"message": "process exiting", "level": "INFO", "name": "livekit.agents", "reason": "parent process shutdown", "pid": 8881, "job_id": "AJ_4vPjEVFaLPMq", "room": "cg-voice-qeghdnxehn", "room_id": "RM_EMAorR7C2evE", "timestamp": "2026-10-27T15:34:12.911612+00:00"}
{"message": "received job request", "level": "INFO", "name": "livekit.agents", "job_id": "AJ_HpxBoFkqfBdm", "dispatch_id": "AD_QPVzQMzStdbx", "room": "cg-voice-ymtapbx42a", "room_id": "RM_5BfQDxZuTsu4", "agent_name": "cybergril-voice-agent", "resuming": false, "enable_recording": false, "pid": 8800, "timestamp": "2026-10-27T15:37:07.605078+00:00"}
{"message": "initializing process", "level": "INFO", "name": "livekit.agents", "pid": 12418, "timestamp": "2026-10-27T15:37:07.631804+00:00"}
{"message": "process initialized", "level": "INFO", "name": "livekit.agents", "pid": 12418, "elapsed_time": 0.11, "timestamp": "2026-10-27T15:37:07.743701+00:00"}
Nvidia Decoder is supported.
{"message": "adaptive interruption is disabled by default in production mode", "level": "INFO", "name": "livekit.agents", "pid": 10801, "job_id": "AJ_HpxBoFkqfBdm", "room": "cg-voice-ymtapbx42a", "room_id": "RM_5BfQDxZuTsu4", "timestamp": "2026-10-27T15:37:08.025546+00:00"}
{"message": "failed to emit event conversation_item_added\nTraceback (most recent call last):\n File \"/opt/voice-agent/agent/venv/lib/python3.12/site-packages/livekit/rtc/event_emitter.py\", line 70, in emit\n callback(*callback_args)\n File \"/opt/voice-agent-cybergril/cybergril/voice/worker.py\", line 193, in _on_item\n if ev.item.role == \"assistant\":\n ^^^^^^^^^^^^\n File \"/opt/voice-agent/agent/venv/lib/python3.12/site-packages/pydantic/main.py\", line 1105, in __getattr__\n raise AttributeError(f'{type(self).__name__!r} object has no attribute {item!r}')\nAttributeError: 'AgentHandoff' object has no attribute 'role'", "level": "ERROR", "name": "livekit", "exc_info": "Traceback (most recent call last):\n File \"/opt/voice-agent/agent/venv/lib/python3.12/site-packages/livekit/rtc/event_emitter.py\", line 70, in emit\n callback(*callback_args)\n File \"/opt/voice-agent-cybergril/cybergril/voice/worker.py\", line 193, in _on_item\n if ev.item.role == \"assistant\":\n ^^^^^^^^^^^^\n File \"/opt/voice-agent/agent/venv/lib/python3.12/site-packages/pydantic/main.py\", line 1105, in __getattr__\n raise AttributeError(f'{type(self).__name__!r} object has no attribute {item!r}')\nAttributeError: 'AgentHandoff' object has no attribute 'role'", "pid": 10801, "job_id": "AJ_HpxBoFkqfBdm", "room": "cg-voice-ymtapbx42a", "room_id": "RM_5BfQDxZuTsu4", "timestamp": "2026-10-27T15:37:08.026426+00:00"}
[cybergril-worker] session started (api=http://127.0.0.1:8360, voice=female_soft, agent_name=cybergril-voice-agent)
{"message": "received job request", "level": "INFO", "name": "livekit.agents", "job_id": "AJ_MLg4JBtoku27", "dispatch_id": "AD_uMQVHwFMfKqf", "room": "cg-voice-5c4y64wxkl", "room_id": "RM_YDaqgLrwdfsB", "agent_name": "cybergril-voice-agent", "resuming": false, "enable_recording": false, "pid": 8800, "timestamp": "2026-10-27T15:37:11.445956+00:00"}
{"message": "initializing process", "level": "INFO", "name": "livekit.agents", "pid": 12620, "timestamp": "2026-10-27T15:37:11.462186+00:00"}
{"message": "process initialized", "level": "INFO", "name": "livekit.agents", "pid": 12620, "elapsed_time": 0.12, "timestamp": "2026-10-27T15:37:11.583631+00:00"}
Nvidia Decoder is supported.
{"message": "RoomIO linked to participant", "level": "INFO", "name": "livekit.agents", "lk.pii.participant_identity": "user-vdywshve", "lk.pii.room_name": "cg-voice-5c4y64wxkl", "pid": 11093, "job_id": "AJ_MLg4JBtoku27", "room": "cg-voice-5c4y64wxkl", "room_id": "RM_YDaqgLrwdfsB", "timestamp": "2026-10-27T15:37:11.840352+00:00"}
{"message": "adaptive interruption is disabled by default in production mode", "level": "INFO", "name": "livekit.agents", "pid": 11093, "job_id": "AJ_MLg4JBtoku27", "room": "cg-voice-5c4y64wxkl", "room_id": "RM_YDaqgLrwdfsB", "timestamp": "2026-10-27T15:37:11.842501+00:00"}
{"message": "failed to emit event conversation_item_added\nTraceback (most recent call last):\n File \"/opt/voice-agent/agent/venv/lib/python3.12/site-packages/livekit/rtc/event_emitter.py\", line 70, in emit\n callback(*callback_args)\n File \"/opt/voice-agent-cybergril/cybergril/voice/worker.py\", line 193, in _on_item\n if ev.item.role == \"assistant\":\n ^^^^^^^^^^^^\n File \"/opt/voice-agent/agent/venv/lib/python3.12/site-packages/pydantic/main.py\", line 1105, in __getattr__\n raise AttributeError(f'{type(self).__name__!r} object has no attribute {item!r}')\nAttributeError: 'AgentHandoff' object has no attribute 'role'", "level": "ERROR", "name": "livekit", "exc_info": "Traceback (most recent call last):\n File \"/opt/voice-agent/agent/venv/lib/python3.12/site-packages/livekit/rtc/event_emitter.py\", line 70, in emit\n callback(*callback_args)\n File \"/opt/voice-agent-cybergril/cybergril/voice/worker.py\", line 193, in _on_item\n if ev.item.role == \"assistant\":\n ^^^^^^^^^^^^\n File \"/opt/voice-agent/agent/venv/lib/python3.12/site-packages/pydantic/main.py\", line 1105, in __getattr__\n raise AttributeError(f'{type(self).__name__!r} object has no attribute {item!r}')\nAttributeError: 'AgentHandoff' object has no attribute 'role'", "pid": 11093, "job_id": "AJ_MLg4JBtoku27", "room": "cg-voice-5c4y64wxkl", "room_id": "RM_YDaqgLrwdfsB", "timestamp": "2026-10-27T15:37:11.844023+00:00"}
[cybergril-worker] session started (api=http://127.0.0.1:8360, voice=female_soft, agent_name=cybergril-voice-agent)
[stt] utterance open, frame sr=24000
[stt] interim: 没
[stt->session] interim_transcript: 没
[stt] interim: 错
[stt->session] interim_transcript: 错
[stt] interim: 我就是个
[stt->session] interim_transcript: 我就是个
[stt] interim: 笨蛋
[stt->session] interim_transcript: 笨蛋
[stt] interim: 派大星
[stt->session] interim_transcript: 派大星
[stt] interim: 我们
[stt->session] interim_transcript: 我们
[stt] interim: 一起去
[stt->session] interim_transcript: 一起去
[stt] interim: 抓水母
[stt->session] interim_transcript: 抓水母
[stt] interim: 吧我
[stt->session] interim_transcript: 吧我
[stt] interim: 们一起度
[stt->session] interim_transcript: 们一起度
[stt] interim: 过快乐的
[stt->session] interim_transcript: 过快乐的
[stt] interim: 一天
[stt->session] interim_transcript: 一天
[stt] FINAL(merged): 没错,我就是个笨蛋派大星,我们一起去抓水母吧,我们一起度过快乐的一天
[stt->session] final_transcript: 没错,我就是个笨蛋派大星,我们一起去抓水母吧,我们一起度过快乐的一天
[tts] first audio after 3.909s (好呀,那我们这就出发去抓水母!不过我可不...)
{"message": "livekit::rtc_engine::rtc_session:704:livekit::rtc_engine::rtc_session - publisher data channel '_reliable' closed unexpectedly", "level": "ERROR", "name": "livekit", "pid": 8883, "job_id": "AJ_FBBgf5Si2JYd", "room": "probe-room-3", "room_id": "RM_msdxb3QWFMYf", "timestamp": "2026-10-27T15:37:32.436074+00:00"}
{"message": "livekit::rtc_engine::rtc_session:704:livekit::rtc_engine::rtc_session - publisher data channel '_lossy' closed unexpectedly", "level": "ERROR", "name": "livekit", "pid": 8883, "job_id": "AJ_FBBgf5Si2JYd", "room": "probe-room-3", "room_id": "RM_msdxb3QWFMYf", "timestamp": "2026-10-27T15:37:32.436405+00:00"}
{"message": "livekit::rtc_engine::rtc_session:704:livekit::rtc_engine::rtc_session - publisher data channel '_data_track' closed unexpectedly", "level": "ERROR", "name": "livekit", "pid": 8883, "job_id": "AJ_FBBgf5Si2JYd", "room": "probe-room-3", "room_id": "RM_msdxb3QWFMYf", "timestamp": "2026-10-27T15:37:32.436558+00:00"}
{"message": "process exiting", "level": "INFO", "name": "livekit.agents", "reason": "room disconnected", "pid": 8883, "job_id": "AJ_FBBgf5Si2JYd", "room": "probe-room-3", "room_id": "RM_msdxb3QWFMYf", "timestamp": "2026-10-27T15:37:34.449378+00:00"}
[stt] interim: 天雷滚
[stt->session] interim_transcript: 天雷滚
[stt] interim: 滚我好
[stt->session] interim_transcript: 滚我好
[stt] interim: 怕怕
[stt->session] interim_transcript: 怕怕
[stt] interim: 劈
[stt->session] interim_transcript: 劈
[stt] interim: 的我浑
[stt->session] interim_transcript: 的我浑
[stt] interim: 身掉渣
[stt->session] interim_transcript: 身掉渣
[stt] interim: 渣突
[stt->session] interim_transcript: 渣突
[stt] interim: 破天
[stt->session] interim_transcript: 破天
[stt] interim: 劫我笑
[stt->session] interim_transcript: 劫我笑
[stt] interim: 哈哈
[stt->session] interim_transcript: 哈哈
[stt] interim: 逆
[stt->session] interim_transcript: 逆
[stt] interim: 天改命
[stt->session] interim_transcript: 天改命
[stt] interim: 我吹喇
[stt->session] interim_transcript: 我吹喇
[stt] interim: 叭滴
[stt->session] interim_transcript: 叭滴
[stt] interim: 答滴答
[stt->session] interim_transcript: 答滴答
[stt] interim: 滴滴
[stt->session] interim_transcript: 滴滴
[stt] FINAL(merged): 天雷滚滚,我好怕怕劈的我浑身掉渣渣,突破天劫,我笑哈哈逆天改命,我吹喇叭滴答滴答,滴滴答
[stt->session] final_transcript: 天雷滚滚,我好怕怕劈的我浑身掉渣渣,突破天劫,我笑哈哈逆天改命,我吹喇叭滴答滴答
[tts] first audio after 3.008s (哈哈,你唱得还挺有节奏感的!...)
[stt] interim: 你是不是
[stt->session] interim_transcript: 你是不是
[stt] interim: 早就想
[stt->session] interim_transcript: 早就想
[stt] interim: 睡我
[stt->session] interim_transcript: 睡我
[stt] interim: 了
[stt->session] interim_transcript: 了
[stt] FINAL(merged): 你是不是早就想睡我了呀
[stt->session] final_transcript: 你是不是早就想睡我了呀
[tts] first audio after 3.304s (哈哈,你思维跳跃得太快了,我都跟不上。...)
[stt] interim: 盯着你看
[stt->session] interim_transcript: 盯着你看
[stt] interim: 怎么啦
[stt->session] interim_transcript: 怎么啦
[stt] FINAL(merged): 盯着你看怎么啦
[stt->session] final_transcript: 盯着你看怎么啦
[tts] first audio after 2.405s (没...)
{"message": "closing agent session due to participant disconnect (disable via `RoomInputOptions.close_on_disconnect=False`)", "level": "INFO", "name": "livekit.agents", "room": "cg-voice-5c4y64wxkl", "participant": "user-vdywshve", "reason": "ROOM_DELETED", "pid": 11093, "job_id": "AJ_MLg4JBtoku27", "room_id": "RM_YDaqgLrwdfsB", "timestamp": "2026-10-27T15:38:32.340079+00:00"}
{"message": "livekit::rtc_engine::rtc_session:704:livekit::rtc_engine::rtc_session - publisher data channel '_reliable' closed unexpectedly", "level": "ERROR", "name": "livekit", "pid": 11093, "job_id": "AJ_MLg4JBtoku27", "room": "cg-voice-5c4y64wxkl", "room_id": "RM_YDaqgLrwdfsB", "timestamp": "2026-10-27T15:38:32.351146+00:00"}
{"message": "livekit::rtc_engine::rtc_session:704:livekit::rtc_engine::rtc_session - publisher data channel '_lossy' closed unexpectedly", "level": "ERROR", "name": "livekit", "pid": 11093, "job_id": "AJ_MLg4JBtoku27", "room": "cg-voice-5c4y64wxkl", "room_id": "RM_YDaqgLrwdfsB", "timestamp": "2026-10-27T15:38:32.352194+00:00"}
{"message": "livekit::rtc_engine::rtc_session:704:livekit::rtc_engine::rtc_session - publisher data channel '_data_track' closed unexpectedly", "level": "ERROR", "name": "livekit", "pid": 11093, "job_id": "AJ_MLg4JBtoku27", "room": "cg-voice-5c4y64wxkl", "room_id": "RM_YDaqgLrwdfsB", "timestamp": "2026-10-27T15:38:32.353282+00:00"}
{"message": "process exiting", "level": "INFO", "name": "livekit.agents", "reason": "parent process shutdown", "pid": 11093, "job_id": "AJ_MLg4JBtoku27", "room": "cg-voice-5c4y64wxkl", "room_id": "RM_YDaqgLrwdfsB", "timestamp": "2026-10-27T15:38:34.358814+00:00"}
{"message": "received job request", "level": "INFO", "name": "livekit.agents", "job_id": "AJ_a8uXggcKPamC", "dispatch_id": "AD_fxrSDuKoYdTr", "room": "cg-voice-6d0grxxwno", "room_id": "RM_bHsWETGZc5cQ", "agent_name": "cybergril-voice-agent", "resuming": false, "enable_recording": false, "pid": 8800, "timestamp": "2026-10-27T15:41:10.456804+00:00"}
{"message": "initializing process", "level": "INFO", "name": "livekit.agents", "pid": 13261, "timestamp": "2026-10-27T15:41:10.492898+00:00"}
{"message": "process initialized", "level": "INFO", "name": "livekit.agents", "pid": 13261, "elapsed_time": 0.12, "timestamp": "2026-10-27T15:41:10.613693+00:00"}
Nvidia Decoder is supported.
{"message": "adaptive interruption is disabled by default in production mode", "level": "INFO", "name": "livekit.agents", "pid": 11326, "job_id": "AJ_a8uXggcKPamC", "room": "cg-voice-6d0grxxwno", "room_id": "RM_bHsWETGZc5cQ", "timestamp": "2026-10-27T15:41:10.904892+00:00"}
{"message": "failed to emit event conversation_item_added\nTraceback (most recent call last):\n File \"/opt/voice-agent/agent/venv/lib/python3.12/site-packages/livekit/rtc/event_emitter.py\", line 70, in emit\n callback(*callback_args)\n File \"/opt/voice-agent-cybergril/cybergril/voice/worker.py\", line 193, in _on_item\n if ev.item.role == \"assistant\":\n ^^^^^^^^^^^^\n File \"/opt/voice-agent/agent/venv/lib/python3.12/site-packages/pydantic/main.py\", line 1105, in __getattr__\n raise AttributeError(f'{type(self).__name__!r} object has no attribute {item!r}')\nAttributeError: 'AgentHandoff' object has no attribute 'role'", "level": "ERROR", "name": "livekit", "exc_info": "Traceback (most recent call last):\n File \"/opt/voice-agent/agent/venv/lib/python3.12/site-packages/livekit/rtc/event_emitter.py\", line 70, in emit\n callback(*callback_args)\n File \"/opt/voice-agent-cybergril/cybergril/voice/worker.py\", line 193, in _on_item\n if ev.item.role == \"assistant\":\n ^^^^^^^^^^^^\n File \"/opt/voice-agent/agent/venv/lib/python3.12/site-packages/pydantic/main.py\", line 1105, in __getattr__\n raise AttributeError(f'{type(self).__name__!r} object has no attribute {item!r}')\nAttributeError: 'AgentHandoff' object has no attribute 'role'", "pid": 11326, "job_id": "AJ_a8uXggcKPamC", "room": "cg-voice-6d0grxxwno", "room_id": "RM_bHsWETGZc5cQ", "timestamp": "2026-10-27T15:41:10.905825+00:00"}
[cybergril-worker] session started (api=http://127.0.0.1:8360, voice=female_soft, agent_name=cybergril-voice-agent)
{"message": "livekit::rtc_engine::rtc_session:704:livekit::rtc_engine::rtc_session - publisher data channel '_reliable' closed unexpectedly", "level": "ERROR", "name": "livekit", "pid": 11326, "job_id": "AJ_a8uXggcKPamC", "room": "cg-voice-6d0grxxwno", "room_id": "RM_bHsWETGZc5cQ", "timestamp": "2026-10-27T15:41:30.437268+00:00"}
{"message": "livekit::rtc_engine::rtc_session:704:livekit::rtc_engine::rtc_session - publisher data channel '_lossy' closed unexpectedly", "level": "ERROR", "name": "livekit", "pid": 11326, "job_id": "AJ_a8uXggcKPamC", "room": "cg-voice-6d0grxxwno", "room_id": "RM_bHsWETGZc5cQ", "timestamp": "2026-10-27T15:41:30.437449+00:00"}
{"message": "livekit::rtc_engine::rtc_session:704:livekit::rtc_engine::rtc_session - publisher data channel '_data_track' closed unexpectedly", "level": "ERROR", "name": "livekit", "pid": 11326, "job_id": "AJ_a8uXggcKPamC", "room": "cg-voice-6d0grxxwno", "room_id": "RM_bHsWETGZc5cQ", "timestamp": "2026-10-27T15:41:30.437534+00:00"}
{"message": "received job request", "level": "INFO", "name": "livekit.agents", "job_id": "AJ_sfYen6Augi9u", "dispatch_id": "AD_UmNB9yQbmDEK", "room": "cg-voice-oleganioz7", "room_id": "RM_anqqGcUZrCbe", "agent_name": "cybergril-voice-agent", "resuming": false, "enable_recording": false, "pid": 8800, "timestamp": "2026-10-27T15:41:30.448381+00:00"}
{"message": "initializing process", "level": "INFO", "name": "livekit.agents", "pid": 13614, "timestamp": "2026-10-27T15:41:30.549122+00:00"}
{"message": "process initialized", "level": "INFO", "name": "livekit.agents", "pid": 13614, "elapsed_time": 0.12, "timestamp": "2026-10-27T15:41:30.671152+00:00"}
Nvidia Decoder is supported.
{"message": "RoomIO linked to participant", "level": "INFO", "name": "livekit.agents", "lk.pii.participant_identity": "user-uqfyuhnt", "lk.pii.room_name": "cg-voice-oleganioz7", "pid": 11830, "job_id": "AJ_sfYen6Augi9u", "room": "cg-voice-oleganioz7", "room_id": "RM_anqqGcUZrCbe", "timestamp": "2026-10-27T15:41:30.841632+00:00"}
{"message": "adaptive interruption is disabled by default in production mode", "level": "INFO", "name": "livekit.agents", "pid": 11830, "job_id": "AJ_sfYen6Augi9u", "room": "cg-voice-oleganioz7", "room_id": "RM_anqqGcUZrCbe", "timestamp": "2026-10-27T15:41:30.843597+00:00"}
{"message": "failed to emit event conversation_item_added\nTraceback (most recent call last):\n File \"/opt/voice-agent/agent/venv/lib/python3.12/site-packages/livekit/rtc/event_emitter.py\", line 70, in emit\n callback(*callback_args)\n File \"/opt/voice-agent-cybergril/cybergril/voice/worker.py\", line 193, in _on_item\n if ev.item.role == \"assistant\":\n ^^^^^^^^^^^^\n File \"/opt/voice-agent/agent/venv/lib/python3.12/site-packages/pydantic/main.py\", line 1105, in __getattr__\n raise AttributeError(f'{type(self).__name__!r} object has no attribute {item!r}')\nAttributeError: 'AgentHandoff' object has no attribute 'role'", "level": "ERROR", "name": "livekit", "exc_info": "Traceback (most recent call last):\n File \"/opt/voice-agent/agent/venv/lib/python3.12/site-packages/livekit/rtc/event_emitter.py\", line 70, in emit\n callback(*callback_args)\n File \"/opt/voice-agent-cybergril/cybergril/voice/worker.py\", line 193, in _on_item\n if ev.item.role == \"assistant\":\n ^^^^^^^^^^^^\n File \"/opt/voice-agent/agent/venv/lib/python3.12/site-packages/pydantic/main.py\", line 1105, in __getattr__\n raise AttributeError(f'{type(self).__name__!r} object has no attribute {item!r}')\nAttributeError: 'AgentHandoff' object has no attribute 'role'", "pid": 11830, "job_id": "AJ_sfYen6Augi9u", "room": "cg-voice-oleganioz7", "room_id": "RM_anqqGcUZrCbe", "timestamp": "2026-10-27T15:41:30.844442+00:00"}
[cybergril-worker] session started (api=http://127.0.0.1:8360, voice=female_soft, agent_name=cybergril-voice-agent)
[stt] utterance open, frame sr=24000
[stt] interim: 师
[stt->session] interim_transcript: 师
[stt] interim: 傅
[stt->session] interim_transcript: 傅
{"message": "process exiting", "level": "INFO", "name": "livekit.agents", "reason": "parent process shutdown", "pid": 11326, "job_id": "AJ_a8uXggcKPamC", "room": "cg-voice-6d0grxxwno", "room_id": "RM_bHsWETGZc5cQ", "timestamp": "2026-10-27T15:41:32.444706+00:00"}
[stt] interim: 哎这
[stt->session] interim_transcript: 哎这
[stt] interim: 取经
[stt->session] interim_transcript: 取经
[stt] interim: 路比
[stt->session] interim_transcript: 路比
[stt] interim: 高老
[stt->session] interim_transcript: 高老
[stt] interim: 庄种
[stt->session] interim_transcript: 庄种
[stt] interim: 地还
[stt->session] interim_transcript: 地还
[stt] interim: 苦俺
[stt->session] interim_transcript: 苦俺
[stt] interim: 老中
[stt->session] interim_transcript: 老中
[stt] interim: 饿的能
[stt->session] interim_transcript: 饿的能
[stt] interim: 吞下
[stt->session] interim_transcript: 吞下
[stt] interim: 石龙
[stt->session] interim_transcript: 石龙
[stt] interim: 素包
[stt->session] interim_transcript: 素包
[stt] interim: 子
[stt->session] interim_transcript: 子
[stt] interim: 猴哥
[stt->session] interim_transcript: 猴哥
[stt] interim: 你筋
[stt->session] interim_transcript: 你筋
[stt] interim: 斗云
[stt->session] interim_transcript: 斗云
[stt] interim: 快
[stt->session] interim_transcript: 快
[stt] interim: 要不
[stt->session] interim_transcript: 要不
[stt] interim: 先
[stt->session] interim_transcript: 先
[stt] interim: 去画个
[stt->session] interim_transcript: 去画个
[stt] FINAL(merged): 师傅,哎,这取经路比高,老庄种地还苦,俺老猪饿的能吞下石龙素包子,猴哥,你筋斗云块,要不先去化个缘
[stt->session] final_transcript: 师傅,哎,这取经路比高,老庄种地还苦,俺老猪饿的能吞下石龙素包子,猴哥,你筋斗云
[tts] first audio after 2.480s (师傅,...)
{"message": "livekit::rtc_engine::rtc_session:704:livekit::rtc_engine::rtc_session - publisher data channel '_reliable' closed unexpectedly", "level": "ERROR", "name": "livekit", "pid": 10801, "job_id": "AJ_HpxBoFkqfBdm", "room": "cg-voice-ymtapbx42a", "room_id": "RM_5BfQDxZuTsu4", "timestamp": "2026-10-27T15:42:07.442254+00:00"}
{"message": "livekit::rtc_engine::rtc_session:704:livekit::rtc_engine::rtc_session - publisher data channel '_lossy' closed unexpectedly", "level": "ERROR", "name": "livekit", "pid": 10801, "job_id": "AJ_HpxBoFkqfBdm", "room": "cg-voice-ymtapbx42a", "room_id": "RM_5BfQDxZuTsu4", "timestamp": "2026-10-27T15:42:07.442585+00:00"}
{"message": "livekit::rtc_engine::rtc_session:704:livekit::rtc_engine::rtc_session - publisher data channel '_data_track' closed unexpectedly", "level": "ERROR", "name": "livekit", "pid": 10801, "job_id": "AJ_HpxBoFkqfBdm", "room": "cg-voice-ymtapbx42a", "room_id": "RM_5BfQDxZuTsu4", "timestamp": "2026-10-27T15:42:07.442726+00:00"}
{"message": "process exiting", "level": "INFO", "name": "livekit.agents", "reason": "parent process shutdown", "pid": 10801, "job_id": "AJ_HpxBoFkqfBdm", "room": "cg-voice-ymtapbx42a", "room_id": "RM_5BfQDxZuTsu4", "timestamp": "2026-10-27T15:42:09.450806+00:00"}
[stt] interim: 俺老
[stt->session] interim_transcript: 俺老
[stt] interim: 孙来
[stt->session] interim_transcript: 孙来
[stt] interim: 也俺
[stt->session] interim_transcript: 也俺
[stt] interim: 乃五百
[stt->session] interim_transcript: 乃五百
[stt] interim: 年前
[stt->session] interim_transcript: 年前
[stt] interim: 大闹
[stt->session] interim_transcript: 大闹
[stt] interim: 天宫的
[stt->session] interim_transcript: 天宫的
[stt] interim: 齐天
[stt->session] interim_transcript: 齐天
[stt] interim: 霸圣
[stt->session] interim_transcript: 霸圣
[stt] interim: 孙悟
[stt->session] interim_transcript: 孙悟
[stt] interim: 空这
[stt->session] interim_transcript: 空这
[stt] interim: 里山
[stt->session] interim_transcript: 里山
[stt] interim: 势险
[stt->session] interim_transcript: 势险
[stt] interim: 峻
[stt->session] interim_transcript: 峻
[stt] interim: 只怕藏
[stt->session] interim_transcript: 只怕藏
[stt] interim: 有妖
[stt->session] interim_transcript: 有妖
[stt] interim: 精妖
[stt->session] interim_transcript: 精妖
[stt] interim: 怪
[stt->session] interim_transcript: 怪
[stt] interim: 哪里
[stt->session] interim_transcript: 哪里
[stt] interim: 逃
[stt->session] interim_transcript: 逃
[stt] interim: 吃完老
[stt->session] interim_transcript: 吃完老
[stt] interim: 我所
[stt->session] interim_transcript: 我所
[stt] interim: 一
[stt->session] interim_transcript: 一
[stt] FINAL(merged): 俺老孙来也,俺乃500年前大闹天宫的齐天大圣孙悟空,这里山势险峻,只怕藏有妖精妖怪,哪里逃吃?俺老孙吧
[stt->session] final_transcript: 俺老孙来也,俺乃500年前大闹天宫的齐天大圣孙悟空,这里山势险峻,只怕藏有妖精妖
[tts] first audio after 2.608s (好...)
[stt] interim: 没
[stt->session] interim_transcript: 没
[stt] interim: 错我
[stt->session] interim_transcript: 错我
[stt] interim: 就是个笨
[stt->session] interim_transcript: 就是个笨
[stt] interim: 蛋
[stt->session] interim_transcript: 蛋
[stt] interim: 派大
[stt->session] interim_transcript: 派大
[stt] interim: 星我们一
[stt->session] interim_transcript: 星我们一
[stt] interim: 起去抓
[stt->session] interim_transcript: 起去抓
[stt] interim: 水母
[stt->session] interim_transcript: 水母
[stt] interim: 吧我们
[stt->session] interim_transcript: 吧我们
[stt] interim: 一起度
[stt->session] interim_transcript: 一起度
[stt] interim: 过快乐的一
[stt->session] interim_transcript: 过快乐的一
[stt] FINAL(merged): 没错,我就是个笨蛋派大星,我们一起去抓水母吧,我们一起度过快乐的一天
[stt->session] final_transcript: 没错,我就是个笨蛋派大星,我们一起去抓水母吧,我们一起度过快乐的一天
[tts] first audio after 2.300s (好...)
[stt] interim: 你是不是
[stt->session] interim_transcript: 你是不是
[stt] interim: 早就想
[stt->session] interim_transcript: 早就想
[stt] interim: 睡我
[stt->session] interim_transcript: 睡我
[stt] interim: 了
[stt->session] interim_transcript: 了
[stt] FINAL(merged): 你是不是早就想睡我了呀
[stt->session] final_transcript: 你是不是早就想睡我了呀
{"message": "failed to generate LLM completion: chat stream ended without final or error (caused by ChatApiConnectionError: chat stream ended without final or error), retrying in 0.1s", "level": "WARNING", "name": "livekit.agents", "llm": "cybergril.voice.llm_chat_api.CyberGrillLLM", "attempt": 1, "pid": 11830, "job_id": "AJ_sfYen6Augi9u", "room": "cg-voice-oleganioz7", "room_id": "RM_anqqGcUZrCbe", "timestamp": "2026-10-27T15:46:34.451048+00:00"}
[tts] first audio after 3.847s (Considering the limi...)
[tts] first audio after 2.284s (</think>...)
{"message": "closing agent session due to participant disconnect (disable via `RoomInputOptions.close_on_disconnect=False`)", "level": "INFO", "name": "livekit.agents", "room": "cg-voice-oleganioz7", "participant": "user-uqfyuhnt", "reason": "CLIENT_INITIATED", "pid": 11830, "job_id": "AJ_sfYen6Augi9u", "room_id": "RM_anqqGcUZrCbe", "timestamp": "2026-10-27T15:46:46.019622+00:00"}
{"message": "process exiting", "level": "INFO", "name": "livekit.agents", "reason": "parent process shutdown", "pid": 11830, "job_id": "AJ_sfYen6Augi9u", "room": "cg-voice-oleganioz7", "room_id": "RM_anqqGcUZrCbe", "timestamp": "2026-10-27T15:47:06.441875+00:00"}
{"message": "livekit_ffi::server:279:livekit_ffi::server - Attempted to drop unknown FFI handle: 16", "level": "WARNING", "name": "livekit", "pid": 11830, "job_id": "AJ_sfYen6Augi9u", "room": "cg-voice-oleganioz7", "room_id": "RM_anqqGcUZrCbe", "timestamp": "2026-10-27T15:47:06.446226+00:00"}
{"message": "draining worker", "level": "INFO", "name": "livekit.agents", "id": "AW_LGViCVagSNNu", "timeout": 3600, "pid": 8800, "timestamp": "2026-10-27T15:58:10.576647+00:00"}
{"message": "shutting down worker", "level": "INFO", "name": "livekit.agents", "id": "AW_LGViCVagSNNu", "pid": 8800, "timestamp": "2026-10-27T15:58:10.577287+00:00"}
@@ -0,0 +1,215 @@
{
"head": "HEL-894 close ownership under cancellation",
"real_llm_requests": 0,
"real_embeddings": 0,
"boundary": "deterministic 512 vectors, non-loopback forbidden",
"protocol": "drain-phase cancel re-arms; finish-phase cancel keeps markers, refuses ticks, lands the layer-owned close exactly once; lock waiters never touch the owner's state",
"checks": [
{
"name": "F1 cancelled caller does not return before its close lands",
"ok": true,
"detail": ""
},
{
"name": "F1 markers stay: closing set, not yet closed, client untouched",
"ok": true,
"detail": "{\"closing\": true, \"closed\": false, \"client_usable\": true}"
},
{
"name": "F1 tick during closing claims nothing and sends nothing",
"ok": true,
"detail": "{\"gateway_arrived\": false, \"gateway_posts\": 0, \"llm_used\": 0, \"batch\": {\"state\": \"queued\", \"reason\": null, \"owner\": null, \"attempts\": 0, \"entry_ids\": null}}"
},
{
"name": "F1 layer-owned close lands exactly once, no live workers",
"ok": true,
"detail": "{\"closed\": true, \"client_usable\": false, \"live_workers\": 0, \"close_calls\": 1, \"closing_still_set\": true, \"batch\": {\"state\": \"queued\", \"reason\": null, \"owner\": null, \"attempts\": 0, \"entry_ids\": null}}"
},
{
"name": "F1 no late error",
"ok": true,
"detail": "None"
},
{
"name": "F1 reopened batch intact and unbilled",
"ok": true,
"detail": "{\"state\": \"queued\", \"reason\": null, \"owner\": null, \"attempts\": 0, \"entry_ids\": null, \"row_done\": 0, \"llm_used\": 0}"
},
{
"name": "F2 cancelled caller does not return before its close lands",
"ok": true,
"detail": ""
},
{
"name": "F2 markers stay: closing set, not yet closed, client untouched",
"ok": true,
"detail": "{\"closing\": true, \"closed\": false, \"client_usable\": true}"
},
{
"name": "F2 tick during closing claims nothing and sends nothing",
"ok": true,
"detail": "{\"gateway_arrived\": false, \"gateway_posts\": 0, \"llm_used\": 0, \"batch\": {\"state\": \"queued\", \"reason\": null, \"owner\": null, \"attempts\": 0, \"entry_ids\": null}}"
},
{
"name": "F2 layer-owned close lands exactly once, no live workers",
"ok": true,
"detail": "{\"closed\": true, \"client_usable\": false, \"live_workers\": 0, \"close_calls\": 1, \"closing_still_set\": true, \"batch\": {\"state\": \"queued\", \"reason\": null, \"owner\": null, \"attempts\": 0, \"entry_ids\": null}}"
},
{
"name": "F2 no late error",
"ok": true,
"detail": "None"
},
{
"name": "F2 reopened batch intact and unbilled",
"ok": true,
"detail": "{\"state\": \"queued\", \"reason\": null, \"owner\": null, \"attempts\": 0, \"entry_ids\": null, \"row_done\": 0, \"llm_used\": 0}"
},
{
"name": "W owner close still pending, waiter cancel keeps the signal",
"ok": true,
"detail": "{\"active_close_pending\": true, \"signal_after_waiter_cancel\": true}"
},
{
"name": "W new batch refused during the owner's close",
"ok": true,
"detail": "{\"state\": \"queued\", \"reason\": null, \"owner\": null, \"attempts\": 0, \"entry_ids\": null}"
},
{
"name": "W original batch still actively owned while blocked",
"ok": true,
"detail": "{\"state\": \"pending\", \"reason\": null, \"owner\": \"a6173b4a79a84dbd6378281c17b23127\", \"attempts\": 1, \"entry_ids\": null}"
},
{
"name": "W original worker completes, one close, new batch untouched",
"ok": true,
"detail": "{\"original_batch\": {\"state\": \"done\", \"reason\": null, \"owner\": null, \"attempts\": 1, \"entry_ids\": \"[\\\"494b27cf-6e99-4e9a-ad17-bc4c9d2f42ec\\\"]\"}, \"new_batch_still\": {\"state\": \"queued\", \"reason\": null, \"owner\": null, \"attempts\": 0, \"entry_ids\": null}, \"row_done\": 1, \"gateway_posts_after_release\": 1, \"client_closed\": true}"
}
],
"finish_cancel_sdk_only": {
"caller_done_after_cancel": false,
"after_cancellation": {
"closing": true,
"closed": false,
"client_usable": true
},
"tick_during_closing": {
"gateway_arrived": false,
"gateway_posts": 0,
"llm_used": 0,
"batch": {
"state": "queued",
"reason": null,
"owner": null,
"attempts": 0,
"entry_ids": null
}
},
"finish_landed": {
"closed": true,
"client_usable": false,
"live_workers": 0,
"close_calls": 1,
"closing_still_set": true,
"batch": {
"state": "queued",
"reason": null,
"owner": null,
"attempts": 0,
"entry_ids": null
}
},
"late_worker_error": null,
"persisted": {
"state": "queued",
"reason": null,
"owner": null,
"attempts": 0,
"entry_ids": null,
"row_done": 0,
"llm_used": 0
}
},
"finish_cancel_then_store_close": {
"caller_done_after_cancel": false,
"after_cancellation": {
"closing": true,
"closed": false,
"client_usable": true
},
"tick_during_closing": {
"gateway_arrived": false,
"gateway_posts": 0,
"llm_used": 0,
"batch": {
"state": "queued",
"reason": null,
"owner": null,
"attempts": 0,
"entry_ids": null
}
},
"finish_landed": {
"closed": true,
"client_usable": false,
"live_workers": 0,
"close_calls": 1,
"closing_still_set": true,
"batch": {
"state": "queued",
"reason": null,
"owner": null,
"attempts": 0,
"entry_ids": null
}
},
"late_worker_error": null,
"persisted": {
"state": "queued",
"reason": null,
"owner": null,
"attempts": 0,
"entry_ids": null,
"row_done": 0,
"llm_used": 0
}
},
"waiter_cancel": {
"active_close_pending": true,
"signal_after_waiter_cancel": true,
"new_batch_during_closing": {
"state": "queued",
"reason": null,
"owner": null,
"attempts": 0,
"entry_ids": null
},
"original_batch_while_blocked": {
"state": "pending",
"reason": null,
"owner": "a6173b4a79a84dbd6378281c17b23127",
"attempts": 1,
"entry_ids": null
},
"after_release": {
"original_batch": {
"state": "done",
"reason": null,
"owner": null,
"attempts": 1,
"entry_ids": "[\"494b27cf-6e99-4e9a-ad17-bc4c9d2f42ec\"]"
},
"new_batch_still": {
"state": "queued",
"reason": null,
"owner": null,
"attempts": 0,
"entry_ids": null
},
"row_done": 1,
"gateway_posts_after_release": 1,
"client_closed": true
}
},
"failures": []
}
@@ -0,0 +1,40 @@
{
"head": "HEL-886 inflight lifecycle",
"real_llm_requests": 0,
"real_embeddings": 0,
"boundary": "deterministic 512 vectors, non-loopback forbidden",
"checks": [
{
"name": "L1_cancel_single_send_and_full_record",
"ok": true,
"detail": "arrived=1 concurrent=1 http_total=1 points=1 batch_live={'state': 'pending', 'owner': '9a6afb62871305fe6c7a056acf0b8797'} batch_concurrent=pending final={'state': 'done', 'reason': None, 'entry_ids': '[\"9838ab55-15ab-4872-b7cd-1ba91faea2d7\"]'} row_done=1"
},
{
"name": "L2_lease_single_send_and_full_record",
"ok": true,
"detail": "arrived=1 concurrent=1 http_total=1 points=1 batch_live={'state': 'pending', 'owner': '8f42725681bcfda7043797c128eb0750'} batch_concurrent=pending final={'state': 'done', 'reason': None, 'entry_ids': '[\"e71ba931-650f-40a3-b571-5945829f6f7c\"]'} row_done=1"
},
{
"name": "L3_shutdown_drains_live_worker_before_close",
"ok": true,
"detail": "arrived=1 waited=0.25s row_done_at=2000000 batch={'state': 'done', 'finished_at': 2000000}"
},
{
"name": "L4a_all_done_finalizes_without_new_budget",
"ok": true,
"detail": "before={'state': 'done', 'entry_ids': '[\"80c8cb6b-e437-478c-a3fb-46d386ac9a07\"]'} after={'state': 'done', 'reason': None, 'entry_ids': '[\"80c8cb6b-e437-478c-a3fb-46d386ac9a07\"]'} http=1 llm_used=1"
},
{
"name": "L4b_partial_keeps_the_hard_gate",
"ok": true,
"detail": "after={'state': 'queued', 'reason': 'semantic_llm_budget_exhausted'} http=0"
},
{
"name": "L5_dead_owner_lease_still_recovers",
"ok": true,
"detail": "recovered={'state': 'failed', 'reason': 'lease_expired'} after={'state': 'done'} http=2"
}
],
"gateway_requests_total": 7,
"failures": []
}
@@ -0,0 +1,172 @@
{
"started": "2026-10-26T19:35:46Z",
"embedding_boundary": "deterministic 512-dimensional vectors installed before TextEmbedding construction; no model loaded; zero real embeddings",
"external_network": "socket.connect rejects all non-loopback hosts",
"install": {
"python": "3.10.12",
"mem0ai": "2.2.1",
"qdrant-client": "1.20.0",
"fastembed": "0.9.1",
"mem0_path": "/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-857-2cf6dc7dfc87/workdir/hel857-venv/lib/python3.10/site-packages/mem0",
"module_sha256": {
"memory/main.py": "f329ca4a17af1fbefb32ac10d4cc7ae7ddbdb7435a87eb74c30915cff98b8b7c",
"memory/storage.py": "5f5ecdadd2b260fd14f36a0f338336eb760275465dd1056a99a47e1ef07702bd",
"embeddings/fastembed.py": "3ec2ae87df5d0bebedb8b5471366c3412975847458499f66097f7b8c9ac8c9b8",
"llms/openai.py": "37e6d3c22b55df0731740f4e0868b95bd480ce44363227fe53461db328c5f67e",
"vector_stores/qdrant.py": "da570579d5cad69e6f123fdfe32b725235a25604e70047666bec763ba6e2bf43"
},
"source_tarball_sha256": "9dcef9303ebcec116eec6739f01b8b35bd24459de6a569686b4d19b9412dda23"
},
"adapter_has_llm_guard": true,
"checks": [
{
"name": "C1_construct",
"ok": true,
"detail": "client_path=sdk-vector-store-client"
},
{
"name": "C2_add_anchor_payload",
"ok": true,
"detail": "created=True payload_keys=['created_at', 'data', 'hash', 'input_sha256', 'src_group', 'text_lemmatized', 'turn_ids', 'turn_roles']"
},
{
"name": "C2_add_user_scope",
"ok": true,
"detail": "user_id present in stored payload"
},
{
"name": "C2_add_ledger_shape",
"ok": true,
"detail": "embed_items=3 llm_calls=1"
},
{
"name": "C3_extraction_input_set",
"ok": true,
"detail": "old_memory=True new_row=True history_carry=True"
},
{
"name": "C3b_unshared_canary_never_outbound",
"ok": true,
"detail": "queued_public_batches=2 leaked=False anchor_taint=False"
},
{
"name": "C4_search_filters",
"ok": true,
"detail": "hits=3"
},
{
"name": "C4_search_single_debit",
"ok": true,
"detail": "embed_delta=1 (pre-fix=2)"
},
{
"name": "C6_concurrency_cap",
"ok": true,
"detail": "outcomes=['ok', 'refused'] used=3 cap=3"
},
{
"name": "C7_reopen_after_close",
"ok": true,
"detail": "hits_after_reopen=3"
},
{
"name": "C5_embed_zero_refuses",
"ok": true,
"detail": "err=semantic_embed_budget_not_approved llm_sent=0"
},
{
"name": "C5_llm_zero_refuses",
"ok": true,
"detail": "err=semantic_llm_budget_not_approved llm_sent=0"
}
],
"embed_rows_after_first_add": [
{
"kind": "authority_prefix",
"items": 0,
"note": "authority:synthetic-verify-prefix"
},
{
"kind": "dense",
"items": 1,
"note": "product:search"
},
{
"kind": "dense",
"items": 1,
"note": "product:batch:add"
},
{
"kind": "dense",
"items": 1,
"note": "product:add"
}
],
"final_embed_rows": [
{
"kind": "authority_prefix",
"items": 0,
"note": "authority:synthetic-verify-prefix"
},
{
"kind": "dense",
"items": 1,
"note": "product:search"
},
{
"kind": "dense",
"items": 1,
"note": "product:batch:add"
},
{
"kind": "dense",
"items": 1,
"note": "product:add"
},
{
"kind": "dense",
"items": 1,
"note": "product:search"
},
{
"kind": "dense",
"items": 1,
"note": "product:batch:add"
},
{
"kind": "dense",
"items": 1,
"note": "product:add"
},
{
"kind": "dense",
"items": 1,
"note": "product:search"
},
{
"kind": "dense",
"items": 1,
"note": "product:batch:add"
},
{
"kind": "dense",
"items": 1,
"note": "product:add"
},
{
"kind": "dense",
"items": 1,
"note": "product:search"
},
{
"kind": "dense",
"items": 1,
"note": "product:search"
}
],
"final_llm_used": 3,
"finished": "2026-10-26T19:35:47Z",
"gateway_requests_total": 3,
"failures": [],
"temp_dir": "/tmp/multica-task-29494715/hel874-real-sdk-8dkofioe"
}
@@ -0,0 +1,45 @@
{
"head": "HEL-885 rework",
"real_llm_requests": 0,
"real_embeddings": 0,
"boundary": "deterministic 512 vectors, non-loopback forbidden",
"checks": [
{
"name": "S0_positive_caps_without_authority_fail_closed",
"ok": true,
"detail": "missing=ledger_authority_required empty=ledger_authority_required"
},
{
"name": "S1_p1_first_tick_partial",
"ok": true,
"detail": "state=queued reason=LLM extraction failed: semantic_llm_budget_exhausted http=1 completed=[2] points=1 per_row_anchor=True"
},
{
"name": "S2_p1_restart_sends_only_missing_row",
"ok": true,
"detail": "state=done http=1 user_sent=True completed=[1, 2] entry_ids=[\"07401cdd-4964-4c74-aab5-516ab09cf5e4\", \"4d76b52d-186c-4b21-8bfa-e81341213589\"]"
},
{
"name": "S3a_lease_expired_batch_recovers",
"ok": true,
"detail": "recovered={'state': 'failed', 'reason': 'lease_expired'} state_after=done http=2"
},
{
"name": "S3b_concurrent_ticks_single_send",
"ok": true,
"detail": "http=2 states=['done']"
},
{
"name": "S4a_aged_unknown_llm_reservation_kept",
"ok": true,
"detail": "second_allowed=False used_after_restart=1"
},
{
"name": "S4b_aged_unknown_embed_reservation_kept",
"ok": true,
"detail": "second_allowed=False used=1 after_release=0"
}
],
"gateway_requests_total": 6,
"failures": []
}
@@ -0,0 +1,182 @@
{
"head": "HEL-889 shutdown ownership & queued-cancel",
"real_llm_requests": 0,
"real_embeddings": 0,
"boundary": "deterministic 512 vectors, non-loopback forbidden",
"checks": [
{
"name": "Q1 queued snapshot shows phantom-liveness",
"ok": true,
"detail": ""
},
{
"name": "Q1 cancel releases registry and requeues",
"ok": true,
"detail": "{\"registered_workers\": 0, \"batch\": {\"state\": \"queued\", \"reason\": \"queued_worker_cancelled\", \"owner\": null, \"attempts\": 0, \"entry_ids\": null}, \"row_done\": 0, \"stored_entries\": 0}"
},
{
"name": "Q1 in-process recovery completes without restart",
"ok": true,
"detail": "{\"batch\": {\"state\": \"done\", \"reason\": null, \"owner\": null, \"attempts\": 1, \"entry_ids\": \"[\\\"stub-1\\\"]\"}, \"registered_workers\": 0, \"stored_entries\": 1, \"row_done\": 1}"
},
{
"name": "S1 aclose does NOT return while the worker is alive",
"ok": true,
"detail": ""
},
{
"name": "S1 worker completes and flushes before store close",
"ok": true,
"detail": "{\"state\": \"done\", \"reason\": null, \"owner\": null, \"attempts\": 1, \"entry_ids\": \"[\\\"19039615-6f32-48e6-979b-715d2cd56fcb\\\"]\"}"
},
{
"name": "S1 no late-worker error after honest drain",
"ok": true,
"detail": ""
},
{
"name": "S1 durable reopen: done + row_done=1 + llm_used=1",
"ok": true,
"detail": "{\"state\": \"done\", \"reason\": null, \"owner\": null, \"attempts\": 1, \"entry_ids\": \"[\\\"19039615-6f32-48e6-979b-715d2cd56fcb\\\"]\", \"row_done\": 1, \"llm_used\": 1, \"gateway_posts\": 1}"
},
{
"name": "S2 shutdown between rows requeues with first row proven",
"ok": true,
"detail": "{\"state\": \"queued\", \"reason\": \"shutdown_between_rows\", \"owner\": null, \"attempts\": 0, \"entry_ids\": null, \"row_done\": 1, \"gateway_posts_during_shutdown\": 1}"
},
{
"name": "S2 resume sends ONLY the missing row and completes",
"ok": true,
"detail": "{\"state\": \"done\", \"reason\": null, \"owner\": null, \"attempts\": 1, \"entry_ids\": \"[\\\"f26d374a-858e-4dde-b2de-ae8af7057021\\\", \\\"258ab9a5-d523-45b0-ada1-d58fbda50ea7\\\"]\", \"row_done\": 2, \"gateway_posts_resume\": 2}"
},
{
"name": "S3 cancelled aclose closes nothing and re-arms",
"ok": true,
"detail": ""
},
{
"name": "S3 worker finishes done and a later aclose closes for real",
"ok": true,
"detail": ""
},
{
"name": "S4 concurrent aclose drains once and closes once",
"ok": true,
"detail": "{\"batch\": {\"state\": \"done\", \"reason\": null, \"owner\": null, \"attempts\": 1, \"entry_ids\": \"[\\\"e553422c-1840-4ff4-b51c-a3586cc34179\\\"]\", \"row_done\": 1}, \"client_usable_after_close\": false}"
}
],
"queued_cancel": {
"queued": {
"registered_workers": 1,
"product_executor_queue": 1
},
"after_cancel": {
"registered_workers": 0,
"batch": {
"state": "queued",
"reason": "queued_worker_cancelled",
"owner": null,
"attempts": 0,
"entry_ids": null
},
"row_done": 0,
"stored_entries": 0
},
"after_recovery": {
"batch": {
"state": "done",
"reason": null,
"owner": null,
"attempts": 1,
"entry_ids": "[\"stub-1\"]"
},
"registered_workers": 0,
"stored_entries": 1,
"row_done": 1
}
},
"shutdown_timeout": {
"deadline": {
"aclose_returned_while_worker_alive": false,
"batch_before_any_close": {
"state": "pending",
"reason": null,
"owner": "4a048d364194a961365c30ee1e570233",
"attempts": 1,
"entry_ids": null
},
"client_usable": true
},
"before_store_close": {
"state": "done",
"reason": null,
"owner": null,
"attempts": 1,
"entry_ids": "[\"19039615-6f32-48e6-979b-715d2cd56fcb\"]"
},
"late_worker_error": null,
"persisted": {
"state": "done",
"reason": null,
"owner": null,
"attempts": 1,
"entry_ids": "[\"19039615-6f32-48e6-979b-715d2cd56fcb\"]",
"row_done": 1,
"llm_used": 1,
"gateway_posts": 1
}
},
"between_rows": {
"after_shutdown": {
"state": "queued",
"reason": "shutdown_between_rows",
"owner": null,
"attempts": 0,
"entry_ids": null,
"row_done": 1,
"gateway_posts_during_shutdown": 1
},
"after_resume": {
"state": "done",
"reason": null,
"owner": null,
"attempts": 1,
"entry_ids": "[\"f26d374a-858e-4dde-b2de-ae8af7057021\", \"258ab9a5-d523-45b0-ada1-d58fbda50ea7\"]",
"row_done": 2,
"gateway_posts_resume": 2
}
},
"cancelled_aclose": {
"after_cancel": {
"client_usable": true,
"batch": {
"state": "pending",
"reason": null,
"owner": "a712e0367aa123b265b47e2d3223846a",
"attempts": 1,
"entry_ids": null
},
"closing_cleared": true
},
"after_worker": {
"state": "done",
"reason": null,
"owner": null,
"attempts": 1,
"entry_ids": "[\"73f87468-f546-46d7-8116-37d4fe2818b1\"]"
},
"client_usable_after_real_close": false
},
"concurrent_aclose": {
"batch": {
"state": "done",
"reason": null,
"owner": null,
"attempts": 1,
"entry_ids": "[\"e553422c-1840-4ff4-b51c-a3586cc34179\"]",
"row_done": 1
},
"client_usable_after_close": false
},
"failures": []
}
+161
View File
@@ -0,0 +1,161 @@
{
"card": "HEL-879",
"generated_by": "release-artifacts inventory (offline, zero downloads)",
"release_code_commit": "01ff91985920162cddabc5f190d146d86793627d",
"rework_head": "see delivery comment (branch hel-879-assembly, appended after 7f0a739; this inventory refreshed on that head)",
"audit_evidence": "docs/rework/hel879/sdk-audit/sdk-audit.json (head c121c42e39174e0f627f129e7f7e5700f33acf26)",
"single_install_entry": "deploy/hel895-release-candidate.md #4 (supersedes deploy/hel874-release.md #4)",
"fixed_source": {
"artifact": "/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-857-2cf6dc7dfc87/workdir/hel857-data/mem0-2c81af3.tar.gz",
"sha256": "9dcef9303ebcec116eec6739f01b8b35bd24459de6a569686b4d19b9412dda23",
"lock_record": "deploy/mem0-stack.lock: 9dcef9303ebcec116eec6739f01b8b35bd24459de6a569686b4d19b9412dda23",
"matches_lock": true,
"source_commit": "2c81af3bbe27d9bccb09c772d583df9e9be72ee5"
},
"closure": {
"total": 55,
"missing": 41,
"missing_pure_python": [
"backoff",
"coloredlogs",
"distro",
"fastembed",
"filelock",
"flatbuffers",
"fsspec",
"h2",
"hpack",
"httpcore2",
"httpx2",
"huggingface-hub",
"humanfriendly",
"hyperframe",
"loguru",
"mem0ai",
"mpmath",
"openai",
"portalocker",
"posthog",
"pytz",
"qdrant-client",
"requests",
"sniffio",
"sympy",
"tqdm",
"truststore",
"urllib3"
],
"missing_native": [
"charset-normalizer",
"greenlet",
"grpcio",
"hf-xet",
"jiter",
"mmh3",
"numpy",
"onnxruntime",
"pillow",
"protobuf",
"py-rust-stemmers",
"sqlalchemy",
"tokenizers"
]
},
"cache_reusable_reverified": [
{
"name": "greenlet",
"version": "3.5.6",
"cache_hits": [
{
"kind": "uv:wheels-v7",
"entry": "3.5.6-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64",
"content_digest": "d7ae14625784af8a268e8c156533b2e9d7694680e80a8466c8e5808cc3f1a005"
},
{
"kind": "uv:wheels-v6",
"entry": "3.5.6-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64",
"content_digest": "2fc3afa56933ea2cbaf6005c1e61565e769db1f202b3d8c53a67e59a2ed307f7"
}
]
},
{
"name": "pillow",
"version": "12.3.0",
"cache_hits": [
{
"kind": "uv:wheels-v7",
"entry": "12.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64",
"content_digest": "02620d3560d2e91e0920c1045ed814b02a4d15d8c1c960f43a060739acf0dc1f"
},
{
"kind": "uv:wheels-v6",
"entry": "12.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64",
"content_digest": "657f02aee576df196a22c130a65a3dd0f7438a8b859c2221448bea2dac6d7112"
},
{
"kind": "uv:wheels-v6",
"entry": "12.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64",
"content_digest": "02620d3560d2e91e0920c1045ed814b02a4d15d8c1c960f43a060739acf0dc1f"
}
]
},
{
"name": "truststore",
"version": "0.10.5",
"cache_hits": [
{
"kind": "uv:wheels-v7",
"entry": "0.10.5-py3-none-any",
"content_digest": "54c5d4c83a1183954a6ca33169f8881c576160c722e93064ece271776ee2eac4"
},
{
"kind": "uv:wheels-v6",
"entry": "0.10.5-py3-none-any",
"content_digest": "54c5d4c83a1183954a6ca33169f8881c576160c722e93064ece271776ee2eac4"
}
]
}
],
"build_backend": {
"hatchling_cached_or_local": true,
"hatchling_version": "1.32.4",
"hatchling_cache_evidence": [
"uv wheels-v6: ~/.cache/uv/wheels-v6/pypi/hatchling/1.32.4-py3-none-any",
"uv wheels-v7: ~/.cache/uv/wheels-v7/pypi/hatchling/1.32.4-py3-none-any",
"uv archive-v0 unpacked: hatchling-1.32.4.dist-info (wheels-v6 + wheels-v7 linked copies)"
],
"offline_wheel_built": {
"built_at": "2026-10-27T04:10:00Z",
"command": "cd <extracted mem0-2c81af3 tarball> && uv build --wheel --offline --python 3.11 -o <out>",
"tool": "uv 0.13.5, uv-managed CPython 3.11.16, build backend hatchling from the offline cache only",
"source_tarball_sha256": "9dcef9303ebcec116eec6739f01b8b35bd24459de6a569686b4d19b9412dda23",
"source_commit": "2c81af3bbe27d9bccb09c772d583df9e9be72ee5",
"wheel": "mem0ai-2.2.1-py3-none-any.whl",
"wheel_sha256": "698284b6083ab8b7761454d65876a6d1bb983d278ad21a7bee38d760cb20e9db",
"wheel_bytes": 352586,
"wheel_files": 153,
"installed_anywhere": false,
"network_used": false,
"note": "HEL-879 勘误:v1 清单一面写 hatchling_cached_or_local=true 一面又称本机缺 hatchling,并把 hatchling 列入待下载——总管只读核查指出 uv v6/v7 均有 1.32.4 缓存。已核实并直接完成可离线构建:固定源码 tarball(sha 与 lock 一致)经缓存 hatchling 离线构建出上面这只 wheel,未安装、未联网、未混 3.10 site-packages。待下载清单据此只余 41 项运行闭包(不含 hatchling)。"
}
},
"fastembed_models": {
"cache_dir": "/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-857-2cf6dc7dfc87/workdir/hel857-data/fastembed-cache",
"exists": true,
"models": {
"models--Qdrant--bge-small-zh-v1.5": {
"files": 13,
"bytes": 190444780
},
"models--Qdrant--bm25": {
"files": 39,
"bytes": 56247
}
},
"default_cache_dir": "/home/xiaomo/.cache/fastembed",
"default_cache_exists": false,
"release_note": "真实 mem0 模式把 fastembed cache_dir 指向发布实例数据目录内的该缓存拷贝;本次零下载、不推理"
},
"downloads_this_card": 0,
"note": "产物清单以 release_code_commit 为准;不要用旧父提交(04eff61/152479b/c121c42)说明冒充该 head。缓存命中为解包目录时记录内容摘要(目录无单一 sha256),安装时以 --offline 命中为准。"
}
@@ -0,0 +1,86 @@
{
"started": "2026-10-26T19:35:15Z",
"venv_python": "/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-879-3c721d56b938/workdir/CyberGril/.venv311/bin/python",
"venv_python_version": "3.11.16",
"checks": [
{
"name": "R1_checkout_fresh_dir",
"ok": true,
"detail": "cloned tracked source into /tmp/multica-task-29494715/hel882-release-jk7k2a25/cybergril-hel874/current"
},
{
"name": "R2_service_imports_product",
"ok": true,
"detail": ""
},
{
"name": "R6_mem0_coimport_status_recorded",
"ok": true,
"detail": "import mem0 → ModuleNotFoundError: No module named 'mem0' (resolution path in deploy/hel874-release.md §4; no download in this check)"
},
{
"name": "R7_pip_download_command_exists",
"ok": true,
"detail": "/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-879-3c721d56b938/workdir/CyberGril/.venv311/bin/python -m pip download --help → rc=0"
},
{
"name": "R7_removed_uv_pip_download_still_absent",
"ok": true,
"detail": "uv pip download --help → rc=2 (kept absent on purpose; do not re-document it)"
},
{
"name": "R2_service_serves_health",
"ok": true,
"detail": "version=R4-assembly semantic=stub"
},
{
"name": "R3_create_status_recorded",
"ok": true,
"detail": "create status=201(409=前次运行的档案仍在,也是保留数据的表现)"
},
{
"name": "R3_dialogue_refused_honestly",
"ok": true,
"detail": "http=200 events=b'data: {\"t\": \"chat.error\", \"epoch\": 1, \"turn_id\": \"00000000-0000-4000-8000-000000000001\", \"phi\": \"\\xe5\\xa5\\xb9\\xe8\\xbf\\x99\\xe4\\xbc\\x9a\\xe5\\x84\\xbf\\xe6\\xb2\\xa1\\xe6\\x8e\\xa5\\xe4\\xb8\\x8a\\xe8\\xaf'"
},
{
"name": "R4_service_restarts_on_same_data",
"ok": true,
"detail": "epoch=2"
},
{
"name": "R5_epoch_bumped_on_reopen",
"ok": true,
"detail": "1 → 2"
},
{
"name": "R4_companion_created_offline",
"ok": true,
"detail": "status=409"
},
{
"name": "R4_dialogue_stream_http",
"ok": true,
"detail": "status=200 events=b'data: {\"t\": \"chat.delta\", \"epoch\": 2, \"turn_id\": \"00000000-0000-4000-8000-000000000002\", \"delta\": \"\\xe6\\x89\\xb9\"}\\n\\ndata: {\"t\": \"c'"
},
{
"name": "R4_messages_roundtrip",
"ok": true,
"detail": "roles=['user', 'user', 'companion']"
},
{
"name": "R5_data_dir_preserved",
"ok": true,
"detail": "['companion.db']"
},
{
"name": "R5_companion_row_survives_restart",
"ok": true,
"detail": "companion rows=1"
}
],
"mem0_import_in_release_venv": "ModuleNotFoundError: No module named 'mem0'",
"removed_uv_pip_download_rc": 2,
"failures": [],
"finished": "2026-10-26T19:35:18Z"
}
@@ -0,0 +1,67 @@
{
"ok": true,
"code_commit": "7c73d392b95f3d55091a066db0ffe9bc6c475e0d",
"tool_version": 3,
"checks": [
{
"name": "naive_online_copy_loses_wal_reproduced",
"ok": true,
"detail": "error=no such table: companion"
},
{
"name": "busy_checkpoint_state_reproduced",
"ok": true,
"detail": "checkpoint=(1, 9, 8) naive_snapshot_events=error=DatabaseError:database disk image is malformed"
},
{
"name": "fix1_busy_checkpoint_fails_no_archive",
"ok": true,
"detail": "error=yes leftovers=0"
},
{
"name": "fix1b_row_mismatch_fails_no_archive",
"ok": true,
"detail": "error=源/快照行数不一致,不打包:/tmp/multica-task-3939683741/hel879-snapshot-s artifacts=[]"
},
{
"name": "fix_verify_rejects_rows_match_false_legacy_package",
"ok": true,
"detail": "ok=False checks=[{'db': 'databases/companion.db', 'restored': '/tmp/multica-task-3939683741/hel879-snapshot-selftest-a4xt_3p_/verify-restore-legacy-pkg.tar-20261027-042723/by-source/tmp__multica-task-3939683741__hel879-snapshot-selftest-a4xt_3p___instance__companion.db', 'sha256_ok': True, 'rows_ok': False, 'counts': {'companion': 1, 'chat_message': 1, 'events': 1}, 'source_counts': {'companion': 1, 'chat_message': 1, 'events': 1}, 'snapshot_counts': {'companion': 1, 'chat_message': 1, 'events': 0}, 'rows_match_recorded': False, 'rejected_legacy_package': True}]"
},
{
"name": "fix2_online_mode_refuses_dir_with_sqlite",
"ok": true,
"detail": "在线模式只允许显式单库 backup API(--db);--file /tmp/multica-task-3939683741/hel879-snapshot-selftest-a4xt_3p_/instance/history_root"
},
{
"name": "fix2_stop_mode_embeds_sqlite_via_consistency_path",
"ok": true,
"detail": "embedded=['/tmp/multica-task-3939683741/hel879-snapshot-selftest-a4xt_3p_/instance/history_root/mem0_public_history.db']"
},
{
"name": "fix3_verify_maps_source_paths_and_passes",
"ok": true,
"detail": "ok=True commit=7c73d392b95f3d55091a066db0ffe9bc6c475e0d plan_entries=4"
},
{
"name": "backup_api_snapshot_complete",
"ok": true,
"detail": "{\"companion\": 1, \"chat_message\": 1, \"events\": 1}"
},
{
"name": "fix_rows_match_true_cannot_bypass_source_counts",
"ok": true,
"detail": "ok=False counts={'companion': 1, 'chat_message': 1, 'events': 1} source={'companion': 1, 'chat_message': 1, 'events': 2}"
},
{
"name": "fix_same_basename_no_overwrite_and_duplicate_refused",
"ok": true,
"detail": "snap_names=['ledger.db', 'ledger__0e75f128.db'] verify_ok=True restored_rows=['first', 'second'] dup_refused=yes"
},
{
"name": "restore_drill_paths_persist",
"ok": true,
"detail": "restore_root=/tmp/multica-task-3939683741/hel879-snapshot-selftest-a4xt_3p_/same-restore"
}
]
}
+886
View File
@@ -0,0 +1,886 @@
{
"head": "7c73d392b95f3d55091a066db0ffe9bc6c475e0d",
"scope": "只读审计:3.10 固定栈安装元数据按 3.11 Linux markers+extras 求闭包,对照产品 3.11 venv 与本机 uv/pip 缓存;这是依赖闭包清单,不是已完成的 cp311 解析或下载结果",
"read_only": true,
"zero_downloads_this_card": true,
"closure_total": 55,
"missing_count": 41,
"missing_pure_python": [
"backoff",
"coloredlogs",
"distro",
"fastembed",
"filelock",
"flatbuffers",
"fsspec",
"h2",
"hpack",
"httpcore2",
"httpx2",
"huggingface-hub",
"humanfriendly",
"hyperframe",
"loguru",
"mem0ai",
"mpmath",
"openai",
"portalocker",
"posthog",
"pytz",
"qdrant-client",
"requests",
"sniffio",
"sympy",
"tqdm",
"truststore",
"urllib3"
],
"missing_native": [
"charset-normalizer",
"greenlet",
"grpcio",
"hf-xet",
"jiter",
"mmh3",
"numpy",
"onnxruntime",
"pillow",
"protobuf",
"py-rust-stemmers",
"sqlalchemy",
"tokenizers"
],
"cache_reusable_now": [
"greenlet",
"pillow",
"truststore"
],
"top_level": {
"mem0ai": {
"name": "mem0ai",
"version": "2.2.1",
"wheel_tags": [
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": "{\"dir_info\": {}, \"url\": \"file:///home/xiaomo/multica_workspaces/hello-af23464d222e/hel-857-2cf6dc7dfc87/workdir/hel857-src\"}",
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "fixed-source"
},
"qdrant-client": {
"name": "qdrant-client",
"version": "1.20.0",
"wheel_tags": [
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "missing-download-at-release"
},
"fastembed": {
"name": "fastembed",
"version": "0.9.1",
"wheel_tags": [
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "missing-download-at-release"
}
},
"missing_detail": [
{
"name": "backoff",
"version": "2.2.1",
"wheel_tags": [
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "missing-download-at-release"
},
{
"name": "charset-normalizer",
"version": "3.5.2",
"wheel_tags": [
"cp310-cp310-manylinux_2_17_x86_64",
"cp310-cp310-manylinux2014_x86_64",
"cp310-cp310-manylinux_2_28_x86_64"
],
"native_extension": true,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "3.11 目标需 cp311-cp311(或 cp39+ abi3)manylinux x86_64 原生 wheel;3.10 安装标签不可复制,禁混 site-packages",
"classification": "missing-download-at-release"
},
{
"name": "coloredlogs",
"version": "15.0.1",
"wheel_tags": [
"py2-none-any",
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "missing-download-at-release"
},
{
"name": "distro",
"version": "1.9.0",
"wheel_tags": [
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "missing-download-at-release"
},
{
"name": "fastembed",
"version": "0.9.1",
"wheel_tags": [
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "missing-download-at-release"
},
{
"name": "filelock",
"version": "4.1.0",
"wheel_tags": [
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "missing-download-at-release"
},
{
"name": "flatbuffers",
"version": "25.12.19",
"wheel_tags": [
"py2-none-any",
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "missing-download-at-release"
},
{
"name": "fsspec",
"version": "2026.9.0",
"wheel_tags": [
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "missing-download-at-release"
},
{
"name": "greenlet",
"version": "3.5.6",
"wheel_tags": [
"cp310-cp310-manylinux_2_24_x86_64",
"cp310-cp310-manylinux_2_28_x86_64"
],
"native_extension": true,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [
{
"kind": "uv:wheels-v7",
"path": "/home/xiaomo/.cache/uv/wheels-v7/pypi/greenlet/3.5.6-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64",
"name": "3.5.6-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64",
"sha256": null
},
{
"kind": "uv:wheels-v6",
"path": "/home/xiaomo/.cache/uv/wheels-v6/pypi/greenlet/3.5.6-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64",
"name": "3.5.6-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64",
"sha256": null
}
],
"cache_hits_usable_for_311": [
"3.5.6-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64"
],
"resolution": "3.11 目标需 cp311-cp311(或 cp39+ abi3)manylinux x86_64 原生 wheel;3.10 安装标签不可复制,禁混 site-packages",
"classification": "cache-reusable"
},
{
"name": "grpcio",
"version": "1.84.1",
"wheel_tags": [
"cp310-cp310-manylinux_2_17_x86_64",
"cp310-cp310-manylinux2014_x86_64"
],
"native_extension": true,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "3.11 目标需 cp311-cp311(或 cp39+ abi3)manylinux x86_64 原生 wheel;3.10 安装标签不可复制,禁混 site-packages",
"classification": "missing-download-at-release"
},
{
"name": "h2",
"version": "4.4.1",
"wheel_tags": [
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "missing-download-at-release"
},
{
"name": "hf-xet",
"version": "1.7.0",
"wheel_tags": [
"cp38-abi3-manylinux_2_17_x86_64",
"cp38-abi3-manylinux2014_x86_64"
],
"native_extension": true,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "3.11 目标需 cp311-cp311(或 cp39+ abi3)manylinux x86_64 原生 wheel;3.10 安装标签不可复制,禁混 site-packages",
"classification": "missing-download-at-release"
},
{
"name": "hpack",
"version": "4.2.0",
"wheel_tags": [
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "missing-download-at-release"
},
{
"name": "httpcore2",
"version": "2.14.0",
"wheel_tags": [
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "missing-download-at-release"
},
{
"name": "httpx2",
"version": "2.14.0",
"wheel_tags": [
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "missing-download-at-release"
},
{
"name": "huggingface-hub",
"version": "1.33.0",
"wheel_tags": [
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "missing-download-at-release"
},
{
"name": "humanfriendly",
"version": "10.0",
"wheel_tags": [
"py2-none-any",
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "missing-download-at-release"
},
{
"name": "hyperframe",
"version": "6.1.0",
"wheel_tags": [
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "missing-download-at-release"
},
{
"name": "jiter",
"version": "0.18.0",
"wheel_tags": [
"cp310-cp310-manylinux_2_17_x86_64",
"cp310-cp310-manylinux2014_x86_64"
],
"native_extension": true,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "3.11 目标需 cp311-cp311(或 cp39+ abi3)manylinux x86_64 原生 wheel;3.10 安装标签不可复制,禁混 site-packages",
"classification": "missing-download-at-release"
},
{
"name": "loguru",
"version": "0.7.3",
"wheel_tags": [
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "missing-download-at-release"
},
{
"name": "mem0ai",
"version": "2.2.1",
"wheel_tags": [
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": "{\"dir_info\": {}, \"url\": \"file:///home/xiaomo/multica_workspaces/hello-af23464d222e/hel-857-2cf6dc7dfc87/workdir/hel857-src\"}",
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "fixed-source"
},
{
"name": "mmh3",
"version": "5.3.1",
"wheel_tags": [
"cp310-cp310-manylinux_2_5_x86_64",
"cp310-cp310-manylinux1_x86_64",
"cp310-cp310-manylinux_2_28_x86_64"
],
"native_extension": true,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "3.11 目标需 cp311-cp311(或 cp39+ abi3)manylinux x86_64 原生 wheel;3.10 安装标签不可复制,禁混 site-packages",
"classification": "missing-download-at-release"
},
{
"name": "mpmath",
"version": "1.3.0",
"wheel_tags": [
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "missing-download-at-release"
},
{
"name": "numpy",
"version": "2.2.6",
"wheel_tags": [
"cp310-cp310-manylinux_2_17_x86_64",
"cp310-cp310-manylinux2014_x86_64"
],
"native_extension": true,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "3.11 目标需 cp311-cp311(或 cp39+ abi3)manylinux x86_64 原生 wheel;3.10 安装标签不可复制,禁混 site-packages",
"classification": "missing-download-at-release"
},
{
"name": "onnxruntime",
"version": "1.23.2",
"wheel_tags": [
"cp310-cp310-manylinux_2_27_x86_64",
"cp310-cp310-manylinux_2_28_x86_64"
],
"native_extension": true,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "3.11 目标需 cp311-cp311(或 cp39+ abi3)manylinux x86_64 原生 wheel;3.10 安装标签不可复制,禁混 site-packages",
"classification": "missing-download-at-release"
},
{
"name": "openai",
"version": "3.37.0",
"wheel_tags": [
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "missing-download-at-release"
},
{
"name": "pillow",
"version": "12.3.0",
"wheel_tags": [
"cp310-cp310-manylinux_2_27_x86_64",
"cp310-cp310-manylinux_2_28_x86_64"
],
"native_extension": true,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [
{
"kind": "uv:wheels-v7",
"path": "/home/xiaomo/.cache/uv/wheels-v7/pypi/pillow/12.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64",
"name": "12.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64",
"sha256": null
},
{
"kind": "uv:wheels-v6",
"path": "/home/xiaomo/.cache/uv/wheels-v6/pypi/pillow/12.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64",
"name": "12.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64",
"sha256": null
},
{
"kind": "uv:wheels-v6",
"path": "/home/xiaomo/.cache/uv/wheels-v6/pypi/pillow/12.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64",
"name": "12.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64",
"sha256": null
}
],
"cache_hits_usable_for_311": [
"12.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64"
],
"resolution": "3.11 目标需 cp311-cp311(或 cp39+ abi3)manylinux x86_64 原生 wheel;3.10 安装标签不可复制,禁混 site-packages",
"classification": "cache-reusable"
},
{
"name": "portalocker",
"version": "4.4.1",
"wheel_tags": [
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "missing-download-at-release"
},
{
"name": "posthog",
"version": "7.75.11",
"wheel_tags": [
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "missing-download-at-release"
},
{
"name": "protobuf",
"version": "6.33.6",
"wheel_tags": [
"cp39-abi3-manylinux2014_x86_64"
],
"native_extension": true,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "3.11 目标需 cp311-cp311(或 cp39+ abi3)manylinux x86_64 原生 wheel;3.10 安装标签不可复制,禁混 site-packages",
"classification": "missing-download-at-release"
},
{
"name": "py-rust-stemmers",
"version": "0.1.8",
"wheel_tags": [
"cp310-cp310-manylinux_2_28_x86_64"
],
"native_extension": true,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "3.11 目标需 cp311-cp311(或 cp39+ abi3)manylinux x86_64 原生 wheel;3.10 安装标签不可复制,禁混 site-packages",
"classification": "missing-download-at-release"
},
{
"name": "pytz",
"version": "2026.5",
"wheel_tags": [
"py2-none-any",
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "missing-download-at-release"
},
{
"name": "qdrant-client",
"version": "1.20.0",
"wheel_tags": [
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "missing-download-at-release"
},
{
"name": "requests",
"version": "2.34.2",
"wheel_tags": [
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "missing-download-at-release"
},
{
"name": "sniffio",
"version": "1.3.1",
"wheel_tags": [
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "missing-download-at-release"
},
{
"name": "sqlalchemy",
"version": "2.0.55",
"wheel_tags": [
"cp310-cp310-manylinux_2_17_x86_64",
"cp310-cp310-manylinux2014_x86_64",
"cp310-cp310-manylinux_2_28_x86_64"
],
"native_extension": true,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "3.11 目标需 cp311-cp311(或 cp39+ abi3)manylinux x86_64 原生 wheel;3.10 安装标签不可复制,禁混 site-packages",
"classification": "missing-download-at-release"
},
{
"name": "sympy",
"version": "1.14.0",
"wheel_tags": [
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "missing-download-at-release"
},
{
"name": "tokenizers",
"version": "0.23.3",
"wheel_tags": [
"cp310-abi3-manylinux_2_17_x86_64",
"cp310-abi3-manylinux2014_x86_64"
],
"native_extension": true,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "3.11 目标需 cp311-cp311(或 cp39+ abi3)manylinux x86_64 原生 wheel;3.10 安装标签不可复制,禁混 site-packages",
"classification": "missing-download-at-release"
},
{
"name": "tqdm",
"version": "4.70.1",
"wheel_tags": [
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "missing-download-at-release"
},
{
"name": "truststore",
"version": "0.10.5",
"wheel_tags": [
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [
{
"kind": "uv:wheels-v7",
"path": "/home/xiaomo/.cache/uv/wheels-v7/pypi/truststore/0.10.5-py3-none-any",
"name": "0.10.5-py3-none-any",
"sha256": null
},
{
"kind": "uv:wheels-v6",
"path": "/home/xiaomo/.cache/uv/wheels-v6/pypi/truststore/0.10.5-py3-none-any",
"name": "0.10.5-py3-none-any",
"sha256": null
}
],
"cache_hits_usable_for_311": [
"0.10.5-py3-none-any",
"0.10.5-py3-none-any"
],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "cache-reusable"
},
{
"name": "urllib3",
"version": "2.8.0",
"wheel_tags": [
"py3-none-any"
],
"native_extension": false,
"installed_sdk": true,
"present_product_311": false,
"product_version": null,
"direct_url": null,
"cache_hits": [],
"cache_hits_usable_for_311": [],
"resolution": "纯 Python py3-none-any:同版本 wheel 可跨 3.10/3.11 使用",
"classification": "missing-download-at-release"
}
],
"mem0_identity": {
"direct_url_of_installed": "{\"dir_info\": {}, \"url\": \"file:///home/xiaomo/multica_workspaces/hello-af23464d222e/hel-857-2cf6dc7dfc87/workdir/hel857-src\"}",
"fixed_source_dir": "/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-857-2cf6dc7dfc87/workdir/hel857-src",
"fixed_source_dir_exists": true,
"pinned_tarball": "/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-857-2cf6dc7dfc87/workdir/hel857-data/mem0-2c81af3.tar.gz",
"pinned_tarball_sha256_measured": "9dcef9303ebcec116eec6739f01b8b35bd24459de6a569686b4d19b9412dda23",
"pinned_tarball_sha256_lock": "9dcef9303ebcec116eec6739f01b8b35bd24459de6a569686b4d19b9412dda23",
"tarball_matches_lock": true,
"index_wheel_2_2_1_proves_same_code": false,
"note": "索引下载的 mem0ai==2.2.1 wheel 不自动等于固定源码;发布必须用本 tarball/源码目录离线构建安装",
"built_wheel_from_hel857_install": {
"filename": "mem0ai-2.2.1-py3-none-any.whl",
"size": 352586,
"sha256": "698284b6083ab8b7761454d65876a6d1bb983d278ad21a7bee38d760cb20e9db",
"ephemeral_wheel_cache": "/tmp/multica-task-1816511421 (不再存在)",
"persisted_anywhere": false
},
"offline_build_backend": {
"hatchling_cached_or_local": true,
"note": "离线构建 mem0 wheel 需要 hatchling 及其依赖可离线获得;未命中则发布卡需一并拉取"
}
},
"fastembed_models": {
"cache_dir": "/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-857-2cf6dc7dfc87/workdir/hel857-data/fastembed-cache",
"exists": true,
"models": {
"models--Qdrant--bge-small-zh-v1.5": {
"files": 13,
"bytes": 190444780
},
"models--Qdrant--bm25": {
"files": 39,
"bytes": 56247
}
},
"default_cache_dir": "/home/xiaomo/.cache/fastembed",
"default_cache_exists": false,
"release_note": "真实 mem0 模式把 fastembed cache_dir 指向发布实例数据目录内的该缓存拷贝;本次零下载、不推理"
},
"pip_2622_event": {
"claim_under_review": "HEL-895 seq48–51:uv pip install --offline pip 失败后去掉 --offline 安装 pip 26.2.2 成功;前卡日志仅存尾两行",
"installed_into": "/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-895-0261e950b9c2/workdir/CyberGril/.venv311/lib/python3.11/site-packages/pip-26.2.2.dist-info",
"installer": "uv",
"uv_cache_entry_created": "2026-10-27T02:10:04.960958",
"uv_http_cache_created": "2026-10-27T02:10:04.972959",
"recorded_source_url": "https://files.pythonhosted.org/packages/3f/06/3e4420302363fbb71be3c7f35ce042f54bfca330e3e66b3a2e3c4cca579f/pip-26.2.2-py3-none-any.whl",
"recorded_sha256": "fd7f4dfa8190e014013b9f08f3e973f4e4c26ffd179a204895533a7254813a70",
"other_new_cache_entries_in_window": [],
"verdict": "勘误成立:uv 缓存中 pip-26.2.2 wheel 条目创建于 2026-10-27 02:10:04 +0800(HEL-895 运行窗口内),来源 files.pythonhosted.org,且是该窗口唯一新增缓存条目——该步发生了网络获取,前卡『零下载』对此步不成立;requirements.lock 装机同期未产生任何新缓存条目(全部命中既有缓存)。字节级『完整下载 vs 条件请求』无法由缓存取证区分,按保守口径记为下载。"
}
}
@@ -0,0 +1,16 @@
# HEL-879 rework: real-SDK check regenerated from THIS head's voice sources
# head: 78a5cfd8d75e3cfd00c60dc5c3592eb08cc757a1
# run: 2026-10-26T20:16:05Z, .12 /opt/voice-agent/agent/venv/bin/python (livekit-agents 1.8.8), stdin-only, zero writes zero network, no service touched
# generator: scripts/gen_hel877_sdk_check.py (10 checks; #9/#10 new: worker callback path on real SpeechHandle/SpeechCreatedEvent; assistant_interim seam through real LLMStream)
HEL-877/879 real-SDK check on livekit-agents 1.8.8: ALL PASS
OK CyberGrillLLM/ChatApiLLMStream subclass real livekit-agents 1.8.8 LLM/LLMStream
OK latest_user_text reads a real ChatContext (items/role/text_content)
OK fake-SSE turn through real LLMStream emits ChatChunks, final text and receipt
OK chat.error phi is spoken verbatim via ChatChunk and carries no receipt
OK mint_token claims byte-parity with livekit.api.AccessToken (video/iss/sub/keys)
OK mint_server_token matches SDK no-grant auth header claims
OK worker.py SDK surface (SpeechHandle, UserInputTranscribedEvent.is_final) present
OK barge-in task cancel closes the in-flight HTTP stream immediately
OK worker callback path on real SpeechHandle/SpeechCreatedEvent: no-generation handle discards, next turn keeps its own stream (explicit binding)
OK assistant_interim seam: SSE deltas through real LLMStream feed her incremental subtitle payloads (replace semantics, reset works)
+9
View File
@@ -0,0 +1,9 @@
# HEL-896 · 固定 SDK 离线余缺审计摘要(只读)
- 闭包 55 项,产品 3.11 venv 缺 41 项:纯 Python 28 项、原生扩展 13 项(charset-normalizer, greenlet, grpcio, hf-xet, jiter, mmh3, numpy, onnxruntime, pillow, protobuf, py-rust-stemmers, sqlalchemy, tokenizers)。
- 三个顶层包 WHEEL 均 py3-none-any(纯 Python,可跨 3.10/3.11)。
- mem0ai 固定源码身份:tarball SHA256 一致(9dcef9303ebcec116eec6739f01b8b35bd24459de6a569686b4d19b9412dda23);索引同名 wheel 不作数;hel857 构建过的 wheel(698284b6083a…)在临时缓存、未持久。
- 当前缓存可直接复用的缺项 wheel:['greenlet', 'pillow', 'truststore']。
- 其余缺项(纯 Python wheel 与 cp311 原生 wheel)由发布卡按 deploy/hel874-release.md §4 获授权后下载并 sha256 留证;缺项不阻塞其余发布工作。
- fastembed 模型缓存实测:{"models--Qdrant--bge-small-zh-v1.5": {"files": 13, "bytes": 190444780}, "models--Qdrant--bm25": {"files": 39, "bytes": 56247}}(hel857-data/fastembed-cache,随发布实例落位,零下载)。
- pip 26.2.2 事件:勘误成立:uv 缓存中 pip-26.2.2 wheel 条目创建于 2026-10-27 02:10:04 +0800(HEL-895 运行窗口内),来源 files.pythonhosted.org,且是该窗口唯一新增缓存条目——该步发生了网络获取,前卡『零下载』对此步不成立;requirements.lock 装机同期未产生任何新缓存条目(全部命中既有缓存)。字节级『完整下载 vs 条件请求』无法由缓存取证区分,按保守口径记为下载。
@@ -0,0 +1,16 @@
/opt/voice-agent/agent/venv/lib/python3.12/site-packages/jwt/api_jwt.py:149: InsecureKeyLengthWarning: The HMAC key is 6 bytes long, which is below the minimum recommended length of 32 bytes for SHA256. See RFC 7518 Section 3.2.
return self._jws.encode(
/opt/voice-agent/agent/venv/lib/python3.12/site-packages/jwt/api_jwt.py:370: InsecureKeyLengthWarning: The HMAC key is 6 bytes long, which is below the minimum recommended length of 32 bytes for SHA256. See RFC 7518 Section 3.2.
decoded = self.decode_complete(
HEL-877/879 real-SDK check on livekit-agents 1.8.8: ALL PASS
OK CyberGrillLLM/ChatApiLLMStream subclass real livekit-agents 1.8.8 LLM/LLMStream
OK latest_user_text reads a real ChatContext (items/role/text_content)
OK fake-SSE turn through real LLMStream emits ChatChunks, final text and receipt
OK chat.error phi is spoken verbatim via ChatChunk and carries no receipt
OK mint_token claims byte-parity with livekit.api.AccessToken (video/iss/sub/keys)
OK mint_server_token matches SDK no-grant auth header claims
OK worker.py SDK surface (SpeechHandle, UserInputTranscribedEvent.is_final) present
OK barge-in task cancel closes the in-flight HTTP stream immediately
OK installed-SDK source verified: speech tasks set agents_speech_handle around the pipeline; speech_created precedes the task; llm_node calls llm.chat inside it
OK ACTUAL worker module loaded; its registered callbacks driven on a real AgentSession: both-queued delayed generations bind exactly (manager interleaving), say never steals a stream, unattributed fails closed, interrupt cancels without final/receipt
OK assistant_interim seam: real LLMStream deltas feed per-speech_id v2 payloads (replace semantics); unattributed streams feed nothing
@@ -0,0 +1,15 @@
HEL-877/879 real-SDK check on livekit-agents 1.8.8: ALL PASS
OK CyberGrillLLM/ChatApiLLMStream subclass real livekit-agents 1.8.8 LLM/LLMStream
OK latest_user_text reads a real ChatContext (items/role/text_content)
OK fake-SSE turn through real LLMStream emits ChatChunks, final text and receipt
OK chat.error phi is spoken verbatim via ChatChunk and carries no receipt
OK mint_token claims byte-parity with livekit.api.AccessToken (video/iss/sub/keys)
OK mint_server_token matches SDK no-grant auth header claims
OK worker.py SDK surface (SpeechHandle, UserInputTranscribedEvent.is_final) present
OK barge-in task cancel closes the in-flight HTTP stream immediately
OK installed-SDK source verified: speech tasks set agents_speech_handle around the pipeline; speech_created precedes the task; llm_node calls llm.chat inside it
OK ACTUAL worker module loaded; its registered callbacks driven on a real AgentSession: both-queued delayed generations bind exactly (manager interleaving), say never steals a stream, unattributed fails closed, interrupt cancels without final/receipt
OK assistant_interim seam: real LLMStream deltas feed per-speech_id v2 payloads (replace semantics); unattributed streams feed nothing
OK speak_proactive_row publishes proactive_start while the real SpeechHandle is still playing (sync-say shape), end/cancel on the handle's done callback
OK interrupted proactive row emits proactive_cancel(reason=interrupted), never proactive_end
OK incomplete/no-receipt attributed stream: actual worker callbacks emit turn_start -> turn_cancel only, no assistant_final, no ack
@@ -0,0 +1,9 @@
{"v": 2, "seq": 1, "kind": "turn_start", "role": "assistant", "text": "", "speech_id": "speech_cut"}
{"v": 2, "seq": 2, "kind": "assistant_interim", "role": "assistant", "text": "我正想跟你说,今天下午", "speech_id": "speech_cut"}
{"v": 2, "seq": 3, "kind": "turn_cancel", "role": "assistant", "text": "", "speech_id": "speech_cut", "reason": "interrupted"}
{"v": 2, "seq": 4, "kind": "interim", "role": "user", "text": "等一下"}
{"v": 2, "seq": 5, "kind": "user_final", "role": "user", "text": "等一下,先听我说。"}
{"v": 2, "seq": 6, "kind": "turn_start", "role": "assistant", "text": "", "speech_id": "speech_next"}
{"v": 2, "seq": 7, "kind": "assistant_interim", "role": "assistant", "text": "好,你先说。", "speech_id": "speech_next"}
{"v": 2, "seq": 8, "kind": "assistant_final", "role": "assistant", "text": "好,你先说。", "speech_id": "speech_next", "turn_id": "0e2f-demo-next"}
{"v": 2, "seq": 9, "kind": "turn_end", "role": "assistant", "text": "", "speech_id": "speech_next", "turn_id": "0e2f-demo-next"}
@@ -0,0 +1,7 @@
{"v": 2, "seq": 1, "kind": "interim", "role": "user", "text": "你在听的"}
{"v": 2, "seq": 2, "kind": "user_final", "role": "user", "text": "你在听吗?"}
{"v": 2, "seq": 3, "kind": "turn_start", "role": "assistant", "text": "", "speech_id": "speech_normal"}
{"v": 2, "seq": 4, "kind": "assistant_interim", "role": "assistant", "text": "在听呢,", "speech_id": "speech_normal"}
{"v": 2, "seq": 5, "kind": "assistant_interim", "role": "assistant", "text": "在听呢,在听呢,你刚才说到一半的那件事我还在想。", "speech_id": "speech_normal"}
{"v": 2, "seq": 6, "kind": "assistant_final", "role": "assistant", "text": "在听呢,你刚才说到一半的那件事我还在想。", "speech_id": "speech_normal", "turn_id": "0e2f-demo-turn"}
{"v": 2, "seq": 7, "kind": "turn_end", "role": "assistant", "text": "", "speech_id": "speech_normal", "turn_id": "0e2f-demo-turn"}
@@ -0,0 +1,4 @@
{"v": 2, "seq": 1, "kind": "proactive_start", "role": "assistant", "text": "我看到一句很适合现在的话,先念给你听。", "speech_id": "speech_pro1", "message_id": 17}
{"v": 2, "seq": 2, "kind": "proactive_end", "role": "assistant", "text": "", "speech_id": "speech_pro1", "message_id": 17}
{"v": 2, "seq": 3, "kind": "proactive_start", "role": "assistant", "text": "还有一件事想告诉你——", "speech_id": "speech_pro2", "message_id": 18}
{"v": 2, "seq": 4, "kind": "proactive_cancel", "role": "assistant", "text": "", "speech_id": "speech_pro2", "message_id": 18, "reason": "interrupted"}
@@ -0,0 +1 @@
{"remote_delete_count_after_ttl": 1, "expired_but_registered": false, "cleanup_owner_present": false}
@@ -0,0 +1 @@
{"remote_delete_count_after_ttl": 0, "expired_but_registered": true, "cleanup_owner_present": false}
@@ -0,0 +1 @@
{"proactive_before_playout_finishes": {"handle_done": false, "events": ["proactive_start"]}, "proactive_after_playout_finishes": ["proactive_start", "proactive_end"], "proactive_payload_fields": {"v": 2, "kind": "proactive_start", "role": "assistant", "text": "offline sample", "speech_id": "speech_f28fdeb15e01", "message_id": 7}, "no_receipt_incomplete_actual_worker_events": ["turn_start", "turn_cancel"], "no_receipt_incomplete_confirmed": [], "complete_with_receipt_actual_worker_events": ["turn_start", "assistant_final", "turn_end"], "complete_with_receipt_confirmed": ["ok-turn"]}
@@ -0,0 +1 @@
{"proactive_before_playout_finishes": {"handle_done": false, "events": []}, "proactive_after_playout_finishes": ["proactive_start", "proactive_end"], "proactive_payload_fields": {"v": 2, "kind": "proactive_start", "role": "assistant", "text": "offline sample", "speech_id": "speech_2880d36ea013", "message_id": 7}, "no_receipt_incomplete_actual_worker_events": ["turn_start", "assistant_final", "turn_end"], "no_receipt_incomplete_confirmed": [], "complete_with_receipt_actual_worker_events": ["turn_start", "assistant_final", "turn_end"], "complete_with_receipt_confirmed": ["ok-turn"]}
+37
View File
@@ -0,0 +1,37 @@
# HEL-879 契约核准后 3 处实现时机收口(2026-10-27)
核对对象:`hel-879-assembly @ 3228b1e0d7f822b7030f945e285eac74126771aa`(总管《HEL-879-契约核准与接续.md》)。
本轮只修 3 处实现与已定契约 v2 不一致的问题:不改 `v` 信封字段、不改事件种类、不动 `web/src`、不重做已过绑定/备份/预算。
## 修前复现(全部先复现失败,与总管证据一致)
| # | 发现 | 修前证据 |
|---|------|----------|
| 1 | `open_session` 只在 `await create_dispatch` 前 kick;建房真实让出事件循环后 cleaner 空转退出,注册后无人再观察截止时间 | `delayed-dispatch-before.json`:`{"remote_delete_count_after_ttl": 0, "expired_but_registered": true, "cleanup_owner_present": false}`(与总管 delayed-dispatch.json 逐字段一致) |
| 2 | `speak_proactive_row` 旧写法 `handle = await session.say(...)`;真实 SDK `say` 为同步函数、`await handle`=等整段播完 → 播完才发 proactive_start | `sdk-probe-before.json`:`proactive_before_playout_finishes.events=[]`,播完后才 `["proactive_start","proactive_end"]` |
| 3 | `_after_playout` 先按 `stream.text` 发 final/end 再查回执;注入 text 非空、completed=False、receipt=None 的确切归属流 → `turn_start→assistant_final→turn_end`,与 v2「无完整同轮回执只 cancel」相反 | 同上:`no_receipt_incomplete_actual_worker_events=["turn_start","assistant_final","turn_end"]`(实际 worker `bind_voice_session` 回调路径,真实 AgentSession/SpeechHandle) |
新增专项测试在**未修复代码上先跑失败**(repro-first):
`tests/test_voice_rework_hel879.py::test_open_session_with_real_dispatch_yield_re_kicks_cleanup`、
`::test_second_registration_wakes_a_sleeping_cleaner_for_earlier_deadline` → 修前 2 failed / 修后 2 passed。
## 修复(精确小修)
1. `src/cybergril/voice/session.py` `open_session`:注册成功并 `_persist_state()` 之后**重新 `_kick_cleanup()`**——建房 await 期间旧 cleaner 可能已因「无可观察对象」退出,新截止时间需要新的所有者;自动截止、共享上限、重启补偿、有界放弃全部保留。测试用**真正让出事件循环**的建房替身(`asyncio.sleep(0.02)`),不再只用立即返回的 MockTransport 替身。
2. `src/cybergril/voice/worker.py` `speak_proactive_row`:先取 handle(`iscoroutine` 只解包 await 型替身——真实 SpeechHandle 自身可 await,不能误等)、**立即**发布 `proactive_start`、注册该 handle 的 `proactive_end/proactive_cancel`,再按需等待这一条播完(逐条顺序念保留)。
3. `src/cybergril/voice/worker.py` `_after_playout`:`assistant_final` 前置必要门——流 `completed` 且 `should_ack`(合法同轮 receipt、非旧 epoch)才 `finish`+`confirm`;未完整/无回执/旧 epoch 一律只 `turn_cancel`(reason=incomplete/no-receipt),**绝不先发 final 再决定不确认**;φ/打断/失败/无归属 fail-closed 原样保留,完整+合法回执路径的 final 与 ack 均保留。
## 修后验证
- 本地:`delayed-dispatch-after.json`:`{"remote_delete_count_after_ttl": 1, "expired_but_registered": false, "cleanup_owner_present": false}`(cleaner 在全部结清后按设计自行退出,无悬空协程)。
- `.12` 真 SDK(stdin 嵌入当前源码,零写入零网络,`/opt/voice-agent/agent/venv` livekit-agents 1.8.8)`sdk-probe-after.json`:
- `proactive_before_playout_finishes={"handle_done": false, "events": ["proactive_start"]}`(播放中即可见 start);
- 播完 `["proactive_start","proactive_end"]`,字段 `v/kind/role/text/speech_id/message_id` 与 v2 完全一致;
- `no_receipt_incomplete=["turn_start","turn_cancel"]`、`confirmed=[]`;
- 完整+合法回执路径 `["turn_start","assistant_final","turn_end"]` + ack 保留。
- 真SDK回归并入常驻检查 `scripts/gen_hel877_sdk_check.py`(**14/14 ALL PASS**,`../sdk-check-hel879-timing.log`):新增「主动播报时机」「主动播报打断只有 cancel」「无回执不 final」三负例,均驱动实际 worker 模块注册的真实回调。
- 产品套件:pytest **352/352**(3 次连跑);ruff(src/tests/tools) **0 错**;`verify_hel877_voice_chain.py` **8/8**;需求检查 **4/4**(origin/main 与 c121c42 各带/不带 handoff-HEL-879.json)。字幕样例 `subtitle-samples/` 与 v2 契约文档无需再生成(字段与生命周期未变)。
## 边界
零真实 LLM/嵌入/ASR/TTS/麦克风;.12 仅 SSH stdin 执行只读检查(零写入零网络,stderr 仅既有 jwt 弱密钥告警);零下载零安装;未部署、未合并;PR#11 与 `hel-870-assembly` 未动;权威账/冻结/共享服务/队员配置未碰;真实 cap 仍 0。R5 真机、完整 CG-011、R6/R7 继续承接,无需求变更(v1.11/60 项)。
@@ -0,0 +1,60 @@
# HEL-879 · 统一候选一次复审收口(总工,2026-10-27)
> 范围:按总管 2026-10-27 交接,把 HEL-898 三项返修的隔离保全候选
> (`origin/hel-898-preserved` @ `18aef2a`:9254c17 = 作者 12 文件逐字节保全,
> 父 b0dcb17;18aef2a = 仅记录/证据)**正常快进纳入** `hel-879-assembly`,
> 对这三项做最后一次复审收口。不开新审核串、不重审已过后台/预算/备份/receipt
> 时机内核、不替原作者改视觉。零真实调用、零下载/安装/部署/合并,真实 cap 仍 0。
## 一、整合事实
- 快进前远端:`hel-879-assembly` @ `b0dcb17`(11fd0e2/9756db3/b0dcb17 材料成果
全保留,未退回 f92a86f)。快进后 head **`18aef2a`**,无冲突、无强推;
PR#11 与 `hel-870-assembly`(c121c42)未动。
- 9254c17 恰为 12 文件(`web/src` 6 + `web/tests` 5 + `handoff-HEL-898.json`),
与总管 preservation-review.md 记录的作者交付逐字节保全一致;18aef2a 仅新增
`docs/rework/hel898/f92a86f-rework/` 记录与证据(31 文件)。
- **b0dcb17→18aef2a 对 `src/`、`tests/`、`tools/`、`deploy/` 零 diff**
(实跑 `git diff --stat` 为空)——后端/协议/预算/备份/receipt 时机零改动成立。
## 二、三项返修复审(源码级 + 测试级,均通过)
1. **agent 离场僵尸界面/麦克风不停**(`web/src/voice/livekit.ts` +
`session.ts`):transport 按 identity 记录已见 agent(连接事件与初始
remoteParticipants 扫描都入 Set);`ParticipantDisconnected` 仅当离场者是
该 agent 且房内再无其它 agent 时触发 `agentLeft`;控制器走**既有** `fail()`
路径(mic 先停 → room 关闭 → 服务器 DELETE,离线欠账照常补发)。普通参与者
离场、旧房间迟到事件、自己挂断、重复第二 worker 均不误触发——
`web/tests/hel898_agent_left.test.ts` 7 例(含 DELETE 欠账重发与 stale 负例)。
2. **closed 64 挤出复活**(`session.ts`):退休 speech 集合由 64 上限环形数组
改为**每通话 `Set<string>` 全程保留、随代次整体释放**;seq 高水位门不变,
未以放宽乱序保护换修复。f92a86f 审核所记 DOCUMENTED GAP 就此关闭。
3. **播放中短回复被压到 final**(`reading.ts` + `lineStage.ts` +
`ChatPanel.tsx` + `voiceLine`):语音打开的她的行改 `live: 'speaking'`,
末块只显示**已成句**部分(`said()` 截到句末标点+引号括号),文字 generating
原持有规则不变;同键行 `grow()` 原位增长(保留 DOM/已演字,非续写才整段
置换),签名纳入语音行文本驱动重呈现;阅读时钟对增长行只增不减时长。
落库接替沿用同键(turn:mi:index),不重播、不重复演字。
## 三、独立检查(针对本次真实变更,均实跑)
- `web` vitest **118/118**(10 文件,含 hel898_agent_left 7 例、hel879_frames
10 例、voice 45 例);typecheck 0 错;lint 0 错/1 既有 fullButton ref 警告;
build 通过(CSS 哈希 `index-BpZaybfD.css` 与既认可视觉一致,JS 因本次修复
变更为 `index-C23OdPgW.js`,属预期)。
- 需求结构检查 3/3 通过:`--base-ref origin/main`、`--base-ref b0dcb17`、
`--base-ref b0dcb17 --handoff docs/requirements/handoff-HEL-898.json`;
本轮结束后再以 origin/main ± 本卡 handoff 复跑(见提交说明)。
- 浏览器统一回放(两主题电脑/手机、减少动态、worker 实际录制帧、8 段 WebM)
**不重复复跑**:总管已在 18aef2a 记录 53/53 与媒体证据
(manager-checks/browser.log),本轮零安装策略下不重开。
## 四、结论与边界
- 三项返修**离线通过**,与后台 v2 契约无冲突;统一候选当前准确 head
`18aef2a`(本轮复审与 D1 试听材料另追加提交,见分支历史)。
- 仍不冒充的部分:以上全部是假 LiveKit/假媒体的离线回放与单测;真实
WebRTC/麦克风/ASR/TTS、三轮/1 秒打断停声停麦、可信 HTTPS+WSS 设备路径、
iOS/Android、API 重启真实释放**均未测**,待主卡批准后 R5 真机窗口执行。
完整 CG-011 会话中主动表达、完整 mem0 统一体验/R6/R7 继续承接。
- v1.11/60 项无变更;冻结账/权威账未写;既有五实例与共享服务零接触。
@@ -0,0 +1,85 @@
{
"sdk": {
"livekit_agents": "1.8.8",
"livekit_api": "1.2.1"
},
"source": "actual fc905fe+fb7ea84 worker driven on .12 real SDK (livekit-agents 1.8.8, livekit-api 1.2.1) via SSH stdin, zero network/writes",
"scenarios": {
"normal": {
"kinds": [
"interim",
"user_final",
"turn_start",
"assistant_interim",
"assistant_interim",
"assistant_interim",
"assistant_final",
"turn_end"
],
"frames": 8,
"acks": [
"382c41d9-0d72-433e-b370-f838fa5d0bd0"
],
"turn_id": "382c41d9-0d72-433e-b370-f838fa5d0bd0"
},
"interrupt": {
"kinds": [
"user_final",
"turn_start",
"assistant_interim",
"assistant_interim",
"turn_cancel",
"user_final",
"turn_start",
"assistant_interim",
"assistant_interim",
"assistant_final",
"turn_end"
],
"frames": 11,
"acks": [
"edbcb837-f19f-47e7-8d5c-057010da43eb"
],
"turn_ids": {
"cut": "09d05c0f-3058-4ff8-a56e-4e94bd17d7b7",
"next": "edbcb837-f19f-47e7-8d5c-057010da43eb"
}
},
"phi": {
"kinds": [
"user_final",
"turn_start",
"assistant_interim",
"assistant_interim",
"assistant_interim",
"error",
"turn_cancel"
],
"frames": 7,
"acks": [],
"turn_id": "31e70e52-1471-4e50-ac49-cce89979a9bf"
},
"no_receipt": {
"kinds": [
"user_final",
"turn_start",
"assistant_interim",
"assistant_interim",
"turn_cancel"
],
"frames": 5,
"acks": [],
"turn_id": "36d160e3-e365-46ff-ad57-c2878c00720f"
},
"proactive": {
"kinds": [
"proactive_start",
"proactive_end",
"proactive_start",
"proactive_cancel"
],
"frames": 4,
"start_while_playing": true
}
}
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
需求结构检查通过:60项;来源和覆盖完整。仍须人工核对授权语义与实际体验。
@@ -0,0 +1 @@
需求结构检查通过:60项;来源和覆盖完整。仍须人工核对授权语义与实际体验。
@@ -0,0 +1 @@
需求结构检查通过:60项;来源和覆盖完整。仍须人工核对授权语义与实际体验。
@@ -0,0 +1 @@
需求结构检查通过:60项;来源和覆盖完整。仍须人工核对授权语义与实际体验。
@@ -0,0 +1,97 @@
# R5 测试包 v2 · .12 只读实测证据(2026-10-27)
> 性质:本轮材料返修的事实底稿。全部为 SSH 只读命令与输出摘录(零写入、零服务变更、
> 不发任何模型/嵌入/ASR/TTS 请求);涉及凭据的值一律以 `<REDACTED>` 脱敏,不落仓库。
> 命令在 .12(root@192.168.200.12)执行;时间 2026-10-27(UTC 时段见 issue)。
## E1 · 端口监听(`ss -tlnp` 摘录)
监听中(与 R5 相关/受保护):
| 端口 | 进程 | 说明 |
| --- | --- | --- |
| 7880、7881 | livekit-server (pid 219106, docker `voice-livekit`, host 网络) | 7880=HTTP/WS 信令;7881=WebRTC TCP 回落口 |
| 10095 | docker-proxy(funasr-runtime-sdk-online-cpu-0.1.13) | 流式 ASR |
| 8090 | python (pid 2136834, `/opt/voice-agent/agent/venv/bin/python server.py`, cwd `/opt/voice-agent/web`) | 语音演示页(https-only,见 E3) |
| 8081 | python (pid 451883, `venv/bin/python agent.py start`, cwd `/opt/voice-agent/agent`) | 通用 cascade worker |
| 8300/8310/8320/8340/8350 | 各既有 CyberGril 实例 | 受保护,不触碰 |
**空闲(无监听)**:8360、8361、8363、8082、9880(TTS 停机中)。
## E2 · LiveKit docker 无 TLS 配置(`docker inspect` + `docker-compose.yml`)
- `voice-livekit`:`command: --bind 0.0.0.0`,`network_mode: host`,无任何证书挂载
(Mounts=[]),环境仅 `LIVEKIT_KEYS`(值脱敏)。
- 探测:`curl http://127.0.0.1:7880` → **200**(纯 HTTP 存活);
`curl -k https://127.0.0.1:7881/` 与 `curl http://127.0.0.1:7881/` → 均失败
(7881 不是 HTTP(S) 端点,是 WebRTC TCP 回落口,不能当 WSS 信令用)。
## E3 · 8090 演示页现状
- `https://127.0.0.1:8090/` → 200;`http://127.0.0.1:8090/` → 失败(https-only)。
- 证书:`CN = minicpmo45-dev`,**无 subjectAltName 扩展**,notAfter 2027-02-11。
- `server.py:66`:`LIVEKIT_URL = "ws://192.168.200.12:7880"` —— 页面 `/api/token`
把该 ws:// 地址发给浏览器 `room.connect(url, token)`。https 页面连 ws:// 属
**混合内容**,库存浏览器默认拦截;**没有任何证据**表明该页在标准浏览器走通过
媒体链路。旧材料「自签一次点击同时覆盖 LiveKit WSS 7880」不成立,已删除。
- 面板功能(index.html/server.py 实读):音色列表 `GET /api/voices`、切换
`POST /api/voice`(写共享 `voice_state.json`)、克隆上传 `POST /api/voices/upload`
(写共享 `voices.json` + TTS 热加载)、删除。**没有既有音色的试听播放功能**
(`vPreview` 仅用于克隆上传的自选文件)。
## E4 · 通用 worker 依赖 7880 保持明文(改 TLS 的破坏面)
`/opt/voice-agent/agent/.env`:`LIVEKIT_URL=ws://127.0.0.1:7880`(另有
LIVEKIT_API_KEY/SECRET,值脱敏)。**7880 一旦改 TLS-only 即断通用 worker 与
8090 演示,禁止作为本卡路线。**
## E5 · .12 上没有已装 mem0 的产品 venv(「零真缺」不能笼统写)
- 8310(`/opt/cybergril-api/current/.venv`,r2-20261022)与 8350
(`/opt/cybergril-hel835/current/.venv`)抽查:`python -c "import mem0"` 均
`ModuleNotFoundError`;两者均为 **Python 3.12.3**(系统 python 建 venv)。
- 8320/8340 同构部署(各自 `current/.venv`),未逐一导入验证。
- 结论:所抽查的两处不能提供已装 mem0 闭包,不能据抽查推断 .12 所有实例均已
验证不存在。完整统一体验依赖分类以本仓库
`docs/rework/hel879/release-artifacts.json`(总管已核准)为准。
## E6 · worker venv 与 TTS 底座(语音专项链已备)
- `/opt/voice-agent/agent/venv`:`import livekit, livekit.agents` 成功,
livekit-agents **1.8.8**(venv 为 python3.12,worker 不依赖产品 mem0 闭包,
经 HTTP 调统一 API)。
- CosyVoice:`/opt/voice-agent/cosyvoice/server.py` 实读——路由 `/health`、`/tts`
存在;`uvicorn.run(host="0.0.0.0", port=9880)`;当前 9880 无监听(停机待授权
重启);权重在位(`/opt/models/CosyVoice3-0.5B-2512`,约 9.1GB)。
## E7 · 音色真实清单(voices.json 实读)
- 11 个 ID:`bailu, bajie, default, female_soft, haimianbaobao, jiaojiao,
male_warm, nezha, rain, sunwukong, xiaoyun`;每项含 `prompt_wav` 指向
`voice_samples/<id>.wav`(zero-shot 参考音频,即该音色的音色来源录音,
bailu.wav 1.77MB 等,目录实存)。
- 共享演示当前 `voice_state.json` = `{"voice": "rain"}` —— 是通用演示状态,
**不是本用户的选择**,不作为本用户音色依据。
## E8 · GPU / 内存 / 磁盘(`nvidia-smi` / `free` / `df`)
- RTX 5090D:总量 32607 MiB,已用 1558 MiB,**空闲 30520 MiB**(ComfyUI 仍处
已授权暂停;GPU 进程仅 gnome-remote-desktop 996MiB)→ CosyVoice 常驻约
5.6GB 无障碍。
- 内存 94GB(可用 83GB);`/` 916GB 用 16%,余 735GB。
## E9 · /opt 无冲突目录
`/opt` 现有 `cybergril-api / cybergril-data / cybergril-hel822 / cybergril-hel829 /
cybergril-hel835 / cybergril-ops / cybergril-s1 / voice-agent / models` 等;
**无 `voice-agent-cybergril`、无 `cybergril-voice-certs`**(新建不冲突)。
## E10 · 预算硬闸语义(本仓库代码,非 .12)
`src/cybergril/think/budget.py` `acquire()`:真实 provider 出口**发送前**原子
预占(类别日顶 `daily_requests` + 一次性总量 `total_requests` + `daily_tokens` +
`total_tokens` 同步检查,任一到顶抛 `BudgetStop`,持久化实现落在单个
`BEGIN IMMEDIATE` 事务)。**失败/重试/打断/后台调用同样计数**——计数粒度是
provider 调用,不是成功聊天行数。封账 `budget_sealed` 拒绝一切 acquire。
语音轮经统一 `/api/chat` 走同一 dialogue 闸;`POST /api/voice/session` 建房
**当前不在 budget 闸内**(如实列为操作侧上限+工程后续小项)。
@@ -0,0 +1,223 @@
# HEL-879 · R5 真机测试包 v2(供主卡拍板的集中材料,未执行)
> 性质:**准备材料**(v2 材料返修版,按总管 2026-10-27 四项返修清单重写)。零真实
> 模型/嵌入/ASR/TTS/麦克风请求、零下载/安装/部署/合并、真实 cap=0。.12 全部事实为
> 2026-10-27 只读实测,底稿与命令摘录见同目录 `r5-facts-20261027.md`(脱敏)。
> 事实三级标注:**已证**(实测/代码实读)|**拟议**(材料备齐待批,未执行)|
> **待真机**(须获批实测后才能写通过)。
>
> v2 勘误(相对 v1):①删「统一实例依赖零真缺/零下载」笼统结论,改两轨拆分(§1);
> ②删「自签一次点击同时覆盖 LiveKit WSS 7880」——7880 现为纯 ws 且被共享服务依赖,
> WSS 改由独立 8363 桥承担(§3);③预算 30 轮表改逐类计数位置+硬闸/操作停机拆分
> (§6);④工程项(端口/目录/健康口)由工程核定(§10),不再交用户选择;D1 改实际
> 音色清单+零合成试听方案(§4)。
## 1. 依赖两轨(不能混称「零真缺」)
### 1a. R5 语音专项(语音链本身)——**已证**零真缺、零下载
| 部件 | 状态 | 依据 |
| --- | --- | --- |
| LiveKit SFU | ✅ 运行中 .12:7880/7881 | 已证 7880 HTTP 200;**纯 ws 无 TLS**(E2) |
| FunASR 流式 ASR | ✅ 运行中 .12:10095 | 已证监听(E1) |
| worker 运行时 | ✅ 在位 `/opt/voice-agent/agent/venv` | 已证 import livekit-agents 1.8.8(py3.12;worker 经 HTTP 调统一 API,不依赖 mem0 闭包,E6) |
| CosyVoice3 TTS | ⛔ 停机待重启授权 | 已证权重在位(约 9.1GB)、9880 无监听、`/health` `/tts` 路由存在(E6);重启=运维动作非下载 |
| GPU/内存/磁盘 | ✅ 充足 | 已证空闲 30520MiB / 83GB / 735GB(E8) |
| 统一 API 实例(8360) | ⛔ 待部署 | 部署物=本候选 head + 已过发布包 §2 全离线基础安装(requirements.lock 装机全部命中既有缓存,已证,见 sdk-audit) |
**语义边界(必须如实)**:本轨在 `semantic=stub` 下验收**语音链**——同一份档案数据、
同一进程、同一把轮次锁,但**嵌入 0、语义记忆未激活**。本轨通过 ≠ 「同档案全记忆
验收」;完整统一体验是 1b,两者分开批、分开验,互不冒充。
### 1b. 完整统一体验(真实 mem0 记忆)——41 项闭包,**真缺待授权**(已证分类)
- **已证范围**:.12 的 8310/8350 两个既有产品实例 venv 抽查均
`ModuleNotFoundError`,且均为系统 3.12.3;8320/8340 未逐一验证,E5。
不能据两处抽查声称所有实例均已验证,也没有可据此采用的已装 mem0 闭包——
「零真缺」对完整体验**不成立**。
- 分类(`docs/rework/hel879/release-artifacts.json`,总管已核准口径):
- **已离线成品**:mem0ai 固定源码轮 `mem0ai-2.2.1-py3-none-any.whl`
(sha256 `698284b6…20e9db`,352,586 B / 153 文件,源 tarball 与
`deploy/mem0-stack.lock` 哈希一致;未安装任何位置)。
- **缓存可复用**:greenlet 3.5.6(cp311)、pillow 12.3.0(cp311)、
truststore 0.10.5(py3-none-any)——本机 uv wheels-v6/v7 实测命中。
- **待准备 37 项**(26 纯 Python + 11 原生;原 28/13 分别扣除
mem0ai、truststore 两项纯 Python 和 greenlet、pillow 两项原生):按当前闭包
扣除已成品/可复用项的数量,尚不等于目标平台已解析完成的下载结果。需下载授权后按
发布包 §4 第 3 步拉取,逐项 sha256 留证;**体积未下载不猜**,下载窗口逐项
记录。hatchling 已离线解决(1.32.4 缓存),不再申请。
- **fastembed 模型缓存**:已证在位 bge-small-zh-v1.5 190,444,780 B +
bm25 56,247 B(artifacts 实测数;hel895 文档「约 92MB」与此不符,以
artifacts 为准——已列勘误待修)。
- 逐项版本/哈希:以 `release-artifacts.json` 与
`docs/rework/hel879/sdk-audit/sdk-audit.json` 为准(不在本文件复制防漂移)。
- 安装位置:统一实例自足 `.venv311`(uv 管 CPython 3.11.16,发布包 §2/§4 唯一
入口);不混 .12 系统 3.12、不碰既有实例 venv。
- 边界重申:不以关闭 mem0、改 stub、少测记忆默认缩减用户完整体验要求。
## 2. 资源现状(已证,2026-10-27)
GPU RTX 5090D 空闲 30520MiB(ComfyUI 仍处已授权暂停);内存 94GB/可用 83GB;
盘 916GB/余 735GB。端口实测:8360/8361/8363/8082 **空闲**;受保护监听
8300/8310/8320/8340/8350/8090/8081/7880/7881/10095 一律不动;9880 停机待授权。
## 3. 可信 HTTPS + WSS(D3)——已证 / 拟议 / 待真机
### 3.1 已证事实(为什么 v1 的说法必须撤回)
1. LiveKit 7880 = **纯 HTTP/WS**(docker 无证书挂载无 TLS 配置,E2);7881 监听
但**不是 HTTP(S) 端点**(http/https 探测均失败),是 WebRTC TCP 回落口,
**不能当 WSS 用**。
2. 通用 worker 与 8090 演示都依赖 `ws://127.0.0.1:7880`/`ws://…:7880`(E4)——
**7880 改 TLS 会破坏共享服务,禁止**。
3. 8090 演示页 https-only(证书 `CN=minicpmo45-dev`、**无 SAN**、至 2027-02-11);
它给浏览器发的就是 `ws://…:7880`(E3)——https 页连 ws 属**混合内容**,无证据
表明曾在标准浏览器走通。旧句「自签一次点击同时覆盖页面 HTTPS 与 WSS 7880」
**不成立,已删除**。
4. `config/voice.yaml` 是部署本地文件(模板 `config/voice.example.yaml`),仓库内
无该文件——v1 写法勘误。
### 3.2 拟议路线(材料已备齐,未执行;缺的就是这个 WSS 桥,如实列出)
- **新自签证书一张**:`openssl req -x509 … subjectAltName=IP:192.168.200.12`,
存 `/opt/cybergril-voice-certs/`(600),同证书服务 8361 与 8363 两个端口。
- **8361**:统一实例 HTTPS 入口(同进程双入口,runbook-hel877-voice §4.4;
本地 18360/18361 已冒烟,.12 未执行——拟议)。
- **8363(新增)**:TLS→明文 TCP 桥 `deploy/voice/wss_bridge.example.py`
(asyncio+ssl 标准库零新依赖;本地冒烟 TLS1.3+字节穿透通过,见
`wss-bridge-smoke.log`):`wss://192.168.200.12:8363` → `127.0.0.1:7880`。
`voice.yaml` 的 `livekit_url` 改 `wss://192.168.200.12:8363`;
`livekit_internal` 保持 `http://127.0.0.1:7880`。不动 LiveKit、通用 worker、
8090(独立进程,回滚=停进程)。
- WebRTC 媒体面(UDP/7881 TCP)不经桥,由 WebRTC 自带 DTLS/SRTP 加密、无需
证书例外——协议性质说明,实际连通**待真机**。
### 3.3 每设备信任步骤(拟议;**分端口各一次**,不共享例外)
1. 打开 `https://192.168.200.12:8361` → 高级→继续(页面例外)。
2. 打开 `https://192.168.200.12:8363` → 高级→继续(WSS 信令例外;该地址 GET
返回 404/400 属预期,只看证书警告页点继续)。
3. 允许麦克风。
- 两个实际端点分别打开并验证;不推断一个端点的例外必然覆盖另一个,也不把
浏览器的例外缓存范围写成已实测事实。**不用** `--ignore-certificate-errors`、
不装根证书、不改系统信任库(未授权,也不作为验收路径)。
- **待真机**:iOS Safari「访问此网站」是否放行 wss、安卓 Chrome 部分版本无
「继续」按钮的已知限制——验收时如实记录;桌面 Chrome/Firefox 为保底组合。
### 3.4 备选路线(不采用,写明理由)
- LiveKit 原生 TLS(7880 直接上证书):破坏通用 worker/8090(E4),**禁止**。
- 系统 nginx 反代:.12 无运行中 nginx(E1/E2),安装/改系统配置超出零安装边界。
- 忽略证书/不安全浏览器开关:明令禁止,不作为验收。
## 4. D1 音色(实际清单 + 零合成试听方案;不替用户定)
- **已证**(voices.json 实读,E7):音色共 **11 个 ID**——`bailu、bajie、default、
female_soft、haimianbaobao、jiaojiao、male_warm、nezha、rain、sunwukong、
xiaoyun`;每支 = zero-shot 参考音频克隆,参考文件
`/opt/voice-agent/cosyvoice/voice_samples/<id>.wav` 实存(即该音色来源说话人
的真实录音)。v1「6 支女声」及各主观描述系笔误+猜测,已删;不猜名字定效果,
不引用通用演示状态(当前 `rain`)或其他测试/旧角色资料作为本用户选择。
- **已证**:8090 面板**没有既有音色试听播放**(只有列表/切换/克隆上传/删除),
且切换/上传写**共享** `voices.json`/`voice_state.json`、影响通用演示——v1
「试听入口=8090 面板」勘误删除,未经授权不用它给本用户做试听。
- **试听已交付(2026-10-27,零合成、零 GPU、零下载)**:`HEL-879-D1-参考试听包.zip`
随 HEL-879 评论附件交付——自包含离线 `index.html` + 11 段参考 wav + `manifest.json`
(逐文件 sha256 与服务器一致;来源与边界见 `docs/rework/hel879/d1-audition/`)。
白栖知解压后用浏览器直接打开试听初选;音色效果以参考音频为初感 + 获批测试窗口内
真实合成复核(计入 §6 TTS 计数)。参考录音原文按来源原样保留,不预筛。
- 可选:克隆(录 5–20s 中文参考)需写共享 `voices.json`(新增条目为增量,但会触
共享 TTS 热加载)→ **需单独授权**,默认不启。
- worker 缺省回落 `default`(代码已证);专属 worker 用独立
`voice_state_cybergril.json`,不写通用演示状态(runbook §4.1)。
## 5. 部署顺序(获批后执行;含回滚)
1. 统一实例(8360 + 8361 同进程双入口 + **8363 WSS 桥**,隔离数据目录与
`/opt/voice-agent-cybergril/`、`/opt/cybergril-voice-certs/`;不动
8300/8310/8320/8340/8350/8090/8081/7880/10095)。
2. TTS 9880 重启:先核实 9880 无监听再按 cosyvoice 运维节启动;历史 `fuser -k`
禁止照抄(当前已证停机,无需杀任何进程)。
3. 专属 worker(`/opt/voice-agent-cybergril/`,agent venv 启动,健康口 8082,
`CYBERGRIL_API_BASE=http://127.0.0.1:8360`)。
4. `config/voice.yaml`(从 `config/voice.example.yaml` 复制):键
`livekit_url: wss://192.168.200.12:8363`、`livekit_internal:
http://127.0.0.1:7880`、api_key/api_secret 按 .12 现值只写服务器本地文件。
- 回滚:停 worker → 停 8363 桥 → 停 8360/8361 → TTS 回停机或原状(全独立进程);
Git 回退=本分支回退到 fc905fe 重新部署;冻结账与既有实例零接触。
## 6. 有限预算与停止条件(逐类;「代码硬闸」与「操作停机」分开写明)
**计数语义(已证,E10)**:budget 账 `acquire()` 在 provider 调用**发送前**原子
预占——失败/重试/打断/后台调用同样计数;**LLM 调用数 ≠ 成功聊天行数**。ASR 流、
TTS 分段、φ 播报、打断重合成、主动 say、闲置通话都**不等于** LLM 轮数,另列。
| 类别 | 建议上限(供拍板,不自动生效) | 计数位置 | 闸性质 | 异常停法 |
| --- | --- | --- | --- | --- |
| LLM 真实调用(dialogue 类,含语音轮与失败重试) | ≤40 次请求 / ≤500k tokens | 实例 budget 账 `request_usage`/`token_usage` 逐笔 | **代码硬闸已实现**:类别日顶+一次性总量+日 token 池发送前拒绝;封账库拒绝一切 | 到线自动停;异常扣费风险=立即停窗口(下 1–3 项全停) |
| 嵌入 | **0**(R5 专项 stub) | semantic 账(`embed_ops` 未启用) | **代码硬闸已实现**(semantic cap=0 fail-closed);1b 另批 | — |
| ASR(FunASR 流) | 测试窗口 ≤2h 且通话 ≤3 次 | worker 日志 job/stream 数(本地 CPU 服务,零外部计费) | **无代码闸(如实)**→操作停机 | 窗口到点/会话数到线/异常即停 worker |
| TTS(CosyVoice 合成段,含 φ/打断重合成/主动 say/分段) | 窗口内 ≤200 合成段 | `tts.log` 合成行(本地 GPU,零外部计费) | **无代码闸(如实)**→操作停机 | 同上;GPU 显存异常(>8GB 或 OOM)即停 |
| 语音会话(建房) | ≤3 次完整通话 | `POST /api/voice/session` 服务端日志 | **无代码闸(如实;列工程后续小项:建房数纳入 config 上限)** | 第 3 次结束或验收完成即停 |
| 主动开口 | 默认关(`CYBERGRIL_PROACTIVE_VOICE` 不设) | — | 已实现默认关;如开只念已落库行、不新增 LLM 调用(已证代码路径) | — |
- **上限不自动生效**:所有 cap 配置当前仍 0,批准后由总管写入实例配置;权威账/
冻结账不写、封账库永不重开;真实 cap 本轮=0。
- 逐笔输出:budget 表逐行 + worker/tts 日志,窗口结束导出逐笔清单交主卡。
- 本表数字为建议值,最终由白栖知定;**不以 30 轮聊天行数顶替任何账**。
## 7. 验收清单(R5 真机,integration-plan 原四条 + 补充)
1. 连续交谈 ≥3 轮(同一她、同档案、记忆延续,音色一致;R5 专项内「记忆」=
档案与对话历史,语义记忆属 1b 不冒充)。
2. 开口打断约 1 秒停声(VAD→TTS 截断;字幕同步消失)。
3. 挂断立即停麦(track.stop + 房间删除;麦克风指示灯灭)。
4. 无旧残音/残影(打断与掉线后旧帧不回显;已有普通离线用例通过,长会话防护等
HEL-898 三项返修仍待交回统一复核,随后真机复验)。
5. 网络失败/重开:诚实提示、重开仍是同一位她。
6. **WSS 桥 8363 + 页面 8361 分端口信任**(§3.3),真机记录各浏览器行为;
桌面 Chrome/Firefox 保底,移动端如实记录不硬闯。
7. 字幕全生命周期(后台实际 v2 帧及前端状态单测已有证据;HEL-898 短回复显示、
agent 离场与长会话防护返修尚未交回,不称界面层离线全通过;之后真机抽查
增量→final→落库一致)。
8. API 重启释放(房间与 worker job 真实回收)一次受控验证。
9. 口型**不在本包**(不伪装接通)。
## 8. 真正待白栖知拍板的项(工程项已移出,见 §10)
1. D1 音色:听 `HEL-879-D1-参考试听包.zip` 后选 1 支(或另行授权克隆)。
2. 有限真实用量与新增费用上限确认/调整(§6 各值;R5 专项嵌入 0)。
3. 测试窗口与共享 TTS 重启授权(占 GPU ~5.6GB;ComfyUI 保持暂停到测试后与否)。
4. 1b 完整统一体验的当前 37 项待准备依赖下载授权(目标 cp311 解析后核定实际
产物清单,可与 R5 专项分开批;已有成品/缓存不重复下载;hatchling 已解决
不再申请)。
> 集中请求口径(总管在主卡一次发出):试听包 + §6 逐类上限/费用 + §5 部署与
> 回滚 + §1b 下载授权,四项一次说清,批准后按 §5 顺序执行;R5 语音专项
> (stub、嵌入 0)与完整统一体验分开批、分开验,ASR/TTS/建房只有操作停机
> 无代码硬闸,均如实表述。
## 9. 本包不做
不合并 PR#11/任何 PR;不替换 8300/8310 正式入口;不改队员运行时/模型/认证;
不动冻结账与既有五实例;CG-011 完整会话中主动表达仍承接(默认关的「开场念一条
旧留言」不冒充完整项);R6/R7 继续承接。
## 10. 工程已定项(工程核冲突后定案,不交用户选)
| 项 | 取值 | 冲突核对(2026-10-27 实测) |
| --- | --- | --- |
| 统一实例端口 | HTTP 8360 + HTTPS 8361 | 空闲(E1);与 8300–8350/8090/8081 不冲突 |
| WSS 桥端口 | 8363 | 空闲(E1;本轮新增,v1 未列) |
| worker 健康端口 | 8082 | 空闲(E1;8081 为通用 worker,差一位不误用) |
| worker 目录 | `/opt/voice-agent-cybergril/` | 不存在、新建无冲突(E9) |
| 证书目录 | `/opt/cybergril-voice-certs/` | 不存在、新建无冲突(E9) |
## 附 · 材料与证据指针
- .12 只读实测底稿:`docs/rework/hel879/unified-review/r5-facts-20261027.md`(脱敏)
- WSS 桥:`deploy/voice/wss_bridge.example.py` + `wss-bridge-smoke.log`
- 依赖分类:`docs/rework/hel879/release-artifacts.json`、`sdk-audit/sdk-audit.json`
- 预算闸:`src/cybergril/think/budget.py`(acquire 语义)、`docs/budget-package-hel895.md`
- 部署草案:`docs/runbook-hel877-voice.md` §4、`deploy/hel895-release-candidate.md` §2/§4
@@ -0,0 +1,42 @@
# HEL-879 统一整合审核 · 焦点2:seq 命名空间与 job/房间/控制器代次(2026-10-27)
问题(原作者 HEL-898 提出):`SubtitleBus` 的 `seq` 按 worker job 从 1 计(`src/cybergril/voice/worker.py` `entrypoint` 内建 bus),而前端 `VoiceController` 按**控制器会话**(一个 `Live`,web/src/voice/session.ts `live.seq`)记高水位。若同一房间在控制器会话存活期间换了新 job(worker 重连/新派发),新 job 的 seq 归零,前端要么误丢新帧、要么误收旧帧。本文件用 .12 真实 SDK 源码(livekit-agents 1.8.8 / livekit-api 1.2.1,`/opt/voice-agent/agent/venv/lib/python3.12/site-packages/`,SSH 只读取证,零写入零网络)与仓库代码逐条核清。
## 1. 一个 job 只跑一次 entrypoint(seq 计数器只生一次)
真实源码 `livekit/agents/ipc/job_proc_lazy_main.py`(默认进程执行器路径):
- L241–246:进程内已有运行中 job 时再收到 `StartJobRequest` 只告警跳过(`"trying to start a new job while one is already running"` → `continue`)——**一个 job 进程最多承载一个 job**。
- L318–323:`_start_job` 为该 job 建 `JobContext` 并启动**唯一** `job_task`。
- `_run_job_task`(L327 起)里 `self._job_entrypoint_fnc(job_ctx)` 只被创建为一个 task(`job_user_entrypoint`);job 在以下任一时刻结束:entrypoint 返回、`_shutdown_fut` 置位(room disconnected / shutdown 请求 / job 崩溃),随后 `_shutdown_job` → `_on_cleanup`,进程随 `_exit_proc_flag` 退出。
结论:worker.py 的 `entrypoint`(含 `SubtitleBus` 实例)在**一个 job 内恰好执行一次**,job 结束即进程结束,不存在「同 job 二次 entrypoint 导致 seq 归零」。
## 2. 断线重连不重跑 entrypoint(seq 不归零)
真实源码 `livekit/rtc/room.py`:
- L85–86 / L1130–1133:瞬断走 `reconnecting` / `reconnected` 房间事件——这是 `rtc.Room` 内部的信号/媒体重连,job 任务不重建,entrypoint 不重跑。
- 只有房间真正关闭才发 `disconnected`;job_proc_lazy_main L288–293 将其置入 `_shutdown_fut`(reason=`room disconnected`)→ job 结束。此时房间已不存在,前端收到 `dropped`(livekit.ts L75),整个 `Live` 连同 `live.seq` 一起终结。
结论:**同一 job 内的 worker 重连,`SubtitleBus` 实例保留,seq 连续递增**;房间没了 = job 没了 = 前端这次通话也没了,不存在跨 job 的旧高水位。
## 3. 本产品没有「同房间第二个 job」的路径
- 后端唯一派发点:`src/cybergril/voice/session.py:396` `open_session` 内 `livekit_wire.create_dispatch(...)`,房间名 `cg-voice-` + 10 位随机(L394),每次 POST /api/voice/session 都是**新房间新派发**;`create_dispatch` 在 src/ 下仅此一处(grep 证据在库)。没有任何对既有房间补发/重发 job 的代码路径。
- 前端 join 的就是该房间(grant.room),一个控制器会话 ↔ 一个房间 ↔ 一个显式派发 ↔ 一个 job ↔ 一个 `SubtitleBus`,五者一一对应。
- 服务器侧 job 重投(resend)只发生在 job 从未启动时(无可供 worker 接单/启动超时):那时第一个 entrypoint 从未运行、从未发过任何帧,前端 `live.seq` 仍为 0,重投 job 从 1 计与前端水位天然一致。SDK 源码中不存在「已运行的 job 迁移到另一进程」的机制(job 进程即 job 本体,崩溃即终止;livekit-agents 侧无 resend 代码,grep `resend` 仅命中无关模块)。
## 4. 真实残余缺口(非 seq,但同场景):worker job 中途死亡 = 前端僵尸「listening」
job 死亡(worker 进程崩溃/被运维重启)时:agent participant 离开房间,但**房间仍在**(用户还在),前端收不到 `dropped`(它只挂 `Disconnected`,web/src/voice/livekit.ts L69–75 只处理 `ParticipantConnected`/`DataReceived`/`Disconnected`/`AudioPlaybackStatusChanged`),页面停在最后相位(多为 listening),再无任何帧。`RoomEvents` 无 agentLeft。这不破坏 seq 或旧帧防护(没有第二命名空间),但用户会面对一个没有她的「通话中」。有限修复建议(归原作者,前端):`livekit.ts` 增加 `ParticipantDisconnected`(agent 身份)→ `RoomEvents.agentLeft` → `VoiceController` 以诚实话术收场(与 dropped 同路径、不删她的话轮)。后端无需改动。
## 5. 可执行复核方式
- 证据 A(本轮):`scripts/gen_hel879_unified_frames.py` 在 .12 真 SDK 上驱动实际 worker 回调生成全部 v2 帧(见 frames/),各场景 seq 严格递增、每 speech 恰一次 `turn_start`。
- 证据 B(源码引用,可重复):本文第 1/2/3 节的 file:line 均可在 .12 venv 直接核对;`.12` 只读 SSH stdin。
- 证据 C(前端侧):`web/tests/hel879_frames.test.ts` 用实际 worker 帧验证 replay/迟到/乱序帧全部被 seq+speech 双门拦下;「新通话 = 新房间 = 新序列」在 drop+重开用例中验证(旧房间帧不再影响新 `Live`)。
## 6. 结论
**现网代码内,seq 命名空间 = job = 房间 = 控制器会话,一一对应;不存在「同房间新 job 序号归零」的产品路径或 SDK 机制。** 前端以控制器会话记高水位是安全的,且对任何假设的第二命名空间表现为 fail-closed(旧水位丢弃全部低 seq 新帧,不会把旧帧当新帧收)。唯一真实缺口是 §4 的 agent 离场僵尸界面(🟡 建议项,归原作者)。
@@ -0,0 +1,44 @@
# HEL-879 统一整合审核记录(2026-10-27)
整合对象:`hel-879-assembly` @ fc905fe(后台时机收口)+ `origin/hel-898-preserved` @ fb7ea84(总管逐字节保全的 HEL-898 前端接线,作者代码 46ac0a7)。本审核不开新串、不重审已过内核(预算/备份/恢复/绑定/三处时机/总管 898 六项检查均沿用其通过记录)。
## 统一 head
`hel-879-assembly` 快进至 **fb7ea84 + 本轮审核提交**(审核物:帧生成脚本、fixtures、前端用例、浏览器回放 harness、SDK 取证、R5 测试包、handoff 追加)。与 fc905fe 的差异 = HEL-898 的 8 文件(6 web/tests+docs/handoff-898+2 web/src:ChatPanel.tsx/session.ts,总管已核逐字节=作者附件)+ 本轮审核新增文件;`web/src` 除作者两文件外零改动,`src/` 零改动,PR#11 与 hel-870-assembly 未动。
## 焦点1 · 实际 worker 帧 → 当前前端(功能)
**帧来源**:`scripts/gen_hel879_unified_frames.py` 在 .12 真实 SDK(livekit-agents 1.8.8 / livekit-api 1.2.1,SSH 只读 stdin,零写入零网络)驱动**实际 worker 接线**(`make_voice_llm` 归属绑定 + `bind_voice_session` 真回调 + 真 SpeechHandle 生命周期 + `speak_proactive_row`)生成五套 v2 帧(frames/ + web/tests/fixtures/hel879/)。唯一替身=HTTP 客户端(离线注入 /api/chat SSE)与播放时钟(`_mark_done`/`interrupt` 定时器)。
场景与结果(vitest `web/tests/hel879_frames.test.ts` 9/9,实际帧逐帧喂 `VoiceController`):
| 场景 | 帧序列(实测) | 前端行为(断言) |
| --- | --- | --- |
| normal | interim→user_final→turn_start→3×interim→**final→end**(receipt ack ✓) | 增量替换生长;final=存稿原文;落库回读接替:`voice-<speech>` 键→存档行 id(同 turn),mergeTurns 恰加一次 |
| interrupt | cut 轮 **无 final 只有 cancel(interrupted)**;下一轮干净 final+ack | 被切行立即清;迟到帧(新 seq、死 speech)不回显;下一轮独立落库一次 |
| φ | interim(φ)→error(φ 原句)→cancel(error_phi),无 ack | spoken 槽可闻不入档;her 即清 |
| no_receipt | **cancel(no-receipt),无 final**(fc905fe 门实测生效) | 无任何「她的回复」残留 |
| proactive | start **播放中**(sidecar `start_while_playing=true`)→end;打断变体→cancel(interrupted) | 她的位置整句显示、绑定存档行 id;打断即清;页面零写请求 |
打断/文字接管/掉线重开(vitest 全过):user_final 立即关她当前 speech;interim 不抢清(由服务端 cancel 定夺);`hush()` 文字接管即清行+静音;`dropped` 整通话收场清残影;重开=新房新序列,旧房间帧无效。舞台身份与键接替(voiceLine/mergeTurns)用实际帧 turn_id 验证。
**界面层**:本机缺浏览器 GUI 系统库(libatk/libgbm 等十项,chromium/chrome-headless-shell 均无法启动),零安装授权下**未独立复跑浏览器**。`web/tests/hel879_unified_shots.mjs`(自持 mock+CDP,回放实际 worker 帧、双主题双尺寸)已入库,供有 Edge/Chromium 环境一键回放。界面证据仍=作者 61/61 假 LiveKit 浏览器与两主题截图(总管保全,未复跑,不称独立通过)。**录屏:本轮无新连续帧/视频产出,如实列未交。**
## 焦点2 · seq 命名空间与 job/房间/控制器代次
结论(详见 `seq-job-analysis.md`,全部 file:line 指向 .12 真 SDK 源码):**job=房间=派发=控制器会话一一对应,无「同房间新 job 序号归零」路径**。一 job 恰一次 entrypoint(job_proc_lazy_main L241-246/L318-323);RTC 瞬断走 reconnecting/reconnected 不重跑(rtc/room.py L85-86/L1130-1133),房间真断=job 结束=前端 Live 同终;后端唯一派发点 session.py:396 每次新房(10 位随机名);服务器重投仅限从未启动的 job(从未发帧,水位天然一致)。若假设性第二命名空间出现,前端表现为 fail-closed(丢帧而非收旧帧)。**残余缺口(🟡 归原作者前端)**:worker job 中途死亡(进程崩溃/重启)时 agent participant 离场但房间仍在,livekit.ts 无 agentLeft → 页面僵尸「listening」。有限修复:`ParticipantDisconnected`(agent 身份)→ 诚实话术收场;后端无需改。
## 焦点3 · closed 64 上限与长会话/清理边界
- **DOCUMENTED GAP(🟡 归原作者,非现网可触发)**:64+ 后续关闭把退休 speech 挤出窗口后,**重复高 seq turn_start 可重进**(vitest 用例以对抗帧实证:重进→重放终稿可再次立为她的行)。现 worker 无法产生该帧(SDK speech_id 每话唯一、`interim.begin` 每流一次、无重发路径),触发需第二/失常来源——与焦点2结论一致。防御性修复建议:`closed` 改无界 `Set<string>`(每通话 speech 数有 TTL 上界,内存可忽略)或对 turn_start 增加按 job 内序号窗的约束。
- 新用户 interim→cancel/终稿清理边界(vitest 全过):interim 只更新 heard 不清她的行(打断由服务端定夺);user_final 立即关闭当前 speech 并清行;cancel/final/turn_end 的即时清理由实际帧验证。
- 舞台回读/身份与键接替:实际帧 turn_id 驱动(焦点1 表),非单测数字。
## 回归与需求检查
- pytest **352/352**(py3.11 venv);vitest **108/108**(作者 99+本轮 9);typecheck/build 通过;lint 0 错 1 既有 fullButton 警告;ruff(src/tests/tools) 0 错。
- 需求检查 4/4:origin/main 与 fc905fe 各带/不带更新后的 handoff-HEL-879.json(本目录 requirements-*.log)。v1.11/60 项无需求变更;总管 898 四项检查沿用。
## 边界
零真实 LLM/嵌入/ASR/TTS/麦克风、零下载/安装/部署/合并;.12 仅 SSH 只读取证(含一次 nvidia-smi/端口探活供 R5 包);真实 cap=0;不选音色、不改绑定、不碰旧服务/档案/冻结账。R5 真机包材料(`r5-test-package.md`)仅备齐待批,未执行。完整 CG-011、R6/R7 继续承接。
@@ -0,0 +1,21 @@
# wss_bridge.example.py 本地冒烟(2026-10-27,零外网、回环 only)
- 一次性本地自签证书(CN=local-bridge-test,2 天有效期,仅本测试)。
- dummy 上游 127.0.0.1:18880(读 5 字节回 echo);桥 127.0.0.1:18363 → 18880。
- 结果:TLSv1.3 握手成功;`sendall(b"hello")` 经桥穿透后收到 `b"echo:hello"`。
- 结论:TLS 终结 + 字节透明转发路径可用;.12 实部署与 LiveKit 真实信令仍属
拟议/待真机(本冒烟不证明后者)。
```
$ python3 deploy/voice/wss_bridge.example.py --listen 127.0.0.1:18363 \
--upstream 127.0.0.1:18880 --cert cert.pem --key key.pem &
wss-bridge(PROPOSAL) tls ('127.0.0.1', 18363) -> ('127.0.0.1', 18880)
$ python3 - <<'PY'
ctx = ssl.create_default_context(); ctx.check_hostname=False; ctx.verify_mode=ssl.CERT_NONE
s = ctx.wrap_socket(socket.create_connection(("127.0.0.1", 18363), timeout=5))
print("TLS:", s.version()); s.sendall(b"hello"); print("payload:", s.recv(64))
PY
TLS: TLSv1.3
payload: b'echo:hello'
```
@@ -0,0 +1,87 @@
# HEL-879 · 语音接缝接口与验收(交原视觉作者接线;总工不改已认可 web/src 视觉)
> 背景:总管独立复核指出「CG-011 语音主动开口 no-op、她的增量字幕缺失」是
> **实现缺口**,不能归为「等音色/预算就能自动完成」。本文件给出后台已落地的
> 最小接缝、前端需要补的**精确接口**与**验收标准**。代码基点:分支
> `hel-879-assembly`(见交付评论精确 head);后台实现
> `src/cybergril/voice/seam.py` + `worker.py` 接线。
>
> **2026-10-27 收口更新**:字幕消息已升级为**版本化契约 v2**(信封
> `v/seq`、确切轮次 `speech_id`、显式起止/取消事件、迟到/乱序保护、主动
> 播报同源 `message_id`)。完整可执行契约与录制样例见
> `docs/voice-subtitle-contract-v2.md` 与
> `docs/rework/hel879/subtitle-samples/*.jsonl`——**接线卡(HEL-898)以
> 该契约为准**,本文 A 节的 v1 语义作为兼容背景保留。
## A. 她的增量回复字幕(assistant_interim)
### 现状与缺口
- 用户侧 ASR 字幕已齐:`interim`(ASR 草稿)与 `user_final`(纠错终稿)在
`web/src/voice/session.ts` 已渲染(现有 `interim` 分支当「听到用户在说」处理,
**不能复用来渲染她的回复**——那是另一个语义)。
- 缺的是**她**的回复在 TTS 首包前/播放中的增量文字。后台现已补齐:worker 把
/api/chat SSE 的每个 delta 累积后发布到同一 `cg.subtitle` 通道。
### 后台已交付(无需前端改动即生效的数据)
- 数据通道:topic `cg.subtitle`(不变)。
- 新 kind:`assistant_interim`,`role: "assistant"`,`text` 为**截至当前的完整
回复文本**(替换语义,同用户 interim;发布时机=SSE 分片到达,先于/伴随 TTS)。
- 既有 kind 不变:`assistant_final`(已落库的完整回复,播放完成且未打断后发)、
`error`(φ 原句,未落库)、`interim`/`user_final`(用户侧)。
- fail-closed 语义不变:打断/失败/无回执的轮**只有 interim,不会有 final**——
前端必须把无 final 收尾的 interim 按「这轮没有成为她的正式回复」处理。
### 前端需补(原视觉作者;具体视觉由其定)
1. `web/src/voice/session.ts` 字幕分支:新增 `assistant_interim` 处理(当前
`if (s.kind === 'interim')` 只覆盖用户草稿)。
2. 渲染位置/样式:与 `assistant_final` 同一气泡位(建议替换式过渡),与用户
`heard` 视觉可区分;**不进入正式聊天记录**(只有 final 落库)。
3. 收尾规则:`assistant_final` 到达 → interim 消失、final 生效;`error` 到达 →
interim 消失、按现有 error 呈现;被打断(无任何收尾)→ 下一次 interim 或
新轮开始时清掉;挂断 → 清掉。
### 验收(离线可测,无需真实通话)
- [ ] 用假 LiveKit/假音频脚本回放 `cg.subtitle` 序列
`interim(user) → user_final → assistant_interim("她在") →
assistant_interim("她在打字中") → assistant_final("她在打字中。")`,
页面按序呈现且 interim 被 final 原子替换。
- [ ] 打断负例:`assistant_interim` 后直接新的 `interim(user)`(无 final/error),
她的 interim 残影不越过新用户轮。
- [ ] φ 负例:`assistant_interim` → `error`,error 文案呈现且不落正式记录。
- [ ] 全程零新增权限申请、零非回环出站(沿用 HEL-896 浏览器检查口径)。
## B. CG-011 语音主动开口(最小接缝,默认关闭)
### 后台已交付
- `worker.py _maybe_open_proactive`(真实代码路径,`CYBERGRIL_PROACTIVE_VOICE=1`
才启用;**默认关**,等接线卡与呈现批准):
- 数据来源:与文字收件箱同一 `GET /api/messages`(`ChatApiClient.recent_messages`),
只选**服务端已落库**(`proactive=1`、`role=companion`、`status=sent`、非空、
本房间未念过)的行,最新优先,每会话 ≤1 条(`PROACTIVE_PER_SESSION`)。
- 播报:`session.say(text, allow_interruptions=True)`——可被用户打断;
无新模型调用、无第二人格、**无第二条回执通道**(文字回执规则独立且已存在)。
- 失败 fail-closed:接缝任何异常只记日志跳过,不影响会话开启。
- 选择逻辑独立在 `seam.py pick_proactive/pending_proactive`(离线可测,
`tests/test_voice_rework_hel879.py` 三例覆盖)。
### 前端/接线卡需定(原视觉作者 + 总管,经用户批准后启用)
1. 入口呈现:她主动开口时页面如何呈现(字幕走 `assistant_final` 同位?
是否复用留言卡「回她」入口语义?未读标记如何联动——**worker 不代标已读**,
阅读态仍归页面)。
2. 节奏:会话开启后何时念(现在=开启即查一次、每会话最多一条);是否需要在
会话中轮询新 proactive(后续卡,不默记完成)。
3. 打断呈现:用户开口打断她的主动播报时,字幕按打断负例处理(无 final 收尾)。
### 验收(启用后)
- [ ] 预置一条落库 proactive 行 → 开语音会话 → 她原文念出该行(与文字收件箱
同一行、一字不差),字幕按定稿呈现。
- [ ] 播报中用户开口 → 立即被打断、无残音;该行不被标记已读(除非用户在页面
读它)、无回执伪造。
- [ ] 无 proactive 行 → 会话正常开启,静默。
- [ ] API 不可达 → 接缝跳过,语音会话不受影响。
## 边界
- 本文件只交接口与验收;**总工未改任何已认可 web/src 视觉**(本卡 web/src 仅
勘正 session.ts 一处无证据注释,无逻辑/样式改动,diff 见交付评论)。
- R5 真机验收(≥3 轮、1 秒打断、停麦无残音、可信 HTTPS/WSS、D1 音色、有限
预算)仍待主卡集中批准,接缝通过≠R5 完成。
+1 -1
View File
@@ -37,5 +37,5 @@
}
],
"failures": [],
"service_stderr_tail": "INFO: Started server process [1535952]\nINFO: Waiting for application startup.\nINFO: Application startup complete.\nINFO: Uvicorn running on http://127.0.0.1:8395 (Press CTRL+C to quit)\nINFO: Shutting down\nINFO: Waiting for application shutdown.\nINFO: Application shutdown complete.\nINFO: Finished server process [1535952]\n"
"service_stderr_tail": "INFO: Started server process [1595966]\nINFO: Waiting for application startup.\nINFO: Application startup complete.\nINFO: Uvicorn running on http://127.0.0.1:8395 (Press CTRL+C to quit)\nINFO: Shutting down\nINFO: Waiting for application shutdown.\nINFO: Application shutdown complete.\nINFO: Finished server process [1595966]\n"
}
@@ -1,5 +1,5 @@
{
"head": "04eff611e7d85e539d43717711172441420ac853",
"head": "7c73d392b95f3d55091a066db0ffe9bc6c475e0d",
"no_provider_calls": true,
"temp_only": true,
"scenes": {
@@ -59,6 +59,20 @@
"after_restart": "budget_total_requests_exhausted:55/55",
"archive_after_backup_api": "budget_total_requests_exhausted:55/55"
},
"S6_early_seal": {
"plan_total": 51,
"used_at_seal": 51,
"marker": {
"requests": 51,
"tokens": 510,
"sealed_at": 1793046443
},
"next_after_seal": "budget_sealed:51/55(封账于 1793046443,真实用量保留,不伪造、不清零)",
"other_scene_after_seal": "budget_sealed:51/55(封账于 1793046443,真实用量保留,不伪造、不清零)",
"after_restart": "budget_sealed:51/55(封账于 1793046443,真实用量保留,不伪造、不清零)",
"after_archive_restore": "budget_sealed:51/55(封账于 1793046443,真实用量保留,不伪造、不清零)",
"idempotent_reseal": true
},
"S3_user_instance": {
"B_after_A": "allowed",
"C_after_A": "allowed",
@@ -103,6 +117,16 @@
"ok": true,
"detail": "restart=budget_total_requests_exhausted:55/55 archive=budget_total_requests_exhausted:55/55 used=55"
},
{
"name": "S6a_early_finish_51_seals_real_usage_no_fake_no_zero",
"ok": true,
"detail": "used=51 dialogue_next=budget_sealed:51/55(封账于 1793046443,真实用量保留,不伪造、不清零) semantic_next=budget_sealed:51/55(封账于 1793046443,真实用量保留,不伪造、不清零) after=51 dialogue_daily=19/60"
},
{
"name": "S6b_seal_survives_restart_and_archive_restore",
"ok": true,
"detail": "restart=budget_sealed:51/55(封账于 1793046443,真实用量保留,不伪造、不清零) archive=budget_sealed:51/55(封账于 1793046443,真实用量保留,不伪造、不清零) arch_used=51 marker={'requests': 51, 'tokens': 510, 'sealed_at': 1793046443}"
},
{
"name": "S3_user_instance_B_and_C_unblocked_after_A",
"ok": true,
+13
View File
@@ -0,0 +1,13 @@
# HEL-897 MANIFEST
Base: HEL-878 author draft as preserved by the supervisor, b95ce798b4a9455d941d247a46865b3ac202bf2c (13 files verified against preservation-878.json before work). Patch is relative to that tree; full tree (no node_modules/dist) in HEL-897-source.zip. Nothing else changed; HEL-878 files and handoff-HEL-878.json untouched except the two test helpers listed below.
## Changed/added files (sha256)
- docs/requirements/handoff-HEL-897.json 56CF0E97B5035CCFAAFF3F7DD219CA30352DF600FA4C9684EB690E4E44731BC9
- web/src/voice/session.ts 4B74731B7DC34534FF8F046B9487E115B3A38CFB202B045E34A12FA27FE528FE
- web/tests/hel878_mock.mjs 1B1608D5E0858842E9A0CF51FAAF9BAECE5E52B546757464B046DF0E66A4B815
- web/tests/hel878_shots.mjs E5706C5A4459315ECDB991CBD8D3776DB3EFE035FE520930FFB09E726EB639C7
- web/tests/review878_late_cleanup.test.ts 65261CD249A36693D0C6B06CE279DE8A60AA74DD7BAAFAD6999D2FEC790F9935
- web/tests/voice.test.ts 4C120B9449CE3AF0B66B3B8A18668D5012D286D6C18C0DEE1347A880F7780471
HEL-897.patch 1D8C43B0FE9984066406E60FAC715764DCD375546713DEB6BD034160F2050070
+18
View File
@@ -0,0 +1,18 @@
HEL-897 返修保全与独立补查(总管,2026-10-27 北京时间)
原作者交付评论:01a1798b-60b0-7562-9a70-82584f52dd54;主卡 HEL-797。需求 v1.11 / 60项,无需求变更。关联 CG-010/018、VIS-013、UX-005/006、GOV-001–006、DEL-001。
本次原样保全提交:761a2331f5898bacada8d9665da517c816af2367;作者原稿基点 b95ce798b4a9455d941d247a46865b3ac202bf2c,实际追加基点 f259bd34e19959db210578b11076e6f29a6bd15e。仅作者补丁所列6文件,业务代码仅 web/src/voice/session.ts;未改视觉、后台或HEL-896。补丁哈希及6文件SHA-256与独立MANIFEST、源码包逐字节匹配,git apply反向检查通过;总管负例与上一轮文件逐字节一致。源码包其余127文件经CRLF/LF规范化匹配;源码包携带旧source-manifest,按作者交接保留实际分支从04eff611补全的版本,逐字节核对未回退。另在本记录及preservation.json保存证据,不改作者handoff中的历史未测记录。
独立实际执行:
- 使用相同的33项语音测试,仅将隔离副本session.ts替换为b95ce798原版:5失败/28通过。原负例仍为Expected listening / Received off;交错start负例有两间未释放房间。
- 修后完整8文件88/88单测通过(包含上述33项)。typecheck通过;lint 0错/1条既有警告;build通过。沿用同锁文件的本机既有node_modules复制树,没有安装或下载。
- Python原版scripts/check_requirements.py:对origin/main、实际总装origin/hel-870-assembly(04eff611)、后台origin/agent/2/6a08a4c18f23(aba2aeb)、本次实际追加目标origin/hel-878-voice-wiring(f259bd3),各不带handoff、带handoff-HEL-878、带handoff-HEL-897,共12次通过。各精确提交在preservation.json。
- 作者的无头Edge浏览器49/49及修前45/49已随卡交证据;本轮未独立复跑浏览器,不把它当真WebRTC、真实麦克风或真机证据。
代码核对:fail同步让出旧会话所有权,启动仅清旧资源的teardown后立即提交本次OFF提示,清理等待后不再提交状态;start在await前取得代次,清理旧会话后先核对是否过期。负例与新增回归覆盖迟到DELETE/settle、掉线、撤销权限、worker超时、正常结束慢DELETE、交错start和迟到麦克风授权;原旧会话资源与新会话存活断言保持。
本次有限返修可交HEL-879独立统一审核,不代表完整R5通过。完整需求原话已核对R5/D1/D3、视觉分工、动态认可、成熟复用、完整体验指令;CG-011主动开口仍为后台no-op、增量字幕仍缺,须列有限接缝修正;真实远端断线清理/租约/API重启、SDK回调和播放回执、真实三轮/打断/停麦/HTTPS/WSS/Safari全未测。R6/R7、CG-028/029继续主卡承接。
推进顺序:总工HEL-896仍在运行,不能并行修改其母线;HEL-879接到此保全候选后,待HEL-896交回且总管核对实际母线再启动统一审核,避免覆盖新预算/发布成果。真实调用、音色、设备路径、预算和部署仍按主卡最终批准,默认真实cap=0。不合并、不部署、不碰旧服务/档案/凭据/运行时绑定。
+43
View File
@@ -0,0 +1,43 @@
{
"author_comment": "01a1798b-60b0-7562-9a70-82584f52dd54",
"baseline_version": "1.11",
"author_base": "b95ce798b4a9455d941d247a46865b3ac202bf2c",
"applied_to": "f259bd34e19959db210578b11076e6f29a6bd15e",
"patch_sha256": "1d8c43b0fe9984066406e60fac715764dcd375546713deb6bd034160f2050070",
"files": [
{
"path": "docs/requirements/handoff-HEL-897.json",
"sha256": "56cf0e97b5035ccfaaff3f7dd219ca30352df600fa4c9684eb690e4e44731bc9"
},
{
"path": "web/src/voice/session.ts",
"sha256": "4b74731b7dc34534ff8f046b9487e115b3a38cfb202b045e34a12fa27fe528fe"
},
{
"path": "web/tests/hel878_mock.mjs",
"sha256": "1b1608d5e0858842e9a0cf51faaf9baece5e52b546757464b046df0e66a4b815"
},
{
"path": "web/tests/hel878_shots.mjs",
"sha256": "e5706c5a4459315ecdb991cbd8d3776db3efe035fe520930ffb09e726eb639c7"
},
{
"path": "web/tests/review878_late_cleanup.test.ts",
"sha256": "65261cd249a36693d0c6b06ce279de8a60aa74dd7baafad6999d2fec790f9935"
},
{
"path": "web/tests/voice.test.ts",
"sha256": "4c120b9449ce3af0b66b3b8a18668d5012d286d6c18c0dee1347a880f7780471"
}
],
"archive_other_files_matched_normalized_lf": 127,
"archive_extras": [],
"source_manifest": "archive retains older manifest; actual branch retains 04eff611 authoritative manifest as author instructed; bytewise verified",
"preserved_commit": "761a2331f5898bacada8d9665da517c816af2367",
"requirement_ref_commits": {
"origin/main": "3a986726d8593e0ed2274f79db6cc5d807d2ebf0",
"origin/hel-870-assembly": "04eff611e7d85e539d43717711172441420ac853",
"origin/agent/2/6a08a4c18f23": "aba2aebeb08d87854b5b037b5e69d76ddfba446e",
"origin/hel-878-voice-wiring": "f259bd34e19959db210578b11076e6f29a6bd15e"
}
}
@@ -0,0 +1,159 @@
RUN v3.1.2 /home/xiaomo/multica_workspaces/hello-af23464d222e/hel-897-cfe565d5b1bd/workdir/baseline-web
❯ tests/review878_late_cleanup.test.ts (1 test | 1 failed) 12ms
× old room disconnect cleanup cannot overwrite a newly opened call 11ms
→ expected 'off' to be 'listening' // Object.is equality
❯ tests/voice.test.ts (32 tests | 4 failed) 49ms
✓ honest switch > reads status only: no room, no microphone 4ms
✓ honest switch > configured && tts_ok never means "verified": voice:false is not read as connected 1ms
✓ honest switch > refuses honestly before touching the microphone when the server cannot speak 1ms
✓ honest switch > an insecure page is told why, without asking for the microphone 0ms
✓ lifecycle > opens, joins, publishes the microphone, follows her state; ending stops everything 1ms
✓ lifecycle > permission denied: nothing opened on the server, nothing left running 1ms
✓ lifecycle > server refusal shows its own words and releases the microphone 0ms
✓ lifecycle > a failed join releases the microphone and gives the room back 0ms
✓ lifecycle > ending while the microphone prompt is open: the late microphone is stopped, no room is asked for 2ms
✓ lifecycle > ending while the room is being opened: the late grant is given back, never joined 3ms
✓ lifecycle > ending while joining: the join is aborted, a late room is closed, the server room deleted 3ms
✓ lifecycle > rapid on/off/on: only the last call stays, every earlier microphone and room is released 3ms
✓ lifecycle > late events from an ended call never bring it back 2ms
✓ lifecycle > the room dropping on its own (network, server ended it) cleans up and says so 1ms
✓ lifecycle > microphone permission revoked mid-call ends the call 1ms
✓ lifecycle > her worker never joining is a failure, not an endless wait 2ms
✓ lifecycle > offline ends with its own words; unload releases at once and the DELETE survives the page 2ms
✓ lifecycle > a DELETE that cannot get through (offline) is owed, survives a new page, and is sent once back online 1ms
✓ lifecycle > an owed room already gone on the server (404) counts as settled 0ms
✓ lifecycle > never confirms a receipt or posts chat itself: the worker owns both 0ms
× a late end of an older call > the room dropped, its DELETE (and the settle retry) answering after a new call is up, leaves the new call alone 6ms
→ expected { phase: 'listening', heard: '', …(3) } to match object { phase: 'off' }
(4 matching properties omitted from actual)
× a late end of an older call > the microphone was taken away, its DELETE (and the settle retry) answering after a new call is up, leaves the new call alone 2ms
→ expected { phase: 'listening', heard: '', …(3) } to match object { phase: 'off' }
(4 matching properties omitted from actual)
× a late end of an older call > her worker never joined, the DELETE is slow, and a new call is started before it answers 1ms
→ expected { phase: 'joining', heard: '', …(3) } to match object { phase: 'off', …(1) }
(3 matching properties omitted from actual)
✓ a late end of an older call > a normal end whose DELETE is slow, then a new call, then the old answer arrives 2ms
× a late end of an older call > starting again while the previous start still waits for the old room to be given back: only the newest call survives 5ms
→ expected [ Room{ closed: +0, …(3) }, …(1) ] to deeply equal [ Room{ closed: +0, …(3) } ]
✓ a late end of an older call > a microphone granted late to an older start is stopped without touching the new call 3ms
✓ subtitles and the two channels > interim is a draft; user_final cancels a text reply and asks for the record; φ is heard but not recorded 0ms
✓ subtitles and the two channels > without the agent state attribute, subtitles still move the state honestly 0ms
✓ subtitles and the two channels > typing over her spoken answer silences what is left of it until she is listening again 0ms
✓ record re-read > adds voice turns once, in id order; leaves reach-outs to the inbox and text turns to the stream 0ms
✓ record re-read > a pending voice row settles in place (sent or cancelled), never duplicated 0ms
✓ record re-read > reads newest pages down to what the page holds, bounded 1ms
Test Files 2 failed (2)
Tests 5 failed | 28 passed (33)
Start at 02:51:41
Duration 268ms (transform 81ms, setup 0ms, collect 150ms, tests 61ms, environment 0ms, prepare 76ms)
⎯⎯⎯⎯⎯⎯⎯ Failed Tests 5 ⎯⎯⎯⎯⎯⎯⎯
FAIL tests/review878_late_cleanup.test.ts > old room disconnect cleanup cannot overwrite a newly opened call
AssertionError: expected 'off' to be 'listening' // Object.is equality
Expected: "listening"
Received: "off"
❯ tests/review878_late_cleanup.test.ts:39:29
37| await new Promise(resolve => setTimeout(resolve, 0));
38| expect(controller.active).toBe(true);
39| expect(view?.phase).toBe('listening');
| ^
40| } finally {
41| releaseOld();
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/5]⎯
FAIL tests/voice.test.ts > a late end of an older call > the room dropped, its DELETE (and the settle retry) answering after a new call is up, leaves the new call alone
FAIL tests/voice.test.ts > a late end of an older call > the microphone was taken away, its DELETE (and the settle retry) answering after a new call is up, leaves the new call alone
AssertionError: expected { phase: 'listening', heard: '', …(3) } to match object { phase: 'off' }
(4 matching properties omitted from actual)
- Expected
+ Received
{
- "phase": "off",
+ "phase": "listening",
}
❯ tests/voice.test.ts:274:30
272| const r = await connected(), slow = hold(r, 's1');
273| cause(r); await flush();
274| expect(r.view()).toMatchObject({ phase: 'off' });
| ^
275| expect(r.transport.mics[0].stopped).toBeGreaterThan(0); …
276| expect(r.c.active).toBe(false);
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[2/5]⎯
FAIL tests/voice.test.ts > a late end of an older call > her worker never joined, the DELETE is slow, and a new call is started before it answers
AssertionError: expected { phase: 'joining', heard: '', …(3) } to match object { phase: 'off', …(1) }
(3 matching properties omitted from actual)
- Expected
+ Received
{
- "notice": "她没有接上这次通话,麦克风已经关闭。文字仍可使用。",
- "phase": "off",
+ "notice": "",
+ "phase": "joining",
}
❯ tests/voice.test.ts:293:26
291| const r = rig(); await r.c.start(); const slow = hold(r, 's1');
292| await vi.advanceTimersByTimeAsync(1100);
293| expect(r.view()).toMatchObject({ phase: 'off', notice: WORDS.n…
| ^
294| await reopened(r, 1);
295| slow.open(); await vi.advanceTimersByTimeAsync(0);
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[3/5]⎯
FAIL tests/voice.test.ts > a late end of an older call > starting again while the previous start still waits for the old room to be given back: only the newest call survives
AssertionError: expected [ Room{ closed: +0, …(3) }, …(1) ] to deeply equal [ Room{ closed: +0, …(3) } ]
- Expected
+ Received
@@ -14,6 +14,23 @@
"ended": [Function anonymous],
"stopped": 0,
},
],
},
+ Room {
+ "closed": 0,
+ "hushed": [],
+ "on": {
+ "agentJoined": [Function agentJoined],
+ "agentState": [Function agentState],
+ "audioBlocked": [Function audioBlocked],
+ "dropped": [Function dropped],
+ "subtitle": [Function subtitle],
+ },
+ "published": [
+ Mic {
+ "ended": [Function anonymous],
+ "stopped": 0,
+ },
+ ],
+ },
]
❯ tests/voice.test.ts:316:58
314| slow.open(); await Promise.all([b, c]); await flush(); // B w…
315| const last = r.transport.rooms.length - 1;
316| expect(r.transport.rooms.filter(x => !x.closed)).toEqual([r.tr…
| ^
317| expect(r.transport.mics.filter(m => !m.stopped)).toEqual([r.tr…
318| expect(r.server.open.size).toBe(1);
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[4/5]⎯
+13
View File
@@ -0,0 +1,13 @@
> cybergril-web@0.1.0 build
> tsc --noEmit && vite build
vite v6.3.6 building for production...
transforming...
✓ 57 modules transformed.
rendering chunks...
computing gzip size...
dist/index.html 0.49 kB │ gzip: 0.35 kB
dist/assets/index-BpZaybfD.css 83.54 kB │ gzip: 17.53 kB
dist/assets/index-BzbZ2t4t.js 306.87 kB │ gzip: 102.13 kB
✓ built in 620ms
+10
View File
@@ -0,0 +1,10 @@
> cybergril-web@0.1.0 lint
> eslint src
/home/xiaomo/multica_workspaces/hello-af23464d222e/hel-897-cfe565d5b1bd/workdir/CyberGril/web/src/chat/ChatPanel.tsx
222:100 warning The ref value 'fullButton.current' will likely have changed by the time this effect cleanup function runs. If this ref points to a node rendered by React, copy 'fullButton.current' to a variable inside the effect, and use that variable in the cleanup function react-hooks/exhaustive-deps
✖ 1 problem (0 errors, 1 warning)
@@ -0,0 +1,2 @@
python3 scripts/check_requirements.py --base-ref origin/main --handoff docs/requirements/handoff-HEL-878.json
需求结构检查通过:60项;来源和覆盖完整。仍须人工核对授权语义与实际体验。
@@ -0,0 +1,2 @@
python3 scripts/check_requirements.py --base-ref origin/main --handoff docs/requirements/handoff-HEL-897.json
需求结构检查通过:60项;来源和覆盖完整。仍须人工核对授权语义与实际体验。
@@ -0,0 +1,2 @@
python3 scripts/check_requirements.py --base-ref origin/main
需求结构检查通过:60项;来源和覆盖完整。仍须人工核对授权语义与实际体验。
@@ -0,0 +1,2 @@
python3 scripts/check_requirements.py --base-ref origin/hel-870-assembly --handoff docs/requirements/handoff-HEL-878.json
需求结构检查通过:60项;来源和覆盖完整。仍须人工核对授权语义与实际体验。
@@ -0,0 +1,2 @@
python3 scripts/check_requirements.py --base-ref origin/hel-870-assembly --handoff docs/requirements/handoff-HEL-897.json
需求结构检查通过:60项;来源和覆盖完整。仍须人工核对授权语义与实际体验。
@@ -0,0 +1,2 @@
python3 scripts/check_requirements.py --base-ref origin/hel-870-assembly
需求结构检查通过:60项;来源和覆盖完整。仍须人工核对授权语义与实际体验。
@@ -0,0 +1,2 @@
python3 scripts/check_requirements.py --base-ref origin/agent/2/6a08a4c18f23 --handoff docs/requirements/handoff-HEL-878.json
需求结构检查通过:60项;来源和覆盖完整。仍须人工核对授权语义与实际体验。
@@ -0,0 +1,2 @@
python3 scripts/check_requirements.py --base-ref origin/agent/2/6a08a4c18f23 --handoff docs/requirements/handoff-HEL-897.json
需求结构检查通过:60项;来源和覆盖完整。仍须人工核对授权语义与实际体验。

Some files were not shown because too many files have changed in this diff Show More