develop
5
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
4b1dd6404b |
fix(voice): v0.1 CRIT cluster — wake / STT / TTS / boot probes (#28, #29, #30, #31, #32, #33, #34, #36, #158)
#28 (CRIT): setCancelInFlight calls stopFeed() (preserves stream); rebuild consumer re-arms DAG against new ring. Wake-word no longer dies on audio-graph rebuild. #29 (CRIT): sttFactory reads PerTurnSnapshot.stt.whisperKitFallback; get_self_state returns canonical snake_case backend constants. Factory signature: () -> any STTProvider → () async -> any STTProvider (existing test sites compile unchanged under Swift 6 implicit-async-closure rules). #30 (CRIT): TTSEngineActor.synthesize emits .ttsStopped after .finished on natural completion (two-emit-site model: barge-in + natural-completion). #31 (CRIT): VoiceTTSAdapter.tierResolver wired to read snap.tts.tier from configStore. #32 (HIGH): Rolling-window trim in OpenWakeWordSession.runORT — mel ≤ 84, embeddings ≤ 16, nextEmbStart slides on drop. #33 (HIGH): three new boot probes — WakeWordBootHealthProbe, STTBootHealthProbe, TTSBootHealthProbe. New WakeWordDAG.isFeedArmed + VoiceTTSAdapter.hasEngine accessors. #34 (HIGH): vad.reset() at top of startSTTSession clears Silero VAD LSTM hidden state + wasSpeech between sessions. #36 (HIGH): new Broadcaster.Priority.agentHud (protects stateChange/turnEnd/error); installAgent adds subscriber pumping AgentHudIntent into dormantAgentContinuation. Voice 90 + AgentCore 242 tests pass; all 18 boundary gates green. |
||
|
|
a6f91e6b73 |
fix(bus,hud): v0.1 S1 cluster — webview didFail + menu-bar icon transition (#40, #41, #151)
#40: BridgeNavigationDelegate gains onDidFail closure (covers didFailProvisionalNavigation + didFail). WebviewBridge.failHandshake(reason:) transitions to new terminal .loadFailed state, cancels pending 2s timeout, fires existing alertPresenter, idempotent. AppDelegate.handleWebviewLoadFailure dual-surfaces: failHandshake + critical non-dismissible banner (defense-in-depth across test seams). WebviewBootHealthProbe gets .loadFailed case. #41: HudStateCoordinator emit closure now ALSO calls menuBarController.transition(to:) in addition to bridge.send(.hudState(...)). Doc-comment correctly notes this is not a HUD-08 single-writer violation — transition writes currentState, not BusOutbound.hudState. scripts/check-single-writer-hudstate.sh still green. Tests: 4 new Handshake cases (load-failure-from-sentHello with alert-not-duplicated, from-idle, idempotency, post-armed no-op), BridgeNavigationFailureTests end-to-end + per-arm, MenuBarIconStateTransitionTests drives dormantAgentContinuation and asserts .thinking → .speaking → .idle. 62/62 Bus pass; all 18 boundary gates green. |
||
|
|
740bed4cbf |
fix(round-4): tag-agnostic ollama probe + mcp probe counts in-process tools
Two false-positive degraded reports surfaced during the second Round 4 dogfood run: 1. Ollama probe was exact-string matching against required model names. `requiredModels` had `"nomic-embed-text"` (no tag), but `/api/tags` returns `"nomic-embed-text:latest"` (with default tag) — so the probe reported "missing models: nomic-embed-text" even after `ollama pull` succeeded. Fix: when a required name has no `:tag`, accept any tag (`name == required OR name.hasPrefix(required + ":")`). Required names that DO include a tag (e.g., `qwen3.6:latest`) keep their strict match. 2. MCP probe was reading ONLY `runtime.client.registeredToolNames()` — the stdio side (3 helpers: get_clipboard, get_time, run_applescript). The other 4+ in-process tools (self-knowledge + memory) live in `InProcessToolRegistry` and are routed through `InProcessAwareToolDispatcher` (commit 1b7fb81's B-01b fix). Probe now reads both registries, unions the names, and reports the combined count. Evidence line breaks out stdio vs in-process counts so the operator can see the split. Verified: app build clean, app target compiles. Re-launch should show ollama=ok (all 3 models present) and mcp=ok (8 tools after self-knowledge tools register at install step 15). The Toby fix verification mid-dogfood: the model received the degradation summary in its system prompt and truthfully reported "my memory subsystem is currently degraded (nomic-embed-text is missing)" instead of yesterday's fake "Got it" promise. Round 4 slice 3 confirmed working end-to-end. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
cfd469f72b |
feat(round-4): probe severity model — ok/soft/loud/critical
Round 4 of boot-health work. The Toby-the-dog incident — Jarvis said "Got
it" while the embedder model was missing and the fact never persisted —
proved that Round 2's probe banner was too quiet. Probes were already
reporting `.degraded` correctly; what was missing was a way for the App
layer to route a critical failure differently from a soft one (e.g.,
non-dismissible banner for memory off vs. silent Status row for camera
TCC not yet requested).
This slice introduces a severity axis without changing behavior:
- `FailureSeverity { critical, loud, soft }` on `ProbeStatus`'s non-ok
cases (degraded/failed/unknown). `.ok` carries no severity.
- `ProbeStatus.severity` accessor (nil for `.ok`).
- `BootHealthSnapshot.overallHealth: OverallHealth { ok, soft, loud,
critical }` — worst severity wins; `.ok` only if every subsystem ok.
- Each concrete probe annotates its failure modes:
* Memory: failed → .critical (agent grounding; the Toby case)
* Anthropic: missing/empty key → .critical (agent can't run)
* Ollama: missing models → .loud (fact extraction broken, chat fine)
* Voice: mic denied → .loud; dormant install → .soft
* Vision: TCC denied → .soft (vision is optional)
* MCP: 0 tools → .critical; <minimum → .loud
* Replay: init failed → .critical; missing file → .loud
* Webview: mismatched/timedOut → .critical; sentHello → .soft
- DormantSubsystemProbe gains `severity` parameter (default .soft);
AppDelegate wires memory-dormant as .critical.
No behavioral change yet — banners still treat all failures equally;
the agent system prompt is unchanged. Slices 2 + 3 land those.
Tests: 13/13 in BootHealthOrchestratorTests pass, including 4 new tests
for overallHealth + severity accessor + JSON roundtrip with severity.
2 pre-existing failures in LLMEventTests/OllamaRequestBodyTests are
unrelated (qwen model-ID literal drift from commit
|
||
|
|
687b80b3f0 |
feat(round-2): boot-phase health probes — NOT FAKED, structured logs
After all install Tasks join, runs one live probe per subsystem and
emits structured `BootHealth: subsystem=X state=Y latencyMs=Z evidence="..."`
log lines. Any subsystem reporting .failed enqueues a non-modal banner
via HUDBannerCoordinator (priority 3 — sits between hotkey-bind-failed
and ollama-url-rejected). The snapshot is stored on bootHealthOrchestrator
so Round 3's Status menu can read + re-probe.
Anti-fake-status invariant is type-level: ProbeStatus is a sum type with
.ok / .degraded(reason:) / .failed(reason:) / .unknown(reason:) — every
non-ok case carries a reason string, so a snapshot can never report
state=unknown without explaining why. A probe that can't execute MUST
return .unknown — never .ok with a fake evidence line.
New types in packages/AgentCore/Sources/AgentCore/BootHealth.swift:
- ProbeStatus, ProbeOutcome, BootHealthProbe protocol
- SubsystemHealth (Codable for Round 3 "Copy report"), BootHealthSnapshot
- BootHealthOrchestrator actor — parallel TaskGroup fan-out,
registration-order-preserved snapshot, injectable clock for tests
Eight concrete probes in App/Boot/BootHealthProbes.swift:
- MemoryBootHealthProbe — MemoryStatsStoreAdapter.getMemoryStats round-trip
- AnthropicBootHealthProbe — Keychain structural check (no billable
network call at boot; Round 3 panel offers explicit network re-probe)
- OllamaBootHealthProbe — GET /api/tags, 3s timeout, asserts qwen3.6 +
qwen2.5-coder + nomic-embed-text all present
- VoiceBootHealthProbe — AudioGraphOwner state + mic TCC
- VisionBootHealthProbe — camera TCC + DiscoverySession (read-only,
does NOT prompt TCC per RESEARCH §Example 2)
- MCPBootHealthProbe — runtime.client.registeredToolNames count
- ReplayBootHealthProbe — replay.sqlite presence + parent-dir writability
- WebviewBootHealthProbe — WebviewBridge.handshakeState (.armed = live)
- DormantSubsystemProbe — placeholder for subsystems whose actor was nil
at probe-registration; reports .unknown honestly rather than skipping
AppDelegate wiring (step 16, runs after selfKnowledgeInstallTask which
transitively awaits every other install Task):
- bootHealthOrchestrator: BootHealthOrchestrator stored property
- bootHealthTask: Task<Void, Never>? cancelled in applicationWillTerminate
- runBootHealth() async: registers probes (idempotent — re-probes from
Round 3 reuse the existing registry), runs, logs, enqueues banners
- emitBootHealthLog / enqueueFailedBootHealthBanners helpers
Tests (packages/AgentCore/Tests/AgentCoreTests/BootHealthOrchestratorTests.swift):
- 8/8 passing (210ms wall — 3×200ms parallel probes prove TaskGroup
fan-out works; serial would be 600ms+)
- Coverage: runAll invokes every probe, snapshot preserves registration
order, parallel execution timing, lastSnapshot persistence, .failed
propagation, .unknown not coerced to .ok, deterministic clock
injection for latencyMs, JSON round-trip for all 4 ProbeStatus variants
Verification before commit:
- swift test --package-path packages/AgentCore --filter
BootHealthOrchestratorTests: 8/8 PASS (0.210s)
- swift test --package-path packages/Memory --filter
MemoryExtractorTests: 11/11 PASS
- bash scripts/check-app-builds.sh: PASS
- 18/18 boundary gates: PASS
Dogfooding next: cold-launch + grep system.log for `BootHealth:
subsystem=` lines; force a failure (e.g. kill Ollama) and verify
banner appears. Status menu (Round 3) is next session task.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|