@agent-native/core 0.153.6 → 0.153.9
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/corpus/templates/slides/app/components/editor/SlideEditor.tsx +5 -3
- package/dist/agent/run-store.d.ts +14 -1
- package/dist/agent/run-store.js +40 -7
- package/dist/client/hooks/index.d.ts +1 -1
- package/dist/client/hooks/index.js +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/progress/routes.d.ts +1 -1
- package/dist/server/agent-chat-plugin.js +4 -1
- package/dist/server/realtime-token.d.ts +1 -1
- package/docs/content/client-advanced.mdx +200 -0
- package/docs/content/client-agent-chat.mdx +313 -0
- package/docs/content/client-data.mdx +432 -0
- package/docs/content/client-entry-points.mdx +27 -0
- package/docs/content/client-overview.mdx +206 -0
- package/docs/content/client-routing.mdx +121 -0
- package/docs/content/client-sync-internals.mdx +124 -0
- package/docs/content/context-awareness.mdx +1 -1
- package/docs/content/docs-components.mdx +201 -0
- package/docs/content/drop-in-agent.mdx +1 -1
- package/docs/content/internationalization.mdx +1 -1
- package/docs/content/key-concepts.mdx +4 -0
- package/docs/content/notifications.mdx +1 -1
- package/docs/content/progress.mdx +2 -2
- package/docs/content/real-time-collaboration.mdx +2 -2
- package/docs/content/template-chat-developers.mdx +1 -1
- package/docs/content/toolkit-command-navigation.mdx +1 -1
- package/package.json +1 -1
- package/docs/content/client.mdx +0 -486
- package/docs/content/routing.mdx +0 -119
|
@@ -5003,15 +5003,17 @@ export default function SlideEditor({
|
|
|
5003
5003
|
const objectOperationSelection = getObjectOperationSelection();
|
|
5004
5004
|
const hasObjectSelection = objectOperationSelection !== null;
|
|
5005
5005
|
const objectSelectionCount = objectOperationSelection?.elements.length ?? 0;
|
|
5006
|
+
const canZoomOut = canvasZoom > MIN_CANVAS_ZOOM;
|
|
5007
|
+
const canZoomIn =
|
|
5008
|
+
canvasZoom < CANVAS_ZOOM_PRESETS[CANVAS_ZOOM_PRESETS.length - 1];
|
|
5006
5009
|
const zoomControls = slide.excalidrawData
|
|
5007
5010
|
? undefined
|
|
5008
5011
|
: {
|
|
5009
5012
|
value: canvasZoom,
|
|
5010
5013
|
onZoomOut: canvasZoomOut,
|
|
5011
5014
|
onZoomIn: canvasZoomIn,
|
|
5012
|
-
canZoomOut
|
|
5013
|
-
canZoomIn
|
|
5014
|
-
canvasZoom < CANVAS_ZOOM_PRESETS[CANVAS_ZOOM_PRESETS.length - 1],
|
|
5015
|
+
canZoomOut,
|
|
5016
|
+
canZoomIn,
|
|
5015
5017
|
};
|
|
5016
5018
|
|
|
5017
5019
|
// Excalidraw slides have no selectable slide content, so the row collapses
|
|
@@ -681,13 +681,26 @@ export declare function listRunsForThread(threadId: string, options?: {
|
|
|
681
681
|
* Best-effort on parse: malformed ledger rows are skipped rather than thrown.
|
|
682
682
|
*/
|
|
683
683
|
export declare function getCurrentTurnEventsForThread(threadId: string, knownTurnId?: string): Promise<AgentChatEvent[]>;
|
|
684
|
+
export interface StaleRunReapResult {
|
|
685
|
+
/** Rows this pass reaped. */
|
|
686
|
+
reaped: number;
|
|
687
|
+
/**
|
|
688
|
+
* Rows whose reap threw. Separate from `reaped` because a pass where every
|
|
689
|
+
* row failed must not read as "nothing was stale" — those were the same
|
|
690
|
+
* number before, and the caller's own `null`-vs-`0` distinction is worthless
|
|
691
|
+
* while the per-row failures underneath it are coerced away.
|
|
692
|
+
*/
|
|
693
|
+
failed: number;
|
|
694
|
+
/** More stale rows remain than the batch cap; the next tick continues. */
|
|
695
|
+
truncated: boolean;
|
|
696
|
+
}
|
|
684
697
|
/**
|
|
685
698
|
* Expire any "running" rows whose heartbeat is stale — producer died.
|
|
686
699
|
* Safe to call at server startup on multi-isolate deployments: only rows
|
|
687
700
|
* without a fresh heartbeat get reaped, so runs owned by OTHER live
|
|
688
701
|
* isolates (which keep heartbeating) are left alone.
|
|
689
702
|
*/
|
|
690
|
-
export declare function reapAllStaleRuns(): Promise<
|
|
703
|
+
export declare function reapAllStaleRuns(): Promise<StaleRunReapResult>;
|
|
691
704
|
/**
|
|
692
705
|
* Read the daily terminal-outcome counters rolled up from pruned runs. Pair
|
|
693
706
|
* with live `agent_runs` rows for a complete picture — see
|
package/dist/agent/run-store.js
CHANGED
|
@@ -2253,6 +2253,18 @@ export async function getCurrentTurnEventsForThread(threadId, knownTurnId) {
|
|
|
2253
2253
|
}
|
|
2254
2254
|
return events;
|
|
2255
2255
|
}
|
|
2256
|
+
/**
|
|
2257
|
+
* Rows one pass will reap. Each costs ~5-10 serial round trips (reconcile,
|
|
2258
|
+
* transactional reap plus recovery, terminal-event append), and this sweep now
|
|
2259
|
+
* runs ahead of `processRecurringJobs` on the shared durable tick — so an
|
|
2260
|
+
* unbounded first pass against a backlog spends the platform wall before jobs
|
|
2261
|
+
* get any. Production carried 1,216 stale rows while nothing periodic reaped
|
|
2262
|
+
* them, so the first tick after that fix lands on exactly such a backlog.
|
|
2263
|
+
* A truncated pass is drained by the next tick (60s durable, 20s in-process),
|
|
2264
|
+
* never dropped — `truncated` is what stops a partial sweep being reported as
|
|
2265
|
+
* a clean one.
|
|
2266
|
+
*/
|
|
2267
|
+
const REAP_ALL_STALE_BATCH_LIMIT = 200;
|
|
2256
2268
|
/**
|
|
2257
2269
|
* Expire any "running" rows whose heartbeat is stale — producer died.
|
|
2258
2270
|
* Safe to call at server startup on multi-isolate deployments: only rows
|
|
@@ -2260,9 +2272,14 @@ export async function getCurrentTurnEventsForThread(threadId, knownTurnId) {
|
|
|
2260
2272
|
* isolates (which keep heartbeating) are left alone.
|
|
2261
2273
|
*/
|
|
2262
2274
|
export async function reapAllStaleRuns() {
|
|
2275
|
+
const nothingStale = {
|
|
2276
|
+
reaped: 0,
|
|
2277
|
+
failed: 0,
|
|
2278
|
+
truncated: false,
|
|
2279
|
+
};
|
|
2263
2280
|
await ensureRunTables();
|
|
2264
2281
|
if (!(await hasRunningRuns()))
|
|
2265
|
-
return
|
|
2282
|
+
return nothingStale;
|
|
2266
2283
|
const client = getDbExec();
|
|
2267
2284
|
const now = Date.now();
|
|
2268
2285
|
// Background-dispatched runs use the wider window; everything else 15s. The
|
|
@@ -2274,13 +2291,23 @@ export async function reapAllStaleRuns() {
|
|
|
2274
2291
|
// server startup across possibly-multiple isolates, so a sibling isolate's
|
|
2275
2292
|
// still-alive, in-flight run must not be reaped just because THIS isolate
|
|
2276
2293
|
// just booted and has no heartbeat history for it.
|
|
2277
|
-
|
|
2294
|
+
// One row over the cap is how a truncated pass is detected without a second
|
|
2295
|
+
// COUNT query against the same predicate.
|
|
2296
|
+
const scanned = await client.execute({
|
|
2278
2297
|
sql: `SELECT id FROM agent_runs
|
|
2279
2298
|
WHERE status = 'running'
|
|
2280
2299
|
AND ${livenessBasisSql()} < ${backgroundAwareStaleCutoffSql()}
|
|
2281
|
-
AND ${inFlightGraceSql()}
|
|
2300
|
+
AND ${inFlightGraceSql()}
|
|
2301
|
+
ORDER BY started_at ASC
|
|
2302
|
+
LIMIT ${REAP_ALL_STALE_BATCH_LIMIT + 1}`,
|
|
2282
2303
|
args: [now, now, now],
|
|
2283
2304
|
});
|
|
2305
|
+
const truncated = scanned.rows.length > REAP_ALL_STALE_BATCH_LIMIT;
|
|
2306
|
+
const stale = {
|
|
2307
|
+
rows: truncated
|
|
2308
|
+
? scanned.rows.slice(0, REAP_ALL_STALE_BATCH_LIMIT)
|
|
2309
|
+
: scanned.rows,
|
|
2310
|
+
};
|
|
2284
2311
|
for (const row of stale.rows) {
|
|
2285
2312
|
const id = row.id;
|
|
2286
2313
|
if (typeof id === "string") {
|
|
@@ -2299,13 +2326,19 @@ export async function reapAllStaleRuns() {
|
|
|
2299
2326
|
// already found stale; on an idle app `hasRunningRuns` returns before any of
|
|
2300
2327
|
// this, so the 20s fast sweep (agent-chat-plugin.ts) costs one probe.
|
|
2301
2328
|
let reapedCount = 0;
|
|
2329
|
+
let failedCount = 0;
|
|
2302
2330
|
for (const row of stale.rows) {
|
|
2303
2331
|
const id = row.id;
|
|
2304
2332
|
if (typeof id !== "string")
|
|
2305
2333
|
continue;
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2334
|
+
try {
|
|
2335
|
+
if (await reapSingleStaleRun(id))
|
|
2336
|
+
reapedCount += 1;
|
|
2337
|
+
}
|
|
2338
|
+
catch (error) {
|
|
2339
|
+
failedCount += 1;
|
|
2340
|
+
console.error(`[run-store] stale reap failed for run ${id}:`, error);
|
|
2341
|
+
}
|
|
2309
2342
|
}
|
|
2310
2343
|
for (const row of stale.rows) {
|
|
2311
2344
|
const id = row.id;
|
|
@@ -2313,7 +2346,7 @@ export async function reapAllStaleRuns() {
|
|
|
2313
2346
|
await safeAppendTerminalRunEvent(id, STALE_RUN_ERROR_EVENT, "reap-all-stale");
|
|
2314
2347
|
}
|
|
2315
2348
|
}
|
|
2316
|
-
return reapedCount;
|
|
2349
|
+
return { reaped: reapedCount, failed: failedCount, truncated };
|
|
2317
2350
|
}
|
|
2318
2351
|
const RUN_OUTCOME_DAY_MS = 86_400_000;
|
|
2319
2352
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { getBrowserTabId } from "../browser-tab-id.js";
|
|
2
|
-
export { deleteClientAppState, readClientAppState, setClientAppState, writeClientAppState, type ClientAppStateReadOptions, type ClientAppStateWriteOptions, } from "../application-state.js";
|
|
2
|
+
export { deleteClientAppState, readClientAppState, readClientAppStateMany, setClientAppState, writeClientAppState, type ClientAppStateBatch, type ClientAppStateReadOptions, type ClientAppStateWriteOptions, } from "../application-state.js";
|
|
3
3
|
export { useDbSync, useFileWatcher, useScreenRefreshKey, } from "../use-db-sync.js";
|
|
4
4
|
export { useChangeVersion, useChangeVersions, getChangeVersion, bumpChangeVersion, } from "../use-change-version.js";
|
|
5
5
|
export { useDemoModeStatus, type DemoModeStatus, } from "../use-demo-mode-status.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { getBrowserTabId } from "../browser-tab-id.js";
|
|
2
|
-
export { deleteClientAppState, readClientAppState, setClientAppState, writeClientAppState, } from "../application-state.js";
|
|
2
|
+
export { deleteClientAppState, readClientAppState, readClientAppStateMany, setClientAppState, writeClientAppState, } from "../application-state.js";
|
|
3
3
|
export { useDbSync, useFileWatcher, useScreenRefreshKey, } from "../use-db-sync.js";
|
|
4
4
|
export { useChangeVersion, useChangeVersions, getChangeVersion, bumpChangeVersion, } from "../use-change-version.js";
|
|
5
5
|
export { useDemoModeStatus, } from "../use-demo-mode-status.js";
|
|
@@ -62,11 +62,11 @@ export declare const postAwareness: import("h3").EventHandlerWithFetch<import("h
|
|
|
62
62
|
error: string;
|
|
63
63
|
states?: undefined;
|
|
64
64
|
} | {
|
|
65
|
-
error?: undefined;
|
|
66
65
|
states: {
|
|
67
66
|
clientId: number;
|
|
68
67
|
state: string;
|
|
69
68
|
}[];
|
|
69
|
+
error?: undefined;
|
|
70
70
|
}>>;
|
|
71
71
|
/**
|
|
72
72
|
* GET /_agent-native/collab/:docId/users
|
|
@@ -77,9 +77,9 @@ export declare const getActiveUsers: import("h3").EventHandlerWithFetch<import("
|
|
|
77
77
|
error: string;
|
|
78
78
|
users?: undefined;
|
|
79
79
|
} | {
|
|
80
|
-
error?: undefined;
|
|
81
80
|
users: {
|
|
82
81
|
clientId: number;
|
|
83
82
|
lastSeen: number;
|
|
84
83
|
}[];
|
|
84
|
+
error?: undefined;
|
|
85
85
|
}>>;
|
|
@@ -4907,7 +4907,10 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
|
|
|
4907
4907
|
//
|
|
4908
4908
|
// Reported, never swallowed, and never fatal to the job sweep: a
|
|
4909
4909
|
// reap failure must not take recurring jobs down with it, and
|
|
4910
|
-
// `null` must stay distinguishable from "reaped nothing".
|
|
4910
|
+
// `null` must stay distinguishable from "reaped nothing". The
|
|
4911
|
+
// result carries `failed` and `truncated` for the same reason one
|
|
4912
|
+
// level down — a pass where every row threw, and a pass that hit
|
|
4913
|
+
// the batch cap, both used to be reportable as a clean sweep.
|
|
4911
4914
|
const { reapAllStaleRuns } = await import("../agent/run-store.js");
|
|
4912
4915
|
const staleRunsReaped = await reapAllStaleRuns().catch((error) => {
|
|
4913
4916
|
console.error("[agent-chat] durable stale-run reap failed:", error);
|
|
@@ -26,8 +26,8 @@ export declare function createRealtimeTokenHandler(): import("h3").EventHandlerW
|
|
|
26
26
|
expiresAt?: undefined;
|
|
27
27
|
ttlSeconds?: undefined;
|
|
28
28
|
} | {
|
|
29
|
+
error?: undefined;
|
|
29
30
|
token: string;
|
|
30
31
|
expiresAt: string;
|
|
31
32
|
ttlSeconds: number;
|
|
32
|
-
error?: undefined;
|
|
33
33
|
}>>;
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Advanced"
|
|
3
|
+
description: "Lower-level app/ primitives and escape hatches: direct application-state access, background chat sends, custom suggestion chips, and the MCP App host bridge."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Advanced
|
|
7
|
+
|
|
8
|
+
Overview, Data & Sync, Agent Chat, and Routing cover the typical case: the hooks and helpers most pages need. This page covers the lower-level primitives and narrower escape hatches underneath them. Most apps never touch these directly. Reach for one when you're building something more custom than the default hooks support, such as a custom selection sync or an embedded MCP App. For syncing a raw `useQuery` with a hand-rolled key, see [Sync Internals](/docs/client-sync-internals) instead.
|
|
9
|
+
|
|
10
|
+
## Application State {#application-state}
|
|
11
|
+
|
|
12
|
+
`application_state` is the SQL-backed key/value store the agent reads to know what the UI is currently looking at, including navigation, selection, and other focused-object context (see [Context Awareness](/docs/context-awareness)). `useAgentRouteState` and `useSemanticNavigationState` (also covered in Context Awareness) already wrap these for the common navigation case. Reach for the primitives below directly when you're syncing something they don't cover, such as a custom selection, or writing from outside a component's render.
|
|
13
|
+
|
|
14
|
+
### readClientAppState {#read-client-app-state}
|
|
15
|
+
|
|
16
|
+
Read the current value for a key. Resolves to whatever was last written for that key, or `null` if the key has never been written.
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import { readClientAppState } from "@agent-native/core/client/hooks";
|
|
20
|
+
|
|
21
|
+
const selection = await readClientAppState("selection");
|
|
22
|
+
// selection is exactly what was last written to that key, e.g.:
|
|
23
|
+
// { kind: "tickets.rows", ids: ["tik_182", "tik_209"] }
|
|
24
|
+
//
|
|
25
|
+
// or, if nothing has written "selection" yet:
|
|
26
|
+
// null
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### writeClientAppState {#write-client-app-state}
|
|
30
|
+
|
|
31
|
+
Write a value for a key. Unlike `setClientAppState` below, it always writes, even if you pass `null`. Passing `undefined` throws instead, since `undefined` isn't valid JSON. Use `setClientAppState(key, undefined)` or `deleteClientAppState()` to clear a key.
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
import { writeClientAppState } from "@agent-native/core/client/hooks";
|
|
35
|
+
|
|
36
|
+
await writeClientAppState(
|
|
37
|
+
"selection",
|
|
38
|
+
{ id: recordId },
|
|
39
|
+
{ requestSource: TAB_ID },
|
|
40
|
+
);
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### setClientAppState {#set-client-app-state}
|
|
44
|
+
|
|
45
|
+
The convenience wrapper most UI code should reach for: writes the value, or deletes the key when you pass `null`/`undefined`. Use this instead of `writeClientAppState` unless a call site genuinely needs "always write, never delete."
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
import { setClientAppState } from "@agent-native/core/client/hooks";
|
|
49
|
+
|
|
50
|
+
await setClientAppState(
|
|
51
|
+
"selection",
|
|
52
|
+
{ id: recordId },
|
|
53
|
+
{ requestSource: TAB_ID },
|
|
54
|
+
);
|
|
55
|
+
await setClientAppState("selection", null); // clears the key, same as deleteClientAppState
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### deleteClientAppState {#delete-client-app-state}
|
|
59
|
+
|
|
60
|
+
Remove a key entirely.
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
import { deleteClientAppState } from "@agent-native/core/client/hooks";
|
|
64
|
+
|
|
65
|
+
await deleteClientAppState("pending-selection-context");
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### readClientAppStateMany {#read-client-app-state-many}
|
|
69
|
+
|
|
70
|
+
Read several keys in one request. Returns `{ values, missing }`. `values` holds an entry for every key the server has a row for, including a key that was explicitly written as `null`. `missing` lists the keys that have never been written at all. This is how "never written" stays distinguishable from "written as null."
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
import { readClientAppStateMany } from "@agent-native/core/client/hooks";
|
|
74
|
+
|
|
75
|
+
const { values, missing } = await readClientAppStateMany([
|
|
76
|
+
"selection",
|
|
77
|
+
"pending-selection-context",
|
|
78
|
+
]);
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Requests over 100 keys are chunked automatically. `readClientAppState()` itself is built on this: calls issued in the same tick are coalesced into one batched request behind the scenes, so calling it many times in a single render pass costs one round trip, not one per call.
|
|
82
|
+
|
|
83
|
+
### Options {#application-state-options}
|
|
84
|
+
|
|
85
|
+
`signal` is accepted by all five functions above, to cancel an in-flight call. `requestSource` and `keepalive` only apply to the three write functions, since they don't mean anything for a read.
|
|
86
|
+
|
|
87
|
+
| Option | Type | Applies to | Description |
|
|
88
|
+
| --------------- | -------------- | ------------------------------------------------------------------ | -------------------------------------------------------------------------------------------- |
|
|
89
|
+
| `requestSource` | `string?` | `writeClientAppState`, `setClientAppState`, `deleteClientAppState` | Tags the write with an id so a matching `useDbSync({ ignoreSource })` can skip refetching it |
|
|
90
|
+
| `keepalive` | `boolean?` | `writeClientAppState`, `setClientAppState`, `deleteClientAppState` | Lets the request outlive page unload. Use for cleanup writes like clearing selection |
|
|
91
|
+
| `signal` | `AbortSignal?` | all five functions | Cancels the request |
|
|
92
|
+
|
|
93
|
+
### Failure behavior {#failure-behavior}
|
|
94
|
+
|
|
95
|
+
All five functions throw on a failed request, a non-2xx response, or a body that can't be parsed. The thrown `Error` carries a `.status` property when the server responded, so a caller can branch on the HTTP status if needed. Wrap a call in `try`/`catch`, or let it propagate to an error boundary, the same as any other network call.
|
|
96
|
+
|
|
97
|
+
### What TAB_ID is {#tab-id}
|
|
98
|
+
|
|
99
|
+
`requestSource` is commonly set to `TAB_ID`, a plain constant every template exports from `app/lib/tab-id.ts`: a short random string that stays the same for the life of a tab and differs between tabs. Pairing `requestSource: TAB_ID` on a write with `useDbSync({ ignoreSource: TAB_ID })` on the read side is how a tab avoids refetching its own write while still reacting to everyone else. See [Context Awareness](/docs/context-awareness#jitter-prevention) for the full walkthrough.
|
|
100
|
+
|
|
101
|
+
## sendToAgentChat: Background Sends {#background-send}
|
|
102
|
+
|
|
103
|
+
Use `background: true` when a UI action should kick off real agent work without
|
|
104
|
+
opening or focusing the sidebar. This still creates a normal chat thread/run,
|
|
105
|
+
uses the agent's tools/actions/context, and keeps the work observable through
|
|
106
|
+
the runs tray. It is not a raw one-shot model call.
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
import { sendToAgentChat } from "@agent-native/core/client/agent-chat";
|
|
110
|
+
|
|
111
|
+
const tabId = sendToAgentChat({
|
|
112
|
+
message: "Analyze this import and create any missing records",
|
|
113
|
+
context: `Import batch id: ${batchId}`,
|
|
114
|
+
submit: true,
|
|
115
|
+
newTab: true,
|
|
116
|
+
background: true,
|
|
117
|
+
openSidebar: false,
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
`background` is intended to be paired with `newTab` so the hidden work does not
|
|
122
|
+
overwrite the user's active conversation. Use the returned `tabId` if the UI
|
|
123
|
+
needs to correlate follow-up status or deep-link into the run later.
|
|
124
|
+
|
|
125
|
+
A few more `AgentChatMessage` options exist beyond the ones covered in [Agent Chat](/docs/client-agent-chat#agentchatmessage), mostly for embedded or downstream-integration cases:
|
|
126
|
+
|
|
127
|
+
| Option | Type | Description |
|
|
128
|
+
| --------------------- | ----------- | -------------------------------------------------------------------------- |
|
|
129
|
+
| `background` | `boolean?` | With `newTab`, run without focusing the tab and show the run in `RunsTray` |
|
|
130
|
+
| `projectSlug` | `string?` | Optional project slug for structured context |
|
|
131
|
+
| `preset` | `string?` | Optional preset name for downstream consumers |
|
|
132
|
+
| `referenceImagePaths` | `string[]?` | Optional reference image paths |
|
|
133
|
+
|
|
134
|
+
## MCP App Host Bridge {#mcp-app-host-bridge}
|
|
135
|
+
|
|
136
|
+
Routes embedded as MCP Apps should be URL-first: load the current artifact from
|
|
137
|
+
path/query params, render the real React route or a focused shared component,
|
|
138
|
+
and use the host bridge only for host-owned behavior. `@agent-native/core/client`
|
|
139
|
+
exports the helpers embedded routes call:
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
import {
|
|
143
|
+
getMcpAppHostContext,
|
|
144
|
+
openMcpAppHostLink,
|
|
145
|
+
requestMcpAppDisplayMode,
|
|
146
|
+
updateMcpAppModelContext,
|
|
147
|
+
useMcpAppHostContext,
|
|
148
|
+
} from "@agent-native/core/client/agent-chat";
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
`getMcpAppHostContext()` reads the latest pushed host context snapshot.
|
|
152
|
+
`useMcpAppHostContext()` subscribes React components to changes. The request
|
|
153
|
+
helpers (`openMcpAppHostLink`, `requestMcpAppDisplayMode`,
|
|
154
|
+
`updateMcpAppModelContext`) return `false` outside an embedded MCP App frame, or
|
|
155
|
+
`Promise<boolean>` inside a frame.
|
|
156
|
+
|
|
157
|
+
`sendToAgentChat()` (see [Agent Chat](/docs/client-agent-chat#sendtoagentchat)) uses the same bridge for auto-submitted prompts from embedded routes. Inside an MCP App embed created with `embedApp()`, auto-submitted messages (`submit` omitted or `true`) are forwarded to the bridge, which asks the containing host to add hidden context and send the visible user turn. `context` stays model-visible without being posted as user-facing chat. `submit: false` keeps the local prefill/review behavior because MCP Apps do not define a standard draft-prefill API. Internally this is the submitted-chat path sometimes surfaced as `agentNative.submitChat`. App code should still call `sendToAgentChat()` rather than posting that event directly.
|
|
158
|
+
|
|
159
|
+
The bridge itself, the `ui/*` JSON-RPC messages, the `agentNative.mcpHost.*`
|
|
160
|
+
wrapper relay, transplant vs. controlled-frame rendering, host context, and
|
|
161
|
+
display-mode requests, is owned by
|
|
162
|
+
[External Agents](/docs/external-agents#mcp-app-bridge). See [MCP Apps](/docs/mcp-apps) for the full protocol.
|
|
163
|
+
|
|
164
|
+
## Custom Suggestion Chips {#custom-suggestions}
|
|
165
|
+
|
|
166
|
+
`<AgentSidebar>`'s `dynamicSuggestions` (see [Agent Chat](/docs/client-agent-chat#dynamic-suggestions)) already merges static `suggestions` with the framework's own context-aware heuristic. Pass a `getSuggestions` function inside `dynamicSuggestions` instead when an app wants deterministic, domain-specific chips computed from the same `application_state` context rather than the framework's default heuristic.
|
|
167
|
+
|
|
168
|
+
```tsx
|
|
169
|
+
<AgentSidebar
|
|
170
|
+
suggestions={["Summarize my inbox"]}
|
|
171
|
+
dynamicSuggestions={{
|
|
172
|
+
getSuggestions: ({ navigation }) => {
|
|
173
|
+
if (
|
|
174
|
+
typeof navigation !== "object" ||
|
|
175
|
+
navigation === null ||
|
|
176
|
+
(navigation as { view?: unknown }).view !== "inbox"
|
|
177
|
+
) {
|
|
178
|
+
return [];
|
|
179
|
+
}
|
|
180
|
+
const label = (navigation as { label?: unknown }).label;
|
|
181
|
+
return [`Summarize ${typeof label === "string" ? label : "this"} label`];
|
|
182
|
+
},
|
|
183
|
+
}}
|
|
184
|
+
>
|
|
185
|
+
<App />
|
|
186
|
+
</AgentSidebar>
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
`getSuggestions` receives the same `navigation`/`selection`/`pendingSelection` snapshot the built-in heuristic reads, typed as `unknown` since its shape is app-defined, so narrow it before reading fields. A custom implementation can react to exactly the state the default one does, just with app-specific rules instead of generic ones.
|
|
190
|
+
|
|
191
|
+
## What's next
|
|
192
|
+
|
|
193
|
+
- [**Overview**](/docs/client-overview) — the `app/` and `public/` directory layout these primitives live in
|
|
194
|
+
- [**Data & Sync**](/docs/client-data) — the default action hooks and `useDbSync` most pages should use instead
|
|
195
|
+
- [**Sync Internals**](/docs/client-sync-internals) — `useChangeVersion`/`useChangeVersions` and the two latency paths a refetch can take
|
|
196
|
+
- [**Agent Chat**](/docs/client-agent-chat) — the default `sendToAgentChat`/`askUserQuestion` helpers most UI should use instead, and the staged agent chat context-state APIs
|
|
197
|
+
- [**Entry Points**](/docs/client-entry-points) — `app/entry.client.tsx`/`app/entry.server.tsx` and SSR streaming customization
|
|
198
|
+
- [**Context Awareness**](/docs/context-awareness) — the `navigation`/`selection` semantics built on `application_state`, and the `TAB_ID`/jitter-prevention walkthrough
|
|
199
|
+
- [**Generative UI**](/docs/generative-ui) — richer inline chat controls beyond `askUserQuestion`
|
|
200
|
+
- [**External Agents**](/docs/external-agents#mcp-app-bridge) — the full MCP App host bridge internals
|