Skip to content

Commit c272d50

Browse files
TylerBarnesMastra Code (openai/gpt-5.5)
andauthored
feat(memory): add provider-aware OM idle activation (#16663)
This adds `activateAfterIdle: 'auto'` for observational memory so idle activations can line up better with the actor model's prompt-cache behavior. The resolver uses the current provider/model plus provider options like OpenAI `promptCacheRetention`, and falls back to a short 5 minute TTL when it can't tell. ```ts memory: new Memory({ options: { observationalMemory: { model: 'openai/gpt-5.5', activateAfterIdle: 'auto', }, }, }) ``` This also updates MastraCode to use `auto`, adds a small idle counter above the input so it's easier to see when idle activation should happen, and combines back-to-back OM activation markers into one line. Verified with the focused memory/core/MastraCode tests, typecheck, prettier, and lint. With `"auto"`, Mastra chooses an idle activation TTL from the active model provider: | Provider | Auto TTL | | - | - | | Anthropic, OpenRouter, unknown providers, xAI | 5 minutes | | DeepSeek | 1 hour | | Google Gemini | 24 hours | | Groq | 2 hours | | OpenAI with `providerOptions.openai.promptCacheRetention: "24h"` | 1 hour (safer cause 24h means up to 24h) | | OpenAI with `providerOptions.openai.promptCacheRetention: "in_memory"` | 5 minutes | | OpenAI `gpt-4*`, `gpt-5`, `gpt-5-*`, `gpt-5.1*`, `gpt-5.2*`, `gpt-5.3*`, and `gpt-5.4*` | 5 minutes | | Other OpenAI models | 1 hour | to test this out I also added an idle time counter in mc <img width="511" height="169" alt="Screenshot 2026-05-15 at 2 12 46 PM" src="https://github.com/user-attachments/assets/9fb2b6f8-9451-40e1-ab5e-d083614dc664" /> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## ELI5 This PR makes observational memory's "wake-up after being idle" setting smart: set it to 'auto' and Mastra picks a timeout based on the active model/provider instead of always using 5 minutes. The TUI shows how long the session has been idle and collapses multiple memory-activation messages into one. ## Overview Introduce provider-aware idle activation for Observational Memory via a new activateAfterIdle: 'auto' option. When configured, a resolver computes a provider- and model-specific TTL (with sensible fallbacks) and the runtime uses that resolved TTL for activation checks and emitted activation markers. ## Key Changes ### TTL resolution & types - New resolver and mapping: packages/memory/src/processors/observational-memory/activation-ttl.ts - Provider/model-to-TTL mapping (high-level): - Anthropic, OpenRouter, xAI, unknown → 5 minutes - DeepSeek → 1 hour - Google Gemini → 24 hours - Groq → 2 hours - OpenAI: respects providerOptions.openai.promptCacheRetention when present (e.g., "24h" → conservative 1 hour, "in_memory" → 5 minutes); specific short-retention OpenAI model patterns (gpt-4*, gpt-5, gpt-5-*, gpt-5.1*–gpt-5.4*) → 5 minutes; other OpenAI models → 1 hour - New exported functions: resolveActivationTTL(...) and resolveAutoActivationTTL(...) - Types updated to accept 'auto' and carry resolved values: - packages/core/src/memory/types.ts - packages/memory/src/processors/observational-memory/types.ts (adds ResolvedActivationTTL) ### Observational Memory integration - parseActivationTTL preserves literal 'auto' and activation-time resolution uses resolveActivationTTL: - packages/memory/src/processors/observational-memory/observational-memory.ts - packages/memory/src/processors/observational-memory/reflector-runner.ts - When TTL-triggered activation occurs, activation markers emit the resolved numeric TTL so clients display the actual timeout. ### ProviderOptions flow (plumbing) - Ensure providerOptions are available and merged where needed so auto resolution can read provider behavior: - packages/core/src/agent/agent.ts, packages/core/src/agent/agent-legacy.ts - Durable execution: packages/core/src/agent/durable/* (types, serialization, LLM execution step) - LLM layer: MastraLLMVNext.getProviderOptions() (packages/core/src/llm/model/model.loop.ts) - Workflow step merging: packages/core/src/loop/workflows/agentic-execution/llm-execution-step.ts ### MastraCode UI & UX - Idle counter: IdleCounterComponent shows "X … idle" above input after 1 minute of inactivity and formatIdleDuration added (mastracode/src/tui/components/idle-counter.ts). - Idle timer lifecycle and seeding at startup: mastracode/src/tui/mastra-tui.ts; new state fields (idleCounter, idleStartedAt, lastRenderedMessageAt). - OM activation marker collapsing: consecutive observation activations are combined into one marker with aggregated tokens and activationCount (mastracode/src/tui/handlers/om.ts, mastracode/src/tui/components/om-marker.ts). - MastraCode now configures observationalMemory.activateAfterIdle: 'auto' (mastracode/src/agents/memory.ts). - Activation markers show resolved numeric TTL when available. ### Tests - TTL resolution tests: packages/memory/src/processors/observational-memory/__tests__/activation-ttl.test.ts - Observational memory API emits resolved TTL in markers: packages/memory/src/processors/observational-memory/__tests__/observational-memory-api.test.ts - Memory config serialization: packages/core/src/memory/memory-config.test.ts - ProviderOptions forwarding: packages/core/src/agent/__tests__/per-model-fallback-settings.test.ts - MastraCode UI tests: idle-counter.test.ts, om-marker.test.ts, om.test.ts, render-messages.test.ts ### Docs & release notes - Docs updated to document activateAfterIdle: 'auto' and provider-to-TTL mapping: - docs/src/content/en/docs/memory/observational-memory.mdx - docs/src/content/en/reference/memory/observational-memory.mdx (option type signatures updated) - Changesets: .changeset/brave-caches-observe.md and .changeset/quiet-walls-rest.md ## Notes for reviewers / behavior highlights - 'auto' is preserved in serialized configs; runtime resolves to a numeric TTL for activation checks and emitted markers. - Default fallback when provider/model is unknown is 5 minutes. - OpenAI handling prefers promptCacheRetention when provided via providerOptions and uses a conservative mapping. - This PR touches runtime resolution, types, durable serialization, provider-options plumbing, and MastraCode TUI rendering; tests were added across core, memory, and MastraCode. <!-- review_stack_entry_start --> [![Review Change Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/mastra-ai/mastra/pull/16663?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Mastra Code (openai/gpt-5.5) <noreply@mastra.ai>
1 parent 4bd4e8e commit c272d50

36 files changed

Lines changed: 759 additions & 57 deletions

.changeset/brave-caches-observe.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
'@mastra/core': minor
3+
'@mastra/memory': minor
4+
---
5+
6+
Added `activateAfterIdle: "auto"` for Observational Memory early activation.
7+
8+
Mastra can now choose an idle activation timeout from the active model provider's prompt cache behavior. OpenAI also respects `providerOptions.openai.promptCacheRetention` when available.
9+
10+
```ts
11+
const memory = new Memory({
12+
options: {
13+
observationalMemory: {
14+
model: 'google/gemini-2.5-flash',
15+
activateAfterIdle: 'auto',
16+
activateOnProviderChange: true,
17+
},
18+
},
19+
})
20+
```

.changeset/quiet-walls-rest.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'mastracode': patch
3+
---
4+
5+
Updated MastraCode to use provider-aware Observational Memory idle activation.
6+
7+
MastraCode now sets `activateAfterIdle: "auto"`, shows an idle-time counter above the input after one minute of inactivity, and combines back-to-back OM activation markers into a single line.

docs/src/content/en/docs/memory/observational-memory.mdx

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ const memory = new Memory({
104104
options: {
105105
observationalMemory: {
106106
model: 'google/gemini-2.5-flash',
107-
activateAfterIdle: '5m',
107+
activateAfterIdle: 'auto',
108108
activateOnProviderChange: true,
109109
},
110110
},
@@ -495,30 +495,43 @@ Reflection works similarly — the Reflector runs in the background when observa
495495
| `observation.bufferTokens` | `0.2` | How often to buffer. `0.2` means every 20% of `messageTokens` — with the default 30k threshold, that's roughly every 6k tokens. Can also be an absolute token count (e.g. `5000`). |
496496
| `observation.bufferActivation` | `0.8` | How aggressively to clear the message window on activation. `0.8` means remove enough messages to keep only 20% of `messageTokens` remaining. Lower values keep more message history. |
497497
| `observation.blockAfter` | `1.2` | Safety threshold as a multiplier of `messageTokens`. At `1.2`, synchronous observation is forced at 36k tokens (1.2 × 30k). Only matters if buffering can't keep up. |
498-
| `activateAfterIdle` | none | Forces buffered observations to activate after a period of inactivity, even before `observation.messageTokens` is reached. Accepts a numeric millisecond value such as `300_000`, or duration strings like `"5m"` or `"1hr"`. Set this to your prompt cache TTL if you want activation to happen before the next cold prompt. |
498+
| `activateAfterIdle` | none | Forces buffered observations to activate after a period of inactivity, even before `observation.messageTokens` is reached. Accepts a numeric millisecond value such as `300_000`, duration strings like `"5m"` or `"1hr"`, or `"auto"` for a provider-aware prompt cache TTL. |
499499
| `activateOnProviderChange` | `false` | Forces buffered observations to activate when the next step uses a different `provider/model` than the one that produced the latest assistant step. Use this when switching providers or models would invalidate prompt cache reuse. |
500500
| `reflection.bufferActivation` | `0.5` | When to start background reflection. `0.5` means reflection begins when observations reach 50% of the `observationTokens` threshold. |
501501
| `reflection.activateAfterIdle` | none | Opts buffered reflections into idle activation. Reflections don't inherit top-level `activateAfterIdle`. |
502502
| `reflection.activateOnProviderChange` | `false` | Opts buffered reflections into provider-change activation. Reflections don't inherit top-level `activateOnProviderChange`. |
503503
| `reflection.blockAfter` | `1.2` | Safety threshold for reflection, same logic as observation. |
504504

505-
If you're relying on prompt caching, set `activateAfterIdle` to match your cache TTL. That way, once a thread has been idle long enough for the cache to expire, the next request can activate buffered observations first and send a smaller compressed context window.
505+
If you're relying on prompt caching, set `activateAfterIdle` to `"auto"` or to a specific cache TTL. That way, once a thread has been idle long enough for the cache to expire, the next request can activate buffered observations first and send a smaller compressed context window.
506+
507+
With `"auto"`, Mastra chooses an idle activation TTL from the active model provider:
508+
509+
| Provider | Auto TTL |
510+
| - | - |
511+
| Anthropic, OpenRouter, unknown providers, xAI | 5 minutes |
512+
| DeepSeek | 1 hour |
513+
| Google Gemini | 24 hours |
514+
| Groq | 2 hours |
515+
| OpenAI with `providerOptions.openai.promptCacheRetention: "24h"` | 1 hour |
516+
| OpenAI with `providerOptions.openai.promptCacheRetention: "in_memory"` | 5 minutes |
517+
| OpenAI `gpt-4*`, `gpt-5`, `gpt-5-*`, `gpt-5.1*`, `gpt-5.2*`, `gpt-5.3*`, and `gpt-5.4*` | 5 minutes |
518+
| Other OpenAI models | 1 hour |
506519

507520
```typescript
508521
const memory = new Memory({
509522
options: {
510523
observationalMemory: {
511524
model: 'google/gemini-2.5-flash',
512-
activateAfterIdle: '5m',
525+
activateAfterIdle: 'auto',
513526
activateOnProviderChange: true,
514527
},
515528
},
516529
})
517530
```
518531

519-
With a 5-minute prompt cache TTL, this activates buffered observations after 5 minutes of inactivity so the next uncached prompt uses compressed observations instead of a larger raw message window. If you prefer, `300_000` works the same way.
532+
With `"auto"`, this activates buffered observations based on the active provider's prompt cache behavior so the next uncached prompt uses compressed observations instead of a larger raw message window. If you prefer a fixed 5-minute TTL, use `"5m"` or `300_000`.
520533

521-
Changing model or providers mid-thread will invalidate the prompt cache. If your agent can switch between providers or models mid-thread, `activateOnProviderChange: true` forces buffered observations to activate before the new provider runs. That avoids sending a large raw window to a provider that can't reuse the previous prompt cache.
534+
Changing models or providers mid-thread will invalidate the prompt cache. If your agent can switch between providers or models mid-thread, `activateOnProviderChange: true` forces buffered observations to activate before the new provider runs. That avoids sending a large raw window to a provider that can't reuse the previous prompt cache.
522535

523536
### Disabling
524537

docs/src/content/en/reference/memory/observational-memory.mdx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,9 @@ OM performs thresholding with fast local token estimation. Text uses `tokenx`, a
6666
},
6767
{
6868
name: 'activateAfterIdle',
69-
type: 'number | string | false',
69+
type: 'number | string | false | "auto"',
7070
description:
71-
'Time before buffered observations are forced to activate after inactivity, even before `observation.messageTokens` is reached. Accepts a numeric millisecond value such as `300_000`, duration strings like `"5m"` or `"1hr"`, or `false` to disable inherited observation idle activation. Reflections do not inherit this setting. Use `reflection.activateAfterIdle` to opt reflections into idle activation.',
71+
'Time before buffered observations are forced to activate after inactivity, even before `observation.messageTokens` is reached. Accepts a numeric millisecond value such as `300_000`, duration strings like `"5m"` or `"1hr"`, `"auto"` for a provider-aware prompt cache TTL, or `false` to disable inherited observation idle activation. Reflections do not inherit this setting. Use `reflection.activateAfterIdle` to opt reflections into idle activation.',
7272
isOptional: true,
7373
},
7474
{
@@ -204,9 +204,9 @@ OM performs thresholding with fast local token estimation. Text uses `tokenx`, a
204204
},
205205
{
206206
name: 'activateAfterIdle',
207-
type: 'number | string | false',
207+
type: 'number | string | false | "auto"',
208208
description:
209-
'Time before buffered observations are forced to activate after inactivity. Accepts milliseconds, a duration string, or `false`. If unset, the top-level `activateAfterIdle` value is used for observations. Set `false` to disable the top-level idle setting for observations.',
209+
'Time before buffered observations are forced to activate after inactivity. Accepts milliseconds, a duration string, `"auto"` for a provider-aware prompt cache TTL, or `false`. If unset, the top-level `activateAfterIdle` value is used for observations. Set `false` to disable the top-level idle setting for observations.',
210210
isOptional: true,
211211
},
212212
{
@@ -305,9 +305,9 @@ OM performs thresholding with fast local token estimation. Text uses `tokenx`, a
305305
},
306306
{
307307
name: 'activateAfterIdle',
308-
type: 'number | string | false',
308+
type: 'number | string | false | "auto"',
309309
description:
310-
'Time before buffered reflections are forced to activate after inactivity. Accepts milliseconds, a duration string, or `false`. Reflections do not inherit top-level `activateAfterIdle`; set this explicitly to opt reflections into idle activation.',
310+
'Time before buffered reflections are forced to activate after inactivity. Accepts milliseconds, a duration string, `"auto"` for a provider-aware prompt cache TTL, or `false`. Reflections do not inherit top-level `activateAfterIdle`; set this explicitly to opt reflections into idle activation.',
311311
isOptional: true,
312312
},
313313
{

mastracode/src/agents/memory.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ export function getDynamicMemory(storage: MastraCompositeStore, vector?: MastraV
112112
temporalMarkers: true,
113113
retrieval: vector ? { vector: true } : true,
114114
scope: omScope,
115-
activateAfterIdle: '5m',
115+
activateAfterIdle: 'auto',
116116
activateOnProviderChange: true,
117117
observation: {
118118
bufferTokens: isResourceScope ? false : 1 / 5,
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { describe, expect, it } from 'vitest';
2+
import { IdleCounterComponent, formatIdleDuration } from '../idle-counter.js';
3+
4+
describe('formatIdleDuration', () => {
5+
it('formats idle time from whole minutes up through larger units', () => {
6+
expect(formatIdleDuration(1)).toBe('1 minute');
7+
expect(formatIdleDuration(15)).toBe('15 minutes');
8+
expect(formatIdleDuration(60)).toBe('1 hour');
9+
expect(formatIdleDuration(96)).toBe('1 hour 36 minutes');
10+
expect(formatIdleDuration(24 * 60)).toBe('1 day');
11+
expect(formatIdleDuration(31 * 24 * 60)).toBe('1 month 1 day');
12+
expect(formatIdleDuration(365 * 24 * 60)).toBe('1 year');
13+
});
14+
});
15+
16+
describe('IdleCounterComponent', () => {
17+
it('stays hidden until one minute idle, then renders like a temporal-gap marker', () => {
18+
const component = new IdleCounterComponent();
19+
20+
expect(component.render(80)).toEqual([]);
21+
22+
component.setIdleStartedAt(0, 59_999);
23+
expect(component.render(80)).toEqual([]);
24+
25+
component.update(60_000);
26+
expect(component.render(80).join('\n')).toContain('1 minute idle');
27+
expect(component.render(80).join('\n')).not.toContain('⏳');
28+
29+
component.update(96 * 60_000);
30+
expect(component.render(80).join('\n')).toContain('1 hour 36 minutes idle');
31+
32+
component.setIdleStartedAt(undefined);
33+
expect(component.render(80)).toEqual([]);
34+
});
35+
});

mastracode/src/tui/components/__tests__/om-marker.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,20 @@ describe('OMMarkerComponent activation rendering', () => {
4646
expect(activationText).not.toContain('TTL');
4747
});
4848

49+
it('renders combined observation activation counts', () => {
50+
const activationMarker = new OMMarkerComponent({
51+
type: 'om_activation',
52+
operationType: 'observation',
53+
tokensActivated: 9300,
54+
observationTokens: 525,
55+
activationCount: 2,
56+
});
57+
58+
const activationText = stripAnsi(activationMarker.render(120).join('\n'));
59+
60+
expect(activationText).toContain('✓ Activated 2 observations: -9.3k msg tokens, +0.5k obs tokens');
61+
});
62+
4963
it('renders provider-change activation as a separate muted line', () => {
5064
const providerChangeMarker = new OMMarkerComponent({
5165
type: 'om_activation_provider_change',
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/**
2+
* Live idle-time indicator shown above the user input after an agent run completes.
3+
*/
4+
5+
import { Container, Text } from '@mariozechner/pi-tui';
6+
import { BOX_INDENT, theme } from '../theme.js';
7+
8+
const MINUTE_MS = 60_000;
9+
const HOUR_MINUTES = 60;
10+
const DAY_MINUTES = HOUR_MINUTES * 24;
11+
const MONTH_MINUTES = DAY_MINUTES * 30;
12+
const YEAR_MINUTES = DAY_MINUTES * 365;
13+
14+
export class IdleCounterComponent extends Container {
15+
private idleStartedAt?: number;
16+
private textChild: Text;
17+
18+
constructor() {
19+
super();
20+
this.textChild = new Text('', BOX_INDENT, 0);
21+
this.addChild(this.textChild);
22+
}
23+
24+
setIdleStartedAt(idleStartedAt: number | undefined, now = Date.now()): void {
25+
this.idleStartedAt = idleStartedAt;
26+
this.update(now);
27+
}
28+
29+
update(now = Date.now()): void {
30+
if (this.idleStartedAt === undefined) {
31+
this.textChild.setText('');
32+
return;
33+
}
34+
35+
const idleMinutes = Math.floor((now - this.idleStartedAt) / MINUTE_MS);
36+
if (idleMinutes < 1) {
37+
this.textChild.setText('');
38+
return;
39+
}
40+
41+
this.textChild.setText(theme.fg('dim', ` ${formatIdleDuration(idleMinutes)} idle`));
42+
}
43+
44+
render(width: number): string[] {
45+
if (this.idleStartedAt === undefined || this.textChild.render(width).join('').length === 0) {
46+
return [];
47+
}
48+
49+
return super.render(width);
50+
}
51+
}
52+
53+
export function formatIdleDuration(totalMinutes: number): string {
54+
const minutes = Math.max(1, Math.floor(totalMinutes));
55+
const units = [
56+
{ name: 'year', minutes: YEAR_MINUTES },
57+
{ name: 'month', minutes: MONTH_MINUTES },
58+
{ name: 'day', minutes: DAY_MINUTES },
59+
{ name: 'hour', minutes: HOUR_MINUTES },
60+
{ name: 'minute', minutes: 1 },
61+
];
62+
63+
let remaining = minutes;
64+
const parts: string[] = [];
65+
66+
for (const unit of units) {
67+
const value = Math.floor(remaining / unit.minutes);
68+
if (value === 0) continue;
69+
70+
parts.push(formatUnit(value, unit.name));
71+
remaining %= unit.minutes;
72+
73+
if (parts.length === 2) break;
74+
}
75+
76+
return parts.join(' ');
77+
}
78+
79+
function formatUnit(value: number, unit: string): string {
80+
return `${value} ${unit}${value === 1 ? '' : 's'}`;
81+
}

mastracode/src/tui/components/om-marker.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ export type OMMarkerData =
7070
operationType: 'observation' | 'reflection';
7171
tokensActivated: number;
7272
observationTokens: number;
73+
activationCount?: number;
7374
}
7475
| {
7576
type: 'om_activation_ttl';
@@ -165,7 +166,9 @@ function formatMarker(data: OMMarkerData): string {
165166
}
166167
const msgTokens = formatTokens(data.tokensActivated);
167168
const obsTokens = formatTokens(data.observationTokens);
168-
return theme.fg('success', ` ✓ Activated observations: -${msgTokens} msg tokens, +${obsTokens} obs tokens`);
169+
const label =
170+
data.activationCount && data.activationCount > 1 ? `${data.activationCount} observations` : 'observations';
171+
return theme.fg('success', ` ✓ Activated ${label}: -${msgTokens} msg tokens, +${obsTokens} obs tokens`);
169172
}
170173
case 'om_activation_ttl': {
171174
return theme.fg(
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { Container } from '@mariozechner/pi-tui';
2+
import stripAnsi from 'strip-ansi';
3+
import { describe, expect, it, vi } from 'vitest';
4+
5+
import type { TUIState } from '../../state.js';
6+
import { handleOMActivation } from '../om.js';
7+
import type { EventHandlerContext } from '../types.js';
8+
9+
function createCtx() {
10+
const state = {
11+
chatContainer: new Container(),
12+
ui: { requestRender: vi.fn() },
13+
} as unknown as TUIState;
14+
15+
const ctx = { state } as EventHandlerContext;
16+
17+
return { ctx, state };
18+
}
19+
20+
describe('handleOMActivation', () => {
21+
it('combines consecutive observation activation markers into one line', () => {
22+
const { ctx, state } = createCtx();
23+
24+
handleOMActivation(ctx, 'observation', 7_300, 400);
25+
handleOMActivation(ctx, 'observation', 2_000, 125);
26+
27+
expect(state.chatContainer.children).toHaveLength(1);
28+
const text = stripAnsi(state.chatContainer.render(120).join('\n'));
29+
expect(text).toContain('Activated 2 observations: -9.3k msg tokens, +0.5k obs tokens');
30+
});
31+
32+
it('does not combine activations separated by another marker', () => {
33+
const { ctx, state } = createCtx();
34+
35+
handleOMActivation(ctx, 'observation', 7_300, 400);
36+
state.chatContainer.addChild(new Container());
37+
handleOMActivation(ctx, 'observation', 2_000, 125);
38+
39+
expect(state.chatContainer.children).toHaveLength(3);
40+
});
41+
});

0 commit comments

Comments
 (0)