@agent-native/core 0.145.3 → 0.145.5
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/analytics/server/plugins/agent-chat.ts +10 -8
- package/corpus/templates/assets/server/plugins/agent-chat.ts +1 -1
- package/corpus/templates/calendar/server/plugins/agent-chat.ts +1 -1
- package/corpus/templates/clips/app/components/editor/editor-layout.tsx +8 -3
- package/corpus/templates/clips/app/components/editor/waveform.tsx +2 -1
- package/corpus/templates/clips/app/components/library/search-bar.tsx +1 -1
- package/corpus/templates/clips/app/global.css +9 -0
- package/corpus/templates/clips/desktop/src/lib/media-capture-constraints.ts +22 -1
- package/corpus/templates/clips/desktop/src-tauri/src/silence_detector.rs +10 -11
- package/corpus/templates/design/actions/view-screen.ts +1 -0
- package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +14 -1
- package/corpus/templates/design/changelog/2026-08-07-cmd-c-now-copies-selected-text-in-the-agent-panel-instead-of.md +6 -0
- package/corpus/templates/design/changelog/2026-08-07-real-time-voice-can-now-read-the-current-selection-and-edit-.md +6 -0
- package/corpus/templates/design/server/lib/reprompt-action-guard.ts +7 -3
- package/corpus/templates/mail/server/plugins/agent-chat.ts +1 -1
- package/corpus/templates/plan/server/lib/plan-framework-tools.ts +11 -0
- package/corpus/templates/plan/server/plugins/00-mcp.ts +25 -68
- package/corpus/templates/plan/server/plugins/agent-chat.ts +8 -2
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/routes.d.ts +1 -1
- package/dist/framework-tools.d.ts +33 -1
- package/dist/framework-tools.js +88 -4
- package/dist/mcp/build-server.d.ts +15 -0
- package/dist/mcp/build-server.js +94 -30
- package/dist/mcp/oauth-client-metadata.js +6 -2
- package/dist/mcp/screen-memory-stdio.d.ts +7 -7
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/routes.d.ts +3 -3
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +4 -4
- package/dist/provider-api/actions/provider-api.d.ts +6 -6
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/action-discovery.d.ts +3 -18
- package/dist/server/action-discovery.js +9 -65
- package/dist/server/agent-chat/action-filters-a2a.d.ts +14 -2
- package/dist/server/agent-chat/action-filters-a2a.js +1 -1
- package/dist/server/agent-chat/mcp-options.d.ts +106 -0
- package/dist/server/agent-chat/mcp-options.js +50 -0
- package/dist/server/agent-chat/plugin-options.d.ts +17 -26
- package/dist/server/agent-chat-plugin.js +21 -11
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/index.d.ts +3 -0
- package/dist/server/index.js +7 -0
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/realtime-voice.d.ts +2 -1
- package/dist/server/realtime-voice.js +38 -56
- package/dist/server/short-lived-token.d.ts +43 -0
- package/dist/server/short-lived-token.js +74 -0
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/package.json +2 -2
package/corpus/README.md
CHANGED
|
@@ -907,14 +907,16 @@ export default createAgentChatPlugin({
|
|
|
907
907
|
durableBackgroundRuns: true,
|
|
908
908
|
runSoftTimeoutMs: ANALYTICS_BACKGROUND_RUN_SOFT_TIMEOUT_MS,
|
|
909
909
|
runNoProgressTimeoutMs: ANALYTICS_BACKGROUND_RUN_NO_PROGRESS_TIMEOUT_MS,
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
910
|
+
mcp: {
|
|
911
|
+
connectorCatalog: [...ANALYTICS_CONNECTOR_CATALOG],
|
|
912
|
+
externalAgents: {
|
|
913
|
+
// Keep the direct MCP surface deliberately curated. External agents
|
|
914
|
+
// should use ask_app by default; cataloged actions are optional stable
|
|
915
|
+
// semantic reads for callers with an exact, fully known contract. They
|
|
916
|
+
// are never a fallback for slow or failed delegation.
|
|
917
|
+
authenticatedReads: "off",
|
|
918
|
+
writes: "ask_app_only",
|
|
919
|
+
},
|
|
918
920
|
},
|
|
919
921
|
resolveOrgId: async (event) => {
|
|
920
922
|
const ctx = await getOrgContext(event);
|
|
@@ -90,7 +90,7 @@ export default createAgentChatPlugin({
|
|
|
90
90
|
// background worker, as content, slides and analytics already do.
|
|
91
91
|
durableBackgroundRuns: true,
|
|
92
92
|
initialToolNames: INITIAL_TOOL_NAMES,
|
|
93
|
-
connectorCatalog: [...CALENDAR_CONNECTOR_CATALOG],
|
|
93
|
+
mcp: { connectorCatalog: [...CALENDAR_CONNECTOR_CATALOG] },
|
|
94
94
|
// Enable sandboxed JavaScript execution so Calendar agents can fetch,
|
|
95
95
|
// paginate, and reduce provider data through providerFetch() without us
|
|
96
96
|
// hardcoding one action per Google Calendar / CRM endpoint.
|
|
@@ -255,6 +255,11 @@ export function EditorLayout({ recordingId, className }: EditorLayoutProps) {
|
|
|
255
255
|
[viewportWidth, zoom],
|
|
256
256
|
);
|
|
257
257
|
|
|
258
|
+
const clampedScrollLeft = Math.min(
|
|
259
|
+
scrollLeft,
|
|
260
|
+
Math.max(0, totalWidth - viewportWidth),
|
|
261
|
+
);
|
|
262
|
+
|
|
258
263
|
const calculateAnchoredScrollLeft = useCallback(
|
|
259
264
|
(
|
|
260
265
|
nextZoom: number,
|
|
@@ -815,7 +820,7 @@ export function EditorLayout({ recordingId, className }: EditorLayoutProps) {
|
|
|
815
820
|
splitPoints={splitPoints}
|
|
816
821
|
activityRanges={transcriptSegments}
|
|
817
822
|
onSeek={seek}
|
|
818
|
-
scrollLeft={
|
|
823
|
+
scrollLeft={clampedScrollLeft}
|
|
819
824
|
onScroll={(s) => setScrollLeft(s)}
|
|
820
825
|
/>
|
|
821
826
|
<div
|
|
@@ -826,7 +831,7 @@ export function EditorLayout({ recordingId, className }: EditorLayoutProps) {
|
|
|
826
831
|
className="relative h-full"
|
|
827
832
|
style={{
|
|
828
833
|
width: totalWidth,
|
|
829
|
-
transform: `translateX(${-
|
|
834
|
+
transform: `translateX(${-clampedScrollLeft}px)`,
|
|
830
835
|
}}
|
|
831
836
|
>
|
|
832
837
|
{durationMs > 0 && (
|
|
@@ -849,7 +854,7 @@ export function EditorLayout({ recordingId, className }: EditorLayoutProps) {
|
|
|
849
854
|
>
|
|
850
855
|
<div
|
|
851
856
|
style={{
|
|
852
|
-
transform: `translateX(${-
|
|
857
|
+
transform: `translateX(${-clampedScrollLeft}px)`,
|
|
853
858
|
width: totalWidth,
|
|
854
859
|
}}
|
|
855
860
|
>
|
|
@@ -185,7 +185,8 @@ export function Waveform({
|
|
|
185
185
|
|
|
186
186
|
const ctx = canvas.getContext("2d");
|
|
187
187
|
if (!ctx) return;
|
|
188
|
-
|
|
188
|
+
const scaleX = canvas.width / totalWidth;
|
|
189
|
+
ctx.setTransform(scaleX, 0, 0, dpr, 0, 0);
|
|
189
190
|
|
|
190
191
|
const hasPeaks = Boolean(
|
|
191
192
|
peaks?.bucketCount &&
|
|
@@ -36,6 +36,8 @@
|
|
|
36
36
|
--sidebar-accent-foreground: 0 0% 15%;
|
|
37
37
|
--sidebar-border: 0 0% 90%;
|
|
38
38
|
--sidebar-ring: 0 0% 40%;
|
|
39
|
+
--highlight: 197 100% 50%;
|
|
40
|
+
--highlight-foreground: 0 0% 10%;
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
.dark {
|
|
@@ -67,6 +69,13 @@
|
|
|
67
69
|
--sidebar-accent-foreground: 0 0% 90%;
|
|
68
70
|
--sidebar-border: 0 0% 20%;
|
|
69
71
|
--sidebar-ring: 0 0% 60%;
|
|
72
|
+
--highlight: 197 100% 50%;
|
|
73
|
+
--highlight-foreground: 0 0% 10%;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
@theme inline {
|
|
77
|
+
--color-highlight: hsl(var(--highlight));
|
|
78
|
+
--color-highlight-foreground: hsl(var(--highlight-foreground));
|
|
70
79
|
}
|
|
71
80
|
|
|
72
81
|
@layer base {
|
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
chooseFallbackAudioInput,
|
|
3
3
|
enumerateAudioInputDevices,
|
|
4
4
|
isLikelyPhoneMicLabel,
|
|
5
|
+
normalizedMediaDeviceId,
|
|
5
6
|
type AudioInputFallback,
|
|
6
7
|
} from "../../../shared/media-device-selection";
|
|
7
8
|
|
|
@@ -204,7 +205,27 @@ export async function getAudioStreamWithFallback(
|
|
|
204
205
|
const stream = await getVoiceFocusedAudioStream(id);
|
|
205
206
|
return id ? stream : replacePhoneDefaultMicIfPossible(stream);
|
|
206
207
|
} catch (err) {
|
|
207
|
-
|
|
208
|
+
let deviceGone = false;
|
|
209
|
+
if (id && err instanceof DOMException && err.name === "NotAllowedError") {
|
|
210
|
+
try {
|
|
211
|
+
deviceGone = !(await enumerateAudioInputDevices()).some(
|
|
212
|
+
(device) => normalizedMediaDeviceId(device.deviceId) === id,
|
|
213
|
+
);
|
|
214
|
+
} catch (enumerateErr) {
|
|
215
|
+
console.warn(
|
|
216
|
+
"[clips-recorder] could not confirm saved mic is still present after NotAllowedError",
|
|
217
|
+
enumerateErr,
|
|
218
|
+
);
|
|
219
|
+
deviceGone = true;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if (deviceGone) {
|
|
223
|
+
console.warn(
|
|
224
|
+
"[clips-recorder] saved mic no longer enumerated after NotAllowedError; falling back",
|
|
225
|
+
{ deviceId: id },
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
if (!isMediaConstraintFailure(err) && !deviceGone) throw err;
|
|
208
229
|
if (id) {
|
|
209
230
|
const fallback = await tryFallbackAudioInput(savedLabel, [id]);
|
|
210
231
|
if (fallback) return fallback;
|
|
@@ -392,7 +392,6 @@ fn install_call_ended_watcher(app: &AppHandle, threshold_ms: u64) {
|
|
|
392
392
|
let mut ever_seen_front = false;
|
|
393
393
|
let mut last_front_at: Option<Instant> = None;
|
|
394
394
|
let mut fired = false;
|
|
395
|
-
let mut last_front_was_generic_browser = false;
|
|
396
395
|
let mut call_app_used_microphone = false;
|
|
397
396
|
let mut microphone_released_at: Option<Instant> = None;
|
|
398
397
|
let mut generation: Option<u64> = None;
|
|
@@ -408,7 +407,6 @@ fn install_call_ended_watcher(app: &AppHandle, threshold_ms: u64) {
|
|
|
408
407
|
ever_seen_front = false;
|
|
409
408
|
last_front_at = None;
|
|
410
409
|
fired = false;
|
|
411
|
-
last_front_was_generic_browser = false;
|
|
412
410
|
call_app_used_microphone = false;
|
|
413
411
|
microphone_released_at = None;
|
|
414
412
|
continue;
|
|
@@ -417,7 +415,6 @@ fn install_call_ended_watcher(app: &AppHandle, threshold_ms: u64) {
|
|
|
417
415
|
ever_seen_front = false;
|
|
418
416
|
last_front_at = None;
|
|
419
417
|
fired = false;
|
|
420
|
-
last_front_was_generic_browser = false;
|
|
421
418
|
call_app_used_microphone = false;
|
|
422
419
|
microphone_released_at = None;
|
|
423
420
|
generation = Some(active_generation);
|
|
@@ -450,7 +447,6 @@ fn install_call_ended_watcher(app: &AppHandle, threshold_ms: u64) {
|
|
|
450
447
|
if is_strong_vc || is_generic_browser {
|
|
451
448
|
ever_seen_front = true;
|
|
452
449
|
last_front_at = Some(Instant::now());
|
|
453
|
-
last_front_was_generic_browser = is_generic_browser;
|
|
454
450
|
}
|
|
455
451
|
if fired {
|
|
456
452
|
continue;
|
|
@@ -474,10 +470,18 @@ fn install_call_ended_watcher(app: &AppHandle, threshold_ms: u64) {
|
|
|
474
470
|
_ => {}
|
|
475
471
|
}
|
|
476
472
|
|
|
473
|
+
// Require audio corroboration for every trigger in this
|
|
474
|
+
// watcher: backgrounding the call app, or its process
|
|
475
|
+
// dropping its mic input, does not by itself prove the call
|
|
476
|
+
// ended — a browser tab can report either transition while
|
|
477
|
+
// the meeting is still playing through system audio. Only
|
|
478
|
+
// quiet mic+system audio alongside the signal does.
|
|
479
|
+
let audio_quiet = audio_recently_silent(&state, threshold_ms);
|
|
480
|
+
|
|
477
481
|
let microphone_released = microphone_release_stop_ready(
|
|
478
482
|
call_app_used_microphone,
|
|
479
483
|
microphone_released_at.map(|at| Instant::now().duration_since(at)),
|
|
480
|
-
);
|
|
484
|
+
) && audio_quiet;
|
|
481
485
|
|
|
482
486
|
let frontmost_call_ended = ever_seen_front
|
|
483
487
|
&& last_front_at
|
|
@@ -485,12 +489,7 @@ fn install_call_ended_watcher(app: &AppHandle, threshold_ms: u64) {
|
|
|
485
489
|
Instant::now().duration_since(t).as_millis() as u64 >= threshold_ms
|
|
486
490
|
})
|
|
487
491
|
.unwrap_or(false)
|
|
488
|
-
|
|
489
|
-
// backgrounding Chrome/Arc alone does not prove that the
|
|
490
|
-
// Meet tab ended. Use the last known conference app, not
|
|
491
|
-
// the unrelated app now in front.
|
|
492
|
-
&& (!last_front_was_generic_browser
|
|
493
|
-
|| audio_recently_silent(&state, threshold_ms));
|
|
492
|
+
&& audio_quiet;
|
|
494
493
|
|
|
495
494
|
if microphone_released || frontmost_call_ended {
|
|
496
495
|
let _ = app.emit("meetings:call-ended", ());
|
|
@@ -220,6 +220,7 @@ export default defineAction({
|
|
|
220
220
|
"See what the user is currently looking at on screen. Returns the current navigation state including which design or template is open, which view they are on (list, templates, editor, design-systems, present, settings), active/focused design screen, selected element, active inspector tab (design, comments, or tweaks), active left rail panel (file, agent, assets, import, tools, tokens, or code), active code file metadata, overview canvas state, review status and feedback queue summary, plus any pending question overlay. Always call this first before taking any action.",
|
|
221
221
|
schema: z.object({}),
|
|
222
222
|
http: false,
|
|
223
|
+
readOnly: true,
|
|
223
224
|
run: async (_, ctx) => {
|
|
224
225
|
const [navigation, designSelection] = await Promise.all([
|
|
225
226
|
readAppStateForCurrentTab("navigation"),
|
|
@@ -277,6 +277,18 @@ export function isDesignHotkeyEditableTarget(target: EventTarget | null) {
|
|
|
277
277
|
return tagName === "input" || tagName === "textarea" || tagName === "select";
|
|
278
278
|
}
|
|
279
279
|
|
|
280
|
+
/** Chat bodies and panel labels are selectable but not editable targets, so the
|
|
281
|
+
* editable guard never sees them. Canvas layers live in the preview iframe and
|
|
282
|
+
* produce no parent-document range, so this cannot mask a real layer copy. */
|
|
283
|
+
export function hasDocumentTextSelection() {
|
|
284
|
+
if (typeof window === "undefined" || !window.getSelection) return false;
|
|
285
|
+
const selection = window.getSelection();
|
|
286
|
+
if (!selection || selection.isCollapsed || selection.rangeCount === 0) {
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
return selection.toString().trim().length > 0;
|
|
290
|
+
}
|
|
291
|
+
|
|
280
292
|
export function isShowKeyboardShortcutsHotkey(event: KeyboardEvent) {
|
|
281
293
|
if (!(event.ctrlKey || event.metaKey) || !event.shiftKey || event.altKey) {
|
|
282
294
|
return false;
|
|
@@ -523,7 +535,7 @@ export function handleDesignHotkey(
|
|
|
523
535
|
if (primary && event.shiftKey && key === "x") {
|
|
524
536
|
return run(props.onToggleStrikethrough);
|
|
525
537
|
}
|
|
526
|
-
if (primary && key === "x") {
|
|
538
|
+
if (primary && key === "x" && !hasDocumentTextSelection()) {
|
|
527
539
|
return runSharedCanvasCommand() || run(props.onCut);
|
|
528
540
|
}
|
|
529
541
|
// Figma's Cmd+U — toggle underline. No existing binding claims plain "u".
|
|
@@ -556,6 +568,7 @@ export function handleDesignHotkey(
|
|
|
556
568
|
if (primary && key === "c") {
|
|
557
569
|
if (event.altKey) return run(props.onCopyProps);
|
|
558
570
|
if (event.shiftKey) return run(props.onCopyAsPng);
|
|
571
|
+
if (hasDocumentTextSelection()) return false;
|
|
559
572
|
return runSharedCanvasCommand() || run(props.onCopy);
|
|
560
573
|
}
|
|
561
574
|
if (primary && key === "v") {
|
|
@@ -77,13 +77,17 @@ export function guardRepromptActionRegistry(
|
|
|
77
77
|
...entry,
|
|
78
78
|
run: async (args, context) => {
|
|
79
79
|
if (context?.caller === "tool" && context.threadId) {
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
let thread: Awaited<ReturnType<typeof getThread>>;
|
|
81
|
+
try {
|
|
82
|
+
thread = await getThread(context.threadId);
|
|
83
|
+
} catch {
|
|
82
84
|
throw new Error(
|
|
83
85
|
`Cannot verify the agent thread for mutating action ${name}.`,
|
|
84
86
|
);
|
|
85
87
|
}
|
|
86
|
-
|
|
88
|
+
// Off-thread callers (realtime voice, MCP, A2A, cron) have no
|
|
89
|
+
// stored thread, so they cannot be inside a reprompt turn.
|
|
90
|
+
const intent = thread ? threadSelectionIntent(thread) : null;
|
|
87
91
|
if (
|
|
88
92
|
intent === "reprompt" &&
|
|
89
93
|
!REPROMPT_MUTATION_ALLOWLIST.has(name)
|
|
@@ -40,7 +40,7 @@ export default createAgentChatPlugin({
|
|
|
40
40
|
// background worker, as content, slides and analytics already do.
|
|
41
41
|
durableBackgroundRuns: true,
|
|
42
42
|
initialToolNames: INITIAL_TOOL_NAMES,
|
|
43
|
-
connectorCatalog: [...MAIL_CONNECTOR_CATALOG],
|
|
43
|
+
mcp: { connectorCatalog: [...MAIL_CONNECTOR_CATALOG] },
|
|
44
44
|
resolveOrgId: async (event) => {
|
|
45
45
|
const ctx = await getOrgContext(event);
|
|
46
46
|
return ctx.orgId;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { FrameworkToolsConfig } from "@agent-native/core/server";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Framework tool policy shared by Plan's two mounts.
|
|
5
|
+
*
|
|
6
|
+
* Plan serves MCP from a dedicated early plugin (`plugins/00-mcp.ts`) rather
|
|
7
|
+
* than the agent-chat plugin, so the policy has to live somewhere both can
|
|
8
|
+
* read. Setting it in only one place is how an app ends up advertising a tool
|
|
9
|
+
* over MCP that its own agent no longer has.
|
|
10
|
+
*/
|
|
11
|
+
export const PLAN_FRAMEWORK_TOOLS: FrameworkToolsConfig = {};
|
|
@@ -1,77 +1,16 @@
|
|
|
1
1
|
import { mountMCP } from "@agent-native/core/mcp";
|
|
2
2
|
import {
|
|
3
|
+
attachToolSearch,
|
|
4
|
+
filterFrameworkToolGroups,
|
|
3
5
|
loadActionsFromStaticRegistry,
|
|
4
|
-
|
|
6
|
+
resolveFrameworkTools,
|
|
5
7
|
} from "@agent-native/core/server";
|
|
6
8
|
import { getRequestUserEmail } from "@agent-native/core/server/request-context";
|
|
7
9
|
import { createAutomationToolEntries } from "@agent-native/core/triggers";
|
|
8
10
|
|
|
9
11
|
import actionsRegistry from "../../.generated/actions-registry.js";
|
|
10
12
|
import { PLAN_CONNECTOR_CATALOG } from "../lib/plan-connector-catalog.js";
|
|
11
|
-
|
|
12
|
-
const TOOL_SEARCH_ACTION_NAME = "tool-search";
|
|
13
|
-
|
|
14
|
-
function attachPlanToolSearch(
|
|
15
|
-
registry: Record<string, ActionEntry>,
|
|
16
|
-
): Record<string, ActionEntry> {
|
|
17
|
-
const searchableNames = new Set(PLAN_CONNECTOR_CATALOG);
|
|
18
|
-
registry[TOOL_SEARCH_ACTION_NAME] = {
|
|
19
|
-
tool: {
|
|
20
|
-
description:
|
|
21
|
-
"Discover callable Plan MCP tools by name and description. Omit query to list the visible Plan connector surface; pass query to filter by capability.",
|
|
22
|
-
parameters: {
|
|
23
|
-
type: "object",
|
|
24
|
-
properties: {
|
|
25
|
-
query: {
|
|
26
|
-
type: "string",
|
|
27
|
-
description:
|
|
28
|
-
"Optional search text, e.g. create plan, share resource, or comments.",
|
|
29
|
-
},
|
|
30
|
-
limit: {
|
|
31
|
-
type: "number",
|
|
32
|
-
description: "Maximum results to return. Defaults to 8.",
|
|
33
|
-
},
|
|
34
|
-
},
|
|
35
|
-
},
|
|
36
|
-
},
|
|
37
|
-
http: false,
|
|
38
|
-
readOnly: true,
|
|
39
|
-
run: async (args: { query?: unknown; limit?: unknown } = {}) => {
|
|
40
|
-
const query = String(args.query ?? "")
|
|
41
|
-
.trim()
|
|
42
|
-
.toLowerCase();
|
|
43
|
-
const rawLimit = Number(args.limit);
|
|
44
|
-
const limit =
|
|
45
|
-
Number.isFinite(rawLimit) && rawLimit > 0
|
|
46
|
-
? Math.min(Math.floor(rawLimit), 25)
|
|
47
|
-
: 8;
|
|
48
|
-
const results = Object.entries(registry)
|
|
49
|
-
.filter(([name, entry]) => {
|
|
50
|
-
if (name === TOOL_SEARCH_ACTION_NAME) return false;
|
|
51
|
-
if (!searchableNames.has(name)) return false;
|
|
52
|
-
const description = entry.tool.description ?? "";
|
|
53
|
-
if (!query) return true;
|
|
54
|
-
return `${name} ${description}`.toLowerCase().includes(query);
|
|
55
|
-
})
|
|
56
|
-
.slice(0, query ? limit : 100)
|
|
57
|
-
.map(([name, entry]) => ({
|
|
58
|
-
name,
|
|
59
|
-
description: entry.tool.description ?? name,
|
|
60
|
-
readOnly: entry.readOnly === true,
|
|
61
|
-
}));
|
|
62
|
-
|
|
63
|
-
return {
|
|
64
|
-
query,
|
|
65
|
-
totalTools: Object.keys(registry).filter((name) =>
|
|
66
|
-
searchableNames.has(name),
|
|
67
|
-
).length,
|
|
68
|
-
count: results.length,
|
|
69
|
-
results,
|
|
70
|
-
};
|
|
71
|
-
},
|
|
72
|
-
};
|
|
73
|
-
return registry;
|
|
74
|
-
}
|
|
13
|
+
import { PLAN_FRAMEWORK_TOOLS } from "../lib/plan-framework-tools.js";
|
|
75
14
|
|
|
76
15
|
function requireMcpOwner(operation: string): string {
|
|
77
16
|
const owner = getRequestUserEmail();
|
|
@@ -84,11 +23,29 @@ function requireMcpOwner(operation: string): string {
|
|
|
84
23
|
}
|
|
85
24
|
|
|
86
25
|
export default function planMcpPlugin(nitroApp: any) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
26
|
+
// This mount deliberately bypasses the agent-chat plugin so the external
|
|
27
|
+
// connector does not wait on chat initialization — which means it has to
|
|
28
|
+
// apply the framework tool policy itself. Reading the same
|
|
29
|
+
// `PLAN_FRAMEWORK_TOOLS` the chat plugin does is what keeps the two mounts
|
|
30
|
+
// from disagreeing about which tools exist.
|
|
31
|
+
const frameworkTools = resolveFrameworkTools({
|
|
32
|
+
frameworkTools: PLAN_FRAMEWORK_TOOLS,
|
|
90
33
|
});
|
|
91
34
|
|
|
35
|
+
const actions = attachToolSearch(
|
|
36
|
+
filterFrameworkToolGroups(
|
|
37
|
+
{
|
|
38
|
+
...loadActionsFromStaticRegistry(actionsRegistry),
|
|
39
|
+
...(frameworkTools.isEnabled("automation")
|
|
40
|
+
? createAutomationToolEntries(() =>
|
|
41
|
+
requireMcpOwner("manage automations"),
|
|
42
|
+
)
|
|
43
|
+
: {}),
|
|
44
|
+
},
|
|
45
|
+
frameworkTools.disabledGroups,
|
|
46
|
+
),
|
|
47
|
+
);
|
|
48
|
+
|
|
92
49
|
mountMCP(nitroApp, {
|
|
93
50
|
name: "Plan",
|
|
94
51
|
title: "Agent-Native Plan",
|
|
@@ -8,6 +8,7 @@ import { z } from "zod";
|
|
|
8
8
|
|
|
9
9
|
import actionsRegistry from "../../.generated/actions-registry.js";
|
|
10
10
|
import { PLAN_CONNECTOR_CATALOG } from "../lib/plan-connector-catalog.js";
|
|
11
|
+
import { PLAN_FRAMEWORK_TOOLS } from "../lib/plan-framework-tools.js";
|
|
11
12
|
import { resolvePlanAnonymousOwner } from "../lib/public-plans.js";
|
|
12
13
|
|
|
13
14
|
const PLAN_BACKGROUND_RUN_SOFT_TIMEOUT_MS = 13 * 60_000;
|
|
@@ -130,8 +131,13 @@ const planAgentChatOptions = {
|
|
|
130
131
|
initialToolNames: INITIAL_TOOL_NAMES,
|
|
131
132
|
anonymousOwner: resolvePlanAnonymousOwner,
|
|
132
133
|
resolveOrgId: async (event) => (await getOrgContext(event)).orgId,
|
|
133
|
-
|
|
134
|
-
|
|
134
|
+
frameworkTools: PLAN_FRAMEWORK_TOOLS,
|
|
135
|
+
mcp: {
|
|
136
|
+
// Plan mounts MCP from a dedicated early plugin (`00-mcp.ts`) so its
|
|
137
|
+
// external connector does not wait on chat plugin initialization.
|
|
138
|
+
enabled: false,
|
|
139
|
+
connectorCatalog: PLAN_CONNECTOR_CATALOG,
|
|
140
|
+
},
|
|
135
141
|
durableBackgroundRuns: true,
|
|
136
142
|
runSoftTimeoutMs: PLAN_BACKGROUND_RUN_SOFT_TIMEOUT_MS,
|
|
137
143
|
};
|
|
@@ -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
|
}>>;
|
package/dist/collab/routes.d.ts
CHANGED
|
@@ -26,8 +26,8 @@ export declare const getCollabState: import("h3").EventHandlerWithFetch<import("
|
|
|
26
26
|
* Body: { update: string (base64), requestSource?: string }
|
|
27
27
|
*/
|
|
28
28
|
export declare const postCollabUpdate: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
29
|
-
error: string;
|
|
30
29
|
ok?: undefined;
|
|
30
|
+
error: string;
|
|
31
31
|
} | {
|
|
32
32
|
error?: undefined;
|
|
33
33
|
ok: boolean;
|
|
@@ -103,18 +103,50 @@ export interface ResolvedFrameworkTools {
|
|
|
103
103
|
* "why can't the agent see db-query" report ends up unexplainable.
|
|
104
104
|
*/
|
|
105
105
|
export declare function resolveFrameworkTools(input: FrameworkToolsInput | undefined): ResolvedFrameworkTools;
|
|
106
|
+
/**
|
|
107
|
+
* Which `frameworkTools` switch owns each framework action kit.
|
|
108
|
+
*
|
|
109
|
+
* This map is the authority. `ActionEntry.frameworkGroup` is an optional
|
|
110
|
+
* pre-resolved copy of the same answer, not a requirement: it is stamped only
|
|
111
|
+
* by `mergeCoreSharingActions`, which runs against the ungated `httpActions`
|
|
112
|
+
* registry, so for a year every app loading core kits through
|
|
113
|
+
* `loadActionsFromStaticRegistry` or its own actions directory carried
|
|
114
|
+
* untagged entries and silently ignored eight of the `frameworkTools`
|
|
115
|
+
* switches. Resolve by name and the switch works no matter how the action was
|
|
116
|
+
* registered.
|
|
117
|
+
*
|
|
118
|
+
* Membership does two jobs: one filter subtracts a whole kit from every agent
|
|
119
|
+
* tool surface at once (the plugin spreads these registries at thirteen
|
|
120
|
+
* composition sites), and it keeps these ~45 schemas out of the DEFAULT
|
|
121
|
+
* first-request tool list. They stay reachable through `tool-search`.
|
|
122
|
+
*
|
|
123
|
+
* Anything absent from this map is always-on and must be listed in
|
|
124
|
+
* `ALWAYS_ON_CORE_ACTIONS` instead. `action-discovery.spec.ts` fails when a new
|
|
125
|
+
* core action appears in neither, so "always-on" stays a decision someone made
|
|
126
|
+
* rather than the default that happens when a map goes un-updated.
|
|
127
|
+
*/
|
|
128
|
+
export declare const CORE_ACTION_GROUPS: Record<string, FrameworkToolGroup>;
|
|
106
129
|
/** Structural view of the one field these helpers read, so tagging utilities
|
|
107
130
|
* stay free of an import cycle with `ActionEntry`. */
|
|
108
131
|
interface FrameworkGrouped {
|
|
109
132
|
frameworkGroup?: FrameworkToolGroup;
|
|
110
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* Which group owns an action, by name first and tag second.
|
|
136
|
+
*
|
|
137
|
+
* Name first because the name is present on every registration path, while the
|
|
138
|
+
* tag is stamped on exactly one — an app is not less entitled to its
|
|
139
|
+
* `frameworkTools` switches because it loaded core kits from a generated
|
|
140
|
+
* registry instead of `autoDiscoverActions`.
|
|
141
|
+
*/
|
|
142
|
+
export declare function resolveFrameworkGroup(name: string, entry: FrameworkGrouped | undefined): FrameworkToolGroup | undefined;
|
|
111
143
|
/**
|
|
112
144
|
* Drop every action belonging to a disabled group. Returns the input untouched
|
|
113
145
|
* when nothing is disabled so the default path allocates nothing.
|
|
114
146
|
*/
|
|
115
147
|
export declare function filterFrameworkToolGroups<T extends FrameworkGrouped>(actions: Record<string, T>, disabledGroups: ReadonlySet<FrameworkToolGroup>): Record<string, T>;
|
|
116
148
|
/** True for actions the framework contributes rather than the app's own. */
|
|
117
|
-
export declare function isFrameworkGroupedAction(entry: FrameworkGrouped): boolean;
|
|
149
|
+
export declare function isFrameworkGroupedAction(name: string, entry: FrameworkGrouped | undefined): boolean;
|
|
118
150
|
/**
|
|
119
151
|
* Whether a group is live, for prompt builders that only receive the disabled
|
|
120
152
|
* set. Prompt text and tool schemas must agree: a prompt that names a tool the
|