# Migration Guide — FUI-2495 (AI Header & Orchestration)

This document lists every breaking change introduced by the `mw/FUI-2495-ai-header-and-orchestration` branch and shows exactly what to change in consumer code.

---

## 1. `ChatConfig` restructured into sub-namespaces

`ChatConfig` (from `@genesislcap/foundation-ai`) was a flat interface. It is now a three-field wrapper. The old fields have been split across `ui`, `agent`, and `suggestions`.

The old `ChatConfig` interface is renamed `ChatUiConfig` and is still exported for typing the `ui` field.

**Before**

```ts
import type { ChatConfig } from '@genesislcap/foundation-ai';

const chatConfig: ChatConfig = {
  showToolCalls: true,
  showThinkingSteps: false,
  allowDebugDownload: true,
  acceptedFiles: '.txt,.md',
  loadingDelay: 5,
  animations: { enabledAnimations: ['halo'] },
  maxToolIterations: 50,
};
```

**After**

```ts
import type { ChatConfig } from '@genesislcap/foundation-ai';

const chatConfig: ChatConfig = {
  ui: {
    showToolCalls: true,
    showThinkingSteps: false,
    allowDebugDownload: true,
    acceptedFiles: '.txt,.md',
    loadingDelay: 5,
    animations: { enabledAnimations: ['halo'] },
  },
  agent: {
    maxToolIterations: 50,
  },
};
```

**New fields available** (all optional):

| Namespace | Field | Description |
|---|---|---|
| `ui` | `showSplash` | Show `slot="splash"` content when no messages exist |
| `ui` | `showAgentSwitchIndicator` | Show a divider when the active specialist changes |
| `ui` | `showContextUsage` | Show context window usage in the settings panel |
| `agent` | `maxHandoffs` | Max agent-to-agent handoffs per user turn (default: 3) |
| `agent` | `classifierHistoryLength` | Recent messages passed to the classifier (default: 4) |
| `agent` | `classifierRetries` | Retries on classifier failure (default: 2) |
| `agent` | `maxFoldOperations` | Max fold open/close cycles before a guidance message is injected (default: 5) |
| `suggestions` | `behavior` | `'never'` \| `'initial'` \| `'always'` |
| `suggestions` | `count` | Number of suggestions to generate (default: 3) |
| `suggestions` | `prompt` | Custom prompt to steer suggestion generation |

---

## 2. `AgentConfig` is now a discriminated union

`AgentConfig` (from `@genesislcap/foundation-mf/ai-assistant`) was a single flat interface. It is now a union of `SpecialistAgentConfig` (requires `description`) and `FallbackAgentConfig` (requires `fallback: true`).

**Existing single-agent usage — no change required.** TypeScript will resolve the object as `SpecialistAgentConfig` automatically as long as `description` is present.

**Before (multi-agent)**

```ts
import type { AgentConfig } from '@genesislcap/foundation-mf/ai-assistant';

const agents: AgentConfig[] = [
  {
    name: 'Trades Agent',
    description: 'Handles trade queries and execution.',
    systemPrompt: '...',
    toolHandlers: { ... },
  },
  {
    name: 'General Agent',
    description: 'Handles everything else.',
    systemPrompt: '...',
  },
];
```

**After** — if you want a dedicated fallback agent, mark it explicitly. Specialist agents are unchanged.

```ts
import type { AgentConfig } from '@genesislcap/foundation-mf/ai-assistant';

const agents: AgentConfig[] = [
  {
    name: 'Trades Agent',
    description: 'Handles trade queries and execution.',  // still required for specialists
    systemPrompt: '...',
    toolHandlers: { ... },
  },
  {
    name: 'General Agent',
    fallback: true,   // replaces description for the catch-all agent
    systemPrompt: '...',
  },
];
```

**Rules:**
- A specialist must have `description` and must not have `fallback`.
- A fallback must have `fallback: true` and must not have `description`.
- Only one fallback is permitted per `agents` array.

---

## 3. `chat-popout` and `chat-popin` bus events no longer carry a state payload

`AiAssistantSerializedState` has been deleted. The `chat-popout` and `chat-popin` bus events now carry `undefined`. State is managed automatically by the Redux session store shared between the bubble and layout instances — no manual transfer is required.

**Before**

```ts
import { agenticActivityBus } from '@genesislcap/foundation-mf/ai-assistant';
import type { AiAssistantSerializedState } from '@genesislcap/foundation-mf/ai-assistant';

agenticActivityBus.subscribe('chat-popin', ({ state }: { state: AiAssistantSerializedState }) => {
  myAssistant.applyState(state);
});
```

**After**

```ts
import { agenticActivityBus } from '@genesislcap/foundation-mf/ai-assistant';

// No payload — state transfer is automatic.
agenticActivityBus.subscribe('chat-popin', () => {
  // React to the event if needed, but state is already synced.
});
```

Remove all imports of `AiAssistantSerializedState` — it no longer exists.

---

## 4. `applyState()` and `serializeState()` removed from `FoundationAiAssistant`

These methods were used to manually transfer state between the bubble and layout instances. The session store makes them redundant.

**Before**

```ts
const state = bubbleAssistant.serializeState();
layoutAssistant.applyState(state);
```

**After** — delete these call sites. The session store handles state continuity automatically when the component is connected/disconnected.

---

## 5. `ChatInteraction.resolved` changed from `boolean` to `InteractionResult<unknown>`

**Before**

```ts
if (this.resolved === true) {
  // show read-only state
}
```

**After** — use a truthy check. The value itself is now the result object, which widgets can also read.

```ts
if (this.resolved) {
  // show read-only state
  // optionally: use this.resolved.value for the result data
}
```

If your interaction component explicitly typed this field:

```ts
// Before
resolved?: boolean;

// After
import type { InteractionResult } from '@genesislcap/foundation-ai';
resolved?: InteractionResult<unknown>;
```

---

## 6. `ChatToolCall` changed from an interface to a union type

`ChatToolCall` is now `ChatToolCallBase | ChatToolCallUnknown`. Code that was written against the original interface will still compile, but exhaustive handling should use the new type guard.

**Before**

```ts
import type { ChatToolCall } from '@genesislcap/foundation-ai';

function handleToolCall(tc: ChatToolCall) {
  console.log(tc.name, tc.args);
}
```

**After** — use `isChatToolCallUnknown` before narrowing:

```ts
import { isChatToolCallUnknown } from '@genesislcap/foundation-ai';
import type { ChatToolCall } from '@genesislcap/foundation-ai';

function handleToolCall(tc: ChatToolCall) {
  if (isChatToolCallUnknown(tc)) {
    console.warn('Unknown tool call', tc.name, 'Available:', tc.availableTools);
    return;
  }
  console.log(tc.name, tc.args);
}
```

New optional fields on all `ChatToolCall` objects (not sent to the AI provider):

- `foldEvent?: 'open' | 'close'` — set when this call is a fold lifecycle event
- `foldPath?: string[]` — breadcrumb path of active folds when the call was made

---

## 7. `ChatRole` has a new `'system-event'` value

If you have an exhaustive `switch` or conditional chain on `ChatRole`, add a case for `'system-event'`.

**Before**

```ts
switch (message.role) {
  case 'user': ...
  case 'assistant': ...
  case 'system': ...
  case 'tool': ...
}
```

**After**

```ts
switch (message.role) {
  case 'user': ...
  case 'assistant': ...
  case 'system': ...
  case 'tool': ...
  case 'system-event': ...
}
```

---

## 8. `GeminiAIConfig.model` is now typed as `GeminiModelId`

The `model` field no longer accepts arbitrary strings. Only the values in `SUPPORTED_GEMINI_MODEL_IDS` are accepted at runtime and at compile time.

**Before**

```ts
const config: GeminiAIConfig = {
  providerType: 'gemini',
  model: 'gemini-2.0-flash',   // was accepted as string
};
```

**After**

```ts
import { SUPPORTED_GEMINI_MODEL_IDS } from '@genesislcap/foundation-ai';
import type { GeminiAIConfig } from '@genesislcap/foundation-ai';

const config: GeminiAIConfig = {
  providerType: 'gemini',
  model: 'gemini-2.5-flash',   // must be a GeminiModelId
};
```

Supported values: `'gemini-2.5-flash'` | `'gemini-2.5-flash-lite'`

If you were using `'gemini-2.0-flash'` or another deprecated model, migrate to `'gemini-2.5-flash'`.

---

## 9. `downloadHistory()` renamed to `getDebugLog()`

**Before**

```ts
assistant.downloadHistory();
```

**After**

```ts
assistant.getDebugLog();
```

---

## 10. `headerTitle` now defaults to `'Genesis Assistant'`

Previously `headerTitle` was `undefined` by default, so the header title area was empty unless explicitly set. It now defaults to `'Genesis Assistant'`.

If you were relying on the absence of a title (e.g. to hide a title element via CSS `:empty` or conditional rendering), set the attribute explicitly to an empty string or adjust your CSS.

**Before** — no attribute set, title area was empty.

**After** — set explicitly if you want a different value or no value:

```html
<!-- Custom title -->
<foundation-ai-assistant header-title="My Assistant"></foundation-ai-assistant>

<!-- No title -->
<foundation-ai-assistant header-title=""></foundation-ai-assistant>
```

---

## Quick reference checklist

- [ ] Move `ChatConfig` fields into `ui`, `agent`, and `suggestions` sub-objects
- [ ] Remove all imports of `AiAssistantSerializedState`
- [ ] Remove `chat-popin`/`chat-popout` state payload destructuring
- [ ] Remove calls to `applyState()` and `serializeState()`
- [ ] Update `ChatInteraction.resolved` checks from `=== true` to truthy
- [ ] Update `AgentConfig` arrays — add `fallback: true` to catch-all agents (remove `description` from them)
- [ ] Add `'system-event'` case to any exhaustive `ChatRole` switches
- [ ] Update `GeminiAIConfig.model` from `'gemini-2.0-flash'` to a supported `GeminiModelId`
- [ ] Rename `downloadHistory()` calls to `getDebugLog()`
- [ ] Audit `header-title` attribute usage if you relied on an empty default
