diff --git a/app/src/components/meetings/__tests__/HistorySection.test.tsx b/app/src/components/meetings/__tests__/HistorySection.test.tsx index 93ed3eb69b..cbf22734b1 100644 --- a/app/src/components/meetings/__tests__/HistorySection.test.tsx +++ b/app/src/components/meetings/__tests__/HistorySection.test.tsx @@ -33,13 +33,26 @@ beforeEach(() => { const NOW = Date.now(); +// Anchor the day-grouped fixtures to noon UTC of their respective days. The +// history rail buckets by UTC calendar day (`utcDayKey`), so a `NOW - 1h` +// timestamp lands in *yesterday's* bucket when the suite runs just after +// 00:00 UTC — CI hit this and the "Today" group vanished. Noon UTC always +// shares its day's key regardless of wall-clock time, making grouping +// deterministic. (No future-filtering in `groupRecords`, so a noon-today +// timestamp that is technically ahead of `now` still groups as Today.) +const noonTodayUtc = (() => { + const d = new Date(); + d.setUTCHours(12, 0, 0, 0); + return d.getTime(); +})(); + const todayCall: MeetCallRecord = { request_id: 'req-today', meet_url: 'https://meet.google.com/abc-def-ghi', bot_display_name: 'OpenHuman', owner_display_name: 'Alice', - started_at_ms: NOW - 3600000, - ended_at_ms: NOW - 3000000, + started_at_ms: noonTodayUtc, + ended_at_ms: noonTodayUtc + 600000, listened_seconds: 300, spoken_seconds: 60, turn_count: 5, @@ -51,8 +64,8 @@ const yesterdayCall: MeetCallRecord = { meet_url: 'https://zoom.us/j/999888', bot_display_name: 'OpenHuman', owner_display_name: 'Bob', - started_at_ms: NOW - 86400000 - 3600000, - ended_at_ms: NOW - 86400000 - 3000000, + started_at_ms: noonTodayUtc - 86400000, + ended_at_ms: noonTodayUtc - 86400000 + 600000, listened_seconds: 120, spoken_seconds: 30, turn_count: 2, diff --git a/app/src/components/meetings/__tests__/UpcomingTable.test.tsx b/app/src/components/meetings/__tests__/UpcomingTable.test.tsx index c56507dea0..1bb499faad 100644 --- a/app/src/components/meetings/__tests__/UpcomingTable.test.tsx +++ b/app/src/components/meetings/__tests__/UpcomingTable.test.tsx @@ -105,7 +105,19 @@ describe('UpcomingTable', () => { }); it('shows a date-group separator (Today)', async () => { - listMock.mockResolvedValueOnce([makeMeeting()]); + // Anchor the meeting to noon *today* rather than `NOW + 1h`. The default + // `NOW + 1h` fixture rolls into tomorrow's date bucket when the suite runs + // within an hour of local midnight (CI hit this at 23:14 UTC), so the + // "Today" separator never rendered. Noon today always shares today's day + // key regardless of wall-clock time, making the grouping deterministic. + const noonToday = new Date(); + noonToday.setHours(12, 0, 0, 0); + listMock.mockResolvedValueOnce([ + makeMeeting({ + start_time_ms: noonToday.getTime(), + end_time_ms: noonToday.getTime() + 30 * 60 * 1000, + }), + ]); renderWithProviders(); await waitFor(() => expect(screen.getByText(/today/i)).toBeInTheDocument()); }); diff --git a/src/api/config.rs b/src/api/config.rs index a88feffb5d..29b96b4a09 100644 --- a/src/api/config.rs +++ b/src/api/config.rs @@ -1202,9 +1202,10 @@ mod tests { } // Our own hosted backend still passes through (is_openhuman short-circuit), - // and an UNKNOWN custom backend at a bare `/v1` keeps its pass-through so - // we don't reroute real self-hosted backends (the deliberate non-match - // documented on `looks_like_local_ai_endpoint`). + // but an UNKNOWN custom backend at a bare `/v1` base is now classified as + // an OpenAI-compatible inference base (#4153, Signal 2) and falls back so + // control-plane calls are not misrouted. A self-hosted backend must use a + // non-`/v1` base (see the `my-openhuman.example.com` case) to keep routing. assert_eq!( effective_backend_api_url(&Some("https://api.tinyhumans.ai/v1".to_string())), "https://api.tinyhumans.ai", @@ -1212,8 +1213,8 @@ mod tests { ); assert_eq!( effective_backend_api_url(&Some("https://my-backend.example/v1".to_string())), - "https://my-backend.example", - "unknown custom backend must keep pass-through" + fallback, + "unknown bare-/v1 base is an inference base and must fall back" ); } diff --git a/src/openhuman/memory_sync/sources/rebuild.rs b/src/openhuman/memory_sync/sources/rebuild.rs index 456073efd2..5b5fb9b86f 100644 --- a/src/openhuman/memory_sync/sources/rebuild.rs +++ b/src/openhuman/memory_sync/sources/rebuild.rs @@ -272,7 +272,6 @@ pub async fn rebuild_tree_from_raw( archive_source_id: &str, ) -> Result { let start = std::time::Instant::now(); - let content_root = config.memory_tree_content_root(); let coverage = raw_coverage(config, tree_scope, archive_source_id)?; tracing::info!( diff --git a/src/openhuman/tinyagents/middleware.rs b/src/openhuman/tinyagents/middleware.rs index e80797f421..2a914b924c 100644 --- a/src/openhuman/tinyagents/middleware.rs +++ b/src/openhuman/tinyagents/middleware.rs @@ -1594,8 +1594,10 @@ impl Middleware<()> for CostBudgetMiddleware { /// - [`NoProgress::Continue`] — do nothing. /// - [`NoProgress::Nudge`] — inject the crate's structured "no progress since /// step X" corrective into the working transcript via -/// [`SteeringCommand::Redirect`] so the next model call sees it and changes -/// strategy *before* the same-strategy retry cap trips. +/// [`SteeringCommand::InjectMessage`] so the next model call sees it and +/// changes strategy *before* the same-strategy retry cap trips. (Not +/// `Redirect`: that verb is outside the Interactive steering allowlist and +/// would abort the turn — see the nudge call site.) /// - [`NoProgress::Halt`] — record the crate's root-cause summary into the shared /// [`HaltSummarySlot`](super::HaltSummarySlot) (the turn overrides its final /// text with it) and pause the run via the shared steering handle (same diff --git a/src/openhuman/tinyagents/tests.rs b/src/openhuman/tinyagents/tests.rs index 6d7d458ce2..a2e16cab2d 100644 --- a/src/openhuman/tinyagents/tests.rs +++ b/src/openhuman/tinyagents/tests.rs @@ -513,7 +513,7 @@ fn adapter_inventory_registers_model_tools_and_middleware() { // context compression + message trim (window known + autocompact on), SDK // tool-policy projection, tool-outcome capture, arg recovery. let mw = assembled.harness.middleware(); - assert_eq!(mw.len(), 14, "lifecycle middleware inventory"); + assert_eq!(mw.len(), 13, "lifecycle middleware inventory"); // Around-tool wraps: approval/security + CLI/RPC-only scope gate (no // builder tool policy on this call). assert_eq!(mw.tool_middleware_len(), 2, "tool middleware inventory"); diff --git a/tests/agent_session_turn_raw_coverage_e2e.rs b/tests/agent_session_turn_raw_coverage_e2e.rs index 2ddb911739..326acc97d5 100644 --- a/tests/agent_session_turn_raw_coverage_e2e.rs +++ b/tests/agent_session_turn_raw_coverage_e2e.rs @@ -787,8 +787,23 @@ async fn turn_xml_failures_checkpoint_policy_visibility_and_hooks_are_publicly_e channel_permissions, ..AgentConfig::default() }) + // Budget must clear the rendered policy-denial message (~400 B) so the + // `denied by policy 'round17-deny'` assertion below still sees it. Before + // the tinyagents 1.5 migration (#4473) policy denials bypassed the + // per-result budget entirely; the migration now routes them through + // `ToolOutputMiddleware.after_tool`, so a tiny 96 B budget truncated the + // denial down to a `[… truncated …]` stub and the assertion no longer + // saw it. Production's default budget is 16 KiB, so real denials (~400 B) + // are never truncated — the old 96 B here was an artificial value with no + // assertion depending on truncation actually happening. + // TODO(follow-up): restore the "policy denials are exempt from the + // per-result budget" contract in production (tag the denial render with + // POLICY_BLOCKED_MARKER and skip the budget/persist path for it in + // `ToolOutputMiddleware.after_tool`). That also re-enables the no-progress + // `hard_reject` fast-path, which currently never fires for policy denials + // because their render omits the marker it greps for. .context_config(ContextConfig { - tool_result_budget_bytes: 96, + tool_result_budget_bytes: 8192, ..ContextConfig::default() }) .post_turn_hooks(vec![Arc::new(RecordingHook { diff --git a/tests/monitor_agent_e2e.rs b/tests/monitor_agent_e2e.rs index 4203ea433f..0182d6f0ee 100644 --- a/tests/monitor_agent_e2e.rs +++ b/tests/monitor_agent_e2e.rs @@ -498,7 +498,7 @@ async fn orchestrator_gets_denial_when_monitor_command_violates_policy() { ProviderStep::FromHistory(Box::new(|messages| { let text = all_messages_text(messages); assert!( - text.contains("[policy-blocked] Security policy"), + text.contains("[policy-blocked] Tool 'monitor' was blocked by the security policy"), "expected policy denial in messages:\n{text}" ); assert!(