c1ec61fa49
- NEW tools/observer-self-assessment-api.mjs
buildSelfAssessmentPrompt({ prompt, recommendedNode, actualNode, chainExecuted })
pure, handles nulls/undefined, returns { system, user } strings
callSelfAssessmentApi(opts) async, fail-quiet — returns string|null
AbortController + timeout race (works even when fetchImpl ignores signal)
guards: !apiKey -> return null immediately (no fetch call)
guards: !response.ok, fetch throw, JSON parse error -> return null
passes x-api-key + authorization headers per ProxyAPI two-header pattern
readRuntimeFlag(name, { homedir, fsImpl }) reads ~/.claude/runtime/<name>.json
returns value field string or 'off' on missing/malformed
- NEW tools/observer-self-assessment-api.test.mjs: 14 tests, 0 failed
1. buildSelfAssessmentPrompt all 4 fields interpolated
2. buildSelfAssessmentPrompt null/undefined inputs (2 tests)
3. callSelfAssessmentApi returns null when apiKey falsy (2 tests)
4. returns content[0].text on 200 ok (fake fetchImpl)
5. returns null on non-2xx (response.ok=false)
6. returns null on fetch throw
7. returns null on timeout (never-resolving fake fetchImpl, timeoutMs=30ms)
8. sends correct headers+body shape (spy fetchImpl)
9. readRuntimeFlag reads {"value":"on"}, returns 'off' on missing/malformed (4 tests)
- EDIT tools/observer-stop-hook.mjs
import { callSelfAssessmentApi, readRuntimeFlag } added
stdin 'end' handler made async
step 3.5 inserted between buildEpisodeFromContext and appendEpisode:
reads self-assessment-mode runtime flag; if 'on' and ROUTER_LLM_KEY set,
calls callSelfAssessmentApi and attaches ep.self_assessment via buildSelfAssessment()
fail-quiet: on any error apiResult=null -> self_assessment_pending: true
Regression: 628/628 tests passed (35 test files), 0 failed
gitleaks: 0 leaks on all 3 files
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>