@agent-native/core 0.153.10 → 0.154.1
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/AGENTS.md +20 -30
- package/corpus/templates/analytics/actions/search-dashboard-references.ts +58 -0
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +1 -1
- package/corpus/templates/analytics/app/lib/utils.ts +1 -1
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/ViewSqlPopover.tsx +1 -1
- package/corpus/templates/analytics/server/lib/agent-chat-plan-mode.ts +2 -0
- package/corpus/templates/analytics/server/lib/dashboards-store.ts +366 -3
- package/corpus/templates/analytics/server/lib/real-data-actions.ts +3 -1
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +6 -2
- package/corpus/templates/calendar/app/components/calendar/EventDialog.tsx +1 -1
- package/corpus/templates/calendar/app/lib/utils.ts +1 -1
- package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +32 -5
- package/corpus/templates/clips/app/components/sharing/share-ui.tsx +15 -15
- package/corpus/templates/clips/app/i18n/en-US.ts +3 -3
- package/corpus/templates/clips/app/lib/utils.ts +5 -6
- package/corpus/templates/clips/chrome-extension/src/offscreen.ts +43 -8
- package/corpus/templates/clips/desktop/src/app.tsx +26 -12
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +23 -9
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen/custom_capture.rs +261 -11
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +21 -3
- package/corpus/templates/clips/shared/mic-audio-cleanup.ts +350 -0
- package/corpus/templates/content/actions/resolve-content-landing.ts +162 -32
- package/corpus/templates/content/app/i18n-data.ts +10 -10
- package/corpus/templates/content/scripts/check-native-deps.mjs +181 -15
- package/corpus/templates/content/shared/content-landing.ts +5 -0
- package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +79 -7
- package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +14 -2
- package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +2 -2
- package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +35 -11
- package/corpus/templates/design/actions/get-design-system.ts +103 -3
- package/corpus/templates/design/actions/import-design-tokens.ts +37 -8
- package/corpus/templates/design/actions/import-github.ts +32 -138
- package/corpus/templates/design/actions/index-design-system-with-builder.ts +49 -16
- package/corpus/templates/design/actions/present-design-variants.ts +93 -4
- package/corpus/templates/design/actions/run-design-audit.ts +229 -0
- package/corpus/templates/design/actions/sync-design-system-with-builder.ts +64 -0
- package/corpus/templates/design/app/components/design/code-workbench/commands.ts +2 -2
- package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +17 -4
- package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +5 -1
- package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +5 -1
- package/corpus/templates/design/app/hooks/use-question-flow.ts +81 -5
- package/corpus/templates/design/app/i18n/en-US.ts +1 -1
- package/corpus/templates/design/app/i18n-data.ts +32 -1
- package/corpus/templates/design/app/i18n-keyboard-shortcuts.ts +10 -10
- package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +83 -11
- package/corpus/templates/design/app/pages/DesignSystems.tsx +14 -0
- package/corpus/templates/design/app/pages/design-editor/generation-prompt-directives.ts +22 -0
- package/corpus/templates/design/changelog/2026-08-13-a-screen-that-would-render-behind-an-invisible-alpine-overla.md +6 -0
- package/corpus/templates/design/changelog/2026-08-13-attach-a-ui-screenshot-and-the-design-is-reproduced-not-rein.md +6 -0
- package/corpus/templates/design/changelog/2026-08-13-linking-a-design-system-means-you-never-get-generic-directio.md +6 -0
- package/corpus/templates/design/changelog/2026-08-13-the-design-audit-now-reports-when-a-screen-ignores-its-linke.md +6 -0
- package/corpus/templates/design/changelog/2026-08-13-the-design-system-preview-now-shows-how-many-named-tokens-we.md +6 -0
- package/corpus/templates/design/changelog/2026-08-13-your-design-system-prompt-and-reference-screenshots-now-reac.md +6 -0
- package/corpus/templates/design/server/lib/builder-design-system-proxy.ts +18 -2
- package/corpus/templates/design/shared/design-review.ts +1 -0
- package/corpus/templates/design/shared/html-integrity.ts +171 -0
- package/corpus/templates/mail/app/lib/utils.ts +1 -1
- package/corpus/templates/slides/.agents/skills/design-systems/SKILL.md +10 -6
- package/corpus/templates/slides/actions/index-design-system-with-builder.ts +41 -3
- package/corpus/templates/slides/actions/patch-deck.ts +12 -3
- package/corpus/templates/slides/actions/sync-design-system-with-builder.ts +64 -0
- package/corpus/templates/slides/app/components/deck/DeckCard.tsx +1 -1
- package/corpus/templates/slides/app/components/design-system/DesignSystemSetup.tsx +193 -20
- package/corpus/templates/slides/app/components/editor/EditorActionCluster.tsx +73 -1
- package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +64 -34
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +34 -41
- package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +119 -91
- package/corpus/templates/slides/app/components/editor/ImageOverlay.tsx +16 -0
- package/corpus/templates/slides/app/components/editor/NewDeckReferenceStep.tsx +15 -0
- package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +158 -31
- package/corpus/templates/slides/app/components/editor/SlideOverflowWarning.tsx +30 -14
- package/corpus/templates/slides/app/components/editor/SpeakerNotesPanel.tsx +2 -2
- package/corpus/templates/slides/app/components/editor/canvas-interactions/slides-canvas-adapter.ts +4 -1
- package/corpus/templates/slides/app/components/editor/rich-text-selection.ts +14 -0
- package/corpus/templates/slides/app/components/editor/slide-text-targets.ts +24 -0
- package/corpus/templates/slides/app/context/DeckContext.tsx +19 -31
- package/corpus/templates/slides/app/global.css +24 -0
- package/corpus/templates/slides/app/hooks/use-navigation-state.ts +20 -17
- package/corpus/templates/slides/app/i18n/en-US.ts +19 -10
- package/corpus/templates/slides/app/lib/deck-filter.ts +47 -0
- package/corpus/templates/slides/app/lib/export-google-slides-client.ts +15 -4
- package/corpus/templates/slides/app/lib/export-pptx-client.ts +637 -1
- package/corpus/templates/slides/app/lib/image-download.ts +84 -0
- package/corpus/templates/slides/app/lib/utils.ts +1 -1
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +9 -0
- package/corpus/templates/slides/app/pages/Index.tsx +25 -9
- package/corpus/templates/slides/changelog/2026-08-13-clicking-a-slide-thumbnail-now-focuses-it-so-the-slide-shortc.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-13-duplicating-or-restoring-a-slide-now-keeps-its-transition-ani.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-13-opening-presentation-mode-from-the-agent-now-starts-on-the-re.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-13-slide-transitions-can-be-set-from-a-visible-control-in-the-sl.md +6 -0
- package/corpus/templates/slides/netlify.toml +1 -0
- package/corpus/templates/slides/server/lib/builder-design-system-proxy.ts +14 -2
- package/dist/a2a/audience.d.ts +2 -0
- package/dist/a2a/audience.js +23 -0
- package/dist/a2a/client.d.ts +4 -0
- package/dist/a2a/client.js +32 -14
- package/dist/a2a/server.d.ts +9 -4
- package/dist/a2a/server.js +52 -25
- package/dist/a2a/types.d.ts +4 -4
- package/dist/agent/production-agent.js +1 -1
- package/dist/brand-kit/types.d.ts +10 -2
- package/dist/cli/code.js +1 -1
- package/dist/cli/index.js +30 -1
- package/dist/cli/native-dependencies.d.ts +24 -0
- package/dist/cli/native-dependencies.js +264 -0
- package/dist/cli/plan-local.js +1 -1
- package/dist/cli/skills-content/design-visual-edit-skill.d.ts +1 -1
- package/dist/cli/skills-content/design-visual-edit-skill.js +2 -2
- package/dist/cli/visualize-repo.js +1 -1
- package/dist/client/FeedbackButton.js +12 -12
- package/dist/client/agent-chat-adapter.js +3 -4
- package/dist/client/assistant-ui-recovery.d.ts +1 -1
- package/dist/client/assistant-ui-recovery.js +4 -0
- package/dist/client/chat/message-components.d.ts +11 -3
- package/dist/client/chat/message-components.js +31 -5
- package/dist/client/chat/tool-call-display.d.ts +2 -1
- package/dist/client/chat/tool-call-display.js +33 -15
- package/dist/client/db-admin/SqlEditor.js +1 -1
- package/dist/client/dev-overlay/DevOverlay.js +2 -2
- package/dist/client/extensions/portable-extension.js +4 -0
- package/dist/client/guided-questions.d.ts +8 -0
- package/dist/client/guided-questions.js +12 -3
- package/dist/client/sharing/ShareButton.js +16 -24
- package/dist/client/sse-event-processor.js +35 -15
- package/dist/client/tool-display.d.ts +9 -0
- package/dist/client/tool-display.js +39 -0
- package/dist/client/vite-dev-recovery-script.js +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/extensions/html-shell.js +5 -0
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +3 -3
- package/dist/provider-api/actions/custom-provider-registration.d.ts +2 -2
- package/dist/server/agent-capabilities.js +0 -0
- package/dist/server/agent-chat/action-filters-a2a.d.ts +14 -13
- package/dist/server/agent-chat/action-filters-a2a.js +59 -23
- package/dist/server/agent-chat/prompt-resources.js +17 -16
- package/dist/server/agent-chat-plugin.d.ts +9 -0
- package/dist/server/agent-chat-plugin.js +29 -7
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/builder-design-systems.d.ts +33 -1
- package/dist/server/builder-design-systems.js +293 -11
- package/dist/server/design-token-utils.d.ts +11 -0
- package/dist/server/design-token-utils.js +68 -16
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { appBasePath } from "@agent-native/core/client/api-path";
|
|
2
|
+
|
|
3
|
+
function imageProxyUrl(src: string): string {
|
|
4
|
+
return `${appBasePath()}/api/image-proxy?url=${encodeURIComponent(src)}`;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function isRemoteHttpUrl(src: string): boolean {
|
|
8
|
+
try {
|
|
9
|
+
const url = new URL(src, window.location.href);
|
|
10
|
+
return (
|
|
11
|
+
(url.protocol === "http:" || url.protocol === "https:") &&
|
|
12
|
+
url.origin !== window.location.origin
|
|
13
|
+
);
|
|
14
|
+
} catch {
|
|
15
|
+
// coercion-ok: malformed image sources are treated as non-remote candidates.
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function imageDownloadFilename(src: string): string {
|
|
21
|
+
if (/^(?:data|blob):/i.test(src)) return "image";
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
const pathname = new URL(src, window.location.href).pathname;
|
|
25
|
+
const filename = pathname.split("/").filter(Boolean).pop();
|
|
26
|
+
const decoded = filename ? decodeURIComponent(filename) : "";
|
|
27
|
+
const sanitized = decoded.replace(/[^a-z0-9._-]+/gi, "-");
|
|
28
|
+
if (sanitized) return sanitized;
|
|
29
|
+
} catch {
|
|
30
|
+
// coercion-ok: malformed image sources use the generic download filename.
|
|
31
|
+
// Use the generic name for malformed or non-URL image sources.
|
|
32
|
+
}
|
|
33
|
+
return "image";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function triggerBlobDownload(blob: Blob, filename: string): void {
|
|
37
|
+
const objectUrl = URL.createObjectURL(blob);
|
|
38
|
+
const anchor = document.createElement("a");
|
|
39
|
+
anchor.href = objectUrl;
|
|
40
|
+
anchor.download = filename;
|
|
41
|
+
anchor.hidden = true;
|
|
42
|
+
document.body.append(anchor);
|
|
43
|
+
anchor.click();
|
|
44
|
+
anchor.remove();
|
|
45
|
+
URL.revokeObjectURL(objectUrl);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function openImageInNewTab(src: string, filename: string): void {
|
|
49
|
+
const anchor = document.createElement("a");
|
|
50
|
+
anchor.href = src;
|
|
51
|
+
anchor.download = filename;
|
|
52
|
+
anchor.target = "_blank";
|
|
53
|
+
anchor.rel = "noopener noreferrer";
|
|
54
|
+
anchor.hidden = true;
|
|
55
|
+
document.body.append(anchor);
|
|
56
|
+
anchor.click();
|
|
57
|
+
anchor.remove();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Download the selected image, proxying external sources when CORS blocks the browser. */
|
|
61
|
+
export async function downloadImage(src: string): Promise<void> {
|
|
62
|
+
const normalizedSrc = src.trim();
|
|
63
|
+
if (!normalizedSrc || typeof document === "undefined") return;
|
|
64
|
+
|
|
65
|
+
const candidates = [normalizedSrc];
|
|
66
|
+
if (isRemoteHttpUrl(normalizedSrc)) {
|
|
67
|
+
candidates.push(imageProxyUrl(normalizedSrc));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const filename = imageDownloadFilename(normalizedSrc);
|
|
71
|
+
for (const candidate of candidates) {
|
|
72
|
+
try {
|
|
73
|
+
const response = await fetch(candidate, { credentials: "include" });
|
|
74
|
+
if (!response.ok) continue;
|
|
75
|
+
triggerBlobDownload(await response.blob(), filename);
|
|
76
|
+
return;
|
|
77
|
+
} catch {
|
|
78
|
+
// coercion-ok: a failed candidate is retried or falls back to opening the source.
|
|
79
|
+
// Try the authenticated image proxy before falling back to the source URL.
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
openImageInNewTab(normalizedSrc, filename);
|
|
84
|
+
}
|
|
@@ -1194,6 +1194,11 @@ export default function DeckEditor() {
|
|
|
1194
1194
|
onDuplicateCurrentSlide={
|
|
1195
1195
|
currentSlide ? () => duplicateSlide(id, currentSlide.id) : undefined
|
|
1196
1196
|
}
|
|
1197
|
+
onChangeSlideTransition={
|
|
1198
|
+
canEdit && currentSlide
|
|
1199
|
+
? (transition) => updateSlide(id, currentSlide.id, { transition })
|
|
1200
|
+
: undefined
|
|
1201
|
+
}
|
|
1197
1202
|
/>
|
|
1198
1203
|
|
|
1199
1204
|
{/* Full-width host for the slide's contextual style toolbar: it spans the
|
|
@@ -1312,6 +1317,10 @@ export default function DeckEditor() {
|
|
|
1312
1317
|
}
|
|
1313
1318
|
textBoxMode={textBoxMode}
|
|
1314
1319
|
onToggleTextBoxMode={toggleTextBoxMode}
|
|
1320
|
+
slideTransition={currentSlide.transition}
|
|
1321
|
+
onChangeSlideTransition={(transition) =>
|
|
1322
|
+
updateSlide(id, currentSlide.id, { transition })
|
|
1323
|
+
}
|
|
1315
1324
|
/>
|
|
1316
1325
|
) : undefined
|
|
1317
1326
|
}
|
|
@@ -67,6 +67,12 @@ import { useWorkspaceDefaults } from "@/hooks/use-workspace-defaults";
|
|
|
67
67
|
import { createDeckAgentMessage } from "@/lib/agent-visible-message";
|
|
68
68
|
import { savePromptToComposerDraft } from "@/lib/composer-draft";
|
|
69
69
|
import { isSourceImprovementRequest } from "@/lib/create-deck-generation";
|
|
70
|
+
import {
|
|
71
|
+
readStoredDeckFilter,
|
|
72
|
+
resolveDeckFilter,
|
|
73
|
+
writeStoredDeckFilter,
|
|
74
|
+
type DeckFilter,
|
|
75
|
+
} from "@/lib/deck-filter";
|
|
70
76
|
import { sortDecksByRecency } from "@/lib/deck-sorting";
|
|
71
77
|
import {
|
|
72
78
|
IMPORT_ACTION_TIMEOUT_MS,
|
|
@@ -291,6 +297,7 @@ export default function Index() {
|
|
|
291
297
|
string | null
|
|
292
298
|
>(null);
|
|
293
299
|
const [deckSearch, setDeckSearch] = useState("");
|
|
300
|
+
const [storedDeckFilter, setStoredDeckFilter] = useState<DeckFilter>("mine");
|
|
294
301
|
// True while the picker still reflects an auto-applied default rather than
|
|
295
302
|
// an explicit user choice. `useWorkspaceDefaults()`/`useDesignSystems()`
|
|
296
303
|
// resolve asynchronously, so the initial value set on dialog open can be a
|
|
@@ -324,7 +331,8 @@ export default function Index() {
|
|
|
324
331
|
const initialDesignSystemId =
|
|
325
332
|
lastUsedDesignSystemId ?? workspaceDesignSystemId;
|
|
326
333
|
const initialReferenceDeckId = lastUsedReferenceDeckId;
|
|
327
|
-
const
|
|
334
|
+
const createdByParam = searchParams.get("createdBy");
|
|
335
|
+
const deckFilter = resolveDeckFilter(createdByParam, storedDeckFilter);
|
|
328
336
|
const normalizedDeckSearch = deckSearch.trim().toLowerCase();
|
|
329
337
|
const visibleDecks = useMemo(
|
|
330
338
|
() =>
|
|
@@ -356,6 +364,12 @@ export default function Index() {
|
|
|
356
364
|
if (result.readable) setRecentReferences(result.items);
|
|
357
365
|
}, []);
|
|
358
366
|
|
|
367
|
+
useEffect(() => {
|
|
368
|
+
if (createdByParam !== null) return;
|
|
369
|
+
const savedFilter = readStoredDeckFilter();
|
|
370
|
+
if (savedFilter) setStoredDeckFilter(savedFilter);
|
|
371
|
+
}, [createdByParam]);
|
|
372
|
+
|
|
359
373
|
const initialPrompt = searchParams.get("initialPrompt")?.trim() ?? "";
|
|
360
374
|
const onboardingPreview = searchParams.get("onboarding") === "preview";
|
|
361
375
|
const firstRunOnboardingEnabled =
|
|
@@ -417,6 +431,8 @@ export default function Index() {
|
|
|
417
431
|
const setDeckFilter = useCallback(
|
|
418
432
|
(value: string) => {
|
|
419
433
|
const nextFilter = value === "mine" ? "mine" : "all";
|
|
434
|
+
setStoredDeckFilter(nextFilter);
|
|
435
|
+
writeStoredDeckFilter(nextFilter);
|
|
420
436
|
setSearchParams(
|
|
421
437
|
(prev) => {
|
|
422
438
|
const next = new URLSearchParams(prev);
|
|
@@ -1377,14 +1393,6 @@ export default function Index() {
|
|
|
1377
1393
|
className="w-fit rounded-lg border border-border bg-card p-0.5"
|
|
1378
1394
|
size="sm"
|
|
1379
1395
|
>
|
|
1380
|
-
<ToggleGroupItem
|
|
1381
|
-
value="all"
|
|
1382
|
-
aria-label={t("home.showAllDecks")}
|
|
1383
|
-
className="h-7 rounded-md px-3 text-xs data-[state=on]:bg-accent"
|
|
1384
|
-
>
|
|
1385
|
-
<IconStack2 className="me-1.5 h-3.5 w-3.5" />
|
|
1386
|
-
{t("home.all")}
|
|
1387
|
-
</ToggleGroupItem>
|
|
1388
1396
|
<ToggleGroupItem
|
|
1389
1397
|
value="mine"
|
|
1390
1398
|
aria-label={t("home.showMineDecks")}
|
|
@@ -1393,6 +1401,14 @@ export default function Index() {
|
|
|
1393
1401
|
<IconUserCircle className="me-1.5 h-3.5 w-3.5" />
|
|
1394
1402
|
{t("home.mine")}
|
|
1395
1403
|
</ToggleGroupItem>
|
|
1404
|
+
<ToggleGroupItem
|
|
1405
|
+
value="all"
|
|
1406
|
+
aria-label={t("home.showAllDecks")}
|
|
1407
|
+
className="h-7 rounded-md px-3 text-xs data-[state=on]:bg-accent"
|
|
1408
|
+
>
|
|
1409
|
+
<IconStack2 className="me-1.5 h-3.5 w-3.5" />
|
|
1410
|
+
{t("home.all")}
|
|
1411
|
+
</ToggleGroupItem>
|
|
1396
1412
|
</ToggleGroup>
|
|
1397
1413
|
<label className="flex h-8 w-full items-center gap-2 rounded-lg border border-border bg-card px-2.5 text-muted-foreground sm:w-60">
|
|
1398
1414
|
<IconSearch className="size-3.5 shrink-0" aria-hidden="true" />
|
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
createBuilderDesignSystemProxyFields,
|
|
3
3
|
localBuilderDesignSystemId,
|
|
4
4
|
type BuilderDesignSystemIndexResult,
|
|
5
|
+
type BuilderDesignSystemGitHubSource,
|
|
5
6
|
type BuilderDesignSystemSourceKind,
|
|
6
7
|
} from "@agent-native/core/server";
|
|
7
8
|
import { and, eq, isNull } from "drizzle-orm";
|
|
@@ -16,6 +17,8 @@ export async function upsertBuilderProxyDesignSystem({
|
|
|
16
17
|
projectName,
|
|
17
18
|
description,
|
|
18
19
|
sourceKind,
|
|
20
|
+
githubSources,
|
|
21
|
+
localDesignSystemId: requestedLocalDesignSystemId,
|
|
19
22
|
}: {
|
|
20
23
|
result: BuilderDesignSystemIndexResult;
|
|
21
24
|
ownerEmail: string;
|
|
@@ -23,6 +26,8 @@ export async function upsertBuilderProxyDesignSystem({
|
|
|
23
26
|
projectName?: string;
|
|
24
27
|
description?: string;
|
|
25
28
|
sourceKind?: BuilderDesignSystemSourceKind;
|
|
29
|
+
githubSources?: BuilderDesignSystemGitHubSource[];
|
|
30
|
+
localDesignSystemId?: string;
|
|
26
31
|
}) {
|
|
27
32
|
const db = getDb();
|
|
28
33
|
const now = new Date().toISOString();
|
|
@@ -35,6 +40,8 @@ export async function upsertBuilderProxyDesignSystem({
|
|
|
35
40
|
description,
|
|
36
41
|
surface: "slides",
|
|
37
42
|
sourceKind,
|
|
43
|
+
githubSources,
|
|
44
|
+
syncedAt: githubSources?.length ? now : undefined,
|
|
38
45
|
});
|
|
39
46
|
const [existing] = await db
|
|
40
47
|
.select({
|
|
@@ -43,7 +50,12 @@ export async function upsertBuilderProxyDesignSystem({
|
|
|
43
50
|
orgId: schema.designSystems.orgId,
|
|
44
51
|
})
|
|
45
52
|
.from(schema.designSystems)
|
|
46
|
-
.where(
|
|
53
|
+
.where(
|
|
54
|
+
eq(
|
|
55
|
+
schema.designSystems.id,
|
|
56
|
+
requestedLocalDesignSystemId ?? baseLocalDesignSystemId,
|
|
57
|
+
),
|
|
58
|
+
)
|
|
47
59
|
.limit(1);
|
|
48
60
|
const existingBelongsToScope =
|
|
49
61
|
existing?.ownerEmail === ownerEmail &&
|
|
@@ -51,7 +63,7 @@ export async function upsertBuilderProxyDesignSystem({
|
|
|
51
63
|
const localDesignSystemId =
|
|
52
64
|
existing && !existingBelongsToScope
|
|
53
65
|
? `${baseLocalDesignSystemId}-${nanoid(8)}`
|
|
54
|
-
: baseLocalDesignSystemId;
|
|
66
|
+
: (requestedLocalDesignSystemId ?? baseLocalDesignSystemId);
|
|
55
67
|
if (existingBelongsToScope) {
|
|
56
68
|
await db
|
|
57
69
|
.update(schema.designSystems)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
function normalizePathname(pathname, stripEndpoint) {
|
|
2
|
+
const normalized = pathname.startsWith("/") ? pathname : `/${pathname}`;
|
|
3
|
+
const withoutTrailingSlashes = normalized.replace(/\/+$/, "");
|
|
4
|
+
const withoutEndpoint = stripEndpoint
|
|
5
|
+
? withoutTrailingSlashes.endsWith("/_agent-native/a2a")
|
|
6
|
+
? withoutTrailingSlashes.slice(0, -"/_agent-native/a2a".length)
|
|
7
|
+
: withoutTrailingSlashes.endsWith("/a2a")
|
|
8
|
+
? withoutTrailingSlashes.slice(0, -"/a2a".length)
|
|
9
|
+
: withoutTrailingSlashes
|
|
10
|
+
: withoutTrailingSlashes;
|
|
11
|
+
return withoutEndpoint === "" ? "/" : withoutEndpoint;
|
|
12
|
+
}
|
|
13
|
+
/** Canonical receiver identifier used by both A2A token issuers and verifiers. */
|
|
14
|
+
export function canonicalA2AAudience(rawUrl, receiverBasePath) {
|
|
15
|
+
try {
|
|
16
|
+
const url = new URL(rawUrl);
|
|
17
|
+
const pathname = normalizePathname(receiverBasePath ?? url.pathname, receiverBasePath === undefined);
|
|
18
|
+
return pathname === "/" ? url.origin : `${url.origin}${pathname}`;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return rawUrl.replace(/\/+$/, "");
|
|
22
|
+
}
|
|
23
|
+
}
|
package/dist/a2a/client.d.ts
CHANGED
|
@@ -62,6 +62,10 @@ export declare class A2AClient {
|
|
|
62
62
|
* Agent-native apps use /_agent-native/a2a, external agents may use /a2a.
|
|
63
63
|
*/
|
|
64
64
|
resolveEndpoint(): Promise<void>;
|
|
65
|
+
/** Resolve the card-advertised endpoint without sending an RPC request. */
|
|
66
|
+
resolveEndpointUrl(timeoutMs?: number): Promise<string>;
|
|
67
|
+
/** Replace caller credentials without discarding resolved endpoint fallbacks. */
|
|
68
|
+
setAuthentication(apiKey?: string, fallbackApiKeys?: string[]): void;
|
|
65
69
|
private headers;
|
|
66
70
|
private markApiKeySucceeded;
|
|
67
71
|
private rpc;
|
package/dist/a2a/client.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import * as jose from "jose";
|
|
3
3
|
import { ssrfSafeFetch } from "../extensions/url-safety.js";
|
|
4
|
+
import { canonicalA2AAudience } from "./audience.js";
|
|
4
5
|
/**
|
|
5
6
|
* A workspace serves every app from one gateway on loopback, so sibling A2A
|
|
6
7
|
* targets are private addresses by construction and the SSRF guard cannot tell
|
|
@@ -185,6 +186,19 @@ export class A2AClient {
|
|
|
185
186
|
}
|
|
186
187
|
}
|
|
187
188
|
}
|
|
189
|
+
/** Resolve the card-advertised endpoint without sending an RPC request. */
|
|
190
|
+
async resolveEndpointUrl(timeoutMs) {
|
|
191
|
+
await this.ensureEndpointCandidates(timeoutMs);
|
|
192
|
+
const endpoint = this.endpointCandidates[0];
|
|
193
|
+
if (!endpoint)
|
|
194
|
+
throw new Error("No A2A endpoint candidates available");
|
|
195
|
+
return endpoint;
|
|
196
|
+
}
|
|
197
|
+
/** Replace caller credentials without discarding resolved endpoint fallbacks. */
|
|
198
|
+
setAuthentication(apiKey, fallbackApiKeys = []) {
|
|
199
|
+
this.apiKey = apiKey;
|
|
200
|
+
this.apiKeyAttempts = uniqueAuthTokens([apiKey, ...fallbackApiKeys]);
|
|
201
|
+
}
|
|
188
202
|
headers(apiKey = this.apiKey) {
|
|
189
203
|
const h = { "Content-Type": "application/json" };
|
|
190
204
|
if (apiKey) {
|
|
@@ -823,14 +837,27 @@ export async function callAction(url, action, input = {}, opts) {
|
|
|
823
837
|
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
824
838
|
throw new Error("A2A action input must be an object");
|
|
825
839
|
}
|
|
826
|
-
const
|
|
827
|
-
const
|
|
840
|
+
const discoveryAudience = normalizeA2AAudience(url);
|
|
841
|
+
const discoveryApiKeyAttempts = await buildA2AApiKeyAttempts(opts, discoveryAudience);
|
|
842
|
+
const discoveryFallbackApiKeys = discoveryApiKeyAttempts
|
|
828
843
|
.slice(1)
|
|
829
844
|
.filter((token) => token !== undefined);
|
|
830
|
-
const client = new A2AClient(url,
|
|
831
|
-
fallbackApiKeys,
|
|
845
|
+
const client = new A2AClient(url, discoveryApiKeyAttempts[0], {
|
|
846
|
+
fallbackApiKeys: discoveryFallbackApiKeys,
|
|
832
847
|
requestTimeoutMs: opts?.requestTimeoutMs,
|
|
833
848
|
});
|
|
849
|
+
const endpointUrl = await client.resolveEndpointUrl(opts?.requestTimeoutMs);
|
|
850
|
+
const invocationAudience = normalizeA2AAudience(endpointUrl);
|
|
851
|
+
if (invocationAudience !== discoveryAudience) {
|
|
852
|
+
const invocationApiKeyAttempts = await buildA2AApiKeyAttempts(opts, invocationAudience);
|
|
853
|
+
const combinedApiKeyAttempts = uniqueAuthTokens([
|
|
854
|
+
...invocationApiKeyAttempts,
|
|
855
|
+
...discoveryApiKeyAttempts,
|
|
856
|
+
]);
|
|
857
|
+
client.setAuthentication(combinedApiKeyAttempts[0], combinedApiKeyAttempts
|
|
858
|
+
.slice(1)
|
|
859
|
+
.filter((token) => token !== undefined));
|
|
860
|
+
}
|
|
834
861
|
return client.invokeAction(actionName, input, {
|
|
835
862
|
metadata: sanitizeA2ACorrelationMetadata(opts?.correlation),
|
|
836
863
|
});
|
|
@@ -876,16 +903,7 @@ async function buildA2AApiKeyAttempts(opts, audience) {
|
|
|
876
903
|
function normalizeA2AAudience(url) {
|
|
877
904
|
const explicit = splitExplicitA2AEndpoint(url.replace(/\/$/, ""));
|
|
878
905
|
const base = (explicit?.baseUrl ?? url).replace(/\/$/, "");
|
|
879
|
-
|
|
880
|
-
// app path. In a workspace every app is mounted under one gateway origin, so
|
|
881
|
-
// signing the path-qualified URL yields an audience the receiver can never
|
|
882
|
-
// match. The origin is the identifier both sides agree on.
|
|
883
|
-
try {
|
|
884
|
-
return new URL(base).origin;
|
|
885
|
-
}
|
|
886
|
-
catch {
|
|
887
|
-
return base;
|
|
888
|
-
}
|
|
906
|
+
return canonicalA2AAudience(base);
|
|
889
907
|
}
|
|
890
908
|
function isA2AAuthRejection(err) {
|
|
891
909
|
const message = err instanceof Error ? err.message : String(err ?? "");
|
package/dist/a2a/server.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { A2AConfig } from "./types.js";
|
|
1
|
+
import type { A2AConfig, AgentSkill } from "./types.js";
|
|
2
2
|
/**
|
|
3
3
|
* Result of verifying an inbound A2A JWT. `email` is the caller identity from
|
|
4
4
|
* the token's `sub` claim (null when verification fails), `orgDomain` mirrors
|
|
@@ -21,9 +21,14 @@ export interface A2ATokenPayload {
|
|
|
21
21
|
* Exported so workspaces can accept A2A callers on the HTTP action route with
|
|
22
22
|
* the same routine — including org-level fallback secrets — instead of
|
|
23
23
|
* reimplementing a partial verifier. Pass the H3 `event` to enable org-domain →
|
|
24
|
-
* org-secret lookup and audience derivation; it is optional.
|
|
24
|
+
* org-secret lookup and audience derivation; it is optional. A custom mounted
|
|
25
|
+
* JSON-RPC route also passes its prefix so endpoint-bound tokens verify against
|
|
26
|
+
* the same audience the client derived from the advertised URL.
|
|
25
27
|
*/
|
|
26
|
-
export declare function verifyA2AToken(token: string, event?: any
|
|
28
|
+
export declare function verifyA2AToken(token: string, event?: any, audienceOptions?: {
|
|
29
|
+
routePrefix?: string;
|
|
30
|
+
allowBaseAudience?: boolean;
|
|
31
|
+
}): Promise<A2ATokenPayload>;
|
|
27
32
|
/**
|
|
28
33
|
* Mount A2A protocol endpoints on an H3/Nitro app.
|
|
29
34
|
*
|
|
@@ -35,4 +40,4 @@ export declare function verifyA2AToken(token: string, event?: any): Promise<A2AT
|
|
|
35
40
|
* cryptographic identity verification for cross-app A2A calls.
|
|
36
41
|
*/
|
|
37
42
|
export declare function mountA2A(nitroApp: any, config: A2AConfig, routePrefix?: string): void;
|
|
38
|
-
export declare function filterPublicAgentCardSkills(config: A2AConfig):
|
|
43
|
+
export declare function filterPublicAgentCardSkills(config: A2AConfig): AgentSkill[];
|
package/dist/a2a/server.js
CHANGED
|
@@ -6,6 +6,7 @@ import { getH3App } from "../server/framework-request-handler.js";
|
|
|
6
6
|
import { readBody } from "../server/h3-helpers.js";
|
|
7
7
|
import { isSameOriginRequest } from "../server/request-origin.js";
|
|
8
8
|
import { generateAgentCard } from "./agent-card.js";
|
|
9
|
+
import { canonicalA2AAudience } from "./audience.js";
|
|
9
10
|
import { hasConfiguredA2ASecret, isA2AProductionRuntime, } from "./auth-policy.js";
|
|
10
11
|
import { handleJsonRpcH3, processA2ATaskFromQueue } from "./handlers.js";
|
|
11
12
|
import { claimA2AApproval, getA2AApprovalForOwner, settleA2AApproval, } from "./task-store.js";
|
|
@@ -39,25 +40,46 @@ function addSecretCandidate(candidates, secret) {
|
|
|
39
40
|
* service must not verify here). Only tokens without an `aud` claim (minted
|
|
40
41
|
* before the audience claim shipped) skip the audience check.
|
|
41
42
|
*/
|
|
42
|
-
function expectedJwtAudience(event) {
|
|
43
|
+
function expectedJwtAudience(event, options) {
|
|
43
44
|
const fromEnv = process.env.APP_URL ||
|
|
44
45
|
process.env.URL ||
|
|
45
46
|
process.env.DEPLOY_URL ||
|
|
46
47
|
process.env.BETTER_AUTH_URL;
|
|
47
|
-
|
|
48
|
-
|
|
48
|
+
const receiverBasePath = process.env.APP_BASE_PATH;
|
|
49
|
+
if (fromEnv) {
|
|
50
|
+
return audienceForRoute(canonicalA2AAudience(String(fromEnv), receiverBasePath), options);
|
|
51
|
+
}
|
|
49
52
|
// Best-effort: derive from the inbound request host. This is forgeable
|
|
50
53
|
// (Host-header attack), but only useful as a hint when env-derived URL
|
|
51
54
|
// is unset; the rest of the JWT verification still uses the secret.
|
|
52
55
|
try {
|
|
53
56
|
const proto = getRequestHeader(event, "x-forwarded-proto") || "https";
|
|
54
57
|
const host = getRequestHeader(event, "host");
|
|
55
|
-
if (host)
|
|
56
|
-
return `${proto}://${host}
|
|
58
|
+
if (host) {
|
|
59
|
+
return audienceForRoute(canonicalA2AAudience(`${proto}://${host}`, receiverBasePath), options);
|
|
60
|
+
}
|
|
61
|
+
// coercion-ok: undefined makes audience-bearing token verification fail closed.
|
|
57
62
|
}
|
|
58
63
|
catch { }
|
|
59
64
|
return undefined;
|
|
60
65
|
}
|
|
66
|
+
function audienceForRoute(baseAudience, options) {
|
|
67
|
+
const trimmedPrefix = options?.routePrefix?.replace(/^\/+|\/+$/g, "");
|
|
68
|
+
if (!trimmedPrefix || trimmedPrefix === "_agent-native")
|
|
69
|
+
return baseAudience;
|
|
70
|
+
const normalizedPrefix = `/${trimmedPrefix}`;
|
|
71
|
+
const routeAudience = `${baseAudience.replace(/\/+$/, "")}${normalizedPrefix}`;
|
|
72
|
+
return options?.allowBaseAudience
|
|
73
|
+
? [routeAudience, baseAudience]
|
|
74
|
+
: routeAudience;
|
|
75
|
+
}
|
|
76
|
+
function tokenHasAudienceClaim(token) {
|
|
77
|
+
return typeof jose.decodeJwt(token).aud !== "undefined";
|
|
78
|
+
}
|
|
79
|
+
function isDirectReadSkill(skill) {
|
|
80
|
+
return (skill.readOnly === true ||
|
|
81
|
+
(skill.readOnly === undefined && skill.publicAgent?.readOnly === true));
|
|
82
|
+
}
|
|
61
83
|
/**
|
|
62
84
|
* Verify an inbound A2A bearer token (HS256) exactly as the
|
|
63
85
|
* `/_agent-native/a2a` endpoint does: it peeks at the unverified `org_domain`
|
|
@@ -71,9 +93,11 @@ function expectedJwtAudience(event) {
|
|
|
71
93
|
* Exported so workspaces can accept A2A callers on the HTTP action route with
|
|
72
94
|
* the same routine — including org-level fallback secrets — instead of
|
|
73
95
|
* reimplementing a partial verifier. Pass the H3 `event` to enable org-domain →
|
|
74
|
-
* org-secret lookup and audience derivation; it is optional.
|
|
96
|
+
* org-secret lookup and audience derivation; it is optional. A custom mounted
|
|
97
|
+
* JSON-RPC route also passes its prefix so endpoint-bound tokens verify against
|
|
98
|
+
* the same audience the client derived from the advertised URL.
|
|
75
99
|
*/
|
|
76
|
-
export async function verifyA2AToken(token, event) {
|
|
100
|
+
export async function verifyA2AToken(token, event, audienceOptions) {
|
|
77
101
|
// Step 1: Peek at JWT claims WITHOUT verification to get org_domain.
|
|
78
102
|
// This is safe because we only use org_domain to look up the secret,
|
|
79
103
|
// then verify the full JWT with that secret. If someone forges a JWT
|
|
@@ -129,7 +153,7 @@ export async function verifyA2AToken(token, event) {
|
|
|
129
153
|
// whose `aud` targets ANOTHER service verify against a shared secret. A
|
|
130
154
|
// token that self-declares an audience must be checked against ours, so
|
|
131
155
|
// when we have nothing to check it against we reject rather than skip.
|
|
132
|
-
const aud = expectedJwtAudience(event);
|
|
156
|
+
const aud = expectedJwtAudience(event, audienceOptions);
|
|
133
157
|
if (!aud)
|
|
134
158
|
return { email: null, orgDomain: null };
|
|
135
159
|
verifyOptions.audience = aud;
|
|
@@ -187,19 +211,26 @@ export function mountA2A(nitroApp, config, routePrefix = "/_agent-native") {
|
|
|
187
211
|
const host = getRequestHeader(event, "host") ?? "localhost";
|
|
188
212
|
const baseUrl = `${protocol}://${host}`;
|
|
189
213
|
// The anonymous card may only advertise actions safe to disclose
|
|
190
|
-
// publicly (`requiresAuth !== true`)
|
|
191
|
-
//
|
|
192
|
-
//
|
|
193
|
-
//
|
|
194
|
-
// back to open-ended delegation. Show a verified caller what it can
|
|
195
|
-
// actually invoke; anonymous fetches keep the public list unchanged.
|
|
214
|
+
// publicly (`requiresAuth !== true`). A verified caller instead sees the
|
|
215
|
+
// authenticated surface: schemas for direct read invocation plus concise
|
|
216
|
+
// message-only capabilities for writes owned by the receiving agent.
|
|
217
|
+
// Anonymous fetches keep the public list unchanged.
|
|
196
218
|
let skills = filterPublicAgentCardSkills(config);
|
|
197
219
|
if (config.authenticatedSkills?.length) {
|
|
198
220
|
const bearer = extractBearerToken(getRequestHeader(event, "authorization"));
|
|
199
221
|
if (bearer) {
|
|
200
|
-
const payload = await verifyA2AToken(bearer, event
|
|
201
|
-
|
|
202
|
-
|
|
222
|
+
const payload = await verifyA2AToken(bearer, event, {
|
|
223
|
+
routePrefix,
|
|
224
|
+
// Capability discovery may begin from either the app URL or an
|
|
225
|
+
// already-advertised endpoint URL. Both identify this receiver;
|
|
226
|
+
// direct POST invocation below remains endpoint-bound.
|
|
227
|
+
allowBaseAudience: true,
|
|
228
|
+
});
|
|
229
|
+
if (payload.email) {
|
|
230
|
+
skills = tokenHasAudienceClaim(bearer)
|
|
231
|
+
? config.authenticatedSkills
|
|
232
|
+
: config.authenticatedSkills.filter((skill) => !isDirectReadSkill(skill));
|
|
233
|
+
}
|
|
203
234
|
}
|
|
204
235
|
}
|
|
205
236
|
return generateAgentCard({ ...config, skills }, baseUrl, `${routePrefix}/a2a`);
|
|
@@ -372,17 +403,13 @@ export function mountA2A(nitroApp, config, routePrefix = "/_agent-native") {
|
|
|
372
403
|
const hasApiKey = !!(config.apiKeyEnv && process.env[config.apiKeyEnv]);
|
|
373
404
|
// Try JWT verification first (org-level or global A2A_SECRET-based identity)
|
|
374
405
|
if (bearerToken) {
|
|
375
|
-
const tokenPayload = await verifyA2AToken(bearerToken, event
|
|
406
|
+
const tokenPayload = await verifyA2AToken(bearerToken, event, {
|
|
407
|
+
routePrefix,
|
|
408
|
+
});
|
|
376
409
|
verifiedCallerEmail = tokenPayload.email;
|
|
377
410
|
verifiedOrgDomain = tokenPayload.orgDomain;
|
|
378
411
|
if (verifiedCallerEmail) {
|
|
379
|
-
|
|
380
|
-
verifiedAudienceBound =
|
|
381
|
-
typeof jose.decodeJwt(bearerToken).aud !== "undefined";
|
|
382
|
-
}
|
|
383
|
-
catch {
|
|
384
|
-
verifiedAudienceBound = false;
|
|
385
|
-
}
|
|
412
|
+
verifiedAudienceBound = tokenHasAudienceClaim(bearerToken);
|
|
386
413
|
}
|
|
387
414
|
bearerTokenRejectedByJwt = !verifiedCallerEmail;
|
|
388
415
|
}
|
package/dist/a2a/types.d.ts
CHANGED
|
@@ -189,10 +189,10 @@ export interface A2AConfig {
|
|
|
189
189
|
version?: string;
|
|
190
190
|
skills: AgentSkill[];
|
|
191
191
|
/**
|
|
192
|
-
* Skills advertised only to a caller with a verified A2A identity
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
192
|
+
* Skills advertised only to a caller with a verified A2A identity. Read-only
|
|
193
|
+
* skills may also be available through `actions/invoke`; mutating skills are
|
|
194
|
+
* message-only capabilities for delegation. Anonymous card fetches never see
|
|
195
|
+
* either set.
|
|
196
196
|
*/
|
|
197
197
|
authenticatedSkills?: AgentSkill[];
|
|
198
198
|
/** If true, public agent-card discovery includes only explicit public-safe skills. */
|
|
@@ -6499,7 +6499,7 @@ export function createProductionAgentHandler(options) {
|
|
|
6499
6499
|
const capturedAt = typeof sel.capturedAt === "number" ? sel.capturedAt : 0;
|
|
6500
6500
|
if (Date.now() - capturedAt > SELECTION_TTL_MS)
|
|
6501
6501
|
return "";
|
|
6502
|
-
return (`\n\nThe user has selected the following text and pressed Cmd
|
|
6502
|
+
return (`\n\nThe user has selected the following text and pressed Cmd I to focus the agent. ` +
|
|
6503
6503
|
`Treat this as the immediate context to act on:\n` +
|
|
6504
6504
|
`<selection>\n${capSelectionContext(sel.text)}\n</selection>`);
|
|
6505
6505
|
}
|
|
@@ -23,8 +23,16 @@ export interface BrandKitColors {
|
|
|
23
23
|
text: string;
|
|
24
24
|
textMuted: string;
|
|
25
25
|
}
|
|
26
|
-
/**
|
|
27
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Semantic category a {@link BrandKitToken} belongs to.
|
|
28
|
+
*
|
|
29
|
+
* `motion` covers durations, easings, and transitions — the DTCG `duration`,
|
|
30
|
+
* `cubicBezier`, and `transition` types. It exists because a design system's
|
|
31
|
+
* motion is part of its identity, and a category-less token is a dropped one:
|
|
32
|
+
* extractors discard what they cannot classify, so "no bucket" silently became
|
|
33
|
+
* "no motion in any imported system".
|
|
34
|
+
*/
|
|
35
|
+
export type BrandKitTokenType = "color" | "typography" | "spacing" | "radius" | "shadow" | "motion" | "other";
|
|
28
36
|
/**
|
|
29
37
|
* One token under the name its source design system uses. The seven
|
|
30
38
|
* {@link BrandKitColors} roles are a summary view, not a substitute: collapsing
|
package/dist/cli/code.js
CHANGED
|
@@ -946,7 +946,7 @@ async function attachCodeAgentRun(runs, args, output) {
|
|
|
946
946
|
const printed = new Set();
|
|
947
947
|
writeLine(output, "");
|
|
948
948
|
writeLine(output, `Attaching to Agent-Native Code run ${run.id}`);
|
|
949
|
-
writeLine(output, "Press Ctrl
|
|
949
|
+
writeLine(output, "Press Ctrl C to detach. The session keeps its transcript.");
|
|
950
950
|
writeLine(output, "");
|
|
951
951
|
const printNewEvents = () => {
|
|
952
952
|
const events = listCodeAgentTranscriptEvents(run.id);
|