develop
12
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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. |
||
|
|
f59f5d12e3 |
fix(memory): v0.1 CRIT cluster — facts_vec / replay correlation / FTS5 sanitize / search_memory preamble (#12, #13, #14, #15, #16, #153)
#14 (CRIT): ContextBuilder.selfAwarePreamble names search_memory + forget_fact with explicit cue 'call search_memory FIRST. Never claim to remember without searching.' #16 (HIGH): MemoryQueries.sanitizeFTS5Query strips non-alphanumeric, quotes tokens; HybridSearch.searchFacts calls it before binding to MATCH ?. Embedding still runs on raw query. #12 (CRIT): MemoryStore.init accepts optional embedder; insertNewFact writes facts_vec after the facts commit. Vec failures log + leave FTS row (no rollback). installMemory shares one embedder between writer + reader. #15 (HIGH): drop stableHash; pass sentinel 0 as sourceTurnId (option 1 per audit — smaller change surface than threading real rowid through 5 files). Schema comment updated. #13 (CRIT): two-layer fix. Layer 1: AppDelegate owns a per-process sentinel TurnID; installAgent writes the matching turns row after beginSession; AppDelegateMemoryReplaySink attributes events to it. Layer 2: ReplayLog.flushPending retries failed batches event-by-event so one poisoned row only drops itself. Memory 98 + Replay 34 + AgentCore 243 tests pass; all 18 boundary gates green. |
||
|
|
beb9d51aa5 |
test(agent-core): update qwen2.5-coder literal to q8_0 variant
Two test assertions still expected `"qwen2.5-coder:32b"` after commit |
||
|
|
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>
|
||
|
|
5bb9371e39 |
test(10-02): add Wave-0 RED preambleDoesNotEnableCacheUnintentionally
VALIDATION map row SELF-06 / T-10-CACHE-01. Asserts the soon-to-land ContextBuilder.selfAwarePreamble does not flip cache-marker emission relative to the prior literal "You are Jarvis, a personal macOS assistant." (Pitfall #4 / Assumption A4 / PATTERNS AP-04), enforces the D-13 lock anchor (hasPrefix "You are Jarvis"), the D-14 no-tool-catalog constraint (no Markdown triple-backtick), and the D-12 composition order (selfAwarePreamble FIRST in systemPrompt(for:)). RED: ContextBuilder symbol does not exist yet — fails to compile. |
||
|
|
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>
|
||
|
|
1f8e03edeb |
fix(track-a): demo-blocker class + audit reports + chat-turn e2e tests
User feedback: 50+ hours of agent work, nothing demonstrably working.
Fanned out 6 deep auditors (voice, HUD, vision, memory, LLM, tests);
all reports landed at .planning/audit-2026-05-03/ with brutal-honesty
mandates. Synthesis (SYNTHESIS.md) confirmed: every modality has at
least one fatal break between code-that-passes-tests and
code-that-runs. Track A is the smallest credible path to one working
modality (text turn + animated rings).
Closed under TDD discipline:
**Item 4 — HUD idle ring static rendering (F-A1-01).**
- webview/packages/hud/src/hud/stateUniforms.ts:30 — was
`pulse: 0.0, rotate: 0.0` for idle, the all-static configuration
that rendered STANDBY as dots instead of animated rings. Now
pulse: 0.4, rotate: 0.05 — gentle ambient distinct from booting +
thinking.
- webview/packages/hud/tests/stateUniforms.test.ts — table updated;
new regression-guard `STATE_PARAMS[*] is not all-static` (7 cases,
one per HudState) prevents anyone from re-zeroing a state.
**Item 1 — CacheHints eligibility gate (likely streamTruncated cause).**
- packages/AgentCore/Sources/AgentCore/CacheHints.swift —
+ static func eligibleForSystemPrompt(_:ttl:). Anthropic's prompt
cache requires ≥1024 tokens at the breakpoint; below that, the
server returns 200 OK + immediate-EOF for `extended-cache-ttl`
hints. The orchestrator's default system prompt is ~10 tokens.
Helper returns nil for sub-threshold prompts so no cache_control
marker is emitted.
- packages/AgentCore/Sources/AgentOrchestrator/AgentOrchestrator.swift
— call sites now pass `CacheHints.eligibleForSystemPrompt(finalSystem)`
through runTurnLoop instead of unconditional `.extended1h`.
- Tests: CacheHintsEligibilityTests (6 cases), RequestBodyTests R5a/R5b
(2 cases verifying small-prompt → no cache_control in encoded JSON,
large-prompt → cache_control present).
**Item 2 — StreamOutcome diagnostic.**
- packages/AgentCore/Sources/AnthropicProvider/StreamOutcome.swift —
new pure classifier that distinguishes:
- non-2xx HTTP status
- 200 OK + 0 bytes (cache_control / auth-header rejection class)
- 200 OK + bytes + 0 frames (malformed body)
- 200 OK + frames + EOF before message_stop (mid-stream drop)
- 200 OK complete (healthy)
AnthropicProvider's read loop counts frames + bytes and logs the
summary once per stream. Production logs now distinguish failure
modes that previously all surfaced as `streamTruncatedFinal`.
- Tests: StreamOutcomeTests (7 cases, all branches).
**Item 3 — KeychainError surfacing.**
- packages/AgentCore/Sources/AnthropicProvider/AnthropicAPIKeyProvider.swift
— new builder `make(keychain:item:)` returns the
`@Sendable () async throws -> String` closure with errors
propagating verbatim. Closes the audit's hidden-401 finding:
`(try? keychainStoreLocal.get(.anthropic)) ?? ""` swallowed the
real KeychainError into an empty x-api-key, producing
`401 x-api-key header is required` indistinguishable from a
stale-key 401. Now the actual error reaches the chat panel via
AnthropicProvider → orchestrator.error → BusForwarder →
submitRejected.
- App/AppDelegate.swift — both Anthropic provider construction sites
(installAgent, installVision T3 wiring) now use the propagating
builder.
- Tests: AnthropicAPIKeyProviderTests (6 cases — itemNotFound,
unexpectedStatus, duplicateItem propagation; happy path; explicit
item; default item).
**Item 5 — F1 #2 chat-turn round-trip e2e tests.**
- packages/Bus/Tests/BusTests/RealWKWebViewIntegrationTests.swift —
+ 2 new tests using a real WKWebView:
- test_chatTurnRoundTrip_busOutboundReachesPageHandler — Swift
`bridge.send(.turnStarted/tokenDelta/turnEnded)` reaches the
page's onOutbound handler. This is the test the v0.12.0 audit
called out as catching BLOCKER-INT-1, INT-2, INT-3,
F-E-RACE-1, F-E-FK-1, F-E-WIRE-1 in one shot.
- test_chatTurnRoundTrip_busInboundFromJSReachesSwiftHandler —
JS-side `window.jarvisBus.send({type:'chatSubmit',...})` —
exactly what ChatInput.tsx does — reaches the bridge's
onInbound handler with the right body.
**Verification:**
- AgentCore: 215/215 (was 178; +37 across BusForwarder, CacheHints,
StreamOutcome, AnthropicAPIKeyProvider, RequestBody R5).
- Replay: 33/33.
- Bus: 57/58 (1 pre-existing failure: F-A2-01 stale `JarvisBusWorld`
assertion documented in AUDIT-FINDINGS, separate scope).
- webview/packages/hud: 94/94 (was 87; +7 stateUniforms regression-
guards + idle table updates).
- All 15 boundary grep gates: PASS.
- scripts/check-app-builds.sh: PASS.
**Audit reports retained at .planning/audit-2026-05-03/:**
- SYNTHESIS.md (consolidated, with triage plan)
- hud-audit.md, llm-audit.md, memory-audit.md, tests-audit.md,
vision-audit.md, voice-audit.md, voice.md (per-modality detail)
**Carry-forward (NOT closed this commit):**
- F-A2-01 stale WebviewBridgeOutboundTests assertion (pre-existing).
- Voice (F): models never bundled, audio graph never instantiated,
STT bridge body is `_ = chunk`, TTS engine nil. ~1 day.
- Vision (F): AVCapturePhotoOutput allocated but capturePhoto never
called; HUD camera button doesn't exist. ~1 day.
- Memory (F): Apple's stripped libsqlite3 prevents sqlite-vec from
loading; "future ops plan" has zero commits. ~3 days.
- LLM (D+): NoopBusGateway still drops tool-call events
(BLOCKER-INT-1).
- Tests (C-): 5 XCTAssertTrue(true) tautologies + multiple
rubber-stamp tests defending stubs.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
202b7e36a6 |
feat(07-05): LLMProvider multimodal protocol extension + ImageBlock + TurnInput.images (Task 1 GREEN)
- ImageBlock(mediaType:data:) Sendable+Equatable canonical frame value type - LLMProvider gains stream(messages:images:tools:...) with default extension forwarding to single-modal when images.isEmpty (D-18) - Non-overriding conformers precondition-fail when images is non-empty - TurnInput.images defaults to []; existing initializers compile unchanged - TurnInput.withImages factory for FrameAttachController consumers - LLMProviderMultimodalTests pass (forward dispatch + override + ImageBlock equality) |
||
|
|
f8f8920bae |
test(07-05): add failing LLMProvider multimodal tests (Task 1 RED)
- LLMProviderMultimodalTests asserts default forwarding when images.isEmpty - Override path receives images on multimodal-conformant mock - ImageBlock equality/Sendable contract - TurnInput.images round-trip (added in GREEN) |
||
|
|
9c0de9f006 |
feat(04-04): TurnNonce + UntrustedWrapper + ToolResultPacker (SEC-06, AGENT-08)
- TurnNonce.fresh(): SecRandomCopyBytes(16) → base64url ~22 chars (SEC-06) - UntrustedWrapper: tag-strip prefilter + nonce-bracketed envelope - ToolResultPacker: 8 KB model-facing cap + truncation marker (AGENT-08); full bytes preserved for ReplayLog routing - Promoted Base64URL.swift from AnthropicProvider (internal) to AgentCore (public) so both targets share the encoder - 13 new tests across TurnNonce / UntrustedWrapper / ToolResultPacker; full AgentCore suite at 94 green |
||
|
|
0020c35fb6 |
feat(04-01): scaffold AgentCore package with LLMProvider protocol + LLMEvent + BoundedAsyncChannel (AGENT-01, AGENT-07, AGENT-10)
- packages/AgentCore SPM package with three library products: AgentCore, AnthropicProvider, OllamaProvider (latter two ship placeholders this wave) - LLMProvider protocol — Sendable, AsyncThrowingStream<LLMEvent, Error>, mandatory toolChoice param - LLMEvent enum: 10 cases (messageStart, textDelta, thinkingDelta, toolUseRequested, toolUseBuffering, partialToolUseAtDisconnect, stopReason, usage, providerError, messageStop) - StopReason: 5 cases (endTurn, toolUse, maxTokens, refusal, streamTruncated) - ToolChoice: 4 cases (auto, none, any, tool(name:)) - ModelID literals per RESEARCH-DELTAS D1 — opus47 = "claude-opus-4-7", qwen25coder32b = "qwen2.5-coder:32b" - CacheHints / TurnID / TurnUsage / ToolSchema / LLMMessage / LLMProviderError supporting types - BoundedAsyncChannel<T> actor with .suspend / .dropOldest / .dropNewest policies, AsyncSequence conformance, single-consumer iterator - 21 AgentCoreTests passing (channel policies, exhaustive enum cases, equality, RawValue literals) |