develop
29
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
795c3bf5c9 |
feat(app): v0.1 UX surfaces — startup panels + InputMonitoring banner + PTT (#87, #88, #89, #156)
#89: new AppDelegate.startupSurfacesMode enum (.minimal/.diagnostic). Debug defaults .diagnostic; Release defaults .minimal. openStartupSurfacesIfDiagnostic() runs at tail of bootHealthTask (skipped under XCTest or when wizard is open). Status panel top-right + Dev Overlay bottom-right of NSScreen.main.visibleFrame. DevOverlay opens to Logs tab. #88: BannerContent.inputMonitoringDenied is now nonDismissible: true. New HUDBannerCoordinator.dismiss(id:) clears a banner programmatically without writing dismissedThisLaunch. AppDelegate.reprobeInputMonitoring fires on NSApplication.didBecomeActiveNotification; rebinds the global hotkey on re-grant. Uses IOHIDCheckAccess (query-only, no prompt) per CLAUDE.md guidance. 3 new coordinator tests. #87: WizardState.pttHotkey persisted under Jarvis.pttHotkey (independent of summon hotkey). Ships unbound per CLAUDE.md hotkey hygiene. AppDelegate.rebindPTTFromState() called from installVoice, foreground TCC re-grant, Settings Save/Clear. SettingsView extended with PTT hotkey section. Audio-graph isolation: NSEvent monitors only, no audio-graph surgery — safe alongside fix/voice-crit-v0.1. 3 new WizardState tests. DevOverlay 14 + Voice 26 tests pass; all 17 grep boundary gates green (incl. single-writer-hudstate + no-modal-presentation). |
||
|
|
bc74494ada |
fix(mcp): v0.1 CRIT cluster — turnID resolver wired + tool-result double-cap (#20, #21, #157)
#21 (CRIT): ToolResultPacker.pack(_:) is now a pass-through decoder; redundant inner 8 KB cap removed. MCP dispatcher's SanitizeForModel.prepareForBoundary(_, capBytes: 8192) is the sole cap site. Doc-comment rewritten honestly: replay row reflects boundary-prepared bytes (8 KB), not original helper output. Replay fidelity beyond 8 KB lifted to v0.5/M-3 once SanitizeForModel can move to a shared module without dep cycle. #20 (CRIT): ReplayingToolResultObserver.turnIDResolver lock-protected via OSAllocatedUnfairLock<ResolverBox> (same Swift-6-async-safe pattern as InProcessConfirmationCache). New attach(turnIDResolver:) method. AgentOrchestrator.currentTurnID() actor-isolated accessor. installAgent attaches the resolver immediately after orchestrator assignment, with defensive systemLogger.warning if observer type mismatches. Tests: ToolResultPackerTests TP2/TP5 updated to assert no [TRUNCATED] marker; OrchestratorSubmitTests OS6 reframed + new OS6b reads SQLite events table directly to assert 8 KB exact; MCPRuntimeWiringTests adds attach pre/post-attach + replay-log dual-write correlation. AgentCore 243 + Memory 98 + Replay 34 + MCP all green; all 18 boundary gates green. |
||
|
|
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. |
||
|
|
a9531e65e3 |
feat(round-4): menu-bar icon turns red when subsystems are degraded
Slice 4 of Round 4 — the visible polish. With Slices 1–3 the agent
preamble + non-dismissible banner cover the "agent must know +
user must see" loop, but a closed HUD with no banner visible can
still hide the warning. This slice paints the menu-bar icon red so
the warning is glanceable from anywhere on the screen.
Option A from the slice plan (single-writer-safe):
- `MenuBarIconController.applyHealth(_ health: OverallHealth)` paints
`button.contentTintColor = NSColor.systemRed` for `.loud` and
`.critical`, clears the tint for `.ok` and `.soft`. Template images
recolor automatically with `contentTintColor` so no second icon
asset is needed.
- `currentHealth: OverallHealth?` exposed read-only for tests.
- `AppDelegate.runBootHealth` calls `menuBarController?.applyHealth(
snapshot.overallHealth)` after computing the degradation summary,
so the icon updates on every probe sweep (cold boot + Status panel
re-probe).
Single-writer invariant for HudState (HUD-08) is intact:
`contentTintColor` lives on `NSStatusItemButton`, not in HudState.
`scripts/check-single-writer-hudstate.sh` is green.
1 new test in MenuBarIconControllerTests:
* test_applyHealthTintsRedForLoudAndCritical — exercises all four
OverallHealth cases and asserts contentTintColor toggles correctly.
Boundary gates 18/18 green; App target compiles cleanly.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
79955b67b7 |
feat(round-4): non-dismissible banners for critical subsystem failures
Slice 2 of Round 4. With Slice 1 the probe layer now reports severity;
this slice routes severity to the banner system so the user CANNOT
silently dismiss a "your memory is dead, Jarvis will lie to you" warning.
- `BannerContent.nonDismissible: Bool` (default false).
- `HUDBanner` hides the dismiss X when `content.nonDismissible == true`.
- `HUDBannerCoordinator.dismissCurrent()` no-ops on non-dismissible
banners; the banner stays on screen until programmatically cleared
(`clear()` on app quit, or a re-probe finding the subsystem healthy
enqueues a different banner that pre-empts it).
- Two new presets:
* `bootHealthCritical(subsystem:reason:)` — priority 1, non-dismissible.
Title format: "Memory: critical failure". Body promises that the
agent will be warned not to rely on this subsystem (Slice 3).
* `bootHealthLoud(subsystem:reason:)` — priority 3, dismissible.
- Existing `bootHealthFailed(...)` preset kept as backwards-compat
fallback (no current call sites after this slice).
- `AppDelegate.enqueueFailedBootHealthBanners` rewritten to iterate
every non-ok subsystem and route by severity:
.critical → bootHealthCritical + `[system] CRITICAL` log line
.loud → bootHealthLoud
.soft → no banner (Status panel only)
Previously only `.failed` cases enqueued banners, which silently
dropped `.degraded` (the Toby case) entirely.
Two new tests in HUDBannerCoordinatorTests cover the new behavior:
* test_nonDismissibleBannerIgnoresDismiss
* test_criticalBannerPreemptsLowerPriorityCurrent
(Note: `xcodebuild test -only-testing JarvisAppTests` is pre-existing-
broken on this checkout due to onnxruntime static-link duplication —
confirmed by `git stash` before this slice. The test code compiles
cleanly via the regular App build; SPM-target tests for the
AgentCore-side severity model are green.)
Boundary gates 18/18 green. `swift test --package-path packages/AgentCore
--filter BootHealthOrchestratorTests` → 13/13.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
f9cd25d66e |
chore(test-naming): converge on Mock/Fake/Stub convention; document in CLAUDE.md; rename worst-mismatch stragglers
Per audit-2026-05-04 P2-15: 26 Mock*, 21 Fake*, 12 Stub* across the test corpus, often used interchangeably for the same role. New CLAUDE.md section "Test naming conventions" (line 127) sets the rule: - Mock*: records calls AND returns scripted responses (assertion driver). - Stub*: returns canned data without recording (deterministic input). - Fake*: alternative implementation with realistic behavior (in-memory state-holder). Renamed 15 worst-mismatch stragglers in the older corpus (per audit constraint, did NOT touch types added in today's P1+P2 fix commits): Stub* → Mock* (records + scripts): - HybridSearchTests: StubEmbedder → MockEmbedder - HybridSearchTests: StubStore → MockStore - SessionHistoryTests: StubStore → MockStore - InProcessMemoryToolsTests: StubHybrid → MockHybrid - InProcessMemoryToolsTests: StubHistory → MockHistory - InProcessMemoryToolsTests: StubForget → MockForget Mock* → Stub* (canned data, no recording): - STTBackendSwitchTests: MockSpeechAnalyzerBridge → StubSpeechAnalyzerBridge - STTBackendSwitchTests: MockWhisperKitBridge → StubWhisperKitBridge - VoiceWiringTests: MockVADEngineForW4 → StubVADEngineForW4 - VoiceWiringTests: MockSTTProviderForW4 → StubSTTProviderForW4 - VoiceWiringTests: MockTTSForW4 → StubTTSForW4 - VoiceWiringTests: MockOrchestratorForW4 → StubOrchestratorForW4 - VoiceWiringTests: MockBannerForW4 → StubBannerForW4 - VoiceWiringTests: MockBusForW4 → StubBusForW4 Fake* → Mock* (records + scripts): - WebviewBridgeOutboundTests: FakeJSEvaluator → MockJSEvaluator - HudStateCoordinatorBusWiringTests: FakeJSEvaluator → MockJSEvaluator (the comment on the type already said "Records each call for assertion") Skipped per audit constraint: - StubCapture/StubSink (Vision FrameAttachConfirmSendWiringTests, P1+P2) - MockVADEngine + MockOrchestratorForController siblings (Voice VoiceControllerTests/VADGatedSessionTests, today's track-B churn) Tests pass: Voice 87+23, MCP 85, Memory 88, Bus 58, AgentCore 19+72, Vision 19, Shell 7+5. App target builds clean. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
81357ca900 |
test(voice p1-2): production chunkPump factory extracted + integration test against real broadcaster
Per audit 2026-05-04 T2: the production chunkPump closure at AppDelegate
.installVoice was tested only end-to-end via synthetic pumps — the real
subscribe + readMono16k loop had zero coverage. Same divergence pattern
that produced BLOCKER-INT-1 (NoopBusGateway).
Extract the closure into App/Voice/ProductionChunkPump.swift as
makeProductionChunkPump(graphOwner:); AppDelegate becomes a one-line
caller. New ProductionChunkPumpTests covers three integration paths:
- PCT1: pump finishes immediately when graphOwner is nil
- PCT2: pump receives buffers via the broadcaster fan-out (publishes
through a tap-block-capturing GraphBuilder against a real
AudioGraphOwner)
- PCT3: pump's subscription does not steal samples from a sibling
subscription (P1-1 / Track B-7 invariant regression guard)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
707c45b355 |
feat(track-d 2): register SearchMemoryTool + ForgetFactTool with MCP runtime
Closes the audit's "tools exist but unregistered" finding from
.planning/audit-2026-05-03/memory-audit.md §4-5: SearchMemoryTool +
ForgetFactTool source files have lived in `packages/MCP/Sources/MCP/
InProcess/` since Plan 07-03 with zero non-test instantiation. The agent
loop has had no way to call into memory.
Wiring:
- New `App/MCP/InProcessMemoryAdapters.swift` bridges MCP's tool
dispatch protocols to Memory's actors:
* `HybridSearchAdapter: HybridSearchDispatching` wraps
`Memory.HybridSearch` and translates `[FactRef]` → `[SearchMemoryHit]`
(so MCP doesn't need to import Memory).
* `ForgetFactStoreAdapter: ForgetFactDispatching` wraps
`MemoryStore.forgetFact`.
- `installMemory` builds an `InProcessToolRegistry` and registers tools
based on preconditions:
* `forget_fact` registers when `memoryStore != nil` (writes need DB)
* `search_memory` registers when `memorySearchAvailable && embedder
init succeeds` (reads need DB + vec0 + Ollama loopback)
- Embedder construction (`OllamaEmbeddingClient` loopback assertion)
failure is caught + logged; degrades to "writes ok, no search"
rather than failing the whole subsystem.
- New `inProcessToolRegistry: InProcessToolRegistry?` ivar, exposed
@testable.
Test seam: `AppDelegate.buildInProcessToolRegistry(forgetDispatcher:
searchDispatcher:)` is the pure registry-construction helper — takes
already-built dispatchers (or nil) and applies the gating rules. Tests
inject stub dispatchers to assert positive registration without needing
a real `MemoryStore` (which requires vec0.dylib, env-gated in CI).
Tests added (compile-time gates per
.planning/debug/xctest-launch-runningboard-error-5.md):
- `test_D2_noToolsRegisteredWhenStoreUnavailable` — production reality
on every cold launch (vec0 placeholder)
- `test_D2_bothToolsRegisterWhenBothDispatchersPresent` — store + search
available
- `test_D2_onlyForgetRegistersWhenSearchUnavailable` — store ok, vec
failed (the future state once custom libsqlite3 lands but vec0.dylib
is missing)
- `test_D2_emptyRegistryWhenBothDispatchersNil` — degenerate case
Out of scope: exposing the registered tools to the LLM via
`AgentOrchestrator.availableTools` (currently `[]` — separate audit gap,
affects helper tools too). The "tools exist but unregistered" gap is
closed; the "tools exposed to LLM" gap is independent.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
75a10be480 |
fix(track-d 1): installMemory no longer short-circuits on vec failure
Pre-fix: a single `return` after `MemoryStore(databaseURL:)` threw left `memoryExtractionOrchestrator` AND `memoryExtractionCoordinator` both nil for the lifetime of the process — so the .turnEnd subscriber was a no-op and nothing about memory extraction was observable in system log even though the LLM call to Ollama is independent of the SQLite store. Post-fix: store nil → log warning, set memorySearchAvailable=false, but extractor + orchestrator + coordinator all construct. The applyOp closure captures `[weak self]` and routes to a debug log when self.memoryStore is nil — drain loop survives, log only the turnId hash (T-06-05-03 / memory- content discipline: never log subject/object content). `memorySearchAvailable` is added as an ivar to track whether search is viable (today: store!=nil ⟺ vec_version() succeeded ⟺ search works; when custom libsqlite3 lands, the flag will split from store!=nil). `installMemory` lowered from `private` to default `internal` so the new JarvisAppTests/MemoryInstallWiringTests can drive it directly via `@testable import Jarvis`. Tests run as compile-time gates (xctest harness launch is broken on Xcode 26 + ad-hoc bundles per .planning/debug/xctest-launch-runningboard-error-5.md), but the structural assertions still catch regressions of the form "the cascade came back". Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
08125a46d8 |
chore(test): remove tautologies and rubber-stamps per tests-audit.md
Per `.planning/audit-2026-05-03/tests-audit.md` section 5, five
`XCTAssertTrue(true)` placeholder tests were identified across the tree
that are mathematically incapable of failing. This commit fixes or
removes each, conservatively. Files touched and disposition:
- App/Tests/AppTests/MCPRuntimeWiringTests.swift:260
fixed: replaced trailing `XCTAssertTrue(true)` rubber-stamp at end of
CR-02 channel-drain test with a load-bearing assertion that the drain
task completed via `finish()`, not cancellation. The implicit `await
drainTask.value` is the real gate; the new assertion ensures regressions
that cancel-instead-of-finish surface.
- App/Tests/AppTests/WebviewBundleLoadTests.swift:60
deleted: `test_assetsDirectoryGitignored_documentedInSmokeScript`. Body
was `XCTAssertTrue(true)` with comment "intentionally has no assertion
— it exists to document the split of responsibility". Documentation,
not a test. Replaced with a top-level comment block documenting the
same split (smoke-test-hud.sh is the runtime gate).
- packages/AgentCore/Tests/AgentCoreTests/ToolChoiceTests.swift:38
fixed: removed trailing `XCTAssertTrue(true) // Test compiles → contract
holds.` from `testLLMProviderStreamRequiresToolChoice`. The closure
body itself is the contract — if the test compiles, the contract holds.
Comment updated to make the compile-time semantics explicit.
- packages/Shell/Tests/ShellTests/InputMonitoringDenialTests.swift:36
deleted: `test_localMonitorOnlyFallback`. Body was `XCTAssertTrue(true)`
documenting that the real fallback test lives in HotkeyBindingTests.
Replaced with a top-level comment pointing readers there.
- packages/Vision/Tests/VisionTests/PackageBoundaryTests.swift:14
deleted: `testVisionModuleCompilesStandalone`. Body was
`XCTAssertTrue(true)` with comment "compile is the assertion." VISION-03
isolation is enforced by `scripts/check-vision-isolation.sh` and the
Package.swift dependency graph, both of which are real gates. Folded
the documentation into the `testPublicAPISurface` doc-comment so the
explanation isn't lost.
HudStateEnumTests rubber-stamps (audit items #4–6 in section 5) were
left as-is. The voiceOverLabel literals are user-facing accessibility
text that VoiceOver actually speaks; they are load-bearing in the sense
the audit calls out as worth preserving ("replace with behavioral
assertions if the literal under test is load-bearing"). A regression
that breaks an a11y label IS a real regression. Conservative read; not
worth the blast radius of guessing wrong.
Pre-existing baseline issues unrelated to this batch:
- `packages/Shell/Tests/ShellTests/InputMonitoringDenialTests.swift`
fails to COMPILE on a clean tree (MockProbe missing
`isListenEventAccessGranted()`). Verified via `git stash` that the
failure is independent of this commit. Not addressed here per the
scope rules — flagged for follow-up.
Net test count: -3 tests (3 deleted, 0 added — the
MCPRuntimeWiringTests assertion replaces the tautology in-place).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
ad93dac665 |
fix(blocker-int-1): replace NoopBusGateway with real MCPBusGatewayAdapter
ConfirmingToolDispatcher emits tool-call events through `BusGateway`. The prior `NoopBusGateway` (AppDelegate.swift:503 + dead struct at :1894-1904) silently dropped every emission, so the HUD's chat-panel ToolCallCard never rendered a Running / Completed transition for any tool dispatch. This is the canonical "test mock and prod stub diverge in the same direction" pattern called out in `.planning/audit-2026-05-03/tests-audit.md` §6: SpyBus in MCPRuntimeWiringTests counted invocations; production shipped with empty bodies — both satisfied the protocol; only the latter was load-bearing in production. The replacement `MCPBusGatewayAdapter` (App/MCPBusGatewayAdapter.swift): - Conforms to `BusGateway`, forwards to `OutboundBatcher.flushAndSend` with `.toolCallStart` / `.toolCallEnd` envelopes. - `updateArgsPreview` re-emits `.toolCallStart` with the same id; the HUD's `upsertToolCall` (webview/packages/hud/src/bus/client.ts) patches the existing card by id, transitioning awaiting-approval → running with the post-approval sanitized args. - `String → UUID` mapping via SHA256-derived RFC 4122 v5-shape UUID, so `emitToolCallStart(id="toolu_01X")` and `emitToolCallEnd(id="toolu_01X")` produce IDENTICAL UUIDs for HUD reconciliation across start/update/end. The dispatcher protocol uses `String` (CR-03 in ConfirmingToolDispatcher.swift — Anthropic toolu_01ABCD… / Ollama free-form, never UUID); the bus protocol uses `UUID` (Phase 2 closed schema). Bridge is one-way; the audit-trail correlation by String id already lives at ReplayingToolResultObserver. - Late binding via `@Sendable () async -> OutboundBatcher?` resolver: the batcher is built in installAgent (step 13), AFTER mcpInstallTask captures the gateway in step 10. The closure resolves it at emit-time; if the batcher is nil (early window before installAgent finishes, or shutdown), emissions drop silently — the replay log path still records the call so audit isn't lost. Tests: App/Tests/AppTests/MCPBusGatewayAdapterTests.swift (+5): - stableUUID determinism (same input → same UUID) - stableUUID distinguishes different ids - emitToolCallStart reaches the sink with right name/argsPreview/id - emitToolCallEnd id matches the start id (reconciliation invariant) - updateArgsPreview re-emits .toolCallStart with same id, updated args - nil batcher drops events silently (no trap) The recording sink + OutboundBatcher path the test exercises is the same path production uses end-to-end through to WebviewBridge.send. The remaining seam (sink → JS-side store) is already covered by RealWKWebViewIntegrationTests + webview/hud streaming.test.tsx D5/D6/D7. App build clean. Bus tests 58/58 (was 57/58 before F-A2-01 fix). MCP tests 85/85 unchanged. All 5 boundary gates green. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
e9c0a34298 |
fix(track-b 4): wire AudioGraphOwner + LiveSpeechAnalyzerBridge SpeechAnalyzer API
Track B-4 of the 2026-05-03 voice audit fix plan (.planning/audit-2026-05-03/voice.md). Two production paths were dead: - installVoice constructed WakeWordDAG but never built the AudioGraphOwner that owns the AVAudioEngine + ring buffer it feeds from. Mic taps were installed inside AudioGraph.init, but no AudioGraph was ever created in production, so the wake-word detached feed Task spun on an empty ring for the entire process lifetime. - LiveSpeechAnalyzerBridge.feed(_:) was `_ = chunk` — a structural shell left over from Plan 06-03 that was meant to be wired in 06-05 but never was. Changes: * AudioGraphOwner: add `setCancelInFlight` / `setReleaseORTSessions` cross-actor setters so MainActor install code can install the cancel-in-flight closure under Swift 6 mode. * PCMBufferBuilder: new pure-AVFoundation translator extracted from the bridge (`AudioChunk.pcm16k` → `AVAudioPCMBuffer` + sample-rate convert via `AVAudioConverter`). Lives outside the macOS-26-gated bridge so unit tests run on every supported macOS. * LiveSpeechAnalyzerBridge: replace the no-op feed with real wiring — `start()` builds `SpeechTranscriber` + `SpeechAnalyzer`, queries `SpeechAnalyzer.bestAvailableAudioFormat`, opens the `AsyncStream<AnalyzerInput>` input pipe, and spawns a result-drain Task. `feed()` packs the chunk into a PCM buffer (with optional format convert) and yields `AnalyzerInput(buffer:)`. `finish()` finalises through end-of-input and awaits the drain. Eager partials stream in init removes the ordering race against the SpeechAnalyzerSTT feed/drain Task pair. * AppDelegate: add strong properties `audioGraphOwner`, `audioGraphDegradationTask`, `audioGraphRebuildTask`. installVoice now constructs AudioGraphOwner with degradation/rebuild stream continuations, spawns degradation + rebuild consumers BEFORE `open()` (so the retry-with-aec=false path's `.aecUnavailable` is observed), opens the graph fail-soft, hands the ring buffer to `WakeWordDAG.start(ring:)`, and sets `cancelInFlight` to cancel the DAG before teardown. Failure to open is non-fatal: voice degrades to dormant + banner, app keeps running. * VoiceWiringTests testW1: assert the three new strong properties exist and start nil. * PCMBufferBuilderTests (new, +7): frame count, payload fidelity, empty input, same-format passthrough, 16k→24k upsample ratio, empty-input convert. Verification: * swift test packages/Voice — 77 tests, +7 PCMBufferBuilderTests, 3 skipped, 1 pre-existing failure (TTSInterruptTests.testI3, the 10s timeout flake unrelated to this work; deterministic before and after). * `bash scripts/check-app-builds.sh` — App target Debug build clean. * Boundary gates: install-order, no-modal-presentation, no-null-voice-adapters, orchestrator-events-single-consumer, single-writer-hudstate — all PASS. What's still TODO (Track B-5): * End-to-end voice loop test: feed a known WAV at the audio graph, assert STT result lands on the orchestrator. The pipe is now wired but no integration test exercises it yet. * VAD-gated chunk feeding from VoiceController to STT — the bridge accepts chunks now but VoiceController.startSTTSession() has no producer feeding `sttChunkCont`. Tracked in audit PLAN 06-03 anti-pattern callout. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
75a4ed6991 |
feat(09-04): replace NullVoiceAdapters with real voice adapter triad + lock install order (D-09, D-10, BLOCKER-1, BLOCKER-2, WARNING-5)
- New App/Voice/{VoiceOrchestratorAdapter,VoiceTTSAdapter,VoiceBusEmitterAdapter,RejectReasonCopy}.swift
replaces NullOrchestratorAdapter / NullTTSAdapter / NullBusEmitterAdapter (D-09).
- VoiceOrchestratorAdapter forwards submit/cancelAndSubmit to AgentOrchestrator,
appends user-side text to TurnTranscriptStore (BLOCKER-1), enqueues a HUDBanner
on every SubmitOutcome.rejected case via shared RejectReasonCopy (D-10).
- VoiceTTSAdapter wraps an optional TTSEngineActor (engine construction lands
in a follow-on plan; nil-engine path no-ops gracefully).
- VoiceBusEmitterAdapter wraps an OutboundBatcher whose sink is the WebviewBridge
so audio-level RMS reaches the HUD's RingMesh pulse.
- AppDelegate.installVoice now uses the production adapters; install order
in applicationWillFinishLaunching reordered to vision -> agent -> voice
with voiceInstallTask awaiting agentInstallTask?.value (WARNING-5).
- New broadcaster .voice subscriber in installAgent: per-turn assistantText
accumulator + turnSourceWasVoice filter so text-originated turns NEVER drive
emitTurnEnded on the voice adapter (BLOCKER-2).
- SubmitOutcome.superseded extended with newTurnId so cancelAndSubmit callers
can append user text under the new turn id (BLOCKER-1 user-side closure on
the cancel path).
- VoiceSubscriberTextTurnFilterTests proves text turns never trigger
emitTurnEnded; VoiceOrchestratorAdapterTests covers RejectReasonCopy +
banner id mapping + VoiceBusEmitterAdapter forwarding.
- New scripts/check-install-order.sh + scripts/check-no-null-voice-adapters.sh
catch regressions structurally.
|
||
|
|
da42c079fd |
feat(06-05): AppDelegate production wiring + VoiceWiringTests (W1-W4)
- AppDelegate: import Voice, add voiceController/pushToTalk/muteWakeWord properties - AppDelegate: installVoice() async method wires VoiceController to dormantVoiceContinuation - AppDelegate: NullOrchestratorAdapter/NullTTSAdapter/NullBusEmitterAdapter (Phase 7 stubs) - AppDelegate: AppDelegateBannerAdapter bridges VoiceBannerInterface→HUDBannerCoordinator - MenuBarIconController: contextMenu is now public (for MuteWakeWord) - VoiceWiringTests: W1-W4 type-level wiring verification - NOTE: check-app-builds.sh fails on pnpm (pre-existing infra limit); Swift compiles clean |
||
|
|
d95c4421fc |
fix(05-review): CR-02 wire ME-04 closure observer→channel→drain→ReplayLog
The orch→replay BoundedAsyncChannel<ReplayEvent> previously had no producer (ReplayingToolResultObserver wrote directly to replayLog) and the drain Task discarded everything it received. The channel + drain were dead code; ME-04's contract was structurally unverifiable in production. Now: - ReplayingToolResultObserver holds a BoundedAsyncChannel<ReplayEnvelope> (TurnID + ReplayEvent envelope so the consumer knows which turn to key against) and sends both pre- and post-sanitize bytes through it. - AppDelegate opens a real ReplayLog at boot and the drain Task consumes envelopes off the channel and calls replayLog.record(...). - AppDelegate calls MCPRuntimeWiring.build(...) (previously referenced only in a doc comment) so the dispatcher chain is actually instantiated. A NoopBusGateway placeholder fills the bus seam until Phase 6/7 orchestrator wiring lands. - MCPRuntimeWiring.build's signature switches replayLog parameter to replayChannel — cleaner separation, since the channel is the load-bearing seam. Adds three CR-02 regression tests in MCPRuntimeWiringTests: observer→channel produces envelopes, observer→channel→drain→ReplayLog end-to-end, and .dropOldest under burst saturation. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
d07ac08f0e |
fix(05-review): CR-03+WR-04 String toolUseId and success-path toolCallEnd
CR-03: BusGateway's toolUseId parameter is now String (matching the bus schema's Phase 2 closed contract) instead of UUID. Previously the dispatcher synthesized `UUID(uuidString: toolUse.id) ?? UUID()`, which silently produced a fresh random UUID for every Anthropic 'toolu_…' or Ollama free-form id — breaking correlation across bus / replay / orchestrator. The original String id now flows verbatim from toolCallStart through argsPreviewUpdate to toolCallEnd. The broker's internal UUID is now a fresh-per-call value and does NOT replace the bus-facing id. WR-04: ConfirmingToolDispatcher now emits toolCallEnd(ok: true, …) on the success path so bus consumers (HUD ToolCallCard) transition Running → Completed. On dispatch error post-approval, toolCallEnd fires with ok: false carrying the error description before re-throwing. Adds three new tests (busToolUseId verbatim across emissions, non-UUID id flows through, success path emits 3 events). Updates SpyBus and SpyBus-equivalent in MCPRuntimeWiringTests to use String toolUseId. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
daae27e720 |
feat(05-05): App-level MCPRuntimeWiring + ME-04 channel + SEC-07 observer
Closes ME-04 (Plan 04-05 deferred state). AppDelegate.applicationWill FinishLaunching now instantiates BoundedAsyncChannel<ReplayEvent>( capacity: 2048, policy: .dropOldest) for the orch→replay seam — the production instance that Plan 04-05's ChannelTopologyTests CT2 only verified as a primitive. - App/MCP/MCPRuntimeWiring.swift: dependency-graph constructor for the full chain (MCPClient → ToolRegistry → MCPToolDispatcher → ConfirmingToolDispatcher) plus a compose() test seam. The ConfirmationPresenterHolder breaks the broker↔presenter cyclic init. - App/MCP/ReplayingToolResultObserver.swift: SEC-07 observer that records pre-/post-sanitize bytes via two ReplayEvent.toolResultFull rows (one canonical, one ':raw'-suffixed toolUseId). Uses an injected turnIDResolver closure since orchestrator wiring lands in a later plan. - AppDelegate stores the orchToReplayChannel + drain Task and tears them down on applicationWillTerminate. - App/Tests/AppTests/MCPRuntimeWiringTests.swift: 3 structural tests (compose-returns-ConfirmingToolDispatcher; channel-capacity-+-policy; MCPRuntime-struct-addressable). No xcodebuild test launch — known Xcode 26 RunningBoard blocker per .planning/debug/. Deviations: - Replay / AgentCore / MCP / Bus Package.swifts had implicit transitive swift-log deps that SPM tolerated but Xcode framework-linker rejects once the App pulls them in as separate frameworks. Added explicit swift-log deps to all four (Rule 3, blocking). - HudStateCoordinatorBusWiringTests qualified two HudState references to Jarvis.HudState — the new framework deps disturbed the App vs Bus symbol disambiguation (Rule 1, surgical). - The plan's plain ReplayEvent.toolResultFull(toolUseId:rawBytes: sanitizedBytes:) variant doesn't exist (Phase 4 schema closed); the observer writes two rows with a ':raw' toolUseId suffix instead. xcodebuild build (Debug) succeeds with the new MCP deps wired into the App target. Test build (build-for-testing) also succeeds; runtime test execution is the documented Xcode 26 RunningBoard blocker per 03-05-PLAN. |
||
|
|
cabc27299a |
feat(03-05): AppDelegate wires HudStateCoordinator + bridge; lint activated; W1-W6 + B1-B3 tests
- App/HUD/HudStateBridge.swift (new): @MainActor public func busHudState(
from: HudState) -> Bus.HudState. RawValue round-trip with assertionFailure
on drift, .idle fallback in Release. Option (b) from the plan — avoids
name-collision between App.HudState and Bus.HudState via module-qualified
Bus.HudState in the helper's scope.
- App/AppDelegate.swift:
* New stored properties: hudStateCoordinator, dormantAgentContinuation,
dormantVoiceContinuation, dormantConfirmContinuation (all internal so
@testable import Jarvis can assert W5 retention), webviewEntryFilename
(internal test seam for W6).
* installBus(): constructs HudStateCoordinator with emit closure that
bridges App.HudState -> Bus.HudState via busHudState() and dispatches
to bridge.send(.hudState(...)) on the MainActor. Creates three dormant
AsyncStream producers (Phase 4/5/6 replace them) and retains their
continuations on self so coordinator.{agent,voice,confirm}Task stay
live. markReady() fires from onHandshakeArmed so the HUD promotes
.booting -> .idle as soon as the webview is responsive.
* Load target: bus-harness.html -> index.html (via
webviewEntryFilename property so W6 can assert post-install value).
XCTest-host short-circuit from 02-03 deviation #5 preserved, updated
to reference the property-driven filename.
- scripts/check-single-writer-hudstate.sh: allowlist extended to
{HudStateCoordinator.swift, HudStateBridge.swift, AppDelegate.swift} +
{BusOutbound.swift, Protocol.swift}. Rationale comment updated: Phase 4+
drives the coordinator via its three input AsyncStreams, not new
.hudState(...) call sites. Already wired as a preBuildScript by Task 1.
- App/Tests/AppTests/HudStateCoordinatorBusWiringTests.swift (new):
* W1 test_coordinatorConstructedInInstallBus — delegate.hudStateCoordinator
non-nil after applicationWillFinishLaunching.
* W2 test_emitClosureBridgesToBusSend — FakeJSEvaluator observes the
closure producing a decoded BusOutbound.hudState(.thinking) after
coordinator.markReady() + agent.yield(.thinking). Proves the exact
closure shape AppDelegate installs routes rawValues end-to-end.
* W3 test_markReadyCalledOnHandshakeArmed — coordinator pinned at .booting
pre-handshake, promotes to .idle after bridge.handleHelloAck.
* W4 test_appHudStateRawValueMatchesBusHudState — table-driven bidirectional
rawValue parity over HudState.allCases and Bus.HudState.allCases. Catches
drift before busHudState()'s assertion fires at runtime.
* W5 test_dormantStreamsHeldByDelegate — all three continuations non-nil.
* W6 test_installBusLoadsIndexNotHarness — webviewEntryFilename == "index"
post-installBus.
- App/Tests/AppTests/WebviewBundleLoadTests.swift (new):
* B1 test_indexHtmlInRepoWebviewDir — structural (#filePath traversal to
repo root), catches missing committed index.html.
* B3 test_indexHtmlReferencesRelativeAssets — content-grep for "./assets/"
present + "src=\"/assets/" absent. Catches Vite misconfig or absolute-
path drift.
* B-extra test_assetsDirectoryGitignored_documentedInSmokeScript — doc-only,
asserts the split of responsibility between XCTest (committed index.html)
and scripts/smoke-test-hud.sh (built-app assets/*.js).
Tests are repo-structural / evaluator-observation only; xcodebuild test
launch still blocked on Xcode 26 RunningBoard error 5 per 02-03 precedent.
Verification is xcodebuild build (succeeds) + scripts/smoke-test-hud.sh
(next task). This follows the 02-03 SUMMARY deviation #5 pattern.
xcodebuild build -configuration Debug: BUILD SUCCEEDED.
bash scripts/check-single-writer-hudstate.sh: PASS (allowlist active).
packages/Bus swift test: 47/47 green.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
79092263bb |
feat(03-01): HudStateCoordinator single-writer + intents + lint
Implements HUD-08 precedence ladder as the sole Swift-side writer of HudState. Three producer streams (agent, voice, confirmation) feed a MainActor-isolated resolver; one emit closure is the only path out. - HudStateIntent.swift: AgentHudIntent/VoiceHudIntent/ConfirmHudIntent Sendable enums. SystemHudIntent intentionally omitted — markReady() is a one-shot direct call, not a stream. - HudStateCoordinator.swift: @MainActor final class. Ladder per HUD-08 verbatim: awaitingConfirmation > speaking > listening > thinking > idle > booting > reconfiguring. Ready-gate tweak on the .booting step (RESEARCH Open Q #4 pre-authorized) so boot-time silent inputs don't prematurely resolve to .idle. - Bus-agnostic by design: takes @escaping @MainActor (HudState) -> Void emit closure so Plan 03-05 can wire it to WebviewBridge without this plan importing Bus or WebKit. - Weak-self capture + deinit cancel on three for-await loops prevents the T-03-03 leak. - HudStateCoordinatorTests.swift: 19 methods covering every precedence pair, booting gate, idempotent emit, cleared-confirm restore, weak-self no-leak, interleaved producers. - scripts/check-single-writer-hudstate.sh: executable; exits 0 on current codebase; exits 1 on planted .hudState() construction outside the allowlist. Pre-build wiring deferred to Plan 03-05. |
||
|
|
ca17844883 |
feat(03-01): extend HudState enum 5→7 cases with voiceOverLabels
- Append .reconfiguring and .booting to App/Theme/HudState.swift; preserve Phase 1 declaration order + existing labels verbatim. - Add voiceOverLabel arms 'Jarvis, reconfiguring audio' / 'Jarvis, starting up' per RESEARCH §HudState Enum Wire Format. - MenuBarIconController: add forced exhaustive switch arm for the two new cases; both share .idle profile (no animation) with TODO for Phase 3/4 dedicated treatment per HUD-08 precedence. - HudStateEnumTests: 6 methods covering case count, non-empty + unique voiceOverLabels, spec-verbatim labels, exhaustive switch. |
||
|
|
20e8647af4 |
feat(02-03): wire bus into AppDelegate + expose HUD webView + bundle harness
- JarvisHUDPanel.webView: private → public. Surgical one-line change per RESEARCH open question #4 — keeps the dep graph App → Bus, not inverted. - AppDelegate.installBus(): new launch-chain step 4.5 (after installHUDPanel / installBannerPanel). Constructs WebviewBridge around panel.webView, wires alertPresenter → TCCAlertService.presentHardBlock + terminate, wires onHandshakeArmed → onBusArmed test hook, installs BridgeNavigationDelegate so WKNavigationDelegate.webView(_:didFinish:) triggers startHandshake, and loads bus-harness.html via Bundle.main.url(forResource:withExtension:subdirectory:). Missing harness is a hard-block (terminates) in production; silently skips under XCTest so unit tests assert bridge construction. - AppDelegate.toggleHUD(): gates on handshakeState == .armed. Unarmed path enqueues BannerContent(id: "hud-not-ready", priority: 2) rather than popping an empty window. - AppDelegate.onHandshakeMismatch + onBusArmed: @MainActor injectable closures for test hooks. Production default calls NSApp.terminate(nil) after the TCCAlertService modal returns. - App/Resources/webview/bus-harness.html: bundle-vendored harness loaded from Jarvis.app/Contents/Resources/webview/bus-harness.html. Plan 04 adds the parity check that keeps this in sync with webview/bus-harness.html. - project.yml: adds Bus package dep, adds App/Resources/webview as a blue-folder reference (type: folder under sources:) so the subdirectory survives into the bundle. Excludes App/Resources/** from the flat source path to avoid double-including. xcodegen regenerates clean. - AppDelegateBusWiringTests: 6 new test cases covering installBus construction, toggleHUD armed/unarmed gating, onHandshakeArmed hook firing, mismatch terminate hook, and Bundle.main harness lookup. Verified: - xcodegen generate + xcodebuild -scheme Jarvis build: BUILD SUCCEEDED. - packages/Bus SPM tests: 47 passed / 0 failed. - bus-harness.html + Bus_Bus.bundle/Injection.js both ship in the .app. - grep -rn '.evaluateJavaScript(' App/ packages/Bus/Sources/: 0 matches. Note: xcodebuild test -only-testing:JarvisAppTests fails at test-host launch with dyld 'different Team IDs' on Xcode 26 + macOS 26 Debug — this is a pre-existing repo-level issue documented in .planning/debug/xctest-launch-runningboard-error-5.md and tracked in 01-HUMAN-UAT.md. Out of scope for Plan 02-03; SPM-level coverage is complete via BatcherTests + WebviewBridgeOutboundTests. |
||
|
|
b76ff75d81 |
fix(01-IN-07): replace tautology with real SEC-01 pasteboard assertion
Phase 1 REVIEW.md IN-07: `test_stateDumpDoesNotIncludeAPIKey` was an
`XCTAssertTrue(true)` tautology. It claimed to test SEC-01 /
T-04-02 but could not fail on a regression where `copyStateDump()`
accidentally started writing the raw API key to the pasteboard.
App/Tests/AppTests/AppDelegateWiringTests.swift
- Introduce `RecognizableKeychain` fake that returns a distinctive
sentinel plaintext ("sk-ant-test-do-not-leak").
- Clear NSPasteboard before the test so prior-test contamination
can't produce a false positive.
- Trigger `copyStateDump` via `perform(Selector("copyStateDump"))`.
- Assert pasteboard string:
* does NOT contain the recognizable plaintext
* does NOT contain any "sk-ant-" prefix
* DOES contain "\"apiKeyStored\"" (sanity — exercised the path)
Now this test literally fails if copyStateDump regresses.
|
||
|
|
9fc053511c |
fix(01-WR-05): cancellable 300ms banner drain, close race windows
Phase 1 REVIEW.md WR-05: the 300ms drain after `dismissCurrent()` was
a fire-and-forget `DispatchQueue.main.asyncAfter`. Two races fell out
of that:
1. `clear()` during the drain window — pending drain still fires,
banner pops up while app is quitting.
2. Higher-priority banner enqueued during the drain — `current` is
nil for 300ms, so `enqueue` calls `showBanner` immediately, then
the drained banner fires afterwards and clobbers it.
App/HUD/HUDBannerCoordinator.swift
- Add `private var drainWorkItem: DispatchWorkItem?` — cancellable
handle on the drain.
- `dismissCurrent()`: cancel any prior drain before scheduling a new
one (belt + suspenders — prevents two drains from racing).
- `enqueue()`: when `current == nil`, cancel any pending drain before
calling `showBanner`. Prevents the clobber race.
- `clear()`: cancel the pending drain. Prevents pop-up-at-quit.
- Drain closure re-checks `current != nil` at fire time for defense
in depth.
App/Tests/AppTests/HUDBannerCoordinatorTests.swift
- `test_clearCancelsPendingDrain` — enqueue two, dismiss, clear,
wait 500ms, assert currentBanner is nil.
- `test_enqueueDuringDrainCancelsDrain` — enqueue/enqueue/dismiss/
enqueue-higher-priority, assert higher-priority wins immediately
AND survives past the 300ms drain window.
|
||
|
|
929e4cee6b |
fix(01-CR-02): shorten API key plaintext lifetime in wizard @State
Phase 1 REVIEW.md CR-02 (SEC-01): `WizardStageAPIKeyView` kept the raw
Anthropic API key in an `@State String` for the lifetime of the view —
outliving the Keychain write, retained by SwiftUI attribute-graph
diffing, and visible to any memory-dump forensic vector (crash report,
Xcode memory graph, `leaks(1)`).
App/Wizard/WizardStageAPIKeyView.swift:53-101
- `verify()` now captures the key into a single `capturedKey` local and
reassigns `apiKey = ""` immediately, so the @State slot drops its
strong reference before the async validate + keychain.set chain runs.
- Error branches selectively restore `apiKey = capturedKey` only where
the user plausibly wants to retry without retyping (unauthorized /
network / generic / keychain-error); `malformed` keeps apiKey empty.
- `.onDisappear { clearAPIKey() }` hook: zeros the @State on view
teardown (including back-navigation and success-path dismissal),
preventing the plaintext from persisting in the attribute graph after
dismiss.
- `clearAPIKey()` first overwrites with null bytes of the same length
(best-effort — Swift Strings are CoW/immutable so the backing page
can't be zeroed in place), then drops the @State to empty so ARC can
release the CoW storage.
App/Tests/AppTests/WizardStageAPIKeyViewTests.swift (new)
- Source-level grep gates for the three regression points: onDisappear
hook, clearAPIKey helper shape, and verify()'s capture-then-clear
pattern. SwiftUI @State lifecycle isn't observable from XCTest
without ViewInspector, so text-level gates are the practical
regression guard.
Jarvis.xcodeproj/project.pbxproj: regenerated via xcodegen to pick up
the new test file.
|
||
|
|
e9a0bb14d2 |
feat(01-05): add JarvisEntitlementProbeTests target + EntitlementProbe scheme
- App/Tests/JarvisEntitlementProbeTests/JarvisEntitlementProbeTests.swift:
one-shot XCTest gated on #if JARVIS_ENTITLEMENT_PROBE. Calls
SpeechTranscriber(locale:preset:) + AssetInventory.status(forModules:)
and accepts any of three failure surfaces as confirmation that the
speech-recognition-assets entitlement is load-bearing:
(a) NSError SFSpeechErrorDomain code 1 (assetUnavailable)
(b) NSError SFSpeechErrorDomain code 10 (macOS 26 'unallocated locales')
(c) AssetInventory.Status description containing 'unavailable' /
'unsupported'
- App/Tests/JarvisEntitlementProbeTests/Info.plist: standard test bundle.
- project.yml: new JarvisEntitlementProbeTests target (OTHER_SWIFT_FLAGS =
-DJARVIS_ENTITLEMENT_PROBE) + EntitlementProbe scheme. Default Jarvis
scheme's test action lists only JarvisAppTests — the probe target is
opt-in via EntitlementProbe scheme only.
- Jarvis.xcodeproj/xcshareddata/xcschemes/EntitlementProbe.xcscheme: new
shared scheme referencing JarvisEntitlementProbeTests as its sole
testable.
Deviations from plan's copy-verbatim XCTest shape:
- Added @available(macOS 26.0, *) to the test class: macOS 26 SpeechModule
/ SpeechTranscriber / AssetInventory are not available pre-Tahoe.
Deployment target stays at 13.0 for the rest of the project.
- preset: .progressiveLiveTranscription -> .progressiveTranscription
(actual Apple SDK name on macOS 26.4).
- AssetInventory.status(forModules:) is async non-throwing on macOS 26.4;
wrapped in a throwing adapter so the historical do/catch shape from
RESEARCH Q4 (NSError SFSpeechErrorDomain codes 1/10) stays intact.
Also: [Rule 3 - Blocking] extend codesign.sh to handle nested .xctest
plugins under Contents/PlugIns/. When build-for-testing is invoked, the
xctest lives inside Jarvis.app/Contents/PlugIns/ and must be signed
before the main app, or codesign on the parent bundle fails with
'bundle format unrecognized'. Empty xctest shells that materialize
before the test target has been compiled are removed (Xcode recreates
+ populates them in a later iteration).
Task 3 acceptance: xcodebuild -list shows EntitlementProbe scheme +
JarvisEntitlementProbeTests target; EntitlementProbe build-for-testing
succeeds with the full codesign + entitlement verification chain.
|
||
|
|
ebefb91043 |
feat(01-04): build first-launch wizard + full AppDelegate launch wiring
- AnthropicKeyValidator: local sk-ant- prefix check + URLSession-injectable GET to api.anthropic.com/v1/models; returns typed enum, never logs key - WizardState: @MainActor ObservableObject; D-09 stateless re-entry via refresh(); firstUnresolvedStage advances apiKey → tcc → hotkey → complete - WizardStageAPIKeyView: SecureField, validator + Keychain round-trip; key value lives only in Keychain (SEC-01) - WizardStageTCCView: Input Monitoring row actively probes; Mic/Camera/ Automation rows are explainer-only (D-08/SEC-04); no permission APIs called from the view - WizardStageHotkeyView: ShortcutRecorderView + CollisionDetector wiring; Skip leaves hotkey unbound (SHELL-02) - WizardView: three-stage root with header dots + Back footer - OnboardingWizardController: NSWindow hosting; first-launch .modalPanel + close-button disabled until API key stored (D-06) - AppDelegate: full 8-step launch chain — logging bootstrap (S-8) → entitlement gate (Plan 03) → config load (NSAlert on malformed, D-19/S-6) → menu bar + HUD + banner install → Keychain fetch (.keychainEmpty banner on itemNotFound) → Input Monitoring probe (.inputMonitoringDenied banner on denial, SHELL-06) → HotkeyBinder init → Wizard open on first launch. copyStateDump writes boolean-only presence; API key VALUE never clipboarded - JarvisApp: Settings scene stub; real NSPanels managed by AppDelegate - Tests: 11 new (WizardState 4, AnthropicKeyValidator 3, WizardTCC 1, AppDelegateWiring 4) + updated AppDelegateEntitlementTests for the new launch chain; all 25 JarvisAppTests pass - Re-inserted PBXCopyFilesBuildPhase for Contents/Helpers per Plan 01/03 precedent (xcodegen strips empty copyFiles) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
122153d544 |
feat(01-03): add JarvisHUDPanel, HUDBannerPanel+Coordinator, AppDelegate entitlement hard-block
- JarvisHUDPanel: borderless 720×720 NSPanel, .statusBar level, .canJoinAllSpaces/.fullScreenAuxiliary/.transient collectionBehavior, transparent WKWebView (drawsBackground=false via KVO), cancelOperation(_:) + performClose(_:) overrides for Escape/⌘W dismiss, Reduce Transparency fallback to windowBackgroundColor@95%, fresh-center on summon via NSScreen.screenWithMouse() - HUDBannerPanel: .floating non-activating 360pt banner panel, canBecomeKey/canBecomeMain both false (never steals focus), top-trailing positioning 24pt/8pt inset - HUDBannerCoordinator (@MainActor): priority queue with pre-emption (lower priority int = higher precedence), dismissed-this-launch dedup by id, 300ms gap to next banner per UI-SPEC Surface 5 - BannerContent with four presets: keychain-empty (1) > input-monitoring-denied (2) > hotkey-bind-failed (3) > ollama-url-rejected (4); Sendable/Equatable/Identifiable - HUDBanner (SwiftUI): 4pt arc-reactor-glow leading stripe + title/body/action/dismiss-× layout on .hudWindow visual-effect background with separatorColor border - AppDelegate: EntitlementGateProbe protocol + InfoPlistEntitlementGateProbe impl + injectable onEntitlementFailure (@MainActor) defaulting to presentEntitlementFailureAlert + NSApp.terminate, skipped under XCTest host; bootstrap chain installs menu-bar + HUD panel + banner panel (Plan 04 prepends logging and appends config/keychain/hotkey) - 9 new tests in JarvisAppTests: 3 HUD panel (style flags, summon/dismiss, Escape), 4 banner coordinator (enqueue, priority pre-emption, dismiss-drain, dismissed-this-launch dedup), 2 AppDelegate (hard-block triggers / does-not-trigger with mocked probe) |
||
|
|
f8da4b7c39 |
feat(01-03): add MenuBarIconController + MenuBarContextMenu + JarvisAppTests target
- MenuBarIconController (@MainActor) owns NSStatusItem, loads Icon-MenuBar-Template asset with isTemplate=true, re-anchors layer.frame after anchorPoint change (Q7 gotcha #2), transitions across 5 HudStates via CAAnimationFactory with 150ms crossfade + rate-limited VoiceOver announcements (3s limiter per UI-SPEC Surface 3 line 421) - MenuBarContextMenu builds Setup… / Settings… (disabled) / Show Dev Overlay / Copy State Dump / Quit Jarvis (⌘Q), with a ClosureTarget trampoline for closure-based actions - Left-click vs right-click disambiguation via NSApp.currentEvent.type inspection (D-16 / UI-SPEC Surface 3 lines 406-409) - JarvisAppTests Xcode unit-test bundle target added via project.yml; scheme wires test action against JarvisAppTests - MenuBarIconControllerTests covers state transitions, no-op-on-same-state, context-menu items, and HudState voiceOverLabels (4 tests pass) - PBXCopyFilesBuildPhase for Contents/Helpers re-applied post-xcodegen (Plan 01 invariant — MCP-05 acceptance-criteria grep) |