@agent-native/core 0.0.0-beta-20260820143516 → 0.0.0-beta-20260820155159
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/README.md +1 -1
- package/corpus/templates/dispatch/changelog/2026-08-20-dispatch-chat-loads-openrouter-provider-reliably.md +6 -0
- package/corpus/templates/dispatch/package.json +2 -0
- package/corpus/templates/macros/netlify.toml +2 -0
- package/dist/action.js +20 -0
- package/dist/agent/production-agent.js +37 -10
- package/dist/agent/run-manager.d.ts +13 -2
- package/dist/agent/run-manager.js +61 -23
- package/dist/agent/types.d.ts +21 -0
- package/dist/catalog.json +1 -0
- package/dist/client/integrations/IntegrationsPanel.js +12 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/framework-tools.d.ts +16 -1
- package/dist/framework-tools.js +15 -0
- package/dist/jobs/background-automation-runner.js +26 -2
- package/dist/mcp/screen-memory-stdio.d.ts +7 -7
- package/dist/mcp-client/oauth-routes.d.ts +1 -0
- package/dist/mcp-client/oauth-routes.js +37 -9
- package/dist/mcp-client/remote-store.d.ts +12 -0
- package/dist/mcp-client/remote-store.js +71 -0
- package/dist/notifications/routes.d.ts +6 -6
- package/dist/observability/routes.d.ts +3 -3
- package/dist/observability/traces.js +54 -11
- package/dist/progress/routes.d.ts +1 -1
- package/dist/secrets/routes.d.ts +3 -3
- package/dist/server/action-discovery.d.ts +11 -4
- package/dist/server/action-discovery.js +11 -16
- package/dist/server/agent-chat/mcp-options.js +16 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/realtime-token.d.ts +1 -1
- package/docs/content/external-agents-catalog.mdx +2 -0
- package/package.json +2 -2
package/corpus/README.md
CHANGED
|
@@ -19,9 +19,11 @@
|
|
|
19
19
|
"@agent-native/toolkit": "workspace:*",
|
|
20
20
|
"@fontsource-variable/inter": "^5.2.8",
|
|
21
21
|
"@libsql/client": "^0.15.8",
|
|
22
|
+
"@openrouter/ai-sdk-provider": "^2.9.1",
|
|
22
23
|
"@radix-ui/react-tooltip": "^1.2.7",
|
|
23
24
|
"@tabler/icons-react": "catalog:",
|
|
24
25
|
"@tanstack/react-query": "^5.99.2",
|
|
26
|
+
"ai": "6.0.258",
|
|
25
27
|
"h3": "catalog:",
|
|
26
28
|
"isbot": "^5",
|
|
27
29
|
"next-themes": "^0.4.6",
|
|
@@ -12,6 +12,8 @@ NPM_CONFIG_PRODUCTION = "false"
|
|
|
12
12
|
|
|
13
13
|
[context.production.environment]
|
|
14
14
|
AGENT_NATIVE_HOSTED_HARNESS = "true"
|
|
15
|
+
AGENT_NATIVE_ENABLE_KEEP_WARM = "1"
|
|
16
|
+
AGENT_NATIVE_DISABLE_KEEP_WARM_BACKGROUND = "1"
|
|
15
17
|
# Macros owns its schema at release time (`migrate:production` above), so
|
|
16
18
|
# production request functions must not probe the database for migrations.
|
|
17
19
|
# Preview functions still run request-path migrations because previews do not
|
package/dist/action.js
CHANGED
|
@@ -415,6 +415,22 @@ const PROVIDER_SUPPORTED_FORMATS = new Set([
|
|
|
415
415
|
"ipv6",
|
|
416
416
|
"uuid",
|
|
417
417
|
]);
|
|
418
|
+
/**
|
|
419
|
+
* Anthropic's function-schema validator rejects regex lookaround -- `(?=`,
|
|
420
|
+
* `(?!`, `(?<=`, `(?<!` -- inside a `pattern`, with "Invalid JSON schema: regex
|
|
421
|
+
* lookaround is not supported", and it rejects the WHOLE request, before a
|
|
422
|
+
* token is generated. Interactive chat surfaces that as an error; a background
|
|
423
|
+
* run just dies with `http_400` in `agent_runs.error_detail` while the UI says
|
|
424
|
+
* nothing, which is how a scout can be down for days looking merely idle.
|
|
425
|
+
*
|
|
426
|
+
* Zod 4's `z.string().email()` compiles to two negative lookaheads, and it is
|
|
427
|
+
* written in ~35 action schemas across core, the templates, and the apps -- so
|
|
428
|
+
* this is answered here rather than by retyping every one of them, the same way
|
|
429
|
+
* typeless schemas and unsupported `format` values are. `pattern` is a hint for
|
|
430
|
+
* the model; the action's own zod schema still validates the value server-side,
|
|
431
|
+
* so dropping it widens nothing that was actually enforced.
|
|
432
|
+
*/
|
|
433
|
+
const LOOKAROUND_IN_PATTERN = /\(\?<?[=!]/;
|
|
418
434
|
/**
|
|
419
435
|
* Keywords that only ever CONSTRAIN a value and that OpenAI's validator rejects
|
|
420
436
|
* outright. Removing a constraint can never make a previously-valid document
|
|
@@ -440,6 +456,10 @@ export function stripUnsupportedSchemaKeywords(node) {
|
|
|
440
456
|
!PROVIDER_SUPPORTED_FORMATS.has(obj.format)) {
|
|
441
457
|
delete obj.format;
|
|
442
458
|
}
|
|
459
|
+
if (typeof obj.pattern === "string" &&
|
|
460
|
+
LOOKAROUND_IN_PATTERN.test(obj.pattern)) {
|
|
461
|
+
delete obj.pattern;
|
|
462
|
+
}
|
|
443
463
|
for (const key of SUBSCHEMA_VALUE_KEYS) {
|
|
444
464
|
// `items`/`additionalItems` may also be an array of subschemas.
|
|
445
465
|
const value = obj[key];
|
|
@@ -759,6 +759,14 @@ const VISIBLE_RETRY_THRESHOLD_MS = 10_000;
|
|
|
759
759
|
* < MODEL_STREAM_NO_PROGRESS_TIMEOUT_MS (90s, above)
|
|
760
760
|
* < RUN_NO_PROGRESS_HARD_TIMEOUT_MS (150s, run-manager.ts)
|
|
761
761
|
*
|
|
762
|
+
* Ordering alone is NOT sufficient between the last two, because they do not
|
|
763
|
+
* measure the same events: the bounds above watch engine-stream frames, while
|
|
764
|
+
* the run-manager backstop watches events this loop FORWARDS. Extended
|
|
765
|
+
* thinking produces the first without the second, so the 150s bound sat inside
|
|
766
|
+
* the working distribution and killed live runs. What keeps them consistent is
|
|
767
|
+
* the `model_stream` start/end bracket around the engine call, which suspends
|
|
768
|
+
* the outer backstop while these inner bounds are the ones on duty.
|
|
769
|
+
*
|
|
762
770
|
* Background-function runs (proven 15-min budget, no ~40s wall) are likewise
|
|
763
771
|
* unaffected — they keep the full 90s window for every event, first or not.
|
|
764
772
|
*/
|
|
@@ -3464,6 +3472,27 @@ export async function runAgentLoop(opts) {
|
|
|
3464
3472
|
const activeToolInputs = new Map();
|
|
3465
3473
|
let zeroByteToolInputRestart;
|
|
3466
3474
|
let endedForNoProgress = false;
|
|
3475
|
+
// Bracket the engine call for the run manager's no-progress backstop
|
|
3476
|
+
// (`inFlightWorkDelta` in run-manager.ts). That backstop measures the
|
|
3477
|
+
// events this loop FORWARDS; extended thinking forwards none for
|
|
3478
|
+
// minutes while the frames arriving here prove the stream is alive, so
|
|
3479
|
+
// without the bracket a healthy generation reads as a wedged run and is
|
|
3480
|
+
// killed mid-stream. Closing is idempotent and MUST happen in a
|
|
3481
|
+
// `finally`: a leaked open suspends the backstop for the rest of the
|
|
3482
|
+
// run, which is worse than the stall it exists to catch.
|
|
3483
|
+
let modelStreamBracketOpen = false;
|
|
3484
|
+
const openModelStreamBracket = () => {
|
|
3485
|
+
if (modelStreamBracketOpen)
|
|
3486
|
+
return;
|
|
3487
|
+
modelStreamBracketOpen = true;
|
|
3488
|
+
send({ type: "model_stream", status: "start" });
|
|
3489
|
+
};
|
|
3490
|
+
const closeModelStreamBracket = () => {
|
|
3491
|
+
if (!modelStreamBracketOpen)
|
|
3492
|
+
return;
|
|
3493
|
+
modelStreamBracketOpen = false;
|
|
3494
|
+
send({ type: "model_stream", status: "end" });
|
|
3495
|
+
};
|
|
3467
3496
|
let lastModelStreamProgressAt = Date.now();
|
|
3468
3497
|
// FIX 2: true once a real (non-heartbeat) engine-stream event has been
|
|
3469
3498
|
// retrieved for THIS model call — gates
|
|
@@ -3540,6 +3569,10 @@ export async function runAgentLoop(opts) {
|
|
|
3540
3569
|
const checkpointNoProgress = () => {
|
|
3541
3570
|
if (endedForNoProgress)
|
|
3542
3571
|
return;
|
|
3572
|
+
// The stream is over as far as this loop is concerned, so end the
|
|
3573
|
+
// bracket before the boundary event rather than after it in the
|
|
3574
|
+
// `finally` — the checkpoint stays the last event of the chunk.
|
|
3575
|
+
closeModelStreamBracket();
|
|
3543
3576
|
send({
|
|
3544
3577
|
type: "auto_continue",
|
|
3545
3578
|
reason: "no_progress",
|
|
@@ -3637,6 +3670,7 @@ export async function runAgentLoop(opts) {
|
|
|
3637
3670
|
};
|
|
3638
3671
|
const eventIterator = eventStream[Symbol.asyncIterator]();
|
|
3639
3672
|
let eventIteratorDone = false;
|
|
3673
|
+
openModelStreamBracket();
|
|
3640
3674
|
try {
|
|
3641
3675
|
while (true) {
|
|
3642
3676
|
const nextEvent = await nextEngineEventWithNoProgressTimeout(eventIterator);
|
|
@@ -3700,11 +3734,7 @@ export async function runAgentLoop(opts) {
|
|
|
3700
3734
|
});
|
|
3701
3735
|
sendToolInputActivity(event.name, key, undefined, true);
|
|
3702
3736
|
if (noteZeroByteToolInputStart(event.name)) {
|
|
3703
|
-
|
|
3704
|
-
type: "auto_continue",
|
|
3705
|
-
reason: "no_progress",
|
|
3706
|
-
});
|
|
3707
|
-
endedForNoProgress = true;
|
|
3737
|
+
checkpointNoProgress();
|
|
3708
3738
|
break;
|
|
3709
3739
|
}
|
|
3710
3740
|
}
|
|
@@ -3745,11 +3775,7 @@ export async function runAgentLoop(opts) {
|
|
|
3745
3775
|
if (startedZeroByteInput &&
|
|
3746
3776
|
toolName &&
|
|
3747
3777
|
noteZeroByteToolInputStart(toolName)) {
|
|
3748
|
-
|
|
3749
|
-
type: "auto_continue",
|
|
3750
|
-
reason: "no_progress",
|
|
3751
|
-
});
|
|
3752
|
-
endedForNoProgress = true;
|
|
3778
|
+
checkpointNoProgress();
|
|
3753
3779
|
break;
|
|
3754
3780
|
}
|
|
3755
3781
|
}
|
|
@@ -3814,6 +3840,7 @@ export async function runAgentLoop(opts) {
|
|
|
3814
3840
|
}
|
|
3815
3841
|
}
|
|
3816
3842
|
finally {
|
|
3843
|
+
closeModelStreamBracket();
|
|
3817
3844
|
if (!eventIteratorDone) {
|
|
3818
3845
|
await requestEventIteratorReturn(eventIterator, !eventIteratorReturnRequested);
|
|
3819
3846
|
}
|
|
@@ -103,11 +103,22 @@ export declare const DEFAULT_BACKGROUND_RUN_SOFT_TIMEOUT_MS: number;
|
|
|
103
103
|
* with the client watching keepalives. This backstop covers every segment by
|
|
104
104
|
* construction: if no REAL progress event (see `shouldBumpProgressForEvent`;
|
|
105
105
|
* keepalives and zero-byte prep activity don't count) lands for this long —
|
|
106
|
-
* and no
|
|
107
|
-
*
|
|
106
|
+
* and no unit of work is in flight (see `inFlightWorkDelta`: tool calls,
|
|
107
|
+
* cross-app calls, and the model stream all legitimately emit nothing for
|
|
108
|
+
* minutes and each carry a bound of their own) — the run manager emits
|
|
108
109
|
* `auto_continue { reason: "no_progress" }` and aborts the chunk, exactly
|
|
109
110
|
* like the soft timeout, so the normal continuation machinery recovers it.
|
|
110
111
|
*
|
|
112
|
+
* Being numerically larger than the in-loop watchdogs is NOT what keeps this
|
|
113
|
+
* from killing a healthy run, and treating it that way is what made it do so:
|
|
114
|
+
* this clock and the loop's `lastModelStreamProgressAt` measure DIFFERENT
|
|
115
|
+
* events. An extended-thinking phase bumps the inner clock on every engine
|
|
116
|
+
* frame while forwarding nothing, so the inner watchdog correctly stayed quiet
|
|
117
|
+
* and this one saw pure silence — runs whose worst gap crossed 150s died while
|
|
118
|
+
* still streaming, some by a single second. Ordering between two clocks only
|
|
119
|
+
* means something when they watch the same events; suspending on in-flight
|
|
120
|
+
* work is what actually makes the two agree.
|
|
121
|
+
*
|
|
111
122
|
* This is now only the CEILING, not the value: `resolveRunNoProgressTimeoutMs`
|
|
112
123
|
* clamps the foreground backstop to a fraction of the chunk's soft timeout
|
|
113
124
|
* (~30s at a 40s chunk), which is BELOW the 90s in-loop watchdogs rather than
|
|
@@ -78,11 +78,22 @@ export const DEFAULT_BACKGROUND_RUN_SOFT_TIMEOUT_MS = BACKGROUND_SOFT_TIMEOUT_CE
|
|
|
78
78
|
* with the client watching keepalives. This backstop covers every segment by
|
|
79
79
|
* construction: if no REAL progress event (see `shouldBumpProgressForEvent`;
|
|
80
80
|
* keepalives and zero-byte prep activity don't count) lands for this long —
|
|
81
|
-
* and no
|
|
82
|
-
*
|
|
81
|
+
* and no unit of work is in flight (see `inFlightWorkDelta`: tool calls,
|
|
82
|
+
* cross-app calls, and the model stream all legitimately emit nothing for
|
|
83
|
+
* minutes and each carry a bound of their own) — the run manager emits
|
|
83
84
|
* `auto_continue { reason: "no_progress" }` and aborts the chunk, exactly
|
|
84
85
|
* like the soft timeout, so the normal continuation machinery recovers it.
|
|
85
86
|
*
|
|
87
|
+
* Being numerically larger than the in-loop watchdogs is NOT what keeps this
|
|
88
|
+
* from killing a healthy run, and treating it that way is what made it do so:
|
|
89
|
+
* this clock and the loop's `lastModelStreamProgressAt` measure DIFFERENT
|
|
90
|
+
* events. An extended-thinking phase bumps the inner clock on every engine
|
|
91
|
+
* frame while forwarding nothing, so the inner watchdog correctly stayed quiet
|
|
92
|
+
* and this one saw pure silence — runs whose worst gap crossed 150s died while
|
|
93
|
+
* still streaming, some by a single second. Ordering between two clocks only
|
|
94
|
+
* means something when they watch the same events; suspending on in-flight
|
|
95
|
+
* work is what actually makes the two agree.
|
|
96
|
+
*
|
|
86
97
|
* This is now only the CEILING, not the value: `resolveRunNoProgressTimeoutMs`
|
|
87
98
|
* clamps the foreground backstop to a fraction of the chunk's soft timeout
|
|
88
99
|
* (~30s at a 40s chunk), which is BELOW the 90s in-loop watchdogs rather than
|
|
@@ -161,6 +172,36 @@ export function resolveRunNoProgressTimeoutMs(params) {
|
|
|
161
172
|
return ceiling;
|
|
162
173
|
return override === 0 ? 0 : Math.min(override, ceiling);
|
|
163
174
|
}
|
|
175
|
+
/**
|
|
176
|
+
* Work-tracking transition for one event: `1` opens a unit of in-flight work,
|
|
177
|
+
* `-1` closes one, `0` is not a work-tracking event.
|
|
178
|
+
*
|
|
179
|
+
* Every pair listed here suspends the no-progress backstop for as long as it is
|
|
180
|
+
* open, so each one MUST be bounded by a watchdog of its own — tool calls by
|
|
181
|
+
* the per-tool timeout, cross-app calls by the A2A poll timeout, the model
|
|
182
|
+
* stream by `MODEL_STREAM_NO_PROGRESS_TIMEOUT_MS`, which the agent loop races
|
|
183
|
+
* against every wait for the next engine frame. A pair added here without such
|
|
184
|
+
* a bound turns the backstop off for the rest of the run, which is strictly
|
|
185
|
+
* worse than the stall it was meant to catch.
|
|
186
|
+
*
|
|
187
|
+
* Note what that leaves: the model-stream bound covers waiting for the engine,
|
|
188
|
+
* not a hang while the loop processes a frame it already has. Nothing here
|
|
189
|
+
* covers that segment, so a suspension is only ever as tight as the chunk's own
|
|
190
|
+
* soft timeout. Keep the pairs narrow around genuinely blocking work.
|
|
191
|
+
*/
|
|
192
|
+
function inFlightWorkDelta(event) {
|
|
193
|
+
switch (event.type) {
|
|
194
|
+
case "tool_start":
|
|
195
|
+
return 1;
|
|
196
|
+
case "tool_done":
|
|
197
|
+
return -1;
|
|
198
|
+
case "agent_call":
|
|
199
|
+
case "model_stream":
|
|
200
|
+
return event.status === "start" ? 1 : -1;
|
|
201
|
+
default:
|
|
202
|
+
return 0;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
164
205
|
/**
|
|
165
206
|
* Default SQL retention for completed run event logs (24 hours).
|
|
166
207
|
*
|
|
@@ -829,9 +870,10 @@ export function startRun(runId, threadId, runFn, onComplete, options) {
|
|
|
829
870
|
// ── No-progress backstop (see RUN_NO_PROGRESS_HARD_TIMEOUT_MS) ──────────
|
|
830
871
|
// Timer-driven and independent of the agent loop, so it fires even when the
|
|
831
872
|
// stall is in a segment the in-loop watchdogs never see (engine-call
|
|
832
|
-
// establishment, setup, a wedged transport emitting keepalives). Tool calls
|
|
833
|
-
//
|
|
834
|
-
// emits nothing for minutes and carries its own
|
|
873
|
+
// establishment, setup, a wedged transport emitting keepalives). Tool calls,
|
|
874
|
+
// sub-agent calls, and the model stream in flight suspend it — each
|
|
875
|
+
// legitimately emits nothing for minutes and each carries a bound of its own
|
|
876
|
+
// (see `inFlightWorkDelta`).
|
|
835
877
|
let lastRealProgressAt = Date.now();
|
|
836
878
|
let inFlightWorkCount = 0;
|
|
837
879
|
let inFlightMarkerSince = null;
|
|
@@ -854,24 +896,11 @@ export function startRun(runId, threadId, runFn, onComplete, options) {
|
|
|
854
896
|
});
|
|
855
897
|
};
|
|
856
898
|
const trackInFlightWork = (event) => {
|
|
899
|
+
const delta = inFlightWorkDelta(event);
|
|
900
|
+
if (delta === 0)
|
|
901
|
+
return; // Not a work-tracking event — no transition.
|
|
857
902
|
const wasIdle = inFlightWorkCount === 0;
|
|
858
|
-
|
|
859
|
-
inFlightWorkCount += 1;
|
|
860
|
-
}
|
|
861
|
-
else if (event.type === "tool_done") {
|
|
862
|
-
inFlightWorkCount = Math.max(0, inFlightWorkCount - 1);
|
|
863
|
-
}
|
|
864
|
-
else if (event.type === "agent_call") {
|
|
865
|
-
if (event.status === "start") {
|
|
866
|
-
inFlightWorkCount += 1;
|
|
867
|
-
}
|
|
868
|
-
else {
|
|
869
|
-
inFlightWorkCount = Math.max(0, inFlightWorkCount - 1);
|
|
870
|
-
}
|
|
871
|
-
}
|
|
872
|
-
else {
|
|
873
|
-
return; // Not a work-tracking event — no transition possible.
|
|
874
|
-
}
|
|
903
|
+
inFlightWorkCount = Math.max(0, inFlightWorkCount + delta);
|
|
875
904
|
// Mirror the 0<->N transition into SQL so a stale reaper running in a
|
|
876
905
|
// DIFFERENT isolate (a client's SQL-subscription poll, a sibling
|
|
877
906
|
// isolate's opportunistic cleanup, a fresh boot's startup sweep) can
|
|
@@ -882,6 +911,14 @@ export function startRun(runId, threadId, runFn, onComplete, options) {
|
|
|
882
911
|
// keep failures observable and preserve transition order. See
|
|
883
912
|
// `setRunInFlightMarker` / `IN_FLIGHT_RUN_STALE_GRACE_MS` in run-store.ts
|
|
884
913
|
// for the full reasoning and bounded-grace derivation.
|
|
914
|
+
//
|
|
915
|
+
// A model stream in flight mirrors the marker too, deliberately: a long
|
|
916
|
+
// thinking phase is exactly when another isolate's reaper would call this
|
|
917
|
+
// run stale, and the stream is as demonstrably alive as a tool call. It
|
|
918
|
+
// cannot latch a corpse — the grace clause additionally requires the
|
|
919
|
+
// liveness basis to be fresh within `IN_FLIGHT_GRACE_MAX_LIVENESS_GAP_MS`,
|
|
920
|
+
// so a marker left set by a dead producer buys nothing. The cost is one
|
|
921
|
+
// extra set/clear pair per model call on the way to each tool call.
|
|
885
922
|
if (wasIdle && inFlightWorkCount > 0) {
|
|
886
923
|
mirrorInFlightMarker(true);
|
|
887
924
|
}
|
|
@@ -924,7 +961,8 @@ export function startRun(runId, threadId, runFn, onComplete, options) {
|
|
|
924
961
|
return;
|
|
925
962
|
if (Date.now() - lastRealProgressAt < noProgressTimeoutMs)
|
|
926
963
|
return;
|
|
927
|
-
console.error(`[run-manager] no real progress for ${noProgressTimeoutMs}ms with no tool
|
|
964
|
+
console.error(`[run-manager] no real progress for ${noProgressTimeoutMs}ms with no tool ` +
|
|
965
|
+
`or model stream in flight — ` +
|
|
928
966
|
`checkpointing run for continuation`, runId);
|
|
929
967
|
// Mirror the soft-timeout semantics exactly: the chunk completes (not
|
|
930
968
|
// aborts) at an auto_continue boundary, so the continuation machinery —
|
package/dist/agent/types.d.ts
CHANGED
|
@@ -276,6 +276,27 @@ export type AgentChatEvent = {
|
|
|
276
276
|
text: string;
|
|
277
277
|
} | {
|
|
278
278
|
type: "stream_keepalive";
|
|
279
|
+
} | {
|
|
280
|
+
/**
|
|
281
|
+
* Lifecycle bracket around ONE engine (model) call, emitted by the agent
|
|
282
|
+
* loop when the stream is established and again — from a `finally` — when
|
|
283
|
+
* it ends, returns, or throws.
|
|
284
|
+
*
|
|
285
|
+
* Exists so the run manager's no-progress backstop can tell "the model is
|
|
286
|
+
* generating" from "nothing is happening". An extended-thinking phase
|
|
287
|
+
* emits frames that keep the loop's own 90s model-stream watchdog fresh
|
|
288
|
+
* without producing any forwarded event, so the backstop's clock saw pure
|
|
289
|
+
* silence and killed demonstrably-alive runs at 150s. `trackInFlightWork`
|
|
290
|
+
* counts this pair exactly like `tool_start`/`tool_done`: an engine call
|
|
291
|
+
* in flight suspends the backstop, bounded by the in-loop watchdog the
|
|
292
|
+
* same way a tool call is bounded by its own timeout.
|
|
293
|
+
*
|
|
294
|
+
* Deliberately NOT a keepalive: a keepalive proves the transport is up,
|
|
295
|
+
* this proves the loop is inside a model call it will be held accountable
|
|
296
|
+
* for by `MODEL_STREAM_NO_PROGRESS_TIMEOUT_MS`.
|
|
297
|
+
*/
|
|
298
|
+
type: "model_stream";
|
|
299
|
+
status: "start" | "end";
|
|
279
300
|
} | {
|
|
280
301
|
type: "tool_start";
|
|
281
302
|
tool: string;
|
package/dist/catalog.json
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"@tiptap/extension-collaboration": "3.30.1",
|
|
15
15
|
"@tiptap/extension-collaboration-caret": "3.30.1",
|
|
16
16
|
"@tiptap/extension-color": "3.30.1",
|
|
17
|
+
"@tiptap/extension-floating-menu": "3.30.1",
|
|
17
18
|
"@tiptap/extension-horizontal-rule": "3.30.1",
|
|
18
19
|
"@tiptap/extension-image": "3.30.1",
|
|
19
20
|
"@tiptap/extension-link": "3.30.1",
|
|
@@ -239,6 +239,15 @@ function compareMcpUrl(value) {
|
|
|
239
239
|
return value.trim().replace(/\/+$/, "");
|
|
240
240
|
}
|
|
241
241
|
}
|
|
242
|
+
function startMcpOAuthReconnect(server) {
|
|
243
|
+
const returnUrl = `${window.location.pathname}${window.location.search}${window.location.hash}`;
|
|
244
|
+
const params = new URLSearchParams({
|
|
245
|
+
serverId: server.id,
|
|
246
|
+
scope: server.scope,
|
|
247
|
+
return: returnUrl,
|
|
248
|
+
});
|
|
249
|
+
window.location.assign(agentNativePath(`/_agent-native/mcp/servers/oauth/start?${params}`));
|
|
250
|
+
}
|
|
242
251
|
function McpServerStatus({ server, onReconnect, reconnecting = false, reconnectError, }) {
|
|
243
252
|
const t = useT();
|
|
244
253
|
if (server.status.state === "connected") {
|
|
@@ -362,7 +371,9 @@ function McpIntegrationsSection({ query }) {
|
|
|
362
371
|
return (_jsxs("div", { className: "flex items-start gap-3 py-3.5", children: [_jsx("span", { className: "mt-0.5 flex size-9 shrink-0 items-center justify-center rounded-lg border border-border/70 bg-background text-muted-foreground", children: _jsx(IconServer, { className: "size-4" }) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx("span", { className: "truncate text-sm font-medium text-foreground", children: server.name }), _jsx("span", { className: "text-[11px] text-muted-foreground", children: server.scope === "user"
|
|
363
372
|
? t("mcpIntegrations.personal")
|
|
364
373
|
: t("mcpIntegrations.sharedWithWorkspace") })] }), _jsx(McpServerStatus, { server: server, onReconnect: server.status.state === "error"
|
|
365
|
-
? () =>
|
|
374
|
+
? () => server.authMode === "oauth"
|
|
375
|
+
? startMcpOAuthReconnect(server)
|
|
376
|
+
: void reconnect(server)
|
|
366
377
|
: undefined, reconnecting: reconnectingKey === key, reconnectError: reconnectError?.key === key
|
|
367
378
|
? reconnectError.message
|
|
368
379
|
: undefined })] }), canRemove && (_jsx("button", { type: "button", onClick: () => void removeServer(server), disabled: deleteServer.isPending, className: "shrink-0 rounded-md border border-border px-2.5 py-1.5 text-xs font-medium text-foreground transition-colors hover:bg-accent disabled:opacity-50", children: deleteTarget === key ? "Confirm" : "Remove" }))] }, key));
|
|
@@ -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;
|
|
65
66
|
states: {
|
|
66
67
|
clientId: number;
|
|
67
68
|
state: string;
|
|
68
69
|
}[];
|
|
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;
|
|
80
81
|
users: {
|
|
81
82
|
clientId: number;
|
|
82
83
|
lastSeen: number;
|
|
83
84
|
}[];
|
|
84
|
-
error?: undefined;
|
|
85
85
|
}>>;
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
* Body: { json: any, fieldName?: string, type?: "map"|"array", requestSource?: string }
|
|
14
14
|
*/
|
|
15
15
|
export declare const postCollabJson: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
16
|
-
error: string;
|
|
17
16
|
ok?: undefined;
|
|
17
|
+
error: string;
|
|
18
18
|
} | {
|
|
19
19
|
error?: undefined;
|
|
20
20
|
ok: boolean;
|
|
@@ -14,7 +14,7 @@ import { type DatabaseToolsOption } from "./scripts/db/tool-mode.js";
|
|
|
14
14
|
* when it names them in `initialToolNames`. They stay in the searchable registry
|
|
15
15
|
* either way.
|
|
16
16
|
*/
|
|
17
|
-
export declare const FRAMEWORK_TOOL_GROUPS: readonly ["sharing", "review", "history", "featureFlags", "localization", "audit", "contextXray", "userProfile", "automation", "docs", "resources", "web", "workspaceApps", "chat", "email"];
|
|
17
|
+
export declare const FRAMEWORK_TOOL_GROUPS: readonly ["sharing", "review", "history", "featureFlags", "localization", "audit", "contextXray", "userProfile", "automation", "docs", "resources", "web", "workspaceApps", "chat", "email", "emailCatalog", "workspaceUserGroups", "orgServiceTokens"];
|
|
18
18
|
export type FrameworkToolGroup = (typeof FRAMEWORK_TOOL_GROUPS)[number];
|
|
19
19
|
/**
|
|
20
20
|
* Per-group switches for the framework's own agent tools. Every group defaults
|
|
@@ -73,6 +73,21 @@ export interface FrameworkToolsOption {
|
|
|
73
73
|
chat?: boolean;
|
|
74
74
|
/** `core-send-email`. */
|
|
75
75
|
email?: boolean;
|
|
76
|
+
/** The read-only transactional-email catalog (`list-transactional-emails`,
|
|
77
|
+
* `list-email-log`, `list-email-activity`, …). Separate from `email`, which
|
|
78
|
+
* owns the SEND capability: Dispatch asks any app what it sends without that
|
|
79
|
+
* app opting into sending from the agent, so these default to on. */
|
|
80
|
+
emailCatalog?: boolean;
|
|
81
|
+
/** Reusable workspace member lists (`list-workspace-user-groups`,
|
|
82
|
+
* `upsert-workspace-user-group`, `bulk-update-workspace-user-groups`,
|
|
83
|
+
* `delete-workspace-user-group`). Their UI is the Team page and the share
|
|
84
|
+
* dialog; an app with neither has no use for them. */
|
|
85
|
+
workspaceUserGroups?: boolean;
|
|
86
|
+
/** `create-org-service-token`, `list-org-service-tokens`,
|
|
87
|
+
* `revoke-org-service-token`. Minting a credential is a real capability:
|
|
88
|
+
* `mcp.enabled` decides whether the ROUTES exist, this decides whether the
|
|
89
|
+
* model can call them. */
|
|
90
|
+
orgServiceTokens?: boolean;
|
|
76
91
|
/** `"minimal"` turns every group above off, for voice-first and
|
|
77
92
|
* single-purpose apps that want the template's own actions and nothing else.
|
|
78
93
|
* Any explicit group key wins over the preset, so
|
package/dist/framework-tools.js
CHANGED
|
@@ -30,6 +30,9 @@ export const FRAMEWORK_TOOL_GROUPS = [
|
|
|
30
30
|
"workspaceApps",
|
|
31
31
|
"chat",
|
|
32
32
|
"email",
|
|
33
|
+
"emailCatalog",
|
|
34
|
+
"workspaceUserGroups",
|
|
35
|
+
"orgServiceTokens",
|
|
33
36
|
];
|
|
34
37
|
function normalizeFrameworkToolsConfig(config) {
|
|
35
38
|
if (config === "minimal")
|
|
@@ -152,6 +155,18 @@ export const CORE_ACTION_GROUPS = {
|
|
|
152
155
|
"get-resource-version": "history",
|
|
153
156
|
"restore-resource-version": "history",
|
|
154
157
|
"list-resource-history": "history",
|
|
158
|
+
"list-transactional-emails": "emailCatalog",
|
|
159
|
+
"render-transactional-email-preview": "emailCatalog",
|
|
160
|
+
"list-email-log": "emailCatalog",
|
|
161
|
+
"list-email-activity": "emailCatalog",
|
|
162
|
+
"list-email-engagement": "emailCatalog",
|
|
163
|
+
"list-workspace-user-groups": "workspaceUserGroups",
|
|
164
|
+
"upsert-workspace-user-group": "workspaceUserGroups",
|
|
165
|
+
"bulk-update-workspace-user-groups": "workspaceUserGroups",
|
|
166
|
+
"delete-workspace-user-group": "workspaceUserGroups",
|
|
167
|
+
"create-org-service-token": "orgServiceTokens",
|
|
168
|
+
"list-org-service-tokens": "orgServiceTokens",
|
|
169
|
+
"revoke-org-service-token": "orgServiceTokens",
|
|
155
170
|
"list-review-comments": "review",
|
|
156
171
|
"create-review-comment": "review",
|
|
157
172
|
"reply-review-comment": "review",
|
|
@@ -10,6 +10,7 @@ import { resolveAutomationExecutionIdentity, } from "../automations/service.js";
|
|
|
10
10
|
import { createThread, getThread, updateThreadData, withThreadDataLock, } from "../chat-threads/store.js";
|
|
11
11
|
import { queryOrgMembers } from "../org/context.js";
|
|
12
12
|
import { organizationIdFromResourceOwner, organizationResourceOwner, } from "../resources/store.js";
|
|
13
|
+
import { captureError } from "../server/capture-error.js";
|
|
13
14
|
import { runWithRequestContext, } from "../server/request-context.js";
|
|
14
15
|
import { attachAutomationRunThread, finishAutomationRun, startAutomationRun, } from "./run-history.js";
|
|
15
16
|
const BACKGROUND_RUN_STUCK_MS = 10 * 60_000;
|
|
@@ -177,11 +178,32 @@ export async function runBackgroundAutomation(options, deps) {
|
|
|
177
178
|
}
|
|
178
179
|
}
|
|
179
180
|
let result;
|
|
181
|
+
// Populated as soon as the run id exists, so a failure that never returns a
|
|
182
|
+
// result can still be joined to its LLM trace (`aiTraceId` -> $ai_trace_id).
|
|
183
|
+
const runIdRef = { current: null };
|
|
180
184
|
try {
|
|
181
|
-
result = await executeBackgroundAutomation(options, deps, historyId);
|
|
185
|
+
result = await executeBackgroundAutomation(options, deps, historyId, runIdRef);
|
|
182
186
|
}
|
|
183
187
|
catch (err) {
|
|
184
188
|
const message = err instanceof Error ? err.message : String(err);
|
|
189
|
+
// Both callers (recurring-jobs scheduler, trigger dispatcher) record this
|
|
190
|
+
// onto the automation's own metadata and console.error it, and neither
|
|
191
|
+
// reports it. A failure visible only in a resource field and stdout is not
|
|
192
|
+
// a failure anyone sees: the run-level no-progress cutoff killed
|
|
193
|
+
// automations across two releases without ever raising an issue.
|
|
194
|
+
captureError(err, {
|
|
195
|
+
tags: {
|
|
196
|
+
area: "background-automation",
|
|
197
|
+
automation: automation.name,
|
|
198
|
+
scope: options.orgId ? "organization" : "personal",
|
|
199
|
+
},
|
|
200
|
+
extra: {
|
|
201
|
+
automationPath: automation.resource.path,
|
|
202
|
+
appId: deps.appId,
|
|
203
|
+
historyId,
|
|
204
|
+
},
|
|
205
|
+
...(runIdRef.current ? { aiTraceId: runIdRef.current } : {}),
|
|
206
|
+
});
|
|
185
207
|
await recordRunOutcome(historyId, "error", `${message}. No delivery was confirmed.`);
|
|
186
208
|
throw err;
|
|
187
209
|
}
|
|
@@ -289,7 +311,7 @@ async function recordRunOutcome(historyId, status, error) {
|
|
|
289
311
|
console.error(`[automations] Could not record run ${historyId} as ${status}:`, err);
|
|
290
312
|
}
|
|
291
313
|
}
|
|
292
|
-
async function executeBackgroundAutomation(options, deps, historyId) {
|
|
314
|
+
async function executeBackgroundAutomation(options, deps, historyId, runIdRef) {
|
|
293
315
|
const { automation, ownerEmail, orgId, prompt, threadTitle, usageLabel } = options;
|
|
294
316
|
return runWithRequestContext({
|
|
295
317
|
...options.requestContext,
|
|
@@ -335,6 +357,8 @@ async function executeBackgroundAutomation(options, deps, historyId) {
|
|
|
335
357
|
orgId: orgId ?? null,
|
|
336
358
|
});
|
|
337
359
|
const runId = createRunId(options.runIdPrefix);
|
|
360
|
+
if (runIdRef)
|
|
361
|
+
runIdRef.current = runId;
|
|
338
362
|
await recordRunThread(historyId, thread.id, runId);
|
|
339
363
|
// Scheduled work is background work: it has no synchronous serverless
|
|
340
364
|
// caller waiting on it, so it must not inherit the interactive clamp
|
|
@@ -137,13 +137,13 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
137
137
|
inputSchema: {
|
|
138
138
|
type: string;
|
|
139
139
|
properties: {
|
|
140
|
-
count?: undefined;
|
|
141
140
|
query?: undefined;
|
|
142
141
|
minutes?: undefined;
|
|
143
142
|
limit?: undefined;
|
|
144
143
|
clientHint?: undefined;
|
|
145
144
|
timestamp?: undefined;
|
|
146
145
|
chapterId?: undefined;
|
|
146
|
+
count?: undefined;
|
|
147
147
|
startAt?: undefined;
|
|
148
148
|
endAt?: undefined;
|
|
149
149
|
reason?: undefined;
|
|
@@ -159,7 +159,6 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
159
159
|
inputSchema: {
|
|
160
160
|
type: string;
|
|
161
161
|
properties: {
|
|
162
|
-
count?: undefined;
|
|
163
162
|
query: {
|
|
164
163
|
type: string;
|
|
165
164
|
description: string;
|
|
@@ -175,6 +174,7 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
175
174
|
clientHint?: undefined;
|
|
176
175
|
timestamp?: undefined;
|
|
177
176
|
chapterId?: undefined;
|
|
177
|
+
count?: undefined;
|
|
178
178
|
startAt?: undefined;
|
|
179
179
|
endAt?: undefined;
|
|
180
180
|
reason?: undefined;
|
|
@@ -190,7 +190,6 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
190
190
|
inputSchema: {
|
|
191
191
|
type: string;
|
|
192
192
|
properties: {
|
|
193
|
-
count?: undefined;
|
|
194
193
|
minutes: {
|
|
195
194
|
type: string;
|
|
196
195
|
description: string;
|
|
@@ -200,6 +199,7 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
200
199
|
clientHint?: undefined;
|
|
201
200
|
timestamp?: undefined;
|
|
202
201
|
chapterId?: undefined;
|
|
202
|
+
count?: undefined;
|
|
203
203
|
startAt?: undefined;
|
|
204
204
|
endAt?: undefined;
|
|
205
205
|
reason?: undefined;
|
|
@@ -216,7 +216,6 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
216
216
|
type: string;
|
|
217
217
|
required: string[];
|
|
218
218
|
properties: {
|
|
219
|
-
count?: undefined;
|
|
220
219
|
query: {
|
|
221
220
|
type: string;
|
|
222
221
|
description: string;
|
|
@@ -235,6 +234,7 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
235
234
|
};
|
|
236
235
|
timestamp?: undefined;
|
|
237
236
|
chapterId?: undefined;
|
|
237
|
+
count?: undefined;
|
|
238
238
|
startAt?: undefined;
|
|
239
239
|
endAt?: undefined;
|
|
240
240
|
reason?: undefined;
|
|
@@ -250,7 +250,6 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
250
250
|
type: string;
|
|
251
251
|
required: string[];
|
|
252
252
|
properties: {
|
|
253
|
-
count?: undefined;
|
|
254
253
|
query?: undefined;
|
|
255
254
|
minutes?: undefined;
|
|
256
255
|
limit?: undefined;
|
|
@@ -264,6 +263,7 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
264
263
|
description: string;
|
|
265
264
|
};
|
|
266
265
|
chapterId?: undefined;
|
|
266
|
+
count?: undefined;
|
|
267
267
|
startAt?: undefined;
|
|
268
268
|
endAt?: undefined;
|
|
269
269
|
includeMicrophone?: undefined;
|
|
@@ -314,13 +314,13 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
314
314
|
type: string;
|
|
315
315
|
required: string[];
|
|
316
316
|
properties: {
|
|
317
|
-
count?: undefined;
|
|
318
317
|
query?: undefined;
|
|
319
318
|
minutes?: undefined;
|
|
320
319
|
limit?: undefined;
|
|
321
320
|
clientHint?: undefined;
|
|
322
321
|
timestamp?: undefined;
|
|
323
322
|
chapterId?: undefined;
|
|
323
|
+
count?: undefined;
|
|
324
324
|
startAt: {
|
|
325
325
|
type: string;
|
|
326
326
|
description: string;
|
|
@@ -349,13 +349,13 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
349
349
|
type: string;
|
|
350
350
|
required: string[];
|
|
351
351
|
properties: {
|
|
352
|
-
count?: undefined;
|
|
353
352
|
query?: undefined;
|
|
354
353
|
minutes?: undefined;
|
|
355
354
|
limit?: undefined;
|
|
356
355
|
clientHint?: undefined;
|
|
357
356
|
timestamp?: undefined;
|
|
358
357
|
chapterId?: undefined;
|
|
358
|
+
count?: undefined;
|
|
359
359
|
startAt?: undefined;
|
|
360
360
|
endAt?: undefined;
|
|
361
361
|
reason?: undefined;
|
|
@@ -8,7 +8,7 @@ import { getH3App } from "../server/framework-request-handler.js";
|
|
|
8
8
|
import { getAppUrl, resolveOAuthRedirectUri } from "../server/google-oauth.js";
|
|
9
9
|
import { runWithRequestContext } from "../server/request-context.js";
|
|
10
10
|
import { finishMcpOAuthAuthorization, startMcpOAuthAuthorization, validateMcpOAuthCallbackIssuer, } from "./oauth-client.js";
|
|
11
|
-
import { addOAuthRemoteServer, normalizeServerName, validateRemoteUrl, } from "./remote-store.js";
|
|
11
|
+
import { addOAuthRemoteServer, listRemoteServers, normalizeServerName, replaceOAuthRemoteServer, validateRemoteUrl, } from "./remote-store.js";
|
|
12
12
|
const FLOW_COOKIE = "an_mcp_oauth_flow";
|
|
13
13
|
const FLOW_TTL_SECONDS = 10 * 60;
|
|
14
14
|
const FLOW_COOKIE_CHUNK_SIZE = 2_800;
|
|
@@ -69,8 +69,33 @@ async function handleMcpOAuthStart(event) {
|
|
|
69
69
|
if (!session?.email)
|
|
70
70
|
return unauthorized(event);
|
|
71
71
|
const query = getQuery(event);
|
|
72
|
-
const
|
|
73
|
-
const
|
|
72
|
+
const reconnectServerId = text(query.serverId);
|
|
73
|
+
const reconnectScope = query.scope === "org" ? "org" : "user";
|
|
74
|
+
const reconnectOrg = reconnectScope === "org" ? await getOrgContext(event) : null;
|
|
75
|
+
const reconnectScopeId = reconnectScope === "user" ? session.email : (reconnectOrg?.orgId ?? "");
|
|
76
|
+
let reconnectServer;
|
|
77
|
+
if (reconnectServerId) {
|
|
78
|
+
if (reconnectScope === "org" &&
|
|
79
|
+
(!reconnectScopeId || !isOrgAdmin(reconnectOrg?.role))) {
|
|
80
|
+
setResponseStatus(event, reconnectScopeId ? 403 : 400);
|
|
81
|
+
return {
|
|
82
|
+
error: reconnectScopeId
|
|
83
|
+
? "Only organization owners and admins can reconnect an org MCP server."
|
|
84
|
+
: "Join an organization before reconnecting an org MCP server.",
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
reconnectServer = (await listRemoteServers(reconnectScope, reconnectScopeId)).find((server) => server.id === reconnectServerId);
|
|
88
|
+
if (!reconnectServer) {
|
|
89
|
+
setResponseStatus(event, 404);
|
|
90
|
+
return { error: "MCP server was not found." };
|
|
91
|
+
}
|
|
92
|
+
if (!reconnectServer.oauthSecretKey) {
|
|
93
|
+
setResponseStatus(event, 400);
|
|
94
|
+
return { error: "This MCP server does not use OAuth credentials." };
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const rawUrl = reconnectServer?.url ?? text(query.url);
|
|
98
|
+
const rawName = reconnectServer?.name ?? text(query.name);
|
|
74
99
|
const returnUrl = text(query.return);
|
|
75
100
|
if (!rawUrl || !rawName) {
|
|
76
101
|
setResponseStatus(event, 400);
|
|
@@ -159,6 +184,7 @@ async function handleMcpOAuthStart(event) {
|
|
|
159
184
|
? { discoveryState: started.discoveryState }
|
|
160
185
|
: {}),
|
|
161
186
|
...(safeReturnUrl ? { returnUrl: safeReturnUrl } : {}),
|
|
187
|
+
...(reconnectServerId ? { replaceServerId: reconnectServerId } : {}),
|
|
162
188
|
expiresAt: Date.now() + FLOW_TTL_SECONDS * 1_000,
|
|
163
189
|
};
|
|
164
190
|
setMcpOAuthFlowCookie(event, flow, redirectUri.startsWith("https://"));
|
|
@@ -245,12 +271,14 @@ async function handleMcpOAuthCallback(event, options) {
|
|
|
245
271
|
authorizationCode: code,
|
|
246
272
|
iss,
|
|
247
273
|
}));
|
|
248
|
-
const result =
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
274
|
+
const result = flow.replaceServerId
|
|
275
|
+
? await replaceOAuthRemoteServer(flow.scope, flow.scopeId, flow.replaceServerId, finished.credentials)
|
|
276
|
+
: await addOAuthRemoteServer(flow.scope, flow.scopeId, {
|
|
277
|
+
name: flow.name,
|
|
278
|
+
url: flow.url,
|
|
279
|
+
description: flow.description,
|
|
280
|
+
credentials: finished.credentials,
|
|
281
|
+
});
|
|
254
282
|
if (!result.ok) {
|
|
255
283
|
setResponseStatus(event, 400);
|
|
256
284
|
return { error: result.error };
|
|
@@ -107,6 +107,18 @@ export declare function addOAuthRemoteServer(scope: RemoteMcpScope, scopeId: str
|
|
|
107
107
|
ok: false;
|
|
108
108
|
error: string;
|
|
109
109
|
}>;
|
|
110
|
+
/**
|
|
111
|
+
* Replace the OAuth grant for an existing server without changing its id or
|
|
112
|
+
* display name. Reconnect must update the saved grant in place; registering a
|
|
113
|
+
* second row makes the original connection impossible to repair.
|
|
114
|
+
*/
|
|
115
|
+
export declare function replaceOAuthRemoteServer(scope: RemoteMcpScope, scopeId: string, serverId: string, credentials: McpOAuthCredentialBundle): Promise<{
|
|
116
|
+
ok: true;
|
|
117
|
+
server: StoredRemoteMcpServer;
|
|
118
|
+
} | {
|
|
119
|
+
ok: false;
|
|
120
|
+
error: string;
|
|
121
|
+
}>;
|
|
110
122
|
export declare function addFirstPartyRemoteServer(orgId: string, input: {
|
|
111
123
|
appId: string;
|
|
112
124
|
name: string;
|
|
@@ -193,6 +193,77 @@ export async function addOAuthRemoteServer(scope, scopeId, input) {
|
|
|
193
193
|
};
|
|
194
194
|
}
|
|
195
195
|
}
|
|
196
|
+
/**
|
|
197
|
+
* Replace the OAuth grant for an existing server without changing its id or
|
|
198
|
+
* display name. Reconnect must update the saved grant in place; registering a
|
|
199
|
+
* second row makes the original connection impossible to repair.
|
|
200
|
+
*/
|
|
201
|
+
export async function replaceOAuthRemoteServer(scope, scopeId, serverId, credentials) {
|
|
202
|
+
const credentialResource = validateRemoteUrl(credentials.serverUrl);
|
|
203
|
+
if (!credentialResource.ok || !credentialResource.url) {
|
|
204
|
+
return {
|
|
205
|
+
ok: false,
|
|
206
|
+
error: "MCP server URL must match the OAuth credential resource URL",
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
const existing = await readList(scope, scopeId);
|
|
210
|
+
const index = existing.findIndex((server) => server.id === serverId);
|
|
211
|
+
const current = index >= 0 ? existing[index] : undefined;
|
|
212
|
+
if (!current)
|
|
213
|
+
return { ok: false, error: "MCP server was not found" };
|
|
214
|
+
if (!current.oauthSecretKey) {
|
|
215
|
+
return {
|
|
216
|
+
ok: false,
|
|
217
|
+
error: "This MCP server does not use OAuth credentials",
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
if (current.url !== credentialResource.url.toString()) {
|
|
221
|
+
return {
|
|
222
|
+
ok: false,
|
|
223
|
+
error: "MCP server URL must match the saved OAuth connection",
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
const nextSecretKey = `mcp_oauth:${shortId()}`;
|
|
227
|
+
const nextCredentials = {
|
|
228
|
+
...credentials,
|
|
229
|
+
serverUrl: credentialResource.url.toString(),
|
|
230
|
+
};
|
|
231
|
+
try {
|
|
232
|
+
await saveMcpOAuthCredentials({
|
|
233
|
+
key: nextSecretKey,
|
|
234
|
+
scope,
|
|
235
|
+
scopeId,
|
|
236
|
+
credentials: nextCredentials,
|
|
237
|
+
});
|
|
238
|
+
const server = {
|
|
239
|
+
...current,
|
|
240
|
+
url: nextCredentials.serverUrl,
|
|
241
|
+
oauthSecretKey: nextSecretKey,
|
|
242
|
+
};
|
|
243
|
+
const next = [...existing];
|
|
244
|
+
next[index] = server;
|
|
245
|
+
await writeList(scope, scopeId, next);
|
|
246
|
+
await revokeMcpOAuthCredentials({
|
|
247
|
+
key: current.oauthSecretKey,
|
|
248
|
+
scope,
|
|
249
|
+
scopeId,
|
|
250
|
+
serverUrl: current.url,
|
|
251
|
+
}).catch(() => undefined);
|
|
252
|
+
return { ok: true, server };
|
|
253
|
+
}
|
|
254
|
+
catch (err) {
|
|
255
|
+
await revokeMcpOAuthCredentials({
|
|
256
|
+
key: nextSecretKey,
|
|
257
|
+
scope,
|
|
258
|
+
scopeId,
|
|
259
|
+
serverUrl: nextCredentials.serverUrl,
|
|
260
|
+
}).catch(() => undefined);
|
|
261
|
+
return {
|
|
262
|
+
ok: false,
|
|
263
|
+
error: `Failed to replace MCP OAuth credentials: ${err?.message ?? err}`,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
}
|
|
196
267
|
export async function addFirstPartyRemoteServer(orgId, input) {
|
|
197
268
|
const trust = await isFirstPartyRemoteEndpointTrusted(orgId, input.appId, input.url);
|
|
198
269
|
if (!trust.ok)
|
|
@@ -11,23 +11,23 @@
|
|
|
11
11
|
* DELETE /_agent-native/notifications/:id — delete
|
|
12
12
|
*/
|
|
13
13
|
export declare function createNotificationsHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<"" | import("./types.js").Notification[] | {
|
|
14
|
+
error?: undefined;
|
|
14
15
|
count: number;
|
|
15
16
|
updated?: undefined;
|
|
16
17
|
ok?: undefined;
|
|
17
|
-
error?: undefined;
|
|
18
18
|
} | {
|
|
19
|
-
|
|
19
|
+
error?: undefined;
|
|
20
20
|
updated: number;
|
|
21
21
|
ok?: undefined;
|
|
22
|
-
error?: undefined;
|
|
23
|
-
} | {
|
|
24
22
|
count?: undefined;
|
|
23
|
+
} | {
|
|
25
24
|
updated?: undefined;
|
|
26
25
|
error: string;
|
|
27
26
|
ok?: undefined;
|
|
28
|
-
} | {
|
|
29
27
|
count?: undefined;
|
|
28
|
+
} | {
|
|
29
|
+
error?: undefined;
|
|
30
30
|
updated?: undefined;
|
|
31
31
|
ok: boolean;
|
|
32
|
-
|
|
32
|
+
count?: undefined;
|
|
33
33
|
}>>;
|
|
@@ -42,22 +42,22 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
|
|
|
42
42
|
avgEvalScore: number;
|
|
43
43
|
} | {
|
|
44
44
|
error?: undefined;
|
|
45
|
-
ok?: undefined;
|
|
46
45
|
summary: import("./types.js").TraceSummary;
|
|
47
46
|
spans: import("./types.js").TraceSpan[];
|
|
48
47
|
id?: undefined;
|
|
48
|
+
ok?: undefined;
|
|
49
49
|
} | {
|
|
50
50
|
error?: undefined;
|
|
51
|
-
ok?: undefined;
|
|
52
51
|
summary?: undefined;
|
|
53
52
|
spans?: undefined;
|
|
54
53
|
id: string;
|
|
55
|
-
} | {
|
|
56
54
|
ok?: undefined;
|
|
55
|
+
} | {
|
|
57
56
|
summary?: undefined;
|
|
58
57
|
spans?: undefined;
|
|
59
58
|
id?: undefined;
|
|
60
59
|
error: any;
|
|
60
|
+
ok?: undefined;
|
|
61
61
|
} | {
|
|
62
62
|
error?: undefined;
|
|
63
63
|
summary?: undefined;
|
|
@@ -24,6 +24,17 @@ function costUsdFromCenticents(value) {
|
|
|
24
24
|
return Math.round((value / 10_000) * 1_000_000) / 1_000_000;
|
|
25
25
|
}
|
|
26
26
|
const MAX_TRACKED_GENERATION_TOOL_CALLS = 50;
|
|
27
|
+
/**
|
|
28
|
+
* `auto_continue` reasons the server PLANNED, which must not read as failures.
|
|
29
|
+
*
|
|
30
|
+
* A hosted foreground chunk ends at `run_timeout` roughly every 40s by design —
|
|
31
|
+
* counting those as errors would bury the boundaries that mean something under
|
|
32
|
+
* the ones that mean "working as intended". Every other reason is a boundary
|
|
33
|
+
* something forced on the run: recoverable, but not normal, and it stays
|
|
34
|
+
* visible as an error carrying its reason as the terminal code. Moving a reason
|
|
35
|
+
* across this line changes what the error rate means, so move it deliberately.
|
|
36
|
+
*/
|
|
37
|
+
const EXPECTED_CONTINUATION_REASONS = new Set(["run_timeout", "auto_continue"]);
|
|
27
38
|
const MAX_TOOL_ERROR_MESSAGE_LENGTH = 500;
|
|
28
39
|
const STANDALONE_API_KEY_PATTERN = /\b(?:sk-(?:proj-|ant-)?[A-Za-z0-9_-]{8,}|(?:sk|rk)_(?:live|test)_[A-Za-z0-9]{8,}|AIza[A-Za-z0-9_-]{16,}|gh[pousr]_[A-Za-z0-9]{16,})\b/g;
|
|
29
40
|
function truncateToolErrorMessage(value) {
|
|
@@ -333,6 +344,11 @@ export async function instrumentAgentLoop(opts) {
|
|
|
333
344
|
let errorMessage = null;
|
|
334
345
|
let runMetadata = opts.metadata ?? null;
|
|
335
346
|
let terminalOutcome;
|
|
347
|
+
// The `auto_continue` boundary this run ended at, if any. A cut-off never
|
|
348
|
+
// reaches the loop's outcome classification (`runAgentLoop` returns early at
|
|
349
|
+
// the checkpoint), so without this the run reports no terminal state at all
|
|
350
|
+
// and the reason is recoverable only from `agent_run_events` in Postgres.
|
|
351
|
+
let cutOffReason = null;
|
|
336
352
|
const instrumentedOutcome = (outcome) => {
|
|
337
353
|
terminalOutcome = outcome;
|
|
338
354
|
if (outcome.state === "completed") {
|
|
@@ -377,6 +393,15 @@ export async function instrumentAgentLoop(opts) {
|
|
|
377
393
|
if (event.type === "clear" || event.type === "done") {
|
|
378
394
|
runStatus = "success";
|
|
379
395
|
errorMessage = null;
|
|
396
|
+
cutOffReason = null;
|
|
397
|
+
}
|
|
398
|
+
else if (event.type === "auto_continue") {
|
|
399
|
+
const reason = event.reason || "auto_continue";
|
|
400
|
+
cutOffReason = reason;
|
|
401
|
+
if (!EXPECTED_CONTINUATION_REASONS.has(reason)) {
|
|
402
|
+
runStatus = "error";
|
|
403
|
+
errorMessage = `Agent run was cut off before finishing (${reason}).`;
|
|
404
|
+
}
|
|
380
405
|
}
|
|
381
406
|
else if (event.type === "error") {
|
|
382
407
|
runStatus = "error";
|
|
@@ -635,6 +660,20 @@ export async function instrumentAgentLoop(opts) {
|
|
|
635
660
|
}
|
|
636
661
|
}
|
|
637
662
|
catch { }
|
|
663
|
+
// A cut-off run never reaches the loop's outcome classification, so stand in
|
|
664
|
+
// for it here rather than reporting no terminal state at all. `failed` +
|
|
665
|
+
// `retryable` is the honest encoding available in `AgentLoopOutcome`: the
|
|
666
|
+
// turn did not finish, and the continuation machinery is expected to
|
|
667
|
+
// recover it. A real reported outcome always wins.
|
|
668
|
+
const effectiveTerminalOutcome = terminalOutcome ??
|
|
669
|
+
(cutOffReason && !EXPECTED_CONTINUATION_REASONS.has(cutOffReason)
|
|
670
|
+
? {
|
|
671
|
+
state: "failed",
|
|
672
|
+
code: cutOffReason,
|
|
673
|
+
retryable: true,
|
|
674
|
+
message: `Agent run was cut off before finishing (${cutOffReason}).`,
|
|
675
|
+
}
|
|
676
|
+
: undefined);
|
|
638
677
|
let llmCallCount = 0;
|
|
639
678
|
if (usage || runStatus === "error") {
|
|
640
679
|
llmCallCount =
|
|
@@ -716,7 +755,7 @@ export async function instrumentAgentLoop(opts) {
|
|
|
716
755
|
.sort(([a], [b]) => a - b)
|
|
717
756
|
.map(([, detail]) => detail),
|
|
718
757
|
toolsTruncated: toolInvocationCounter > MAX_TRACKED_GENERATION_TOOL_CALLS,
|
|
719
|
-
terminalOutcome,
|
|
758
|
+
terminalOutcome: effectiveTerminalOutcome,
|
|
720
759
|
delegation: opts.delegation,
|
|
721
760
|
createdAt: runStart,
|
|
722
761
|
experimentAssignments: opts.experimentAssignments,
|
|
@@ -752,13 +791,13 @@ export async function instrumentAgentLoop(opts) {
|
|
|
752
791
|
try {
|
|
753
792
|
const aiError = runStatus === "error"
|
|
754
793
|
? toAiErrorDetail(errorMessage, {
|
|
755
|
-
state:
|
|
756
|
-
code:
|
|
757
|
-
|
|
758
|
-
?
|
|
794
|
+
state: effectiveTerminalOutcome?.state,
|
|
795
|
+
code: effectiveTerminalOutcome?.state === "failed" ||
|
|
796
|
+
effectiveTerminalOutcome?.state === "input_required"
|
|
797
|
+
? effectiveTerminalOutcome.code
|
|
759
798
|
: undefined,
|
|
760
|
-
retryable:
|
|
761
|
-
?
|
|
799
|
+
retryable: effectiveTerminalOutcome?.state === "failed"
|
|
800
|
+
? effectiveTerminalOutcome.retryable
|
|
762
801
|
: undefined,
|
|
763
802
|
})
|
|
764
803
|
: undefined;
|
|
@@ -792,6 +831,10 @@ export async function instrumentAgentLoop(opts) {
|
|
|
792
831
|
source: "agent_observability",
|
|
793
832
|
run_id: runId,
|
|
794
833
|
thread_id: threadId,
|
|
834
|
+
// Present for planned boundaries too, which are not errors: the ratio
|
|
835
|
+
// of run_timeout to no_progress is the signal, and it is unreadable
|
|
836
|
+
// if only one side of it is recorded.
|
|
837
|
+
...(cutOffReason ? { terminal_reason: cutOffReason } : {}),
|
|
795
838
|
// A truncated run must not read as a complete one.
|
|
796
839
|
...(droppedToolSpans > 0
|
|
797
840
|
? {
|
|
@@ -897,10 +940,10 @@ export async function instrumentAgentLoop(opts) {
|
|
|
897
940
|
"agent.input_tokens": usage?.inputTokens ?? 0,
|
|
898
941
|
"agent.output_tokens": usage?.outputTokens ?? 0,
|
|
899
942
|
"agent.cost_cents_x100": costCentsX100,
|
|
900
|
-
"agent.terminal_state":
|
|
901
|
-
"agent.terminal_code":
|
|
902
|
-
|
|
903
|
-
?
|
|
943
|
+
"agent.terminal_state": effectiveTerminalOutcome?.state,
|
|
944
|
+
"agent.terminal_code": effectiveTerminalOutcome?.state === "failed" ||
|
|
945
|
+
effectiveTerminalOutcome?.state === "input_required"
|
|
946
|
+
? effectiveTerminalOutcome.code
|
|
904
947
|
: undefined,
|
|
905
948
|
},
|
|
906
949
|
});
|
package/dist/secrets/routes.d.ts
CHANGED
|
@@ -34,16 +34,16 @@ export declare function createListSecretsHandler(): import("h3").EventHandlerWit
|
|
|
34
34
|
/** POST /_agent-native/secrets/:key — write a secret. */
|
|
35
35
|
export declare function createWriteSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
36
36
|
error: string;
|
|
37
|
-
status?: undefined;
|
|
38
37
|
ok?: undefined;
|
|
38
|
+
status?: undefined;
|
|
39
39
|
} | {
|
|
40
40
|
ok: boolean;
|
|
41
41
|
status: string;
|
|
42
42
|
error?: undefined;
|
|
43
43
|
} | {
|
|
44
|
+
ok?: undefined;
|
|
44
45
|
error: string;
|
|
45
46
|
removed?: undefined;
|
|
46
|
-
ok?: undefined;
|
|
47
47
|
} | {
|
|
48
48
|
ok: boolean;
|
|
49
49
|
removed: boolean;
|
|
@@ -54,9 +54,9 @@ export declare function createWriteSecretHandler(): import("h3").EventHandlerWit
|
|
|
54
54
|
* or the current stored value without changing anything.
|
|
55
55
|
*/
|
|
56
56
|
export declare function createTestSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
57
|
+
ok?: undefined;
|
|
57
58
|
error: string;
|
|
58
59
|
note?: undefined;
|
|
59
|
-
ok?: undefined;
|
|
60
60
|
} | {
|
|
61
61
|
ok: boolean;
|
|
62
62
|
note?: undefined;
|
|
@@ -86,10 +86,17 @@ export { CORE_ACTION_GROUPS };
|
|
|
86
86
|
* Core actions with no `frameworkTools` switch, and why:
|
|
87
87
|
*
|
|
88
88
|
* - `upload-image` is load-bearing for ordinary work.
|
|
89
|
-
* -
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
89
|
+
* - MCP tools and the hosted-harness routes never enter the model's action
|
|
90
|
+
* surface at all (`agentTool: false`), so a switch would gate nothing.
|
|
91
|
+
*
|
|
92
|
+
* Membership is expensive in a way that is easy to miss: an untagged action is
|
|
93
|
+
* also in every app's DEFAULT first-request tool list (see
|
|
94
|
+
* `resolveInitialToolNames`), so each entry here is a schema every app pays for
|
|
95
|
+
* on turn one whether or not it has the surface. The email catalog, the
|
|
96
|
+
* workspace user groups, and the org service tokens each sat here for that
|
|
97
|
+
* reason alone and now answer to `emailCatalog`, `workspaceUserGroups`, and
|
|
98
|
+
* `orgServiceTokens` — all defaulting to on, so availability is unchanged, but
|
|
99
|
+
* reachable through `tool-search` instead of riding along in every request.
|
|
93
100
|
*/
|
|
94
101
|
export declare const ALWAYS_ON_CORE_ACTIONS: ReadonlySet<string>;
|
|
95
102
|
export declare function mergeCoreSharingActions(registry: Record<string, ActionEntry>): Promise<void>;
|
|
@@ -508,25 +508,20 @@ export { CORE_ACTION_GROUPS };
|
|
|
508
508
|
* Core actions with no `frameworkTools` switch, and why:
|
|
509
509
|
*
|
|
510
510
|
* - `upload-image` is load-bearing for ordinary work.
|
|
511
|
-
* -
|
|
512
|
-
*
|
|
513
|
-
*
|
|
514
|
-
*
|
|
511
|
+
* - MCP tools and the hosted-harness routes never enter the model's action
|
|
512
|
+
* surface at all (`agentTool: false`), so a switch would gate nothing.
|
|
513
|
+
*
|
|
514
|
+
* Membership is expensive in a way that is easy to miss: an untagged action is
|
|
515
|
+
* also in every app's DEFAULT first-request tool list (see
|
|
516
|
+
* `resolveInitialToolNames`), so each entry here is a schema every app pays for
|
|
517
|
+
* on turn one whether or not it has the surface. The email catalog, the
|
|
518
|
+
* workspace user groups, and the org service tokens each sat here for that
|
|
519
|
+
* reason alone and now answer to `emailCatalog`, `workspaceUserGroups`, and
|
|
520
|
+
* `orgServiceTokens` — all defaulting to on, so availability is unchanged, but
|
|
521
|
+
* reachable through `tool-search` instead of riding along in every request.
|
|
515
522
|
*/
|
|
516
523
|
export const ALWAYS_ON_CORE_ACTIONS = new Set([
|
|
517
524
|
"upload-image",
|
|
518
|
-
"list-workspace-user-groups",
|
|
519
|
-
"upsert-workspace-user-group",
|
|
520
|
-
"bulk-update-workspace-user-groups",
|
|
521
|
-
"delete-workspace-user-group",
|
|
522
|
-
"list-transactional-emails",
|
|
523
|
-
"render-transactional-email-preview",
|
|
524
|
-
"list-email-log",
|
|
525
|
-
"list-email-activity",
|
|
526
|
-
"list-email-engagement",
|
|
527
|
-
"create-org-service-token",
|
|
528
|
-
"list-org-service-tokens",
|
|
529
|
-
"revoke-org-service-token",
|
|
530
525
|
"list-mcp-tools",
|
|
531
526
|
"call-mcp-tool",
|
|
532
527
|
// Hosted harness capability/policy routes are UI-facing and deliberately
|
|
@@ -36,10 +36,25 @@ export function resolveAgentChatMcpOptions(input) {
|
|
|
36
36
|
// otherwise `disableMcp: true` + `enabled: false` would read as a conflict.
|
|
37
37
|
const legacyEnabled = input?.disableMcp === undefined ? undefined : !input.disableMcp;
|
|
38
38
|
const enabled = pick("enabled", "disableMcp", legacyEnabled, mcp.enabled);
|
|
39
|
+
const connectorCatalog = pick("connectorCatalog", "connectorCatalog", input?.connectorCatalog, mcp.connectorCatalog);
|
|
40
|
+
// `catalogMode: "app"` short-circuits `connectorCatalogActive` in
|
|
41
|
+
// `build-server.ts`, so declaring both served the app's FULL registry while
|
|
42
|
+
// the allow-list sat in the config looking authoritative. That is the same
|
|
43
|
+
// unexplainable-report shape `pick` throws on, minus the legacy/nested pair:
|
|
44
|
+
// an allow-list is written to keep something off the external surface, and
|
|
45
|
+
// silently ignoring it hands every action schema to every connector client.
|
|
46
|
+
if (mcp.catalog === "app" &&
|
|
47
|
+
connectorCatalog &&
|
|
48
|
+
connectorCatalog.length > 0) {
|
|
49
|
+
throw new Error(`[agent-native] Conflicting agent-chat options: \`mcp.catalog: "app"\` serves this ` +
|
|
50
|
+
`app's entire action registry flat, which makes \`mcp.connectorCatalog\` ` +
|
|
51
|
+
`(${connectorCatalog.length} name(s)) inert. Keep \`connectorCatalog\` for a curated ` +
|
|
52
|
+
`external surface, or drop it and keep \`catalog: "app"\` — not both.`);
|
|
53
|
+
}
|
|
39
54
|
return {
|
|
40
55
|
enabled: enabled ?? true,
|
|
41
56
|
catalog: mcp.catalog,
|
|
42
|
-
connectorCatalog
|
|
57
|
+
connectorCatalog,
|
|
43
58
|
externalAgents: pick("externalAgents", "externalAgents", input?.externalAgents, mcp.externalAgents),
|
|
44
59
|
builtinCrossAppTools: mcp.builtinCrossAppTools,
|
|
45
60
|
title: pick("title", "mcpServerInfo.title", legacyInfo?.title, mcp.title),
|
|
@@ -27,10 +27,10 @@ export declare function resolveAgentEngineApiKeyWriteTarget(event: H3Event, scop
|
|
|
27
27
|
export declare function createAgentEngineApiKeyHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
28
28
|
error: any;
|
|
29
29
|
} | {
|
|
30
|
+
error?: undefined;
|
|
30
31
|
ok: boolean;
|
|
31
32
|
key: string;
|
|
32
33
|
baseUrlKey?: string;
|
|
33
34
|
scope: AgentEngineApiKeyScope;
|
|
34
|
-
error?: undefined;
|
|
35
35
|
}>>;
|
|
36
36
|
export {};
|
|
@@ -26,8 +26,8 @@ export declare function createRealtimeTokenHandler(): import("h3").EventHandlerW
|
|
|
26
26
|
expiresAt?: undefined;
|
|
27
27
|
ttlSeconds?: undefined;
|
|
28
28
|
} | {
|
|
29
|
-
error?: undefined;
|
|
30
29
|
token: string;
|
|
31
30
|
expiresAt: string;
|
|
32
31
|
ttlSeconds: number;
|
|
32
|
+
error?: undefined;
|
|
33
33
|
}>>;
|
|
@@ -201,6 +201,8 @@ export default createAgentChatPlugin({
|
|
|
201
201
|
|
|
202
202
|
The builtin cross-app tools (`list_apps`, `open_app`, `ask_app`, `create_embed_session`, `create_workspace_app`, `list_templates`) are always included regardless of the declared list.
|
|
203
203
|
|
|
204
|
+
`mcp.catalog: "app"` is the opposite choice: it serves this app's entire action registry flat, with no compact or connector trimming. The two are mutually exclusive — declaring both throws at plugin init rather than silently ignoring the allow-list, since an allow-list exists to keep something off the external surface.
|
|
205
|
+
|
|
204
206
|
## Tool exposure once connected {#tool-exposure}
|
|
205
207
|
|
|
206
208
|
Once an agent is connected, it sees the [compact catalog](#catalog-tiers) by default: template-declared app actions plus the builtin cross-app verbs (`list_apps`, `open_app`, `ask_app`, and the app-only embed helper). Use `ask_app` to route a natural-language task through an app agent (the same cross-app entry point [A2A](/docs/a2a-protocol) uses).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.0.0-beta-
|
|
3
|
+
"version": "0.0.0-beta-20260820155159",
|
|
4
4
|
"description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
|
|
5
5
|
"homepage": "https://github.com/BuilderIO/agent-native#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -428,7 +428,7 @@
|
|
|
428
428
|
"yjs": "^13.6.32",
|
|
429
429
|
"zod": "^4.3.6",
|
|
430
430
|
"@agent-native/recap-cli": "0.5.5",
|
|
431
|
-
"@agent-native/toolkit": "^0.16.
|
|
431
|
+
"@agent-native/toolkit": "^0.16.8"
|
|
432
432
|
},
|
|
433
433
|
"devDependencies": {
|
|
434
434
|
"@ai-sdk/anthropic": "^3.0.71",
|