| ... | ... | @@ -3449,10 +3449,14 @@ describe("ClaudeAdapterLive", () => { |
| 3449 | 3449 | const harness = makeHarness(); |
| 3450 | 3450 | return Effect.gen(function* () { |
| 3451 | 3451 | const adapter = yield* ClaudeAdapter; |
| 3452 | | const runtimeEvents: Array<ProviderRuntimeEvent> = []; |
| 3453 | | const runtimeEventsFiber = yield* Stream.runForEach(adapter.streamEvents, (event) => |
| 3454 | | Effect.sync(() => runtimeEvents.push(event)), |
| 3455 | | ).pipe(Effect.forkChild); |
| 3452 | const runtimeEventsFiber = yield* adapter.streamEvents.pipe( |
| 3453 | Stream.takeUntil( |
| 3454 | (event) => |
| 3455 | event.type === "session.state.changed" && event.payload.reason === "api_retry:3/10", |
| 3456 | ), |
| 3457 | Stream.runCollect, |
| 3458 | Effect.forkChild, |
| 3459 | ); |
| 3456 | 3460 | |
| 3457 | 3461 | yield* adapter.startSession({ |
| 3458 | 3462 | threadId: THREAD_ID, |
| ... | ... | @@ -3498,7 +3502,6 @@ describe("ClaudeAdapterLive", () => { |
| 3498 | 3502 | uuid: "tu", |
| 3499 | 3503 | }, |
| 3500 | 3504 | { type: "system", subtype: "commands_changed", session_id: "session", uuid: "cc" }, |
| 3501 | | { type: "system", subtype: "model_refusal_fallback", session_id: "session", uuid: "mrf" }, |
| 3502 | 3505 | { type: "system", subtype: "local_command_output", session_id: "session", uuid: "lco" }, |
| 3503 | 3506 | { type: "system", subtype: "plugin_install", session_id: "session", uuid: "pi" }, |
| 3504 | 3507 | { type: "system", subtype: "memory_recall", session_id: "session", uuid: "mr" }, |
| ... | ... | @@ -3545,6 +3548,21 @@ describe("ClaudeAdapterLive", () => { |
| 3545 | 3548 | ]) { |
| 3546 | 3549 | harness.query.emit(message as unknown as SDKMessage); |
| 3547 | 3550 | } |
| 3551 | // Safety model-fallback notices DO surface as a warning row. |
| 3552 | harness.query.emit({ |
| 3553 | type: "system", |
| 3554 | subtype: "model_refusal_fallback", |
| 3555 | trigger: "refusal", |
| 3556 | direction: "retry", |
| 3557 | original_model: "claude-fable-5", |
| 3558 | fallback_model: "claude-opus-4-8", |
| 3559 | request_id: "req_test", |
| 3560 | api_refusal_category: "cyber", |
| 3561 | api_refusal_explanation: null, |
| 3562 | content: "Safeguards flagged this message. Switched to Opus 4.8.", |
| 3563 | session_id: "session", |
| 3564 | uuid: "mrf", |
| 3565 | } as unknown as SDKMessage); |
| 3548 | 3566 | // High-priority notifications DO surface as a warning row. |
| 3549 | 3567 | harness.query.emit({ |
| 3550 | 3568 | type: "system", |
| ... | ... | @@ -3602,15 +3620,15 @@ describe("ClaudeAdapterLive", () => { |
| 3602 | 3620 | session_id: "session", |
| 3603 | 3621 | uuid: "retry", |
| 3604 | 3622 | } as unknown as SDKMessage); |
| 3605 | | yield* Effect.yieldNow; |
| 3606 | | yield* Effect.yieldNow; |
| 3623 | const runtimeEvents = Array.from(yield* Fiber.join(runtimeEventsFiber)); |
| 3607 | 3624 | |
| 3608 | 3625 | const warnings = runtimeEvents.filter((event) => event.type === "runtime.warning"); |
| 3609 | | // Exactly three warnings: the high-priority notification, the |
| 3626 | // Exactly four warnings: the fallback notice, high-priority notification, |
| 3610 | 3627 | // warning-level informational note, and the refusal. Nothing else. |
| 3611 | 3628 | assert.deepEqual( |
| 3612 | 3629 | warnings.map((event) => event.payload.message), |
| 3613 | 3630 | [ |
| 3631 | "Safeguards flagged this message. Switched to Opus 4.8.", |
| 3614 | 3632 | "context window nearly full", |
| 3615 | 3633 | "Stop hook prevented continuation", |
| 3616 | 3634 | "The request was declined by the API.", |
| ... | ... | @@ -3638,7 +3656,6 @@ describe("ClaudeAdapterLive", () => { |
| 3638 | 3656 | event.payload.reason.startsWith("api_retry:"), |
| 3639 | 3657 | ); |
| 3640 | 3658 | assert.equal(heartbeat?.type, "session.state.changed"); |
| 3641 | | runtimeEventsFiber.interruptUnsafe(); |
| 3642 | 3659 | }).pipe( |
| 3643 | 3660 | Effect.provideService(Random.Random, makeDeterministicRandomService()), |
| 3644 | 3661 | Effect.provide(harness.layer), |