@tomtom-org/maps-sdk-plugin-agent-toolkit 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -23,6 +23,7 @@ import { Position } from 'geojson';
23
23
  import { PrepareStepFunction } from 'ai';
24
24
  import { ReachableRangeBudget } from '../../../../services/dist';
25
25
  import { Routes } from '../../../../core/dist';
26
+ import { Routes as Routes_2 } from '../../../../../core/dist';
26
27
  import { RoutingModule } from '../../../../map/dist';
27
28
  import { TomTomMap } from '../../../../map/dist';
28
29
  import { TomTomMap as TomTomMap_2 } from '../../map/dist';
@@ -60,14 +61,167 @@ declare type AddBYODEntryOptions = {
60
61
  explicitId?: string;
61
62
  };
62
63
 
64
+ /**
65
+ * Session-level registry of EVERY analysis — the single source of truth that replaces the per-entry
66
+ * `_analyses` stores. Holds one {@link AnalysisRecord} per analysis (keyed by `analysisId`), each with
67
+ * its own bounded result history. Entries carry no analysis state; per-entry views call
68
+ * {@link getAnalysesForEntry}. Held on `ToolState` as `state.analyses`.
69
+ *
70
+ * Pure storage + lifecycle. The one recurrence engine lives elsewhere: the standing sweep in
71
+ * `tools/state/analyses-runtime.ts` (reads {@link sweepCandidates}), which replays both `sandbox` and
72
+ * `deterministic` records. `_wired` is a one-shot guard the sweep reads so it subscribes to slice
73
+ * events once.
74
+ *
75
+ * @group Agent Toolkit
76
+ */
77
+ declare class Analyses {
78
+ private readonly _records;
79
+ private _wired;
80
+ /** Subscribe to {@link AnalysesEvents} — fired whenever a result is attached or recomputed. */
81
+ readonly events: StateEvents<AnalysesEvents>;
82
+ /** True once the tools-layer sweep has subscribed to slice change events. Set via {@link markWired}. */
83
+ get wired(): boolean;
84
+ /** Mark the session as wired so {@link wired} guards a repeat subscription. */
85
+ markWired(): void;
86
+ /**
87
+ * Insert or replace a record's metadata + lifecycle (NOT its result — use {@link attachResult} for
88
+ * that). Re-registering an existing `analysisId` preserves its `enabled` flag and result history,
89
+ * UNLESS the sandbox `code` changed — in which case the history is dropped so the new recipe never
90
+ * inherits the old `previous`.
91
+ */
92
+ register(input: AnalysisRegistration): void;
93
+ /**
94
+ * Append a result to a record's timeline, evicting the oldest when the per-analysis cap is hit. A
95
+ * same-timestamp re-run (an id-stability re-read of an unchanged snapshot) overwrites the last entry
96
+ * rather than duplicating it. No-op (no emit) when the `analysisId` is unknown. Emits `analysis-change`.
97
+ */
98
+ attachResult(analysisId: string, result: EntryAnalysis): void;
99
+ /**
100
+ * Latest result of every analysis attached to `entryId`, in registration order. The on-demand
101
+ * replacement for the old per-entry `_analyses.results`. Linear scan over records — fine at session
102
+ * scale (one record per distinct analyseData name+sources, plus one per clustered incidents entry).
103
+ */
104
+ getAnalysesForEntry(entryId: string, type?: AnalysisType): readonly EntryAnalysis[];
105
+ /**
106
+ * Every analysis record, in registration order — the combined, cross-entry view. Pass a
107
+ * {@link AnalysisType} to restrict to one category (`'analysis'` for the operator-facing set,
108
+ * `'tracker-rule'` for tracker verdicts); omit it for every record. Read `record.history.at(-1)`
109
+ * for each one's latest result.
110
+ */
111
+ all(type?: AnalysisType): readonly AnalysisRecord[];
112
+ /** Full result timeline for one analysis, oldest first. Empty when unknown. */
113
+ history(analysisId: string): readonly EntryAnalysis[];
114
+ /** Latest result data threaded into the next replay as `previous`, or undefined. */
115
+ lastResult(analysisId: string): unknown;
116
+ get(analysisId: string): AnalysisRecord | undefined;
117
+ /** Flip an analysis on/off. Returns false when the id is unknown. */
118
+ setEnabled(analysisId: string, enabled: boolean): boolean;
119
+ /** Every enabled record, in registration order — the standing sweep's replay candidates. */
120
+ sweepCandidates(): AnalysisRecord[];
121
+ remove(analysisId: string): void;
122
+ /**
123
+ * Remove every record sharing a `name`, across all source entries. Mirrors the old per-entry
124
+ * `unregister(name)` which dropped a named analysis wherever it was attached.
125
+ */
126
+ removeByName(name: string): void;
127
+ /**
128
+ * Drop every record whose source entries are ALL gone — the single GC path now that entries no
129
+ * longer hold their analyses. The sweep wiring calls this on each `entries-change` with the live
130
+ * source-entry ids, so a removed entry's records are collected on the next change. A record over
131
+ * `[A, B]` survives while either is live (its `affectedEntryIds` keeps the dead id, which is
132
+ * harmless — nothing queries it).
133
+ */
134
+ pruneToLiveEntries(liveIds: ReadonlySet<string>): void;
135
+ /** Drop every record. Walked automatically by `destroyState` (duck-typed `reset`). */
136
+ reset(): void;
137
+ }
138
+
139
+ /** Events emitted by {@link Analyses}. One event for "an analysis result was attached or recomputed". */
140
+ export declare type AnalysesEvents = {
141
+ /**
142
+ * An analysis result landed (one-shot attach or a recompute). Carries the `analysisId` and the ids
143
+ * of every entry it touches — a per-entry consumer filters with `affectedEntryIds.includes(id)`.
144
+ */
145
+ 'analysis-change': {
146
+ analysisId: string;
147
+ affectedEntryIds: readonly string[];
148
+ };
149
+ };
150
+
151
+ /**
152
+ * The metadata + lifecycle fields shared by {@link AnalysisRecord} and its {@link AnalysisRegistration}
153
+ * input — everything except `enabled` (defaulted on register) and the runtime-built `history`. The
154
+ * single source of truth for an analysis's declared shape, so the registry and its register input never
155
+ * drift.
156
+ *
157
+ * @group Agent Toolkit
158
+ */
159
+ declare type AnalysisBase = {
160
+ /** Stable handle minted by {@link makeAnalysisId} (name + sorted source ids). */
161
+ analysisId: string;
162
+ name: string;
163
+ description?: string;
164
+ outputFormat: AnalysisOutputFormat;
165
+ /** What this record is for — `'analysis'` (default) or `'tracker-rule'`. See {@link AnalysisType}. */
166
+ type: AnalysisType;
167
+ /** Sandbox code replayed by the standing sweep. */
168
+ code?: string;
169
+ /** Original `analyseData` input descriptor (`MultiInputIds`); opaque here. */
170
+ inputs?: unknown;
171
+ /** Ids of every entry this analysis is attached to. The per-entry lookup key. */
172
+ affectedEntryIds: readonly string[];
173
+ };
174
+
63
175
  declare type AnalysisOutputFormat = 'json' | 'chart';
64
176
 
177
+ /**
178
+ * One analysis tracked by the session-level {@link Analyses} registry — the single home for every
179
+ * `analyseData` analysis, one-shot or monitored, across every entry kind. The shared {@link AnalysisBase}
180
+ * plus the registry-owned `enabled` flag and the runtime-built `history`. `affectedEntryIds` is the
181
+ * per-entry lookup key ({@link Analyses.getAnalysesForEntry}); `enabled` means "recomputes on source
182
+ * change". `history` is the bounded result timeline (replaces the old per-entry store); the latest is
183
+ * the current result and is threaded back into the sandbox code as `previous`.
184
+ *
185
+ * @group Agent Toolkit
186
+ */
187
+ export declare type AnalysisRecord = AnalysisBase & {
188
+ /** Whether the standing sweep recomputes it when its source entry changes. */
189
+ enabled: boolean;
190
+ /** Bounded result timeline, oldest first (see {@link MAX_HISTORY_PER_ANALYSIS}). */
191
+ history: EntryAnalysis[];
192
+ };
193
+
194
+ /**
195
+ * {@link Analyses.register} input — the shared {@link AnalysisBase} plus an optional `enabled`
196
+ * (defaulted on register). No `history`; the registry builds that as results land.
197
+ *
198
+ * @group Agent Toolkit
199
+ */
200
+ declare type AnalysisRegistration = Omit<AnalysisBase, 'type'> & {
201
+ type?: AnalysisType;
202
+ enabled?: boolean;
203
+ };
204
+
205
+ /**
206
+ * What an analysis record is FOR — its purpose, not its execution kind (every record is sandbox code).
207
+ * - `'analysis'`: an operator-facing `analyseData` result (the default), surfaced in the Analyses tab
208
+ * and per-entry detail.
209
+ * - `'tracker-rule'`: a tracker rule's verdict. Recomputes, GC's, and reads by id exactly like an
210
+ * analysis, but is its own enumerable category so the tracker can list its rules and operator views
211
+ * can exclude them. Pass it to {@link Analyses.all} / {@link Analyses.getAnalysesForEntry} to filter.
212
+ *
213
+ * @group Agent Toolkit
214
+ */
215
+ declare type AnalysisType = 'analysis' | 'tracker-rule';
216
+
65
217
  /**
66
218
  * The base system prompt that teaches the LLM how to use the agent toolkit tools.
67
219
  *
68
220
  * @remarks
69
- * Export this constant so consumers can reference or extend it when providing
70
- * a custom system prompt via `MapAgentOptions.systemPrompt`.
221
+ * Equivalent to `composeSystemPrompt()` with no overrides. Export this constant
222
+ * so consumers can reference or extend it when providing a custom system prompt
223
+ * via `MapAgentOptions.systemPrompt`. To replace just one section, prefer
224
+ * {@link composeSystemPrompt} instead of rebuilding the whole string.
71
225
  *
72
226
  * @example
73
227
  * ```typescript
@@ -81,7 +235,7 @@ declare type AnalysisOutputFormat = 'json' | 'chart';
81
235
  *
82
236
  * @group Agent Toolkit
83
237
  */
84
- export declare const BASE_SYSTEM_PROMPT = "You are a helpful map assistant with access to a TomTom interactive map and location services.\nIf a request is completely unrelated to geospatial topics, gracefully decline and steer the user back to map-related tasks you can help with.\n\nCOORDINATE ORDER:\n- All coordinates are [longitude, latitude] (GeoJSON standard)\n- All bounding boxes are [minLng, minLat, maxLng, maxLat]\n\nTOOL EXECUTION:\n- Call context-independent tools in the same step (parallel execution).\n- Many service tools have a show or showOnMap parameter \u2014 use it to display results on the map in one step instead of calling a separate show tool afterward.\n- Use standalone updatePlacesDisplay / updateRoutesDisplay / updateWaypointsDisplay only when displaying results already in context from a previous step.\n\nSESSION STATE & ENTRIES:\n- Seven append-only histories live in the session: `places`, `routes`, `ranges`, `customGeometries`, `trafficAreaAnalytics`, `trafficIncidents`, `byod`. Each tool call that produces results stores a new ENTRY (id like `places-3`, `routes-1`, `byod-0`) \u2014 these ids stay stable and are accepted by every show / recall / `processData` / `analyseData` tool via the matching `*EntryIDs` field.\n- Each slice has an `entryMode`: `multiple` (default \u2014 overlay several entries on the map) or `single` (only the latest entry stays; switching to it auto-drops every older entry from history). Inspect via `recallState` / `recallPlaces` / `recallRoutes` / `recallRanges` / `recallGeometries` / `recallByod` (traffic slices surface via `recallState`). Flip with `setEntryMode({ slice, mode })` when the user asks to \"only show one at a time\" or \"let me overlay multiple\".\n- Mode is sticky across turns \u2014 under `single`, calling another `discoverPlaces` automatically replaces the previous places entry.\n- Always check `recallX` (or `recallState`) before guessing entry ids; never invent them.\n\nLOCATION REFERENCE:\n- \"near me\" / \"my location\" / \"where I am\" \u2192 getCurrentLocation (user's physical GPS position, may prompt permission)\n- \"in this area\" / \"here on the map\" / \"near the map center\" \u2192 getViewport (map's current view)\n- If getCurrentLocation fails due to permission denial, fall back to getViewport as the reference point\n\nRESPONSE FORMATTING:\n- Use markdown in all responses\n- Show key information in bold\n- Use bullet lists when presenting multiple items or results\n- Keep responses concise \u2014 lead with the most relevant information\n\n";
238
+ export declare const BASE_SYSTEM_PROMPT: string;
85
239
 
86
240
  /**
87
241
  * State for base map display: style, language, viewport, layers, and hillshade.
@@ -104,30 +258,20 @@ declare class BaseMapState implements StateSlice {
104
258
 
105
259
  /* Excluded from this release type: buildSystemPrompt */
106
260
 
261
+ /* Excluded from this release type: BuildSystemPromptOptions */
262
+
107
263
  /**
108
- * A single analysis result attached to a BYOD entry. Produced by `analyseData`
109
- * when the entry is passed via `byodEntryIDs`; `data` is whatever the dynamic
110
- * code returned. Structurally identical to the other slices' analysis records
111
- * so the shared attach helper treats every slice uniformly.
264
+ * A single analysis result attached to a BYOD entry. Alias of the shared {@link EntryAnalysis} —
265
+ * every entry kind uses the same result shape; kept as a named export for back-compat.
112
266
  *
113
267
  * @group Agent Toolkit
114
268
  */
115
- export declare type BYODAnalysis = {
116
- /** Unique name within the parent entry (used as a key for the UI). */
117
- name: string;
118
- timestamp: number;
119
- /** Optional human-readable description of what the analysis computed. */
120
- description?: string;
121
- /** How `data` should be interpreted — plain JSON or a Chart.js configuration. */
122
- outputFormat: AnalysisOutputFormat;
123
- /** Arbitrary analysis result returned by the dynamic code. */
124
- data: unknown;
125
- };
269
+ export declare type BYODAnalysis = EntryAnalysis;
126
270
 
127
271
  /**
128
272
  * Token-frugal description of a BYOD FeatureCollection's runtime shape: feature
129
273
  * count, the geometry types present, and a per-property profile. Stored on every
130
- * {@link BYODEntry} and surfaced through `addByodSource` / `recallByod`
274
+ * {@link BYODEntry} and surfaced through `addByodSource` / `recallState`
131
275
  * so the model never has to receive raw GeoJSON just to learn the schema.
132
276
  *
133
277
  * @group Agent Toolkit
@@ -177,11 +321,6 @@ export declare type BYODEntry = {
177
321
  _module?: CustomGeoJSONModule;
178
322
  /** True while this entry's `_module` is rendering on the map. */
179
323
  _shown?: boolean;
180
- /**
181
- * Analyses attached by `analyseData` (keyed by `name` — re-running an analysis with the
182
- * same name replaces it). Empty / absent until the first analysis runs against this entry.
183
- */
184
- _analysis?: BYODAnalysis[];
185
324
  };
186
325
 
187
326
  /** Compact profile of one feature-`properties` key across the whole collection. */
@@ -215,6 +354,38 @@ export declare type BYODSource = {
215
354
  description?: string;
216
355
  };
217
356
 
357
+ /**
358
+ * Verdict returned by a {@link ByodSourceUrlValidator}: explicitly allow the fetch with
359
+ * `{ valid: true }`, or block it with `{ valid: false, reason }` — the `reason` is surfaced
360
+ * to the model as the tool error.
361
+ *
362
+ * @group Agent Toolkit
363
+ */
364
+ export declare type ByodSourceUrlValidation = {
365
+ valid: true;
366
+ } | {
367
+ valid: false;
368
+ reason: string;
369
+ };
370
+
371
+ /**
372
+ * App-supplied authorization hook for BYOD source URLs. Runs inside
373
+ * `addByodSource` AFTER the built-in scheme / size / timeout policy and BEFORE
374
+ * the network fetch. Return `{ valid: true }` to allow the fetch or
375
+ * `{ valid: false, reason }` to block it (see {@link ByodSourceUrlValidation}).
376
+ * May be sync or async — e.g. an allowlist lookup.
377
+ *
378
+ * The toolkit can't enforce SSRF guarantees the host itself can't, so it leaves
379
+ * the URL surface open by default; this hook lets a deployment add its own
380
+ * policy — refuse cloud-metadata endpoints (`169.254.169.254`), restrict to
381
+ * known hosts, etc. — without the SDK prescribing one for everyone. Configure
382
+ * via the `byod.validateSourceUrl` option on `createMapAgent`, or assign
383
+ * `state.byod.sourceUrlValidator` directly.
384
+ *
385
+ * @group Agent Toolkit
386
+ */
387
+ export declare type ByodSourceUrlValidator = (url: URL) => ByodSourceUrlValidation | Promise<ByodSourceUrlValidation>;
388
+
218
389
  /**
219
390
  * State slice for BYOD (bring-your-own-data) GeoJSON layers — customer-owned
220
391
  * data that doesn't fit places / routes / ranges / custom-geometries. Each
@@ -232,6 +403,12 @@ declare class BYODState implements ShownEntriesSlice, StateSlice {
232
403
  private _entryMode;
233
404
  /** Subscribe to state changes — see {@link BYODStateEvents}. */
234
405
  readonly events: StateEvents<BYODStateEvents>;
406
+ /**
407
+ * Optional authorization hook for `addByodSource` URL fetches — see
408
+ * {@link ByodSourceUrlValidator}. Unset = no restriction beyond the built-in
409
+ * scheme / size / timeout policy.
410
+ */
411
+ sourceUrlValidator?: ByodSourceUrlValidator;
235
412
  constructor(_ttMap: TomTomMap);
236
413
  get entries(): readonly BYODEntry[];
237
414
  get latestEntry(): BYODEntry | undefined;
@@ -253,13 +430,6 @@ declare class BYODState implements ShownEntriesSlice, StateSlice {
253
430
  * specs are stored and the next {@link showEntry} picks them up. Throws on an unknown id.
254
431
  */
255
432
  setEntryLayers(entryId: string, layers: CustomGeoJSONLayerSpec[]): void;
256
- /**
257
- * Attach an analysis to an existing entry (replace if a result of the same name exists).
258
- * Mirrors {@link CustomGeometriesState.addAnalysisToEntry} so the shared `analyseData`
259
- * attach helper treats BYOD like every other analysable slice. No-op (returns `false`)
260
- * when the entry is unknown.
261
- */
262
- addAnalysisToEntry(entryId: string, analysis: BYODAnalysis): boolean;
263
433
  /**
264
434
  * Lazy-init the entry's CustomGeoJSONModule. The module is created with a
265
435
  * single source named `data`, configured with this entry's `layers`. To swap
@@ -288,17 +458,19 @@ declare class BYODState implements ShownEntriesSlice, StateSlice {
288
458
  * @group Agent Toolkit
289
459
  */
290
460
  declare type BYODStateEvents = {
291
- /** History changed — entry added, removed, or cleared via `reset()`. */
292
- 'entries-change': readonly BYODEntry[];
461
+ /**
462
+ * History changed — entry added, removed, or cleared via `reset()`. `entries` is the full
463
+ * snapshot after the change; `changedIds` lists the ids of the entries this specific change
464
+ * added, replaced in place, or removed.
465
+ */
466
+ 'entries-change': {
467
+ entries: readonly BYODEntry[];
468
+ changedIds: readonly string[];
469
+ };
293
470
  /** Set of entries currently rendered on the map changed. */
294
471
  'shown-change': ReadonlySet<string>;
295
472
  /** Display policy switched between `single` and `multiple`. */
296
473
  'mode-change': EntryMode;
297
- /** An analysis was attached to (or replaced on) an entry by `analyseData`. */
298
- 'analysis-added': {
299
- entryId: string;
300
- analysis: BYODAnalysis;
301
- };
302
474
  };
303
475
 
304
476
  /**
@@ -381,6 +553,124 @@ export declare type ClassifierOptions = {
381
553
  */
382
554
  export declare const classifyUserIntent: (conversation: ConversationMessage[], options: ClassifierOptions) => Promise<ClassificationResult>;
383
555
 
556
+ /** Output of {@link runClustering}. */
557
+ export declare type ClusteringOutput = {
558
+ groups: ClusterResult[];
559
+ };
560
+
561
+ /** Tuneable parameters for {@link runClustering}. All optional with sensible defaults. */
562
+ export declare type ClusteringParams = {
563
+ /** DBSCAN neighbourhood radius in km. Default: 0.5 */
564
+ eps?: number;
565
+ /** Minimum members to form a cluster. Default: 3 */
566
+ minMembers?: number;
567
+ /** Maximum clusters to return (top-N by total delay). Default: 6 */
568
+ maxClusters?: number;
569
+ /** Pre-filter rules applied before clustering. */
570
+ preFilter?: {
571
+ /** Categories to exclude entirely. Default: `['roadworks', 'road-closed']` */
572
+ excludeCategories?: string[];
573
+ /** Incidents with `delayInSeconds` below this are excluded (within excludeCategories). Default: 1 (i.e. exclude 0-delay) */
574
+ minDelaySeconds?: number;
575
+ };
576
+ };
577
+
578
+ /** A single cluster produced by {@link runClustering}. */
579
+ export declare type ClusterResult = {
580
+ /** Stable identifier — reused across runs when member overlap >= 50%. */
581
+ id: string;
582
+ /** Geographic centre of the cluster `[lng, lat]`. */
583
+ centroid: [number, number];
584
+ /** Incident IDs belonging to this cluster. */
585
+ memberIds: string[];
586
+ /** Number of member incidents. */
587
+ size: number;
588
+ /** Sum of `delayInSeconds` across all members. */
589
+ totalDelaySeconds: number;
590
+ /** Maximum `delayInSeconds` among all members. */
591
+ peakDelaySeconds: number;
592
+ /** Diagonal of the cluster's bounding box in km. */
593
+ diameterKm: number;
594
+ /** Most-frequently referenced road numbers across members. */
595
+ primaryRoads: string[];
596
+ /** Most-frequent incident category across members (e.g. "jam"). */
597
+ primaryCategory: string;
598
+ /** Trend across recent snapshots — see {@link classifyTrend}. */
599
+ trend: ClusterTrend;
600
+ /**
601
+ * Rolling window of `totalDelaySeconds` across recent matched snapshots
602
+ * (bounded to {@link TREND_WINDOW}). Internal trend state threaded forward
603
+ * via the previous run; not display data.
604
+ */
605
+ delaySamples: number[];
606
+ };
607
+
608
+ /**
609
+ * Dedicated per-entry clustering state. Incident clustering is NOT a generic analysis — it is typed,
610
+ * first-class incidents state with its own home here. `output` is the latest {@link SandboxClusteringOutput}
611
+ * (the DBSCAN groups + the moment it was computed); `recipe` is how to recompute it (re-used by
612
+ * `tools/state/clusters-runtime.ts` to re-run on each monitor tick so clusters stay live). `recipe`
613
+ * is opaque here (a tools-layer `ClusterRecipe`) — the slice never interprets it, keeping the state layer
614
+ * tools-agnostic.
615
+ *
616
+ * @group Agent Toolkit
617
+ */
618
+ declare type ClustersRecord = {
619
+ recipe: unknown;
620
+ output: SandboxClusteringOutput;
621
+ };
622
+
623
+ /** Direction a cluster's total delay is trending across recent snapshots. */
624
+ export declare type ClusterTrend = 'growing' | 'fading' | 'steady' | 'new';
625
+
626
+ /**
627
+ * Tunes `analyseData` / `processData` sandbox execution. **Where** the code runs is
628
+ * not configurable: it is chosen by environment — a sandboxed, opaque-origin
629
+ * iframe + Web Worker in the browser (no DOM/storage access, no network via CSP,
630
+ * terminable on timeout), and the main thread in Node / SSR (where that boundary
631
+ * has no equivalent). The options below only tune the isolated browser run.
632
+ *
633
+ * @group Agent Toolkit
634
+ */
635
+ export declare type CodeExecutionConfig = {
636
+ /** Wall-clock budget per isolated (browser) run, in ms. Default: 5000. */
637
+ timeoutMs?: number;
638
+ /**
639
+ * Optional override of the worker's library source. By default the SDK loads its own
640
+ * inlined turf/h3 UMD (a separate lazy chunk), so this is rarely needed — supply it only
641
+ * to pin a specific build or add libraries. Return UMD/IIFE JavaScript that defines
642
+ * `self.turf` and `self.h3` (string or Promise of one).
643
+ */
644
+ loadWorkerLibrarySource?: () => string | Promise<string>;
645
+ };
646
+
647
+ /**
648
+ * Composes the base system prompt from {@link SYSTEM_PROMPT_SECTIONS}, applying
649
+ * any per-section overrides.
650
+ *
651
+ * @param overrides - Replacement **body** text keyed by section; omitted
652
+ * sections fall back to the default. The section heading is added automatically,
653
+ * so an override does not repeat it. Pass the result to
654
+ * `MapAgentOptions.systemPrompt`.
655
+ * @returns The composed system prompt string.
656
+ *
657
+ * @example
658
+ * ```typescript
659
+ * import { createMapAgent, composeSystemPrompt } from '@tomtom-org/maps-sdk-plugin-agent-toolkit';
660
+ *
661
+ * const agent = createMapAgent(map, {
662
+ * model: openai('gpt-4o'),
663
+ * // No need to repeat "RESPONSE FORMATTING:" — it is prepended for you.
664
+ * systemPrompt: composeSystemPrompt({
665
+ * responseFormatting: 'Always respond in Spanish, and keep it terse.',
666
+ * }),
667
+ * });
668
+ * ```
669
+ *
670
+ * @group Agent Toolkit
671
+ */
672
+ export declare const composeSystemPrompt: (overrides?: SystemPromptSectionOverrides) => string;
673
+
384
674
  /**
385
675
  * A single turn in the conversation passed to {@link classifyUserIntent}.
386
676
  *
@@ -447,22 +737,13 @@ export declare const createDefaultClassifier: (options: {
447
737
  export declare const createMapAgent: <CS extends ToolState = ToolState>(map: TomTomMap_2, options: MapAgentOptions<CS>) => MapAgentInstance<CS>;
448
738
 
449
739
  /**
450
- * A single aggregation/analysis result attached to a custom-geometries entry.
451
- * Produced by `analyseData`; `data` is whatever the dynamic code returned.
740
+ * A single analysis result attached to a custom-geometries entry. Alias of the shared
741
+ * {@link EntryAnalysis} — every entry kind uses the same result shape; kept as a named export for
742
+ * back-compat.
452
743
  *
453
744
  * @group Agent Toolkit
454
745
  */
455
- export declare type CustomGeometriesAnalysis = {
456
- /** Unique name within the parent entry (used as a key for future UI). */
457
- name: string;
458
- timestamp: number;
459
- /** Optional human-readable description of what the analysis computed. */
460
- description?: string;
461
- /** How `data` should be interpreted — plain JSON or a Chart.js configuration. */
462
- outputFormat: AnalysisOutputFormat;
463
- /** Arbitrary aggregation result returned by the dynamic code. */
464
- data: unknown;
465
- };
746
+ export declare type CustomGeometriesAnalysis = EntryAnalysis;
466
747
 
467
748
  /**
468
749
  * A single entry in the custom-geometries history — output of `processData`
@@ -479,8 +760,6 @@ export declare type CustomGeometriesEntry = {
479
760
  features: PolygonFeature[];
480
761
  /** Inputs and operation that produced this entry. */
481
762
  provenance: GeometryProvenance;
482
- /** Accumulated analysis results linked to this entry. */
483
- _analysis?: CustomGeometriesAnalysis[];
484
763
  /**
485
764
  * Per-entry GeometriesModule. Each entry owns its own module so display state
486
765
  * (theme, current features) lives on the entry instead of a shared slice-level
@@ -527,8 +806,6 @@ declare class CustomGeometriesState implements ShownEntriesSlice, StateSlice {
527
806
  * Returns the assigned id. Under `single` mode the new entry replaces every older one.
528
807
  */
529
808
  addEntry(features: PolygonFeature<CommonPlaceProps>[], provenance: CustomGeometriesEntry['provenance'], label: string, explicitId?: string): Promise<string>;
530
- /** Attach an analysis to an existing entry (replace if a result of the same name exists). */
531
- addAnalysisToEntry(entryId: string, analysis: CustomGeometriesAnalysis): boolean;
532
809
  /** Lazy-init the entry's GeometriesModule, applying / re-applying the requested theme. */
533
810
  getEntryGeometriesModule(entryId: string, theme?: GeometryTheme): Promise<GeometriesModule>;
534
811
  /**
@@ -563,15 +840,17 @@ declare class CustomGeometriesState implements ShownEntriesSlice, StateSlice {
563
840
  * @group Agent Toolkit
564
841
  */
565
842
  declare type CustomGeometriesStateEvents = {
566
- /** History changed — entry added or cleared via `reset()`. */
567
- 'entries-change': readonly CustomGeometriesEntry[];
843
+ /**
844
+ * History changed — entry added or cleared via `reset()`. `entries` is the full snapshot after
845
+ * the change; `changedIds` lists the ids of the entries this specific change added, replaced in
846
+ * place, or removed.
847
+ */
848
+ 'entries-change': {
849
+ entries: readonly CustomGeometriesEntry[];
850
+ changedIds: readonly string[];
851
+ };
568
852
  /** Set of entries currently rendered on the map changed. */
569
853
  'shown-change': ReadonlySet<string>;
570
- /** A new analysis was attached to (or replaced on) an entry. */
571
- 'analysis-added': {
572
- entryId: string;
573
- analysis: CustomGeometriesAnalysis;
574
- };
575
854
  /** Display policy switched between `single` and `multiple`. */
576
855
  'mode-change': EntryMode;
577
856
  };
@@ -650,25 +929,26 @@ export declare const DEFAULT_TOOLS: {
650
929
  getPOICategoryCodes: ToolDefinition;
651
930
  getTrafficIncidents: ToolDefinition;
652
931
  focusIncidents: ToolDefinition;
653
- startTrafficIncidentsMonitor: ToolDefinition;
654
- stopTrafficIncidentsMonitor: ToolDefinition;
932
+ clusterIncidents: ToolDefinition;
933
+ monitorAnalysis: ToolDefinition;
934
+ createTracker: ToolDefinition;
935
+ clearTracker: ToolDefinition;
936
+ getTrackerHistory: ToolDefinition;
937
+ getTrackers: ToolDefinition;
938
+ setTrafficIncidentsMonitor: ToolDefinition;
939
+ startRouteMonitor: ToolDefinition;
940
+ stopRouteMonitor: ToolDefinition;
655
941
  getTrafficAreaAnalytics: ToolDefinition;
656
- queryTrafficAnalytics: ToolDefinition;
657
942
  updatePlacesDisplay: ToolDefinition;
658
943
  updateRoutesDisplay: ToolDefinition;
659
944
  updateTrafficAreaAnalyticsDisplay: ToolDefinition;
660
945
  updateWaypointsDisplay: ToolDefinition;
661
- getShownTileIncidents: ToolDefinition;
662
946
  clearMap: ToolDefinition;
663
947
  flyTo: ToolDefinition;
664
948
  getViewport: ToolDefinition;
665
- recallGeometries: ToolDefinition;
666
- recallPlaces: ToolDefinition;
667
- recallByod: ToolDefinition;
668
949
  addByodSource: ToolDefinition;
669
950
  setByodLayers: ToolDefinition;
670
951
  updateByodDisplay: ToolDefinition;
671
- recallRoutes: ToolDefinition;
672
952
  getCurrentWaypoints: ToolDefinition;
673
953
  getStandardMapStyles: ToolDefinition;
674
954
  setMapStandardStyle: ToolDefinition;
@@ -684,12 +964,9 @@ export declare const DEFAULT_TOOLS: {
684
964
  zoomInOrOut: ToolDefinition;
685
965
  executeMaplibreCode: ToolDefinition;
686
966
  calculateBBox: ToolDefinition;
687
- formatDistance: ToolDefinition;
688
- formatDuration: ToolDefinition;
689
967
  getCurrentLocation: ToolDefinition;
690
968
  help: ToolDefinition;
691
969
  findReachableAreas: ToolDefinition;
692
- recallRanges: ToolDefinition;
693
970
  recallState: ToolDefinition;
694
971
  resetState: ToolDefinition;
695
972
  setEntryMode: ToolDefinition;
@@ -704,6 +981,24 @@ export declare const DEFAULT_TOOLS: {
704
981
  */
705
982
  export declare const ENTRY_MODE_SLICE_NAMES: readonly EntryModeSliceName[];
706
983
 
984
+ /**
985
+ * A single analysis result — the result of an `analyseData` run (one-shot or a `monitorAnalysis`
986
+ * standing replay). `data` is whatever the dynamic code returned. Identical across every entry kind.
987
+ *
988
+ * @group Agent Toolkit
989
+ */
990
+ export declare type EntryAnalysis = {
991
+ /** Unique name within the parent entry — used as the key (re-attaching the same name replaces). */
992
+ name: string;
993
+ timestamp: number;
994
+ /** Optional human-readable description of what the analysis computed. */
995
+ description?: string;
996
+ /** How `data` should be interpreted — plain JSON or a Chart.js configuration. */
997
+ outputFormat: AnalysisOutputFormat;
998
+ /** Arbitrary aggregation result returned by the dynamic code. */
999
+ data: unknown;
1000
+ };
1001
+
707
1002
  /**
708
1003
  * Tool-facing union of state slices that own analysable entries — the
709
1004
  * vocabulary used by tools that read from multiple slices (`analyseData`,
@@ -919,9 +1214,14 @@ declare class IncidentMonitor {
919
1214
  get isActive(): boolean;
920
1215
  /**
921
1216
  * Begin polling. Captures `deps` for the lifetime of the run, fires the first tick
922
- * immediately, then schedules an interval. No-op when already running.
1217
+ * immediately (unless `skipInitialTick`), then schedules an interval. No-op when already running.
1218
+ *
1219
+ * `skipInitialTick` is for the caller that JUST fetched the data itself (e.g. getTrafficIncidents
1220
+ * arming monitoring on the entry it just loaded): the entry is already fresh, so an eager first
1221
+ * tick would be a redundant duplicate fetch. Standalone callers leave it false so the monitor
1222
+ * produces an initial snapshot without waiting a full interval.
923
1223
  */
924
- start(area: MonitoredArea, deps: IncidentMonitorDeps, intervalMs?: number): void;
1224
+ start(area: MonitoredArea, deps: IncidentMonitorDeps, intervalMs?: number, skipInitialTick?: boolean): void;
925
1225
  /** Stop polling and reset state. No-op when idle. */
926
1226
  stop(): void;
927
1227
  private _tick;
@@ -959,92 +1259,13 @@ declare type IncidentMonitorEvents = {
959
1259
  };
960
1260
 
961
1261
  /**
962
- * Per-entry analysis registry: owns re-executing specs *and* the timeline of
963
- * their results. One thing, three parts — name, recipe (spec), output history —
964
- * kept together instead of split across sibling fields.
965
- *
966
- * Each named analysis keeps a bounded chronological history (see
967
- * {@link MAX_HISTORY_PER_ANALYSIS}) so consumers can trend results over time;
968
- * `getResult(name)` returns the most recent, `history(name)` the full timeline.
969
- *
970
- * Errors from individual specs are dropped silently — replay should never break
971
- * the monitor tick that triggered it.
1262
+ * A single analysis result attached to a traffic-incidents entry. Alias of the shared
1263
+ * {@link EntryAnalysis} — every entry kind uses the same result shape; kept as a named export for
1264
+ * back-compat.
972
1265
  *
973
1266
  * @group Agent Toolkit
974
1267
  */
975
- declare class IncidentsAnalyses {
976
- private _specs;
977
- private readonly _historyByName;
978
- get specs(): readonly IncidentsAnalysisSpec[];
979
- /**
980
- * Latest result for each registered spec, in registration order. Specs whose
981
- * history is empty (e.g. only failed runs) are skipped. UI layers that "render
982
- * whatever analyses this entry has" should iterate this instead of poking at
983
- * private history state.
984
- */
985
- get results(): readonly IncidentsAnalysis[];
986
- /** Full timeline for a single named analysis, oldest first. Empty when unknown. */
987
- history(name: string): readonly IncidentsAnalysis[];
988
- /** Most recent result for a given analysis name, or undefined. */
989
- getResult(name: string): IncidentsAnalysis | undefined;
990
- /**
991
- * Register a spec, or replace an existing one with the same name. When the new spec
992
- * differs from the existing one (code, outputFormat, or description), drops history
993
- * for that name — old results belong to a different analysis and must not leak into
994
- * `getResult` / `results`, which describe the *current* spec. Identical re-registers
995
- * are no-ops and preserve history (so trend continuity survives idempotent retries).
996
- */
997
- register(spec: IncidentsAnalysisSpec): void;
998
- /** Drop a spec by name. No-op when unknown. Result history is preserved. */
999
- unregister(name: string): void;
1000
- /**
1001
- * Append a result to the timeline for its name, evicting the oldest entry
1002
- * when the per-name cap is hit. Used by the slice for one-shot tool runs and
1003
- * by {@link replay} for each fresh tick.
1004
- */
1005
- attach(result: IncidentsAnalysis): void;
1006
- /**
1007
- * Re-run every registered spec against `data`, threading the previous result
1008
- * from the timeline. `sampledAt` is the canonical moment for this replay —
1009
- * shared with the entry data and the sandbox's `now`, so consumers can join
1010
- * across them safely. Each fresh result is appended before being returned.
1011
- * Failed specs are skipped.
1012
- */
1013
- replay(data: TrafficIncident[], sampledAt: number): Promise<IncidentsAnalysis[]>;
1014
- }
1015
-
1016
- /**
1017
- * A single aggregation/analysis result attached to a traffic-incidents entry.
1018
- * Produced by `analyseData` (one-shot or `monitor`-rerun); `data` is whatever the dynamic code returned.
1019
- *
1020
- * @group Agent Toolkit
1021
- */
1022
- export declare type IncidentsAnalysis = {
1023
- /** Unique name within the parent entry (used as a key for future UI). */
1024
- name: string;
1025
- timestamp: number;
1026
- /** Optional human-readable description of what the analysis computed. */
1027
- description?: string;
1028
- /** How `data` should be interpreted — plain JSON or a Chart.js configuration. */
1029
- outputFormat: AnalysisOutputFormat;
1030
- /** Arbitrary aggregation result returned by the dynamic code. */
1031
- data: unknown;
1032
- };
1033
-
1034
- /**
1035
- * A registered, re-executing analysis. Produced by `analyseData` with `monitor: { entryId }`;
1036
- * replayed by an entry's {@link IncidentsAnalyses} on every monitor-tick of its source.
1037
- *
1038
- * @group Agent Toolkit
1039
- */
1040
- declare type IncidentsAnalysisSpec = {
1041
- name: string;
1042
- description?: string;
1043
- outputFormat: AnalysisOutputFormat;
1044
- code: string;
1045
- /** Source entry id the spec reads from. */
1046
- source: string;
1047
- };
1268
+ export declare type IncidentsAnalysis = EntryAnalysis;
1048
1269
 
1049
1270
  /**
1050
1271
  * A fresh batch of incidents from a single monitor tick, with the moment it was sampled.
@@ -1097,13 +1318,28 @@ export declare type MapAgentOptions<CS extends ToolState = ToolState> = {
1097
1318
  /** AI SDK language model instance. REQUIRED — no default provider. */
1098
1319
  model: LanguageModel;
1099
1320
  /**
1100
- * Complete system prompt that replaces the default.
1101
- * If provided, `systemPromptSuffix` is ignored.
1321
+ * Either a full prompt string that replaces the default entirely, or a
1322
+ * {@link SystemPromptSectionOverrides} map whose entries override individual
1323
+ * sections of the built-in prompt (omitted sections keep their defaults).
1324
+ *
1325
+ * To extend a single section rather than replace it, read its default from
1326
+ * the exported `SYSTEM_PROMPT_SECTIONS` map and override with a derived value
1327
+ * (e.g. ``responseFormatting: `${SYSTEM_PROMPT_SECTIONS.responseFormatting}\n- Answer in Spanish.` ``).
1328
+ *
1329
+ * A full string ignores `systemPromptPrefix` and `systemPromptSuffix`;
1330
+ * section overrides still honor both.
1331
+ */
1332
+ systemPrompt?: string | SystemPromptSectionOverrides;
1333
+ /**
1334
+ * Text prepended above the whole prompt as a preamble, separated by a blank
1335
+ * line and carrying no heading. Applied on top of the base prompt or section
1336
+ * overrides; ignored only when `systemPrompt` is a full replacement string.
1102
1337
  */
1103
- systemPrompt?: string;
1338
+ systemPromptPrefix?: string;
1104
1339
  /**
1105
- * Additional text appended to the built-in prompt.
1106
- * Ignored if `systemPrompt` is provided.
1340
+ * Additional text appended under an `ADDITIONAL INSTRUCTIONS` heading.
1341
+ * Applied on top of the base prompt or section overrides; ignored only when
1342
+ * `systemPrompt` is a full replacement string.
1107
1343
  */
1108
1344
  systemPromptSuffix?: string;
1109
1345
  /**
@@ -1161,13 +1397,48 @@ export declare type MapAgentOptions<CS extends ToolState = ToolState> = {
1161
1397
  * dataEntries: {
1162
1398
  * routes: { entryMode: 'single' },
1163
1399
  * incidents: { entryMode: 'single' },
1164
- * byod: { enabled: false }, // disables recallByod, addByodSource, setByodLayers, updateByodDisplay + byod scope on analyse/process
1165
- * ranges: { enabled: false }, // disables findReachableAreas + recallRanges
1400
+ * byod: { enabled: false }, // disables recallState, addByodSource, setByodLayers, updateByodDisplay + byod scope on analyse/process
1401
+ * ranges: { enabled: false }, // disables findReachableAreas + recallState
1166
1402
  * },
1167
1403
  * });
1168
1404
  * ```
1169
1405
  */
1170
1406
  dataEntries?: Partial<Record<DataEntryKind, DataEntryConfig>>;
1407
+ /**
1408
+ * BYOD (bring-your-own-data) options.
1409
+ *
1410
+ * @example
1411
+ * ```typescript
1412
+ * createMapAgent(map, {
1413
+ * model: openai('gpt-4o'),
1414
+ * byod: {
1415
+ * // Refuse cloud-metadata / private addresses; allow only known hosts.
1416
+ * validateSourceUrl: (url) =>
1417
+ * url.hostname === 'data.example.com'
1418
+ * ? { valid: true }
1419
+ * : { valid: false, reason: `Host "${url.hostname}" is not allowed.` },
1420
+ * },
1421
+ * });
1422
+ * ```
1423
+ */
1424
+ byod?: {
1425
+ /**
1426
+ * Authorize each `addByodSource` URL before it is fetched — see
1427
+ * {@link ByodSourceUrlValidator}. Return `{ valid: true }` to allow or
1428
+ * `{ valid: false, reason }` to block (sync or async). Runs in addition
1429
+ * to the built-in scheme / size / timeout policy.
1430
+ */
1431
+ validateSourceUrl?: ByodSourceUrlValidator;
1432
+ };
1433
+ /**
1434
+ * Tunes `analyseData` / `processData` sandbox execution — see {@link CodeExecutionConfig}.
1435
+ * Where the code runs is chosen by environment (off-thread iframe-worker in the browser,
1436
+ * main thread in Node/SSR), not configured here; these options only adjust the isolated
1437
+ * browser run (`timeoutMs`, `loadWorkerLibrarySource`).
1438
+ *
1439
+ * @experimental
1440
+ */
1441
+ codeExecution?: CodeExecutionConfig;
1171
1442
  /**
1172
1443
  * Pluggable classifier for automatic tool selection.
1173
1444
  * - Omit to use the default LLM-based classifier (uses main model)
@@ -1254,6 +1525,18 @@ declare class MapPOIsState implements StateSlice {
1254
1525
  reset(): void;
1255
1526
  }
1256
1527
 
1528
+ /**
1529
+ * The features within ONE source entry that matched the rule — the per-entry unit of a {@link Verdict}.
1530
+ * `featureIds` is the matched SUBSET, not the whole entry: a rule selects features by arbitrary sandbox
1531
+ * logic (proximity, threshold, filter), and that selection isn't recoverable from `entryId` alone (the
1532
+ * entry holds every feature) without re-running the rule — so the verdict records exactly what matched.
1533
+ * These are the features the UI highlights and the `summary` cites.
1534
+ */
1535
+ export declare type MemberRef = {
1536
+ entryId: string;
1537
+ featureIds: string[];
1538
+ };
1539
+
1257
1540
  /**
1258
1541
  * The geographic area a {@link TrafficIncidentsEntry} monitor is polling, captured at
1259
1542
  * monitor-start time and replayed on every tick.
@@ -1267,22 +1550,12 @@ export declare type MonitoredArea = {
1267
1550
  };
1268
1551
 
1269
1552
  /**
1270
- * A single aggregation/analysis result attached to a places entry.
1271
- * Produced by `analyseData`; `data` is whatever the dynamic code returned.
1553
+ * A single analysis result attached to a places entry. Alias of the shared {@link EntryAnalysis} —
1554
+ * every entry kind uses the same result shape; kept as a named export for back-compat.
1272
1555
  *
1273
1556
  * @group Agent Toolkit
1274
1557
  */
1275
- export declare type PlacesAnalysis = {
1276
- /** Unique name within the parent entry (used as a key for future UI). */
1277
- name: string;
1278
- timestamp: number;
1279
- /** Optional human-readable description of what the analysis computed. */
1280
- description?: string;
1281
- /** How `data` should be interpreted — plain JSON or a Chart.js configuration. */
1282
- outputFormat: AnalysisOutputFormat;
1283
- /** Arbitrary aggregation result returned by the dynamic code. */
1284
- data: unknown;
1285
- };
1558
+ export declare type PlacesAnalysis = EntryAnalysis;
1286
1559
 
1287
1560
  /**
1288
1561
  * A single entry in the place search history.
@@ -1303,8 +1576,6 @@ export declare type PlacesEntry = {
1303
1576
  * (`place.properties.dataSources.geometry.id`), matching the response shape of `geometryData`.
1304
1577
  */
1305
1578
  geometries?: PolygonFeature<CommonPlaceProps>[];
1306
- /** Accumulated analysis results linked to this entry. */
1307
- _analysis?: PlacesAnalysis[];
1308
1579
  /** Lazy place-id → Place index, built on first ID lookup through PlacesState.findPlaceById. */
1309
1580
  _byId?: Record<string, Place>;
1310
1581
  /** Lazy place-id → boundary polygon index, built on first lookup through PlacesState.getGeometryForPlace. */
@@ -1375,12 +1646,6 @@ declare class PlacesState implements StateSlice {
1375
1646
  */
1376
1647
  get latestPlace(): Place[] | undefined;
1377
1648
  addPlaceResult(place: Place | Places, label: string, explicitId?: string, connections?: PlaceConnectionDisplay[], geometries?: PolygonFeature<CommonPlaceProps>[]): Promise<string>;
1378
- /**
1379
- * Attach an analysis result to an existing entry. Analysis names are unique
1380
- * within a single entry — adding one with an existing name replaces it.
1381
- * Returns true on success, false if the entry doesn't exist.
1382
- */
1383
- addAnalysisToEntry(entryId: string, analysis: PlacesAnalysis): boolean;
1384
1649
  findPlaceById(placeId: string): {
1385
1650
  place: Place;
1386
1651
  entryId: string;
@@ -1475,15 +1740,18 @@ declare class PlacesState implements StateSlice {
1475
1740
  * @group Agent Toolkit
1476
1741
  */
1477
1742
  declare type PlacesStateEvents = {
1478
- /** History changed — entry added, replaced, or cleared via `reset()`. Payload: the full entries snapshot. */
1479
- 'entries-change': readonly PlacesEntry[];
1743
+ /**
1744
+ * History changed — entry added, replaced, or cleared via `reset()`. `entries` is the full
1745
+ * snapshot after the change; `changedIds` lists the ids of the entries this specific change
1746
+ * added, replaced in place, or removed (the standing-analysis sweep matches these against each
1747
+ * record's `affectedEntryIds`).
1748
+ */
1749
+ 'entries-change': {
1750
+ entries: readonly PlacesEntry[];
1751
+ changedIds: readonly string[];
1752
+ };
1480
1753
  /** Set of entries currently rendered on the map changed (or marker type swapped). */
1481
1754
  'shown-change': ReadonlySet<string>;
1482
- /** A new analysis was attached to (or replaced on) an entry. */
1483
- 'analysis-added': {
1484
- entryId: string;
1485
- analysis: PlacesAnalysis;
1486
- };
1487
1755
  /** Boundary polygons were fetched and cached on an entry. */
1488
1756
  'geometries-change': {
1489
1757
  entryId: string;
@@ -1608,8 +1876,15 @@ declare class RangeState implements ShownEntriesSlice {
1608
1876
  * @group Agent Toolkit
1609
1877
  */
1610
1878
  declare type RangeStateEvents = {
1611
- /** Range history changed — entry added or cleared via `reset()`. */
1612
- 'entries-change': readonly RangesEntry[];
1879
+ /**
1880
+ * Range history changed — entry added or cleared via `reset()`. `entries` is the full snapshot
1881
+ * after the change; `changedIds` lists the ids of the entries this specific change added,
1882
+ * replaced in place, or removed.
1883
+ */
1884
+ 'entries-change': {
1885
+ entries: readonly RangesEntry[];
1886
+ changedIds: readonly string[];
1887
+ };
1613
1888
  /** Set of ranges entries currently rendered on the map changed (multi-show under `multiple`). */
1614
1889
  'shown-change': ReadonlySet<string>;
1615
1890
  /** Display policy switched between `single` and `multiple`. */
@@ -1652,6 +1927,72 @@ export declare type ReachableRange = {
1652
1927
  */
1653
1928
  export declare const resolveTools: <S extends ToolState>(defaults: Record<string, ToolDefinition<S>>, overrides?: { [K in ToolNameHint]?: ToolEntry<S> | false | undefined; }) => Record<string, ToolDefinition<S>>;
1654
1929
 
1930
+ /**
1931
+ * One-shot polling loop that recalculates a single route on an interval. Owns its timer, status, and
1932
+ * error. Re-starting after `stop()` is allowed and resets all state.
1933
+ *
1934
+ * The monitor never knows about the entry that owns it — the `recalculate` closure captures the
1935
+ * waypoints + params, and the caller decides what to do with each fresh snapshot. Mirrors
1936
+ * `IncidentMonitor`; routes carry live traffic, so periodic recalculation keeps the entry fresh.
1937
+ *
1938
+ * @group Agent Toolkit
1939
+ */
1940
+ declare class RouteMonitor {
1941
+ readonly events: StateEvents<RouteMonitorEvents>;
1942
+ private _status;
1943
+ private _error?;
1944
+ private _intervalHandle?;
1945
+ private _clearInterval?;
1946
+ private _recalculate?;
1947
+ get status(): RoutePollingStatus;
1948
+ get error(): string | undefined;
1949
+ /** True when the monitor is *currently* recalculating on its interval. */
1950
+ get isRunning(): boolean;
1951
+ /** True when the monitor has any activity (running or stopped-error). */
1952
+ get isActive(): boolean;
1953
+ /**
1954
+ * Begin polling. Captures `deps` for the lifetime of the run, fires the first recalculation
1955
+ * immediately (unless `skipInitialTick`), then schedules an interval. No-op when already running.
1956
+ *
1957
+ * `skipInitialTick` is for a caller that JUST calculated the route and only wants the recurring
1958
+ * refresh — an eager first tick would be a redundant duplicate recalculation.
1959
+ */
1960
+ start(deps: RouteMonitorDeps, intervalMs?: number, skipInitialTick?: boolean): void;
1961
+ /** Stop polling and reset state. No-op when idle. */
1962
+ stop(): void;
1963
+ private _tick;
1964
+ private _failed;
1965
+ private _releaseTimer;
1966
+ }
1967
+
1968
+ /**
1969
+ * Dependency injection for a {@link RouteMonitor}. Production code wires `recalculate` to a closure
1970
+ * over `calculateRoute` + the entry's waypoints / params (so each tick re-runs the SAME route);
1971
+ * tests pass a deterministic fake and override the timer.
1972
+ *
1973
+ * The recalculator returns just the routes — the monitor stamps `takenAt` itself so every consumer of
1974
+ * one tick (entry data, derived analyses) agrees on the same canonical moment.
1975
+ *
1976
+ * @group Agent Toolkit
1977
+ */
1978
+ declare type RouteMonitorDeps = {
1979
+ recalculate: () => Promise<Routes_2>;
1980
+ /** Override for tests. Defaults to the global `setInterval`. */
1981
+ setInterval?: (fn: () => void, ms: number) => unknown;
1982
+ clearInterval?: (handle: unknown) => void;
1983
+ };
1984
+
1985
+ /**
1986
+ * Events fired by {@link RouteMonitor}. The slice subscribes once and re-emits translated copies
1987
+ * (`monitor-tick` / `monitor-error`) keyed by entry id.
1988
+ */
1989
+ declare type RouteMonitorEvents = {
1990
+ /** A recalculation succeeded. Payload: the fresh snapshot. */
1991
+ tick: RouteSnapshot;
1992
+ /** A recalculation failed — the monitor has cleared its timer and moved to `stopped-error`. */
1993
+ error: string;
1994
+ };
1995
+
1655
1996
  /**
1656
1997
  * Route planning parameters stored in routing state.
1657
1998
  * Consumed by setRoute, addWaypointsToRoute, removeWaypointsFromRoute, and replaceWaypointInRoute.
@@ -1673,22 +2014,20 @@ export declare type RouteParams = {
1673
2014
  };
1674
2015
 
1675
2016
  /**
1676
- * A single aggregation/analysis result attached to a routes entry.
1677
- * Produced by `analyseData`; `data` is whatever the dynamic code returned.
2017
+ * Lifecycle state of a per-entry route monitor. `stopped-error` indicates the last recalculation
2018
+ * failed and the timer has been cleared — start the monitor again to retry.
1678
2019
  *
1679
2020
  * @group Agent Toolkit
1680
2021
  */
1681
- export declare type RoutesAnalysis = {
1682
- /** Unique name within the parent entry (used as a key for future UI). */
1683
- name: string;
1684
- timestamp: number;
1685
- /** Optional human-readable description of what the analysis computed. */
1686
- description?: string;
1687
- /** How `data` should be interpreted — plain JSON or a Chart.js configuration. */
1688
- outputFormat: AnalysisOutputFormat;
1689
- /** Arbitrary aggregation result returned by the dynamic code. */
1690
- data: unknown;
1691
- };
2022
+ declare type RoutePollingStatus = 'idle' | 'running' | 'stopped-error';
2023
+
2024
+ /**
2025
+ * A single analysis result attached to a routes entry. Alias of the shared {@link EntryAnalysis} —
2026
+ * every entry kind uses the same result shape; kept as a named export for back-compat.
2027
+ *
2028
+ * @group Agent Toolkit
2029
+ */
2030
+ export declare type RoutesAnalysis = EntryAnalysis;
1692
2031
 
1693
2032
  /**
1694
2033
  * A single entry in the route calculation history.
@@ -1702,8 +2041,6 @@ export declare type RoutesEntry = {
1702
2041
  data: Routes;
1703
2042
  waypoints: WaypointLike[];
1704
2043
  params: RouteParams;
1705
- /** Accumulated analysis results linked to this entry. */
1706
- _analysis?: RoutesAnalysis[];
1707
2044
  /**
1708
2045
  * Per-entry RoutingModule. Each entry owns its own module so display state (which route
1709
2046
  * variant is selected, which waypoints are shown, layer theme) lives on the entry instead
@@ -1713,6 +2050,29 @@ export declare type RoutesEntry = {
1713
2050
  _module?: RoutingModule;
1714
2051
  /** True while this entry's `_module` is rendering routes/waypoints on the map. */
1715
2052
  _shown?: boolean;
2053
+ /**
2054
+ * Per-entry route monitor. Lazy-created on first {@link RoutingState.startMonitoring}; reused
2055
+ * across stop/start cycles. Recalculates this route (same waypoints + params) on its interval so
2056
+ * the entry stays fresh with live traffic.
2057
+ */
2058
+ _monitor?: RouteMonitor;
2059
+ /**
2060
+ * Monotonic counter bumped on every {@link RoutingState.replaceRouteData}. Guards against an
2061
+ * out-of-order render: a slow `showRoutes` from an earlier tick that resolves after a newer tick
2062
+ * has already overwritten `data` bails before touching the map.
2063
+ */
2064
+ _tick?: number;
2065
+ };
2066
+
2067
+ /**
2068
+ * A freshly recalculated route from a single monitor tick, with the moment it was sampled.
2069
+ * Payload of {@link RoutingStateEvents.monitor-tick}.
2070
+ *
2071
+ * @group Agent Toolkit
2072
+ */
2073
+ declare type RouteSnapshot = {
2074
+ takenAt: number;
2075
+ routes: Routes_2;
1716
2076
  };
1717
2077
 
1718
2078
  /**
@@ -1772,9 +2132,14 @@ declare class RoutingState implements ShownEntriesSlice {
1772
2132
  * layers don't pile up. Under `entryMode === 'multiple'` previously-shown entries stay
1773
2133
  * rendered, letting the caller overlay several routes at once.
1774
2134
  *
1775
- * Emits `shown-change` once the new entry is up.
1776
- */
1777
- showEntry(entryId: string): Promise<void>;
2135
+ * Emits `shown-change` once the new entry is up. Pass `showWaypoints: false` to render the
2136
+ * route lines without origin/stop/destination pins, and `showSummaryBubbles: false` to drop the
2137
+ * per-route ETA bubbles (e.g. a monitored corridor backdrop).
2138
+ */
2139
+ showEntry(entryId: string, opts?: {
2140
+ showWaypoints?: boolean;
2141
+ showSummaryBubbles?: boolean;
2142
+ }): Promise<void>;
1778
2143
  /** Slice-level main color persisted across entry swaps. Undefined when no theme has been set. */
1779
2144
  get mainColor(): string | undefined;
1780
2145
  /**
@@ -1807,14 +2172,29 @@ declare class RoutingState implements ShownEntriesSlice {
1807
2172
  get planningSlots(): PlanningWaypoint[];
1808
2173
  get params(): RouteParams;
1809
2174
  addRoutes(routes: Routes, waypoints: WaypointLike[], label: string): Promise<string>;
1810
- /**
1811
- * Attach an analysis result to an existing routes entry. Names are unique within a single
1812
- * entry — adding one with an existing name replaces it. Returns true on success, false if
1813
- * the entry doesn't exist.
1814
- */
1815
- addAnalysisToEntry(entryId: string, analysis: RoutesAnalysis): boolean;
1816
2175
  setWaypointAt(index: number, waypoint: WaypointLike): void;
1817
2176
  setParams(params: Partial<RouteParams>): void;
2177
+ /**
2178
+ * Replace a route entry's data in place (a monitor recalculated it). Bumps `_tick`, updates
2179
+ * `data`/`timestamp`, emits `entries-change`, and re-renders the route on its module when shown.
2180
+ * The `_tick` guard bails if a newer tick overwrote `data` while `showRoutes` awaited. No-op
2181
+ * (returns false) when the entry is unknown. Mirrors `TrafficIncidentsState.replaceEntryData`.
2182
+ */
2183
+ replaceRouteData(entryId: string, routes: Routes, sampledAt: number): Promise<boolean>;
2184
+ private _createMonitorForEntry;
2185
+ /**
2186
+ * Arm background recalculation on an entry. Lazy-creates its monitor, then starts polling with
2187
+ * the caller-supplied `recalculate` closure. Emits `monitor-start` on the idle → running
2188
+ * transition. Throws on unknown id.
2189
+ */
2190
+ startMonitoring(entryId: string, deps: RouteMonitorDeps, opts?: {
2191
+ intervalMs?: number;
2192
+ skipInitialTick?: boolean;
2193
+ }): void;
2194
+ /** Stop recalculating an entry; its last data stays. Emits `monitor-stop`. No-op when not running. */
2195
+ stopMonitoring(entryId: string): void;
2196
+ /** True when the entry is currently being recalculated on its interval. */
2197
+ isMonitored(entryId: string): boolean;
1818
2198
  /**
1819
2199
  * Hide every route + waypoint set currently on the map. Map-only — history (`entries`)
1820
2200
  * stays intact. Implements {@link ClearableMapSlice}.
@@ -1829,21 +2209,77 @@ declare class RoutingState implements ShownEntriesSlice {
1829
2209
  * @group Agent Toolkit
1830
2210
  */
1831
2211
  declare type RoutingStateEvents = {
1832
- /** Route history changed — new calculation stored, analysis attached, or cleared via `reset()`. */
1833
- 'entries-change': readonly RoutesEntry[];
2212
+ /**
2213
+ * Route history changed — new calculation stored, analysis attached, or cleared via `reset()`.
2214
+ * `entries` is the full snapshot after the change; `changedIds` lists the ids of the entries
2215
+ * this specific change added, replaced in place, or removed.
2216
+ */
2217
+ 'entries-change': {
2218
+ entries: readonly RoutesEntry[];
2219
+ changedIds: readonly string[];
2220
+ };
1834
2221
  /** Sparse waypoint planning slots changed (origin/stop/destination assignment). */
1835
2222
  'planning-change': readonly PlanningWaypoint[];
1836
2223
  /** Route parameters (alternatives, cost model, depart/arrive time) changed. */
1837
2224
  'params-change': RouteParams;
1838
- /** A new analysis was attached to (or replaced on) a routes entry. */
1839
- 'analysis-added': {
1840
- entryId: string;
1841
- analysis: RoutesAnalysis;
1842
- };
1843
2225
  /** Set of routes entries currently rendered on the map changed (multi-show under `multiple`). */
1844
2226
  'shown-change': ReadonlySet<string>;
1845
2227
  /** Display policy switched between `single` and `multiple`. */
1846
2228
  'mode-change': EntryMode;
2229
+ /** Monitoring began on an entry (idle → running). Skipped when already running. */
2230
+ 'monitor-start': {
2231
+ entryId: string;
2232
+ };
2233
+ /**
2234
+ * Monitoring halted on an entry. `reason: 'manual'` covers explicit `stopMonitoring` and entry
2235
+ * teardown (remove / reset); `reason: 'error'` fires alongside `monitor-error`.
2236
+ */
2237
+ 'monitor-stop': {
2238
+ entryId: string;
2239
+ reason: 'manual' | 'error';
2240
+ };
2241
+ /** A monitored route was recalculated (per-tick). The entry's data has been updated in place. */
2242
+ 'monitor-tick': {
2243
+ entryId: string;
2244
+ snap: RouteSnapshot;
2245
+ };
2246
+ /** A monitored route's recalculation failed — the monitor cleared its timer and stopped. */
2247
+ 'monitor-error': {
2248
+ entryId: string;
2249
+ error: string;
2250
+ };
2251
+ };
2252
+
2253
+ /**
2254
+ * {@link runClustering} output plus the moment it was computed. The extra `sampledAt` lets the sandbox
2255
+ * `cluster()` primitive thread the trend window forward without the caller juggling timestamps: it is
2256
+ * stamped onto the result and read back off `previous` on the next call.
2257
+ *
2258
+ * @group Agent Toolkit
2259
+ */
2260
+ declare type SandboxClusteringOutput = ClusteringOutput & {
2261
+ sampledAt?: number;
2262
+ };
2263
+
2264
+ /**
2265
+ * Pluggable strategy for executing sandbox code. The default
2266
+ * {@link mainThreadExecutor} compiles + runs in the host realm; an alternative
2267
+ * executor (e.g. the iframe-worker one) can run the same code in a locked-down
2268
+ * realm off the main thread while honouring this exact contract.
2269
+ *
2270
+ * `run` MUST resolve to `{ value }` or `{ error }` and never reject — execution
2271
+ * failures are reported as `{ error }` via {@link formatSandboxExecutionError}.
2272
+ *
2273
+ * @group Agent Toolkit
2274
+ */
2275
+ export declare type SandboxExecutor = {
2276
+ run<Result = unknown>(code: string, paramNames: readonly string[], args: readonly unknown[], verb: string): Promise<{
2277
+ value: Result;
2278
+ } | {
2279
+ error: string;
2280
+ }>;
2281
+ /** Release any held resources (iframe / worker). No-op for the main-thread executor. */
2282
+ destroy?(): void;
1847
2283
  };
1848
2284
 
1849
2285
  /**
@@ -1940,7 +2376,7 @@ export declare type StateDigest = {
1940
2376
  *
1941
2377
  * Usage from outside the slice (subscribe-only):
1942
2378
  * ```ts
1943
- * const unsub = state.places.events.on('entries-change', (entries) => render(entries));
2379
+ * const unsub = state.places.events.on('entries-change', ({ entries }) => render(entries));
1944
2380
  * // ...
1945
2381
  * unsub();
1946
2382
  * ```
@@ -1973,6 +2409,90 @@ export declare interface StateSlice {
1973
2409
  reset(): void;
1974
2410
  }
1975
2411
 
2412
+ /**
2413
+ * The default body text for each base-prompt section — heading excluded.
2414
+ *
2415
+ * @remarks
2416
+ * Each value is the section's content only; the structural heading (e.g.
2417
+ * `RESPONSE FORMATTING:`) is added by {@link composeSystemPrompt}. So an override
2418
+ * passes just the body — `composeSystemPrompt({ responseFormatting: 'Reply in Spanish.' })`
2419
+ * still renders under the `RESPONSE FORMATTING:` heading.
2420
+ *
2421
+ * The behavioral prose is split so each concern is overridable on its own:
2422
+ * - `identity` — who the assistant is and its tone of voice; the natural hook to
2423
+ * rebrand or set a persona.
2424
+ * - `capabilities` — the map environment plus a compact summary of what the
2425
+ * toolkit can do. Kept at the group level (not per-tool) so it does not drift
2426
+ * as tools change; the exact mechanics live in each tool's description and the
2427
+ * classifier prompt. It also gives `rejectionRules` something concrete to steer
2428
+ * the user back toward.
2429
+ * - `rejectionRules` — how to handle in-scope / mixed / out-of-scope / illegal
2430
+ * requests, and the requirement to always explain a rejection.
2431
+ * - `dataConfidence` — how to surface incomplete, partial, stale, or conflicting
2432
+ * data instead of presenting it as complete.
2433
+ *
2434
+ * Location-reference routing ("near me" → getCurrentLocation, "here" →
2435
+ * getViewport) is intentionally NOT a section: it is a tool-selection rule
2436
+ * already owned by the classifier prompt and the two tools' cross-referencing
2437
+ * descriptions.
2438
+ *
2439
+ * This map is the canonical source of the default body text, exported so a
2440
+ * consumer can read a default and override with a derived value (extend, prefix,
2441
+ * or rewrite it) — see {@link SystemPromptSectionOverrides}.
2442
+ *
2443
+ * @group Agent Toolkit
2444
+ */
2445
+ export declare const SYSTEM_PROMPT_SECTIONS: Record<SystemPromptSection, string>;
2446
+
2447
+ /**
2448
+ * @module agent-toolkit-system-prompt
2449
+ *
2450
+ * Behavioral contract: persona, tone, dual-response, cadence — lives here.
2451
+ *
2452
+ * Tool mechanics: call shape, return shape, error handling, when-to-use vs.
2453
+ * which-tool-next — lives in the relevant tool's description string and Zod
2454
+ * schema. Each tool re-explains its own role; the LLM sees those alongside
2455
+ * this prompt. The intent classifier (see `utils/intent-classifier.ts`) also
2456
+ * scopes tools per step, so a tool's description is only in context when that
2457
+ * tool is active — keep cross-cutting safety rules here (always present), and
2458
+ * leave per-tool mechanics to the tool itself.
2459
+ *
2460
+ * If you find yourself adding lines here to fix an LLM mistake, ask first
2461
+ * whether the fix belongs in a tool description or a tool error message.
2462
+ *
2463
+ * The prompt is split into named {@link SYSTEM_PROMPT_SECTIONS} so consumers can
2464
+ * override a single section via {@link composeSystemPrompt} without rebuilding
2465
+ * the whole string.
2466
+ */
2467
+ /**
2468
+ * Named, individually-overridable sections of the base system prompt.
2469
+ *
2470
+ * @group Agent Toolkit
2471
+ */
2472
+ export declare type SystemPromptSection = 'identity' | 'capabilities' | 'rejectionRules' | 'responseFormatting' | 'dataConfidence' | 'toolExecution' | 'sessionState';
2473
+
2474
+ /**
2475
+ * Per-section body overrides. Each key replaces that section's body; omitted
2476
+ * sections fall back to {@link SYSTEM_PROMPT_SECTIONS}. Accepted by
2477
+ * {@link composeSystemPrompt} and `MapAgentOptions.systemPrompt`.
2478
+ *
2479
+ * @remarks
2480
+ * To *extend* rather than replace a section, read its current default from the
2481
+ * exported {@link SYSTEM_PROMPT_SECTIONS} map and override with a derived string
2482
+ * — e.g. append a rule, prepend a note, or hand the default to a coding agent to
2483
+ * rewrite under some criteria:
2484
+ *
2485
+ * ```typescript
2486
+ * composeSystemPrompt({
2487
+ * // Keep the default behaviour, add one extra line.
2488
+ * responseFormatting: `${SYSTEM_PROMPT_SECTIONS.responseFormatting}\n- Always answer in Spanish.`,
2489
+ * });
2490
+ * ```
2491
+ *
2492
+ * @group Agent Toolkit
2493
+ */
2494
+ export declare type SystemPromptSectionOverrides = Partial<Record<SystemPromptSection, string>>;
2495
+
1976
2496
  /**
1977
2497
  * All valid default tool names, derived from {@link DEFAULT_TOOLS}.
1978
2498
  *
@@ -2185,26 +2705,140 @@ export declare type ToolState = {
2185
2705
  customGeometries: CustomGeometriesState;
2186
2706
  /** Bring-your-own-data GeoJSON layers — customer-authored data the agent can read and render. */
2187
2707
  byod: BYODState;
2708
+ /**
2709
+ * Strategy used to run code-generation tools (`analyseData` / `processData`).
2710
+ * Initialised to the main-thread executor by `createToolState`, and overridden
2711
+ * by `createMapAgent` from {@link MapAgentOptions.codeExecution}. Tools read this
2712
+ * to select where sandbox code runs.
2713
+ */
2714
+ codeExecution: SandboxExecutor;
2715
+ /**
2716
+ * Single session-level registry of every analysis (one-shot + recurring) across all entry kinds.
2717
+ * Replaces the old per-entry `_analyses` stores: results live here, and per-entry views read them
2718
+ * on demand via {@link Analyses.getAnalysesForEntry}. `analyseData` registers; `monitorAnalysis`
2719
+ * toggles `enabled`.
2720
+ */
2721
+ analyses: Analyses;
2722
+ /**
2723
+ * Generic trackers — thin rising-edge reducers over `type: 'tracker-rule'` analyses. A tracker fires
2724
+ * an alert/event when its rule's {@link Verdict} crosses an edge. Reacts to `analysis-change`; owns
2725
+ * the durable {@link TrackerEvent} log.
2726
+ */
2727
+ trackers: TrackerState;
2188
2728
  };
2189
2729
 
2190
2730
  /**
2191
- * A single aggregation/analysis result attached to a traffic-area-analytics entry.
2192
- * Produced by `analyseData`; `data` is whatever the dynamic code returned.
2731
+ * One tracker = one rule: an NL condition compiled to a `tracker-rule` analysis, plus its rising-edge
2732
+ * state. Inputs are NOT stored — read `analyses.get(analysisId)?.affectedEntryIds` for the entries the
2733
+ * rule watches, and a verdict's `members` for the ones that matched.
2734
+ */
2735
+ export declare type Tracker = {
2736
+ id: string;
2737
+ name: string;
2738
+ /** Natural-language condition (LLM-readable); the compiled code lives on the analysis record. */
2739
+ rule: string;
2740
+ /** The `type: 'tracker-rule'` analysis whose `data` is this tracker's {@link Verdict}. */
2741
+ analysisId: string;
2742
+ enabled: boolean;
2743
+ /** The one bit of memory: was the verdict active at the last recompute (rising-edge detection). */
2744
+ wasActive: boolean;
2745
+ /** Rising-edge time of the current open episode, if active. */
2746
+ openedAt?: number;
2747
+ };
2748
+
2749
+ /** One thing that happened — a tracker crossing an edge. The durable record the feed + toasts render. */
2750
+ export declare type TrackerEvent = {
2751
+ id: string;
2752
+ /** Source-data sample time of the recompute that fired it (NOT wall-clock). */
2753
+ at: number;
2754
+ trackerId: string;
2755
+ trackerName: string;
2756
+ type: TrackerEventType;
2757
+ kind: TrackerEventStatus;
2758
+ summary: string;
2759
+ members: MemberRef[];
2760
+ /** Rising-edge time of the episode this closes — present on `resolved`. */
2761
+ openedAt?: number;
2762
+ };
2763
+
2764
+ /** Rising edge (`opened`) vs falling edge (`resolved`). The only two statuses in the rising-edge model. */
2765
+ export declare type TrackerEventStatus = 'opened' | 'resolved';
2766
+
2767
+ /** Toast-worthy (`alert`) vs quietly-logged (`event`). One store; the type discriminates. */
2768
+ export declare type TrackerEventType = 'alert' | 'event';
2769
+
2770
+ /**
2771
+ * Generic tracker store + rising-edge reducer. Held on `ToolState` as `state.trackers`. Pure
2772
+ * storage + reaction: tools register/unregister trackers and register their rule analyses in
2773
+ * {@link Analyses}; this slice maps analysis → tracker and reacts to `analysis-change`.
2193
2774
  *
2194
2775
  * @group Agent Toolkit
2195
2776
  */
2196
- export declare type TrafficAreaAnalyticsAnalysis = {
2197
- /** Unique name within the parent entry (used as a key for future UI). */
2198
- name: string;
2199
- timestamp: number;
2200
- /** Optional human-readable description of what the analysis computed. */
2201
- description?: string;
2202
- /** How `data` should be interpreted — plain JSON or a Chart.js configuration. */
2203
- outputFormat: AnalysisOutputFormat;
2204
- /** Arbitrary aggregation result returned by the dynamic code. */
2205
- data: unknown;
2777
+ declare class TrackerState implements StateSlice {
2778
+ private readonly analyses;
2779
+ private readonly _trackers;
2780
+ private readonly _log;
2781
+ /** analysisId → tracker id, so the reducer ignores non-tracker analyses in O(1). */
2782
+ private readonly _trackerByAnalysis;
2783
+ private _seq;
2784
+ readonly events: StateEvents<TrackerStateEvents>;
2785
+ /**
2786
+ * Subscribe the rising-edge reducer to the analyses registry for the slice's lifetime. The
2787
+ * subscription is never torn down (it no-ops without trackers); `reset()` only clears data.
2788
+ */
2789
+ constructor(analyses: Analyses);
2790
+ /**
2791
+ * Register a tracker and index its analysis. The caller (createTracker) registers it with the
2792
+ * rising-edge bit clear (`wasActive: false`) and attaches the arm-time verdict AFTER this call, so a
2793
+ * condition already live at creation crosses false→true through the reducer and fires one `opened`
2794
+ * alert. Emits `trackers-change`.
2795
+ */
2796
+ register(tracker: Tracker): void;
2797
+ /** Remove a tracker and drop its analysis index entry. Returns the analysis id it freed, if any. */
2798
+ unregister(trackerId: string): string | undefined;
2799
+ /** Pause/resume a tracker. A paused tracker never crosses edges. Emits `trackers-change`. */
2800
+ setEnabled(trackerId: string, enabled: boolean): boolean;
2801
+ /** Live trackers, as copies so external mutation can't corrupt the store. */
2802
+ get trackers(): readonly Tracker[];
2803
+ get(trackerId: string): Tracker | undefined;
2804
+ /**
2805
+ * Trackers whose rule reads `entryId` — e.g. every tracker watching a given incidents area. Derived
2806
+ * from each rule's analysis `affectedEntryIds` (the inputs are NOT duplicated onto the tracker), so it
2807
+ * stays correct if a rule's inputs change. Drives "show all trackers for this area" in the UI.
2808
+ */
2809
+ trackersForEntry(entryId: string): readonly Tracker[];
2810
+ /**
2811
+ * The event log, newest last. Pass `sinceMs` to filter and `trackerId` to scope. Returns deep copies
2812
+ * so a caller can't mutate a logged event (or its `members`) and corrupt the store.
2813
+ */
2814
+ log(opts?: {
2815
+ trackerId?: string;
2816
+ sinceMs?: number;
2817
+ }): readonly TrackerEvent[];
2818
+ /** Clear all trackers + log. The analyses subscription persists (no-ops without trackers). */
2819
+ reset(): void;
2820
+ private onAnalysisChange;
2821
+ private append;
2822
+ private emitTrackers;
2823
+ }
2824
+
2825
+ /** Events emitted by {@link TrackerState}. */
2826
+ export declare type TrackerStateEvents = {
2827
+ /** The tracker set or a tracker's live state changed (create / clear / enable / edge crossed). */
2828
+ 'trackers-change': readonly Tracker[];
2829
+ /** A tracker crossed an edge — the freshly-logged event (drives toasts + feed append). */
2830
+ 'tracker-event': TrackerEvent;
2206
2831
  };
2207
2832
 
2833
+ /**
2834
+ * A single analysis result attached to a traffic-area-analytics entry. Alias of the shared
2835
+ * {@link EntryAnalysis} — every entry kind uses the same result shape; kept as a named export for
2836
+ * back-compat.
2837
+ *
2838
+ * @group Agent Toolkit
2839
+ */
2840
+ export declare type TrafficAreaAnalyticsAnalysis = EntryAnalysis;
2841
+
2208
2842
  /**
2209
2843
  * A single entry in the traffic-area-analytics history — output of one
2210
2844
  * `getTrafficAreaAnalytics` call. Mirrors the per-entry model used by
@@ -2232,8 +2866,6 @@ export declare type TrafficAreaAnalyticsEntry = {
2232
2866
  _configChangeUnsub?: () => void;
2233
2867
  /** True while `_module` is rendering on the map. */
2234
2868
  _shown?: boolean;
2235
- /** Accumulated analysis results linked to this entry. */
2236
- _analysis?: TrafficAreaAnalyticsAnalysis[];
2237
2869
  };
2238
2870
 
2239
2871
  /**
@@ -2274,12 +2906,6 @@ declare class TrafficAreaAnalyticsState implements ShownEntriesSlice, StateSlice
2274
2906
  * replaces every older one (hidden + dropped from history).
2275
2907
  */
2276
2908
  addEntry(data: TrafficAreaAnalytics, label: string, params: TrafficAreaAnalyticsParams, explicitId?: string): Promise<string>;
2277
- /**
2278
- * Attach an analysis to an existing entry. Replaces the prior result keyed by `analysis.name`
2279
- * on the same entry (mirrors PlacesState / CustomGeometriesState semantics). Returns `false`
2280
- * when the entry id is unknown.
2281
- */
2282
- addAnalysisToEntry(entryId: string, analysis: TrafficAreaAnalyticsAnalysis): boolean;
2283
2909
  /**
2284
2910
  * Lazy-init the entry's TrafficAreaAnalyticsModule, wiring per-entry config-change events.
2285
2911
  * Throws on unknown id.
@@ -2307,8 +2933,15 @@ declare class TrafficAreaAnalyticsState implements ShownEntriesSlice, StateSlice
2307
2933
  * @group Agent Toolkit
2308
2934
  */
2309
2935
  declare type TrafficAreaAnalyticsStateEvents = {
2310
- /** History changed — entry added, removed, or cleared via `reset()`. */
2311
- 'entries-change': readonly TrafficAreaAnalyticsEntry[];
2936
+ /**
2937
+ * History changed — entry added, removed, or cleared via `reset()`. `entries` is the full
2938
+ * snapshot after the change; `changedIds` lists the ids of the entries this specific change
2939
+ * added, replaced in place, or removed.
2940
+ */
2941
+ 'entries-change': {
2942
+ entries: readonly TrafficAreaAnalyticsEntry[];
2943
+ changedIds: readonly string[];
2944
+ };
2312
2945
  /** Set of entries currently rendered on the map changed. */
2313
2946
  'shown-change': ReadonlySet<string>;
2314
2947
  /** Per-entry viz config changed (mode / metric / scaleMode / …). */
@@ -2316,18 +2949,15 @@ declare type TrafficAreaAnalyticsStateEvents = {
2316
2949
  entryId: string;
2317
2950
  config: TrafficAreaAnalyticsConfig | undefined;
2318
2951
  };
2319
- /** A new analysis was attached to (or replaced on) an entry. */
2320
- 'analysis-added': {
2321
- entryId: string;
2322
- analysis: TrafficAreaAnalyticsAnalysis;
2323
- };
2324
2952
  /** Display policy switched between `single` and `multiple`. */
2325
2953
  'mode-change': EntryMode;
2326
2954
  };
2327
2955
 
2328
2956
  /**
2329
2957
  * A single traffic-incidents history entry: the captured query, its incidents, and the
2330
- * per-entry rendering / monitoring / analysis state owned by {@link TrafficIncidentsState}.
2958
+ * per-entry rendering / monitoring state owned by {@link TrafficIncidentsState}. `analyseData` analyses
2959
+ * are NOT held here — they live in the session-level `state.analyses` registry. Clustering IS dedicated
2960
+ * incidents state, kept in the slice's `clusters` map keyed by entry id (see {@link ClustersRecord}).
2331
2961
  *
2332
2962
  * @group Agent Toolkit
2333
2963
  */
@@ -2345,12 +2975,6 @@ export declare type TrafficIncidentsEntry = {
2345
2975
  params: TrafficIncidentDetailsByBBoxParams & {
2346
2976
  bbox: BBox;
2347
2977
  };
2348
- /**
2349
- * Per-entry analysis registry: owns re-executing specs *and* their results.
2350
- * Lazy-created on first {@link TrafficIncidentsState.setAnalysisSpec} or
2351
- * {@link TrafficIncidentsState.addAnalysisToEntry}.
2352
- */
2353
- _analyses?: IncidentsAnalyses;
2354
2978
  /**
2355
2979
  * Per-entry incident-details module. Each entry owns its own module so two areas can be
2356
2980
  * rendered on the map at the same time without sharing a single source. Lazy-initialised
@@ -2377,7 +3001,7 @@ export declare type TrafficIncidentsEntry = {
2377
3001
  * Monotonic counter incremented on every {@link TrafficIncidentsState.replaceEntryData}
2378
3002
  * call. Used to detect out-of-order ticks: if a slow `module.show()` from an earlier
2379
3003
  * tick resolves after a newer tick has already overwritten `data`, the late path bails
2380
- * before reapplying focus / replaying analyses.
3004
+ * before reapplying focus.
2381
3005
  */
2382
3006
  _tick?: number;
2383
3007
  };
@@ -2396,6 +3020,7 @@ declare class TrafficIncidentsState implements StateSlice {
2396
3020
  private readonly _ttMap;
2397
3021
  private _entries;
2398
3022
  private _entryMode;
3023
+ private readonly _clusters;
2399
3024
  /** Subscribe to state changes — see {@link TrafficIncidentsStateEvents}. */
2400
3025
  readonly events: StateEvents<TrafficIncidentsStateEvents>;
2401
3026
  constructor(_ttMap: TomTomMap);
@@ -2427,9 +3052,11 @@ declare class TrafficIncidentsState implements StateSlice {
2427
3052
  * Replace an entry's data in place and repaint its module without moving the camera.
2428
3053
  * Used by the per-entry monitor tick so incident geometries and any open detail
2429
3054
  * panel stay in sync with the freshest snapshot. `sampledAt` is the canonical moment
2430
- * for this update — propagated to the entry timestamp and to every analysis the
2431
- * registry replays, so all derived data shares one tick. Returns true on success,
2432
- * false when the entry is unknown.
3055
+ * for this update — propagated to the entry timestamp before the `entries-change` emit.
3056
+ * That emit is what drives analysis replay: the session-level standing sweep
3057
+ * (`tools/state/analyses-runtime.ts`) re-runs every analysis bound to this entry off the
3058
+ * change, ASYNCHRONOUSLY (a microtask later) — the slice itself owns no analysis logic.
3059
+ * Returns true on success, false when the entry is unknown.
2433
3060
  */
2434
3061
  replaceEntryData(entryId: TrafficIncidentsEntry['id'], data: TrafficIncident[], sampledAt: number): Promise<boolean>;
2435
3062
  /**
@@ -2456,19 +3083,17 @@ declare class TrafficIncidentsState implements StateSlice {
2456
3083
  */
2457
3084
  removeEntry(entryId: TrafficIncidentsEntry['id']): Promise<void>;
2458
3085
  /**
2459
- * Attach an analysis result to an existing entry. Names are unique within a single
2460
- * entry — adding one with an existing name replaces it. Returns true on success, false
2461
- * if the entry doesn't exist. Lazy-creates the entry's {@link IncidentsAnalyses}.
2462
- */
2463
- addAnalysisToEntry(entryId: TrafficIncidentsEntry['id'], analysis: IncidentsAnalysis): boolean;
2464
- /**
2465
- * Register (or replace) a re-executing analysis spec on its source entry. Names are unique
2466
- * per entry — adding one with an existing name replaces it. Lazy-creates the entry's
2467
- * {@link IncidentsAnalyses} on first call.
2468
- */
2469
- setAnalysisSpec(spec: IncidentsAnalysisSpec): void;
2470
- /** Remove a spec by name from every entry that carries it. */
2471
- removeAnalysisSpec(name: string): void;
3086
+ * Store (or replace) an entry's clustering record and emit `clusters-change`. No-op when the entry is
3087
+ * unknown — clusters are entry-scoped, so an orphan record is meaningless. Called by `clusterIncidents`
3088
+ * and its monitor-tick re-run (`tools/state/clusters-runtime.ts`).
3089
+ */
3090
+ setClusters(entryId: TrafficIncidentsEntry['id'], record: ClustersRecord): void;
3091
+ /** Latest clustering output for an entry, or undefined when none has been computed. */
3092
+ getClusters(entryId: TrafficIncidentsEntry['id']): SandboxClusteringOutput | undefined;
3093
+ /** Full clustering record (recipe + output) for an entry — the re-run reads this back. */
3094
+ getClustersRecord(entryId: TrafficIncidentsEntry['id']): ClustersRecord | undefined;
3095
+ /** Drop an entry's clustering. Emits `clusters-change` with `null`. No-op when nothing is stored. */
3096
+ clearClusters(entryId: TrafficIncidentsEntry['id']): void;
2472
3097
  /** Snapshot of an entry's focus subset, or null when no focus is active or the entry is unknown. */
2473
3098
  getFocus(entryId: TrafficIncidentsEntry['id']): IncidentFocus | null;
2474
3099
  /**
@@ -2505,6 +3130,7 @@ declare class TrafficIncidentsState implements StateSlice {
2505
3130
  */
2506
3131
  startMonitoring(entryId: TrafficIncidentsEntry['id'], area: MonitoredArea, deps: IncidentMonitorDeps, opts?: {
2507
3132
  intervalMs?: number;
3133
+ skipInitialTick?: boolean;
2508
3134
  }): void;
2509
3135
  /** Stop polling. No-op when idle or unknown. */
2510
3136
  stopMonitoring(entryId: TrafficIncidentsEntry['id']): void;
@@ -2517,7 +3143,6 @@ declare class TrafficIncidentsState implements StateSlice {
2517
3143
  isMonitored(entryId: TrafficIncidentsEntry['id']): boolean;
2518
3144
  private _createMonitorForEntry;
2519
3145
  private _requireEntry;
2520
- private _rerunRegisteredSpecs;
2521
3146
  private _teardownEntry;
2522
3147
  /**
2523
3148
  * Hide every traffic-incidents entry currently on the map. Map-only — history is kept.
@@ -2534,20 +3159,18 @@ declare class TrafficIncidentsState implements StateSlice {
2534
3159
  * @group Agent Toolkit
2535
3160
  */
2536
3161
  declare type TrafficIncidentsStateEvents = {
2537
- /** History changed — entry added, replaced, removed, or cleared via `reset()`. Payload: full entries snapshot. */
2538
- 'entries-change': readonly TrafficIncidentsEntry[];
2539
- /** Set of entries currently rendered on the map changed. */
2540
- 'shown-change': ReadonlySet<TrafficIncidentsEntry['id']>;
2541
3162
  /**
2542
- * A fresh analysis result landed on an entry. Fires when a spec is first
2543
- * registered, when it is replaced under the same name, AND on every monitor-tick
2544
- * replay — the event is intentionally agnostic about whether the spec is new
2545
- * or recomputed; subscribers re-render off the latest result either way.
3163
+ * History changed — entry added, replaced, removed, or cleared via `reset()`. `entries` is the
3164
+ * full snapshot after the change; `changedIds` lists the ids of the entries this specific change
3165
+ * added, replaced in place, or removed. The standing-analysis sweep and the clustering re-run
3166
+ * match these against the entries they depend on.
2546
3167
  */
2547
- 'analysis-change': {
2548
- entryId: TrafficIncidentsEntry['id'];
2549
- analysis: IncidentsAnalysis;
3168
+ 'entries-change': {
3169
+ entries: readonly TrafficIncidentsEntry[];
3170
+ changedIds: readonly string[];
2550
3171
  };
3172
+ /** Set of entries currently rendered on the map changed. */
3173
+ 'shown-change': ReadonlySet<TrafficIncidentsEntry['id']>;
2551
3174
  /** Focus subset for an entry changed (or cleared, when `focus === null`). */
2552
3175
  'focus-change': {
2553
3176
  entryId: TrafficIncidentsEntry['id'];
@@ -2582,6 +3205,15 @@ declare type TrafficIncidentsStateEvents = {
2582
3205
  entryId: TrafficIncidentsEntry['id'];
2583
3206
  reason: 'manual' | 'error';
2584
3207
  };
3208
+ /**
3209
+ * An entry's clustering result changed — set, recomputed on a tick, or cleared (`clusters === null`).
3210
+ * UI that renders cluster pins re-derives off this (not `entries-change`): the clustering re-run is
3211
+ * async to the monitor tick, so the fresh result lands here a step later.
3212
+ */
3213
+ 'clusters-change': {
3214
+ entryId: TrafficIncidentsEntry['id'];
3215
+ clusters: SandboxClusteringOutput | null;
3216
+ };
2585
3217
  };
2586
3218
 
2587
3219
  /**
@@ -2604,4 +3236,26 @@ declare class TrafficTilesState implements StateSlice {
2604
3236
  reset(): void;
2605
3237
  }
2606
3238
 
3239
+ /**
3240
+ * What a tracker's sandboxed `evaluateCode` returns — stored verbatim as the rule analysis's `data`
3241
+ * and read back by the reducer. `members` groups matches by source entry so a verdict can span
3242
+ * multiple entry kinds (e.g. "an incident within 100m of a hospital" returns a hospitals group and an
3243
+ * incidents group). `summary` is a grounded headline the code produces deterministically.
3244
+ */
3245
+ export declare type Verdict = {
3246
+ active: boolean;
3247
+ members: MemberRef[];
3248
+ summary: string;
3249
+ };
3250
+
2607
3251
  export { }
3252
+
3253
+
3254
+
3255
+ // Build-time virtual module (see the `sandbox-sdk-utils` plugin in
3256
+ // `vite-sandbox-build.ts`): the bundled SDK worker-utilities IIFE (`routeUtils`, …),
3257
+ // inlined as a string into the worker source. Declared here so `tsc` treats it as a
3258
+ // string module.
3259
+ declare module 'virtual:sandbox-sdk-utils' {
3260
+ export const source: string;
3261
+ }