@agent-native/core 0.145.4 → 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/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-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/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/mcp/screen-memory-stdio.d.ts +7 -7
- package/dist/provider-api/actions/custom-provider-registration.d.ts +4 -4
- package/dist/provider-api/actions/provider-api.d.ts +5 -5
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/agent-engine-api-key-route.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/package.json +2 -2
package/corpus/README.md
CHANGED
|
@@ -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 {
|
|
@@ -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)
|
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;
|
|
@@ -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;
|
|
@@ -137,13 +137,13 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
137
137
|
inputSchema: {
|
|
138
138
|
type: string;
|
|
139
139
|
properties: {
|
|
140
|
+
count?: undefined;
|
|
140
141
|
query?: undefined;
|
|
141
142
|
minutes?: undefined;
|
|
142
143
|
limit?: undefined;
|
|
143
144
|
clientHint?: undefined;
|
|
144
145
|
timestamp?: undefined;
|
|
145
146
|
chapterId?: undefined;
|
|
146
|
-
count?: undefined;
|
|
147
147
|
startAt?: undefined;
|
|
148
148
|
endAt?: undefined;
|
|
149
149
|
reason?: undefined;
|
|
@@ -159,6 +159,7 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
159
159
|
inputSchema: {
|
|
160
160
|
type: string;
|
|
161
161
|
properties: {
|
|
162
|
+
count?: undefined;
|
|
162
163
|
query: {
|
|
163
164
|
type: string;
|
|
164
165
|
description: string;
|
|
@@ -174,7 +175,6 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
174
175
|
clientHint?: undefined;
|
|
175
176
|
timestamp?: undefined;
|
|
176
177
|
chapterId?: undefined;
|
|
177
|
-
count?: undefined;
|
|
178
178
|
startAt?: undefined;
|
|
179
179
|
endAt?: undefined;
|
|
180
180
|
reason?: undefined;
|
|
@@ -190,6 +190,7 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
190
190
|
inputSchema: {
|
|
191
191
|
type: string;
|
|
192
192
|
properties: {
|
|
193
|
+
count?: undefined;
|
|
193
194
|
minutes: {
|
|
194
195
|
type: string;
|
|
195
196
|
description: string;
|
|
@@ -199,7 +200,6 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
199
200
|
clientHint?: undefined;
|
|
200
201
|
timestamp?: undefined;
|
|
201
202
|
chapterId?: undefined;
|
|
202
|
-
count?: undefined;
|
|
203
203
|
startAt?: undefined;
|
|
204
204
|
endAt?: undefined;
|
|
205
205
|
reason?: undefined;
|
|
@@ -216,6 +216,7 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
216
216
|
type: string;
|
|
217
217
|
required: string[];
|
|
218
218
|
properties: {
|
|
219
|
+
count?: undefined;
|
|
219
220
|
query: {
|
|
220
221
|
type: string;
|
|
221
222
|
description: string;
|
|
@@ -234,7 +235,6 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
234
235
|
};
|
|
235
236
|
timestamp?: undefined;
|
|
236
237
|
chapterId?: undefined;
|
|
237
|
-
count?: undefined;
|
|
238
238
|
startAt?: undefined;
|
|
239
239
|
endAt?: undefined;
|
|
240
240
|
reason?: undefined;
|
|
@@ -250,6 +250,7 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
250
250
|
type: string;
|
|
251
251
|
required: string[];
|
|
252
252
|
properties: {
|
|
253
|
+
count?: undefined;
|
|
253
254
|
query?: undefined;
|
|
254
255
|
minutes?: undefined;
|
|
255
256
|
limit?: undefined;
|
|
@@ -263,7 +264,6 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
263
264
|
description: string;
|
|
264
265
|
};
|
|
265
266
|
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;
|
|
317
318
|
query?: undefined;
|
|
318
319
|
minutes?: undefined;
|
|
319
320
|
limit?: undefined;
|
|
320
321
|
clientHint?: undefined;
|
|
321
322
|
timestamp?: undefined;
|
|
322
323
|
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;
|
|
352
353
|
query?: undefined;
|
|
353
354
|
minutes?: undefined;
|
|
354
355
|
limit?: undefined;
|
|
355
356
|
clientHint?: undefined;
|
|
356
357
|
timestamp?: undefined;
|
|
357
358
|
chapterId?: undefined;
|
|
358
|
-
count?: undefined;
|
|
359
359
|
startAt?: undefined;
|
|
360
360
|
endAt?: undefined;
|
|
361
361
|
reason?: undefined;
|
|
@@ -75,7 +75,6 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
|
|
|
75
75
|
user: "user";
|
|
76
76
|
}>>;
|
|
77
77
|
}, z.core.$strip>>, {
|
|
78
|
-
message?: undefined;
|
|
79
78
|
deleted?: undefined;
|
|
80
79
|
found?: undefined;
|
|
81
80
|
providers: {
|
|
@@ -92,8 +91,8 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
|
|
|
92
91
|
registered?: undefined;
|
|
93
92
|
id?: undefined;
|
|
94
93
|
label?: undefined;
|
|
95
|
-
} | {
|
|
96
94
|
message?: undefined;
|
|
95
|
+
} | {
|
|
97
96
|
deleted?: undefined;
|
|
98
97
|
providers?: undefined;
|
|
99
98
|
count?: undefined;
|
|
@@ -102,8 +101,8 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
|
|
|
102
101
|
registered?: undefined;
|
|
103
102
|
id?: undefined;
|
|
104
103
|
label?: undefined;
|
|
105
|
-
} | {
|
|
106
104
|
message?: undefined;
|
|
105
|
+
} | {
|
|
107
106
|
deleted?: undefined;
|
|
108
107
|
providers?: undefined;
|
|
109
108
|
count?: undefined;
|
|
@@ -112,8 +111,8 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
|
|
|
112
111
|
id: string;
|
|
113
112
|
registered?: undefined;
|
|
114
113
|
label?: undefined;
|
|
115
|
-
} | {
|
|
116
114
|
message?: undefined;
|
|
115
|
+
} | {
|
|
117
116
|
found?: undefined;
|
|
118
117
|
providers?: undefined;
|
|
119
118
|
count?: undefined;
|
|
@@ -122,6 +121,7 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
|
|
|
122
121
|
id: string;
|
|
123
122
|
registered?: undefined;
|
|
124
123
|
label?: undefined;
|
|
124
|
+
message?: undefined;
|
|
125
125
|
} | {
|
|
126
126
|
deleted?: undefined;
|
|
127
127
|
found?: undefined;
|
|
@@ -312,8 +312,8 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
|
|
|
312
312
|
scope?: "org" | "user" | undefined;
|
|
313
313
|
}, {
|
|
314
314
|
id?: undefined;
|
|
315
|
-
deleted?: undefined;
|
|
316
315
|
provider?: undefined;
|
|
316
|
+
deleted?: undefined;
|
|
317
317
|
found?: undefined;
|
|
318
318
|
providers: {
|
|
319
319
|
id: string;
|
|
@@ -339,9 +339,9 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
|
|
|
339
339
|
label?: undefined;
|
|
340
340
|
message?: undefined;
|
|
341
341
|
} | {
|
|
342
|
+
provider?: undefined;
|
|
342
343
|
deleted?: undefined;
|
|
343
344
|
count?: undefined;
|
|
344
|
-
provider?: undefined;
|
|
345
345
|
providers?: undefined;
|
|
346
346
|
found: boolean;
|
|
347
347
|
id: string;
|
|
@@ -349,9 +349,9 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
|
|
|
349
349
|
label?: undefined;
|
|
350
350
|
message?: undefined;
|
|
351
351
|
} | {
|
|
352
|
-
count?: undefined;
|
|
353
352
|
provider?: undefined;
|
|
354
353
|
found?: undefined;
|
|
354
|
+
count?: undefined;
|
|
355
355
|
providers?: undefined;
|
|
356
356
|
deleted: boolean;
|
|
357
357
|
id: string;
|
|
@@ -359,10 +359,10 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
|
|
|
359
359
|
label?: undefined;
|
|
360
360
|
message?: undefined;
|
|
361
361
|
} | {
|
|
362
|
-
deleted?: undefined;
|
|
363
|
-
count?: undefined;
|
|
364
362
|
provider?: undefined;
|
|
363
|
+
deleted?: undefined;
|
|
365
364
|
found?: undefined;
|
|
365
|
+
count?: undefined;
|
|
366
366
|
providers?: undefined;
|
|
367
367
|
registered: boolean;
|
|
368
368
|
id: string;
|
|
@@ -48,8 +48,8 @@ export declare function handleUpdateResource(event: any): Promise<import("./stor
|
|
|
48
48
|
}>;
|
|
49
49
|
/** DELETE /_agent-native/resources/:id — delete a resource */
|
|
50
50
|
export declare function handleDeleteResource(event: any): Promise<{
|
|
51
|
-
ok?: undefined;
|
|
52
51
|
error: string;
|
|
52
|
+
ok?: undefined;
|
|
53
53
|
} | {
|
|
54
54
|
error?: undefined;
|
|
55
55
|
ok: boolean;
|
package/dist/secrets/routes.d.ts
CHANGED
|
@@ -34,37 +34,37 @@ 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
|
-
ok?: undefined;
|
|
38
37
|
status?: undefined;
|
|
38
|
+
ok?: undefined;
|
|
39
39
|
} | {
|
|
40
|
-
error?: undefined;
|
|
41
40
|
ok: boolean;
|
|
42
41
|
status: string;
|
|
42
|
+
error?: undefined;
|
|
43
43
|
} | {
|
|
44
|
-
ok?: undefined;
|
|
45
44
|
error: string;
|
|
46
45
|
removed?: undefined;
|
|
46
|
+
ok?: undefined;
|
|
47
47
|
} | {
|
|
48
|
-
error?: undefined;
|
|
49
48
|
ok: boolean;
|
|
50
49
|
removed: boolean;
|
|
50
|
+
error?: undefined;
|
|
51
51
|
}>>;
|
|
52
52
|
/**
|
|
53
53
|
* POST /_agent-native/secrets/:key/test — validate an optional candidate value
|
|
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;
|
|
58
57
|
error: string;
|
|
59
58
|
note?: undefined;
|
|
59
|
+
ok?: undefined;
|
|
60
60
|
} | {
|
|
61
|
-
error?: undefined;
|
|
62
61
|
ok: boolean;
|
|
63
62
|
note?: undefined;
|
|
64
|
-
} | {
|
|
65
63
|
error?: undefined;
|
|
64
|
+
} | {
|
|
66
65
|
ok: boolean;
|
|
67
66
|
note: string;
|
|
67
|
+
error?: undefined;
|
|
68
68
|
} | {
|
|
69
69
|
note?: undefined;
|
|
70
70
|
ok: boolean;
|
|
@@ -95,11 +95,11 @@ export interface AdHocSecretPayload {
|
|
|
95
95
|
export declare function createAdHocSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<AdHocSecretPayload[] | {
|
|
96
96
|
error: string;
|
|
97
97
|
} | {
|
|
98
|
-
error?: undefined;
|
|
99
98
|
ok: boolean;
|
|
100
99
|
key: string;
|
|
101
|
-
} | {
|
|
102
100
|
error?: undefined;
|
|
101
|
+
} | {
|
|
103
102
|
ok: boolean;
|
|
104
103
|
removed: boolean;
|
|
104
|
+
error?: undefined;
|
|
105
105
|
}>>;
|
|
@@ -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;
|
|
31
30
|
ok: boolean;
|
|
32
31
|
key: string;
|
|
33
32
|
baseUrlKey?: string;
|
|
34
33
|
scope: AgentEngineApiKeyScope;
|
|
34
|
+
error?: undefined;
|
|
35
35
|
}>>;
|
|
36
36
|
export {};
|
|
@@ -8,8 +8,9 @@ export declare const REALTIME_VOICE_MAX_TOOL_OUTPUT_CHARS = 16000;
|
|
|
8
8
|
export declare const REALTIME_VOICE_MAX_TOOLS = 32;
|
|
9
9
|
export declare const REALTIME_VOICE_MAX_TOOL_SCHEMA_BYTES = 32000;
|
|
10
10
|
export declare const REALTIME_VOICE_MAX_SESSION_BYTES = 64000;
|
|
11
|
+
/** Absolute, not sliding — a signed grant cannot be extended server-side, so
|
|
12
|
+
* this must outlast the provider's 60-minute maximum realtime session. */
|
|
11
13
|
export declare const REALTIME_VOICE_TOOL_GRANT_TTL_MS: number;
|
|
12
|
-
export declare const REALTIME_VOICE_MAX_TOOL_GRANT_SESSIONS = 256;
|
|
13
14
|
export declare const REALTIME_VOICE_CAPABILITY_HEADER = "X-Agent-Native-Realtime-Capability";
|
|
14
15
|
export interface RealtimeVoiceRequestContext {
|
|
15
16
|
event: H3Event;
|
|
@@ -9,6 +9,7 @@ import { getBuilderGatewayBaseUrl, resolveBuilderCredentials, resolveSecret, } f
|
|
|
9
9
|
import { getH3App } from "./framework-request-handler.js";
|
|
10
10
|
import { runWithRequestContext } from "./request-context.js";
|
|
11
11
|
import { isSameOriginRequest } from "./request-origin.js";
|
|
12
|
+
import { signRealtimeVoiceCapability, verifyRealtimeVoiceCapability, } from "./short-lived-token.js";
|
|
12
13
|
export const REALTIME_VOICE_SESSION_PATH = "/_agent-native/realtime-voice/session";
|
|
13
14
|
export const REALTIME_VOICE_TOOL_PATH = "/_agent-native/realtime-voice/tool";
|
|
14
15
|
export const REALTIME_VOICE_MAX_SDP_BYTES = 64 * 1024;
|
|
@@ -17,8 +18,9 @@ export const REALTIME_VOICE_MAX_TOOL_OUTPUT_CHARS = 16_000;
|
|
|
17
18
|
export const REALTIME_VOICE_MAX_TOOLS = 32;
|
|
18
19
|
export const REALTIME_VOICE_MAX_TOOL_SCHEMA_BYTES = 32_000;
|
|
19
20
|
export const REALTIME_VOICE_MAX_SESSION_BYTES = 64_000;
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
/** Absolute, not sliding — a signed grant cannot be extended server-side, so
|
|
22
|
+
* this must outlast the provider's 60-minute maximum realtime session. */
|
|
23
|
+
export const REALTIME_VOICE_TOOL_GRANT_TTL_MS = 75 * 60 * 1_000;
|
|
22
24
|
export const REALTIME_VOICE_CAPABILITY_HEADER = "X-Agent-Native-Realtime-Capability";
|
|
23
25
|
const OPENAI_REALTIME_CALLS_URL = "https://api.openai.com/v1/realtime/calls";
|
|
24
26
|
const DEFAULT_MODEL = "gpt-realtime-2.1";
|
|
@@ -207,56 +209,30 @@ function packRealtimeTools(session, eligibleTools) {
|
|
|
207
209
|
}
|
|
208
210
|
return packed;
|
|
209
211
|
}
|
|
210
|
-
function mintRealtimeToolCapability() {
|
|
211
|
-
return
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
}
|
|
218
|
-
while (capabilities.size > REALTIME_VOICE_MAX_TOOL_GRANT_SESSIONS) {
|
|
219
|
-
let oldestKey;
|
|
220
|
-
let oldestExpiry = Number.POSITIVE_INFINITY;
|
|
221
|
-
for (const [key, capability] of capabilities) {
|
|
222
|
-
if (capability.expiresAt < oldestExpiry) {
|
|
223
|
-
oldestKey = key;
|
|
224
|
-
oldestExpiry = capability.expiresAt;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
if (!oldestKey)
|
|
228
|
-
break;
|
|
229
|
-
capabilities.delete(oldestKey);
|
|
230
|
-
}
|
|
212
|
+
function mintRealtimeToolCapability(auth, capability) {
|
|
213
|
+
return signRealtimeVoiceCapability({
|
|
214
|
+
userEmail: auth.userEmail,
|
|
215
|
+
...(auth.orgId ? { orgId: auth.orgId } : {}),
|
|
216
|
+
...(auth.browserTabId ? { browserTabId: auth.browserTabId } : {}),
|
|
217
|
+
toolNames: [...capability.initialNames],
|
|
218
|
+
discoveredToolNames: [...capability.names],
|
|
219
|
+
}, REALTIME_VOICE_TOOL_GRANT_TTL_MS / 1_000);
|
|
231
220
|
}
|
|
232
|
-
function
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
capabilities.set(token, {
|
|
236
|
-
userEmail: auth.userEmail.trim().toLowerCase(),
|
|
221
|
+
function resolveRealtimeToolCapability(token, auth) {
|
|
222
|
+
const verified = verifyRealtimeVoiceCapability(token, {
|
|
223
|
+
userEmail: auth.userEmail,
|
|
237
224
|
...(auth.orgId ? { orgId: auth.orgId } : {}),
|
|
238
225
|
...(auth.browserTabId ? { browserTabId: auth.browserTabId } : {}),
|
|
239
|
-
expiresAt: Date.now() + REALTIME_VOICE_TOOL_GRANT_TTL_MS,
|
|
240
|
-
initialNames: new Set(initialNames),
|
|
241
|
-
names: new Set(),
|
|
242
226
|
});
|
|
243
|
-
|
|
244
|
-
return token;
|
|
245
|
-
}
|
|
246
|
-
function resolveRealtimeToolCapability(capabilities, token, auth) {
|
|
247
|
-
cleanRealtimeToolCapabilities(capabilities);
|
|
248
|
-
if (!token)
|
|
249
|
-
return null;
|
|
250
|
-
const capability = capabilities.get(token);
|
|
251
|
-
if (!capability)
|
|
252
|
-
return null;
|
|
253
|
-
if (capability.userEmail !== auth.userEmail.trim().toLowerCase() ||
|
|
254
|
-
capability.orgId !== auth.orgId ||
|
|
255
|
-
capability.browserTabId !== auth.browserTabId) {
|
|
227
|
+
if (!verified.ok)
|
|
256
228
|
return null;
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
229
|
+
return {
|
|
230
|
+
userEmail: verified.userEmail,
|
|
231
|
+
...(verified.orgId ? { orgId: verified.orgId } : {}),
|
|
232
|
+
...(verified.browserTabId ? { browserTabId: verified.browserTabId } : {}),
|
|
233
|
+
initialNames: new Set(verified.toolNames),
|
|
234
|
+
names: new Set(verified.discoveredToolNames),
|
|
235
|
+
};
|
|
260
236
|
}
|
|
261
237
|
function parseSuccessfulToolSearchNames(output) {
|
|
262
238
|
try {
|
|
@@ -302,7 +278,6 @@ function grantDiscoveredRealtimeTools(input) {
|
|
|
302
278
|
input.capability.names.add(tool.name);
|
|
303
279
|
expandedTools.push(tool);
|
|
304
280
|
}
|
|
305
|
-
input.capability.expiresAt = Date.now() + REALTIME_VOICE_TOOL_GRANT_TTL_MS;
|
|
306
281
|
return expandedTools;
|
|
307
282
|
}
|
|
308
283
|
function declaredBodyBytes(event) {
|
|
@@ -364,7 +339,7 @@ function invalidMethod(event) {
|
|
|
364
339
|
setResponseStatus(event, 405);
|
|
365
340
|
return { error: "Method not allowed" };
|
|
366
341
|
}
|
|
367
|
-
function createSessionHandler(tools,
|
|
342
|
+
function createSessionHandler(tools, options) {
|
|
368
343
|
return defineEventHandler(async (event) => {
|
|
369
344
|
if (getMethod(event) !== "POST")
|
|
370
345
|
return invalidMethod(event);
|
|
@@ -510,7 +485,10 @@ function createSessionHandler(tools, capabilities, options) {
|
|
|
510
485
|
}
|
|
511
486
|
setResponseStatus(event, upstream.status);
|
|
512
487
|
setResponseHeader(event, "Content-Type", "application/sdp");
|
|
513
|
-
setResponseHeader(event, REALTIME_VOICE_CAPABILITY_HEADER,
|
|
488
|
+
setResponseHeader(event, REALTIME_VOICE_CAPABILITY_HEADER, mintRealtimeToolCapability(auth, {
|
|
489
|
+
initialNames: new Set(packedTools.map((tool) => tool.name)),
|
|
490
|
+
names: new Set(),
|
|
491
|
+
}));
|
|
514
492
|
return answerSdp;
|
|
515
493
|
});
|
|
516
494
|
});
|
|
@@ -563,7 +541,7 @@ function normalizeExecutionResult(result) {
|
|
|
563
541
|
: {}),
|
|
564
542
|
};
|
|
565
543
|
}
|
|
566
|
-
function createToolHandler(toolsByName,
|
|
544
|
+
function createToolHandler(toolsByName, options) {
|
|
567
545
|
return defineEventHandler(async (event) => {
|
|
568
546
|
if (getMethod(event) !== "POST")
|
|
569
547
|
return invalidMethod(event);
|
|
@@ -577,7 +555,7 @@ function createToolHandler(toolsByName, capabilities, options) {
|
|
|
577
555
|
setResponseStatus(event, 401);
|
|
578
556
|
return { error: "Authentication required" };
|
|
579
557
|
}
|
|
580
|
-
const capability = resolveRealtimeToolCapability(
|
|
558
|
+
const capability = resolveRealtimeToolCapability(readSafeHeader(event, REALTIME_VOICE_CAPABILITY_HEADER), auth);
|
|
581
559
|
if (!capability) {
|
|
582
560
|
setResponseStatus(event, 403);
|
|
583
561
|
return { error: "Invalid or expired realtime voice capability" };
|
|
@@ -648,7 +626,12 @@ function createToolHandler(toolsByName, capabilities, options) {
|
|
|
648
626
|
return {
|
|
649
627
|
callId: request.callId,
|
|
650
628
|
...result,
|
|
651
|
-
...(expandedTools.length > 0
|
|
629
|
+
...(expandedTools.length > 0
|
|
630
|
+
? {
|
|
631
|
+
expandedTools,
|
|
632
|
+
capability: mintRealtimeToolCapability(auth, capability),
|
|
633
|
+
}
|
|
634
|
+
: {}),
|
|
652
635
|
};
|
|
653
636
|
}
|
|
654
637
|
catch (error) {
|
|
@@ -669,10 +652,9 @@ export function mountRealtimeVoiceRoutes(nitroApp, actions, options) {
|
|
|
669
652
|
}
|
|
670
653
|
const tools = buildRealtimeTools(actions);
|
|
671
654
|
const toolsByName = new Map(tools.map((tool) => [tool.name, tool]));
|
|
672
|
-
const capabilities = new Map();
|
|
673
655
|
const app = getH3App(nitroApp);
|
|
674
|
-
app.use(REALTIME_VOICE_SESSION_PATH, createSessionHandler(tools,
|
|
675
|
-
app.use(REALTIME_VOICE_TOOL_PATH, createToolHandler(toolsByName,
|
|
656
|
+
app.use(REALTIME_VOICE_SESSION_PATH, createSessionHandler(tools, options));
|
|
657
|
+
app.use(REALTIME_VOICE_TOOL_PATH, createToolHandler(toolsByName, options));
|
|
676
658
|
return {
|
|
677
659
|
sessionPath: REALTIME_VOICE_SESSION_PATH,
|
|
678
660
|
toolPath: REALTIME_VOICE_TOOL_PATH,
|
|
@@ -121,6 +121,49 @@ export declare function verifyRealtimeSubscribeToken(token: string, expected: {
|
|
|
121
121
|
projectId: string;
|
|
122
122
|
key: string;
|
|
123
123
|
}): RealtimeVerifyResult;
|
|
124
|
+
/** Payload `typ` discriminator for realtime voice tool capabilities. */
|
|
125
|
+
export declare const REALTIME_VOICE_CAPABILITY_TOKEN_TYPE = "rt-voice-capability";
|
|
126
|
+
/** Inputs for {@link signRealtimeVoiceCapability}. */
|
|
127
|
+
export interface RealtimeVoiceCapabilityClaims {
|
|
128
|
+
/** Authenticated caller. Re-checked against the session on every tool call. */
|
|
129
|
+
userEmail: string;
|
|
130
|
+
orgId?: string;
|
|
131
|
+
/** Binds the capability to the tab that opened the session. */
|
|
132
|
+
browserTabId?: string;
|
|
133
|
+
/** Tool names packed into the session's opening manifest. */
|
|
134
|
+
toolNames: readonly string[];
|
|
135
|
+
/** Names added later by a successful `tool-search`, capped separately. */
|
|
136
|
+
discoveredToolNames?: readonly string[];
|
|
137
|
+
ttlSeconds?: number;
|
|
138
|
+
}
|
|
139
|
+
/** Result of {@link verifyRealtimeVoiceCapability}. */
|
|
140
|
+
export type RealtimeVoiceCapabilityVerifyResult = {
|
|
141
|
+
ok: true;
|
|
142
|
+
userEmail: string;
|
|
143
|
+
orgId?: string;
|
|
144
|
+
browserTabId?: string;
|
|
145
|
+
toolNames: string[];
|
|
146
|
+
discoveredToolNames: string[];
|
|
147
|
+
} | {
|
|
148
|
+
ok: false;
|
|
149
|
+
reason: string;
|
|
150
|
+
};
|
|
151
|
+
/**
|
|
152
|
+
* Mint a capability authorising `claims.toolNames` for one realtime voice
|
|
153
|
+
* session. The token is a bounded manifest scope layered on top of the
|
|
154
|
+
* caller's session cookie — never a standalone credential.
|
|
155
|
+
*/
|
|
156
|
+
export declare function signRealtimeVoiceCapability(claims: RealtimeVoiceCapabilityClaims, ttlSecondsDefault: number): string;
|
|
157
|
+
/**
|
|
158
|
+
* Verify a realtime voice capability and confirm it was minted for exactly the
|
|
159
|
+
* identity now making the request. Identity mismatch is reported separately
|
|
160
|
+
* from a bad signature so callers can tell a replay from a stale tab.
|
|
161
|
+
*/
|
|
162
|
+
export declare function verifyRealtimeVoiceCapability(token: string | undefined, expected: {
|
|
163
|
+
userEmail: string;
|
|
164
|
+
orgId?: string;
|
|
165
|
+
browserTabId?: string;
|
|
166
|
+
}): RealtimeVoiceCapabilityVerifyResult;
|
|
124
167
|
/** Payload `typ` discriminator for gateway access-check tokens. */
|
|
125
168
|
export declare const GATEWAY_ACCESS_TOKEN_TYPE = "rt-access-check";
|
|
126
169
|
/** Inputs for {@link signGatewayAccessToken}. */
|
|
@@ -241,6 +241,80 @@ export function verifyRealtimeSubscribeToken(token, expected) {
|
|
|
241
241
|
absExp: claims.absExp,
|
|
242
242
|
};
|
|
243
243
|
}
|
|
244
|
+
// ── Realtime voice tool capabilities ─────────────────────────────────────────
|
|
245
|
+
// Signed, not stored: minting and spending are separate requests, and under a
|
|
246
|
+
// serverless preset they land on different instances.
|
|
247
|
+
/** Payload `typ` discriminator for realtime voice tool capabilities. */
|
|
248
|
+
export const REALTIME_VOICE_CAPABILITY_TOKEN_TYPE = "rt-voice-capability";
|
|
249
|
+
/**
|
|
250
|
+
* Mint a capability authorising `claims.toolNames` for one realtime voice
|
|
251
|
+
* session. The token is a bounded manifest scope layered on top of the
|
|
252
|
+
* caller's session cookie — never a standalone credential.
|
|
253
|
+
*/
|
|
254
|
+
export function signRealtimeVoiceCapability(claims, ttlSecondsDefault) {
|
|
255
|
+
const payload = {
|
|
256
|
+
typ: REALTIME_VOICE_CAPABILITY_TOKEN_TYPE,
|
|
257
|
+
userEmail: claims.userEmail.trim().toLowerCase(),
|
|
258
|
+
toolNames: [...claims.toolNames],
|
|
259
|
+
exp: Math.floor(Date.now() / 1000) + (claims.ttlSeconds ?? ttlSecondsDefault),
|
|
260
|
+
};
|
|
261
|
+
if (claims.orgId)
|
|
262
|
+
payload.orgId = claims.orgId;
|
|
263
|
+
if (claims.browserTabId)
|
|
264
|
+
payload.browserTabId = claims.browserTabId;
|
|
265
|
+
if (claims.discoveredToolNames?.length) {
|
|
266
|
+
payload.discovered = [...claims.discoveredToolNames];
|
|
267
|
+
}
|
|
268
|
+
const payloadStr = base64UrlEncode(JSON.stringify(payload));
|
|
269
|
+
return `${payloadStr}.${hmacB64(payloadStr, getSigningKey())}`;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Verify a realtime voice capability and confirm it was minted for exactly the
|
|
273
|
+
* identity now making the request. Identity mismatch is reported separately
|
|
274
|
+
* from a bad signature so callers can tell a replay from a stale tab.
|
|
275
|
+
*/
|
|
276
|
+
export function verifyRealtimeVoiceCapability(token, expected) {
|
|
277
|
+
if (typeof token !== "string" || !token.includes(".")) {
|
|
278
|
+
return { ok: false, reason: "malformed" };
|
|
279
|
+
}
|
|
280
|
+
const [payloadStr, sig] = token.split(".", 2);
|
|
281
|
+
if (!payloadStr || !sig)
|
|
282
|
+
return { ok: false, reason: "malformed" };
|
|
283
|
+
if (!timingSafeEqualB64(sig, hmacB64(payloadStr, getSigningKey()))) {
|
|
284
|
+
return { ok: false, reason: "bad_signature" };
|
|
285
|
+
}
|
|
286
|
+
let claims;
|
|
287
|
+
try {
|
|
288
|
+
claims = JSON.parse(base64UrlDecode(payloadStr).toString("utf8"));
|
|
289
|
+
}
|
|
290
|
+
catch {
|
|
291
|
+
return { ok: false, reason: "bad_payload" };
|
|
292
|
+
}
|
|
293
|
+
if (claims.typ !== REALTIME_VOICE_CAPABILITY_TOKEN_TYPE) {
|
|
294
|
+
return { ok: false, reason: "wrong_type" };
|
|
295
|
+
}
|
|
296
|
+
if (typeof claims.exp !== "number" || !Array.isArray(claims.toolNames)) {
|
|
297
|
+
return { ok: false, reason: "bad_payload" };
|
|
298
|
+
}
|
|
299
|
+
if (claims.exp * 1000 < Date.now())
|
|
300
|
+
return { ok: false, reason: "expired" };
|
|
301
|
+
if (claims.userEmail !== expected.userEmail.trim().toLowerCase() ||
|
|
302
|
+
claims.orgId !== expected.orgId ||
|
|
303
|
+
claims.browserTabId !== expected.browserTabId) {
|
|
304
|
+
return { ok: false, reason: "identity_mismatch" };
|
|
305
|
+
}
|
|
306
|
+
const stringsOnly = (value) => Array.isArray(value)
|
|
307
|
+
? value.filter((name) => typeof name === "string")
|
|
308
|
+
: [];
|
|
309
|
+
return {
|
|
310
|
+
ok: true,
|
|
311
|
+
userEmail: claims.userEmail,
|
|
312
|
+
orgId: claims.orgId,
|
|
313
|
+
browserTabId: claims.browserTabId,
|
|
314
|
+
toolNames: stringsOnly(claims.toolNames),
|
|
315
|
+
discoveredToolNames: stringsOnly(claims.discovered),
|
|
316
|
+
};
|
|
317
|
+
}
|
|
244
318
|
// ── Gateway access-check tokens ──────────────────────────────────────────────
|
|
245
319
|
//
|
|
246
320
|
// The hosted gateway has no access to an app's shareable-resource registry, so
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.145.
|
|
3
|
+
"version": "0.145.5",
|
|
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": {
|
|
@@ -413,7 +413,7 @@
|
|
|
413
413
|
"yjs": "^13.6.31",
|
|
414
414
|
"zod": "^4.3.6",
|
|
415
415
|
"@agent-native/recap-cli": "0.5.3",
|
|
416
|
-
"@agent-native/toolkit": "^0.13.
|
|
416
|
+
"@agent-native/toolkit": "^0.13.4"
|
|
417
417
|
},
|
|
418
418
|
"devDependencies": {
|
|
419
419
|
"@ai-sdk/anthropic": "^3.0.71",
|