diff --git a/apps/console/src/app/api/agent-address/calendar-window/route.ts b/apps/console/src/app/api/agent-address/calendar-window/route.ts
new file mode 100644
index 00000000..46b5bac8
--- /dev/null
+++ b/apps/console/src/app/api/agent-address/calendar-window/route.ts
@@ -0,0 +1,27 @@
+// SOURCING: none. Route handler for Index calendar window overlap flags.
+
+import { NextResponse } from 'next/server';
+import { readLifeCalendarWindow } from '@/lib/server/agent-address-harness';
+import { resolveHarnessPrincipal } from '@/lib/server/harness-principal';
+
+export async function POST(request: Request) {
+ const body = (await request.json().catch(() => null)) as {
+ windowStart?: string;
+ windowEnd?: string;
+ events?: unknown;
+ } | null;
+ if (!body?.windowStart || !body.windowEnd || !body.events) {
+ return NextResponse.json({ error: 'window_and_events_required' }, { status: 400 });
+ }
+ const resolution = await resolveHarnessPrincipal();
+ if (!resolution.ok) return resolution.response;
+ const result = await readLifeCalendarWindow({
+ windowStart: body.windowStart,
+ windowEnd: body.windowEnd,
+ eventsJson: JSON.stringify(body.events),
+ });
+ if (!result.ok) {
+ return NextResponse.json({ error: result.error }, { status: result.status });
+ }
+ return NextResponse.json({ events: result.events });
+}
diff --git a/apps/console/src/components/agent-address/AgentAliasPane.tsx b/apps/console/src/components/agent-address/AgentAliasPane.tsx
index ddf46718..7f678da1 100644
--- a/apps/console/src/components/agent-address/AgentAliasPane.tsx
+++ b/apps/console/src/components/agent-address/AgentAliasPane.tsx
@@ -153,6 +153,11 @@ export function AgentAliasPane() {
Domain {state.domain}
+
+ Interrupt items appear in the Index urgent banner until 10DLC or
+ toll-free SMS registration is complete. The agent cannot text
+ interrupts yet.
+
{
+ const response = await fetch('/api/agent-address/calendar-window', {
+ method: 'POST',
+ headers: { 'content-type': 'application/json' },
+ cache: 'no-store',
+ body: JSON.stringify({
+ windowStart: '2026-08-18T00:00:00Z',
+ windowEnd: '2026-08-19T00:00:00Z',
+ events: DEFAULT_EVENTS,
+ }),
+ });
+ if (response.status === 404) return null;
+ if (!response.ok) {
+ throw new Error(`calendar-window ${response.status}`);
+ }
+ const data = (await response.json()) as { events: CalendarWindowRow[] };
+ return data.events;
+}
+
+export function LifeCalendarPane() {
+ const [state, setState] = useState
({ status: 'loading' });
+
+ const refresh = useCallback(async () => {
+ setState({ status: 'loading' });
+ try {
+ const events = await fetchWindow();
+ if (!events) {
+ setState({ status: 'unconfigured' });
+ return;
+ }
+ setState({ status: 'ready', events });
+ } catch (error) {
+ setState({
+ status: 'error',
+ message: error instanceof Error ? error.message : 'calendar load failed',
+ });
+ }
+ }, []);
+
+ useEffect(() => {
+ void refresh();
+ }, [refresh]);
+
+ return (
+
+ Today and tomorrow
+ {state.status === 'loading' ? Loading events…
: null}
+ {state.status === 'unconfigured' ? (
+
+ Connect CONSOLE_HARNESS_URL to render owned-inbox calendar events.
+
+ ) : null}
+ {state.status === 'error' ? (
+
+ {state.message}
+
+ ) : null}
+ {state.status === 'ready' ? (
+ state.events.length === 0 ? (
+
+ No events in this window.
+
+ ) : (
+
+ {state.events.map((row) => (
+
+ {row.title}
+
+ {row.start} → {row.end}
+
+ {row.overlaps ? (
+
+ overlaps
+
+ ) : null}
+
+ ))}
+
+ )
+ ) : null}
+
+ );
+}
diff --git a/apps/console/src/lib/server/agent-address-harness.ts b/apps/console/src/lib/server/agent-address-harness.ts
index a0926fec..36a826a0 100644
--- a/apps/console/src/lib/server/agent-address-harness.ts
+++ b/apps/console/src/lib/server/agent-address-harness.ts
@@ -67,3 +67,32 @@ export async function revokeAgentAlias(alias: string): Promise<
if (!result.ok) return { ok: false, status: result.status, error: result.error };
return { ok: true, alias: result.data.revokeAgentAlias as AliasBlock };
}
+
+export type CalendarWindowEvent = {
+ readonly externalId: string;
+ readonly title: string;
+ readonly start: string;
+ readonly end: string;
+ readonly overlaps: boolean;
+};
+
+export async function readLifeCalendarWindow(input: {
+ windowStart: string;
+ windowEnd: string;
+ eventsJson: string;
+}): Promise<
+ | { readonly ok: true; readonly events: CalendarWindowEvent[] }
+ | { readonly ok: false; readonly status: number; readonly error: string }
+> {
+ const result = await callHarnessGraphql(
+ `query LifeCalendarWindow($windowStart: String!, $windowEnd: String!, $eventsJson: String!) {
+ lifeCalendarWindow(windowStart: $windowStart, windowEnd: $windowEnd, eventsJson: $eventsJson) {
+ externalId title start end overlaps
+ }
+ }`,
+ input,
+ );
+ if (!result.ok) return { ok: false, status: result.status, error: result.error };
+ const events = (result.data.lifeCalendarWindow as CalendarWindowEvent[] | undefined) ?? [];
+ return { ok: true, events };
+}
diff --git a/apps/console/src/views/IndexRulesView.tsx b/apps/console/src/views/IndexRulesView.tsx
index 3fdec85d..de8704c3 100644
--- a/apps/console/src/views/IndexRulesView.tsx
+++ b/apps/console/src/views/IndexRulesView.tsx
@@ -23,6 +23,7 @@ import {
useFilingRules,
} from './filing/filing-client';
import { AgentAliasPane } from '@/components/agent-address/AgentAliasPane';
+import { LifeCalendarPane } from '@/components/agent-address/LifeCalendarPane';
import { YourDataEntry } from '@/components/console-plugin/YourDataEntry';
const PREDICATE_KINDS: ReadonlyArray<{
@@ -261,6 +262,7 @@ export function IndexRulesView({ host }: ViewRenderProps) {
{content}
+
);
}
diff --git a/apps/console/src/views/UrgentLaneView.tsx b/apps/console/src/views/UrgentLaneView.tsx
index 090f3162..931b7767 100644
--- a/apps/console/src/views/UrgentLaneView.tsx
+++ b/apps/console/src/views/UrgentLaneView.tsx
@@ -61,7 +61,9 @@ export function UrgentLaneView(_props: ViewRenderProps) {
Nothing needs you today. Everything that arrived is filed and will
- keep until you go looking for it.
+ keep until you go looking for it. Interrupt-by-SMS waits on 10DLC
+ or toll-free registration; until then interrupts are this Index
+ banner only.