@agent-native/core 0.176.4 → 0.176.5-nightly-20260902205402
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/calendar/.agents/skills/event-management/SKILL.md +6 -0
- package/corpus/templates/calendar/AGENTS.md +4 -0
- package/corpus/templates/calendar/actions/delete-event.ts +2 -2
- package/corpus/templates/calendar/actions/event-action-helpers.ts +7 -0
- package/corpus/templates/calendar/actions/get-event.ts +40 -3
- package/corpus/templates/calendar/actions/list-events.ts +49 -2
- package/corpus/templates/calendar/actions/list-google-calendars.ts +24 -0
- package/corpus/templates/calendar/actions/update-calendar-visual-preferences.ts +52 -1
- package/corpus/templates/calendar/actions/update-event.ts +2 -2
- package/corpus/templates/calendar/actions/view-screen.ts +54 -2
- package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +33 -19
- package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +31 -5
- package/corpus/templates/calendar/app/components/layout/Sidebar.tsx +102 -0
- package/corpus/templates/calendar/app/hooks/use-events.ts +45 -10
- package/corpus/templates/calendar/app/hooks/use-google-auth.ts +8 -1
- package/corpus/templates/calendar/app/hooks/use-google-calendars.ts +41 -0
- package/corpus/templates/calendar/app/hooks/use-view-preferences.ts +56 -4
- package/corpus/templates/calendar/app/lib/shared-calendar-demo.ts +187 -0
- package/corpus/templates/calendar/app/pages/CalendarView.tsx +50 -5
- package/corpus/templates/calendar/changelog/2026-09-01-calendars-shared-with-your-connected-google-accounts-can-now.md +6 -0
- package/corpus/templates/calendar/changelog/2026-09-02-updated-the-booking-link-og-preview-image-with-the-new-monoc.md +6 -0
- package/corpus/templates/calendar/server/lib/booking-og-image.ts +8 -18
- package/corpus/templates/calendar/server/lib/google-api.ts +10 -0
- package/corpus/templates/calendar/server/lib/google-calendar.ts +215 -21
- package/corpus/templates/calendar/server/plugins/agent-chat.ts +2 -0
- package/corpus/templates/calendar/server/plugins/feature-flags.ts +5 -0
- package/corpus/templates/calendar/shared/api.ts +20 -0
- package/corpus/templates/calendar/shared/calendar-view-preferences.ts +28 -1
- package/corpus/templates/calendar/shared/feature-flags.ts +8 -0
- package/corpus/templates/calendar/shared/google-calendar-sources.ts +46 -0
- package/corpus/templates/factory/.env.example +5 -0
- package/corpus/templates/factory/README.md +9 -0
- package/corpus/templates/factory/server/connectors/credentials.ts +28 -6
- package/corpus/templates/factory/server/triage/github-client.ts +144 -7
- package/corpus/templates/forms/server/lib/form-og-image.ts +7 -17
- package/corpus/templates/slides/actions/add-slide.ts +1 -0
- package/corpus/templates/slides/actions/create-deck.ts +1 -0
- package/corpus/templates/slides/actions/get-deck.ts +1 -0
- package/corpus/templates/slides/actions/navigate.ts +1 -0
- package/corpus/templates/slides/actions/patch-deck.ts +1 -0
- package/corpus/templates/slides/actions/update-slide.ts +1 -0
- package/corpus/templates/slides/actions/view-screen.ts +1 -0
- package/corpus/templates/slides/server/plugins/agent-chat.ts +2 -0
- package/dist/action.d.ts +4 -0
- package/dist/action.js +3 -0
- package/dist/agent/types.d.ts +1 -0
- package/dist/automation/index.d.ts +1 -1
- package/dist/client/webmcp.js +7 -2
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/mcp/build-server.d.ts +2 -0
- package/dist/mcp/build-server.js +4 -0
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +6 -6
- package/dist/provider-api/actions/provider-api.d.ts +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/action-routes.d.ts +1 -0
- package/dist/server/action-routes.js +4 -0
- package/dist/server/agent-chat/mcp-options.d.ts +4 -0
- package/dist/server/agent-chat/mcp-options.js +1 -0
- package/dist/server/agent-chat-plugin.js +2 -0
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/social-og-image.js +6 -20
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/shared/agent-mcp-metadata.d.ts +3 -0
- package/dist/shared/agent-mcp-metadata.js +18 -0
- package/dist/shared/social-meta.d.ts +1 -1
- package/dist/shared/social-meta.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export interface GoogleCalendarSourceIdentity {
|
|
2
|
+
accountEmail: string;
|
|
3
|
+
calendarId: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Calendar ids are provider data and may contain separators, so encode the
|
|
8
|
+
* complete identity rather than joining two user-controlled strings.
|
|
9
|
+
*/
|
|
10
|
+
export function createGoogleCalendarSourceKey({
|
|
11
|
+
accountEmail,
|
|
12
|
+
calendarId,
|
|
13
|
+
}: GoogleCalendarSourceIdentity): string {
|
|
14
|
+
return `google-calendar:${Buffer.from(
|
|
15
|
+
JSON.stringify([accountEmail.trim().toLowerCase(), calendarId]),
|
|
16
|
+
).toString("base64url")}`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function parseGoogleCalendarSourceKey(
|
|
20
|
+
sourceKey: string,
|
|
21
|
+
): GoogleCalendarSourceIdentity | null {
|
|
22
|
+
const prefix = "google-calendar:";
|
|
23
|
+
if (!sourceKey.startsWith(prefix)) return null;
|
|
24
|
+
try {
|
|
25
|
+
const parsed: unknown = JSON.parse(
|
|
26
|
+
Buffer.from(sourceKey.slice(prefix.length), "base64url").toString("utf8"),
|
|
27
|
+
);
|
|
28
|
+
if (
|
|
29
|
+
!Array.isArray(parsed) ||
|
|
30
|
+
parsed.length !== 2 ||
|
|
31
|
+
typeof parsed[0] !== "string" ||
|
|
32
|
+
typeof parsed[1] !== "string" ||
|
|
33
|
+
!parsed[0].trim() ||
|
|
34
|
+
!parsed[1]
|
|
35
|
+
) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
accountEmail: parsed[0].trim().toLowerCase(),
|
|
40
|
+
calendarId: parsed[1],
|
|
41
|
+
};
|
|
42
|
+
} catch {
|
|
43
|
+
// coercion-ok: malformed opaque input is a typed invalid-key result.
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -22,6 +22,11 @@ FACTORY_PUBLIC_URL=https://agent-native-factory.netlify.app
|
|
|
22
22
|
# or vault row exists. Hosted Factory ignores them.
|
|
23
23
|
# SLACK_BOT_TOKEN=
|
|
24
24
|
# GITHUB_TOKEN=
|
|
25
|
+
# Local-only GitHub App fallback during migration. Hosted Factory reads these
|
|
26
|
+
# from the shared vault, not deployment environment variables.
|
|
27
|
+
# GITHUB_APP_ID=
|
|
28
|
+
# GITHUB_APP_INSTALLATION_ID=
|
|
29
|
+
# GITHUB_APP_PRIVATE_KEY=
|
|
25
30
|
# SENTRY_AUTH_TOKEN=
|
|
26
31
|
|
|
27
32
|
# Skip login/signup (local dev / preview only)
|
|
@@ -49,6 +49,15 @@ env-only read in a provider client.
|
|
|
49
49
|
|
|
50
50
|
### GitHub token permissions
|
|
51
51
|
|
|
52
|
+
Factory prefers the new Agent-Native GitHub App. Configure `GITHUB_APP_ID`,
|
|
53
|
+
`GITHUB_APP_INSTALLATION_ID`, and `GITHUB_APP_PRIVATE_KEY` together. The App
|
|
54
|
+
needs repository `Pull requests: Read and write`, `Issues: Read and write`,
|
|
55
|
+
and `Checks: Read`, plus organization `Members: Read` for governance. Hosted
|
|
56
|
+
Factory stores the private key in the shared vault and generates short-lived
|
|
57
|
+
installation tokens server-side. Do not send a static token or private key to
|
|
58
|
+
developers. During migration, an existing `GITHUB_TOKEN` remains supported
|
|
59
|
+
when no App keys are configured.
|
|
60
|
+
|
|
52
61
|
For Factory pull-request polling and babysitting, scope a fine-grained token to
|
|
53
62
|
the target repository and grant these repository permissions:
|
|
54
63
|
|
|
@@ -20,8 +20,14 @@ const WORKSPACE_PROVIDER_BY_KEY: Record<string, string> = {
|
|
|
20
20
|
SLACK_BOT_TOKEN: "slack",
|
|
21
21
|
SLACK_BOT_TOKEN_2: "slack",
|
|
22
22
|
};
|
|
23
|
+
const GITHUB_APP_KEYS = [
|
|
24
|
+
"GITHUB_APP_ID",
|
|
25
|
+
"GITHUB_APP_INSTALLATION_ID",
|
|
26
|
+
"GITHUB_APP_PRIVATE_KEY",
|
|
27
|
+
] as const;
|
|
23
28
|
const VAULT_ONLY_KEYS = new Set([
|
|
24
29
|
...Object.keys(WORKSPACE_PROVIDER_BY_KEY),
|
|
30
|
+
...GITHUB_APP_KEYS,
|
|
25
31
|
"SENTRY_ORG_SLUG",
|
|
26
32
|
]);
|
|
27
33
|
|
|
@@ -235,6 +241,19 @@ export async function hasConnectorSecret(
|
|
|
235
241
|
return false;
|
|
236
242
|
}
|
|
237
243
|
|
|
244
|
+
async function hasGitHubConnectorSecret(
|
|
245
|
+
ownerEmail: string,
|
|
246
|
+
options: ResolveConnectorSecretOptions,
|
|
247
|
+
): Promise<boolean> {
|
|
248
|
+
if (await hasConnectorSecret("GITHUB_TOKEN", ownerEmail, options)) {
|
|
249
|
+
return true;
|
|
250
|
+
}
|
|
251
|
+
const appSecrets = await Promise.all(
|
|
252
|
+
GITHUB_APP_KEYS.map((key) => hasConnectorSecret(key, ownerEmail, options)),
|
|
253
|
+
);
|
|
254
|
+
return appSecrets.every(Boolean);
|
|
255
|
+
}
|
|
256
|
+
|
|
238
257
|
export async function resolveFactoryConnectorReadiness(
|
|
239
258
|
ownerEmail: string,
|
|
240
259
|
options: ResolveConnectorSecretOptions = {},
|
|
@@ -248,7 +267,7 @@ export async function resolveFactoryConnectorReadiness(
|
|
|
248
267
|
const [slack, slackSecondary, github, sentry] = await Promise.all([
|
|
249
268
|
hasConnectorSecret("SLACK_BOT_TOKEN", ownerEmail, readinessOptions),
|
|
250
269
|
hasConnectorSecret("SLACK_BOT_TOKEN_2", ownerEmail, readinessOptions),
|
|
251
|
-
|
|
270
|
+
hasGitHubConnectorSecret(ownerEmail, readinessOptions),
|
|
252
271
|
hasConnectorSecret(
|
|
253
272
|
["SENTRY_SERVER_TOKEN", "SENTRY_AUTH_TOKEN"],
|
|
254
273
|
ownerEmail,
|
|
@@ -269,11 +288,14 @@ export async function assertFactoryConnectorReady(
|
|
|
269
288
|
const verb = options.verb ?? "creating";
|
|
270
289
|
const label =
|
|
271
290
|
source === "slack" ? "Slack" : source === "github" ? "GitHub" : "Sentry";
|
|
272
|
-
const ready =
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
291
|
+
const ready =
|
|
292
|
+
source === "github"
|
|
293
|
+
? await hasGitHubConnectorSecret(ownerEmail, options)
|
|
294
|
+
: await hasConnectorSecret(
|
|
295
|
+
connectorKeysForSource(source, options.slackWorkspace),
|
|
296
|
+
ownerEmail,
|
|
297
|
+
options,
|
|
298
|
+
);
|
|
277
299
|
if (!ready) {
|
|
278
300
|
throw new Error(
|
|
279
301
|
`Connect ${label} in Dispatch or add a vault token before ${verb} this job.`,
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { createSign } from "node:crypto";
|
|
2
|
+
|
|
1
3
|
import { resolveConnectorSecret } from "../connectors/credentials.js";
|
|
2
4
|
import type { TriageCoverage } from "./contracts.js";
|
|
3
5
|
import type { ReviewCommentObservation } from "./pr-babysit.js";
|
|
@@ -21,6 +23,45 @@ export interface GitHubClientOptions extends GitHubClientIdentity {
|
|
|
21
23
|
fetchImpl?: FetchLike;
|
|
22
24
|
}
|
|
23
25
|
|
|
26
|
+
const GITHUB_APP_KEYS = [
|
|
27
|
+
"GITHUB_APP_ID",
|
|
28
|
+
"GITHUB_APP_INSTALLATION_ID",
|
|
29
|
+
"GITHUB_APP_PRIVATE_KEY",
|
|
30
|
+
] as const;
|
|
31
|
+
const INSTALLATION_TOKEN_CACHE_BUFFER_MS = 60_000;
|
|
32
|
+
|
|
33
|
+
interface GitHubAppConfig {
|
|
34
|
+
appId: string;
|
|
35
|
+
installationId: string;
|
|
36
|
+
privateKey: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function base64Url(value: string): string {
|
|
40
|
+
return Buffer.from(value).toString("base64url");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function positiveIntegerString(value: string | undefined, key: string): string {
|
|
44
|
+
if (!value || !/^[1-9]\d*$/.test(value)) {
|
|
45
|
+
throw new Error(`${key} must be a positive integer string`);
|
|
46
|
+
}
|
|
47
|
+
return value;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function normalizePrivateKey(value: string): string {
|
|
51
|
+
return value.trim().replace(/\\n/g, "\n");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function createGitHubAppJwt(config: GitHubAppConfig): string {
|
|
55
|
+
const now = Math.floor(Date.now() / 1000);
|
|
56
|
+
const header = base64Url(JSON.stringify({ alg: "RS256", typ: "JWT" }));
|
|
57
|
+
const payload = base64Url(
|
|
58
|
+
JSON.stringify({ iat: now - 60, exp: now + 540, iss: config.appId }),
|
|
59
|
+
);
|
|
60
|
+
const signer = createSign("RSA-SHA256");
|
|
61
|
+
signer.update(`${header}.${payload}`);
|
|
62
|
+
return `${header}.${payload}.${signer.sign(config.privateKey, "base64url")}`;
|
|
63
|
+
}
|
|
64
|
+
|
|
24
65
|
export interface GitHubRepositoryRef {
|
|
25
66
|
owner: string;
|
|
26
67
|
repo: string;
|
|
@@ -368,14 +409,83 @@ export function createGitHubClient(options: GitHubClientOptions) {
|
|
|
368
409
|
const fetchImpl = options.fetchImpl ?? fetch;
|
|
369
410
|
const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
370
411
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
412
|
+
let cachedAppConfig: GitHubAppConfig | null | undefined;
|
|
413
|
+
let cachedInstallationToken: { value: string; expiresAt: number } | undefined;
|
|
414
|
+
let cachedAppBotIdentity: { login: string; id: number } | undefined;
|
|
415
|
+
|
|
416
|
+
async function connectorSecret(key: string): Promise<string | undefined> {
|
|
417
|
+
return resolveConnectorSecret(key, options.ownerEmail, {
|
|
418
|
+
orgId: options.orgId,
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
async function appConfig(): Promise<GitHubAppConfig | null> {
|
|
423
|
+
if (cachedAppConfig !== undefined) return cachedAppConfig;
|
|
424
|
+
const [appId, installationId, privateKey] = await Promise.all(
|
|
425
|
+
GITHUB_APP_KEYS.map((key) => connectorSecret(key)),
|
|
378
426
|
);
|
|
427
|
+
const configured = [appId, installationId, privateKey].filter(
|
|
428
|
+
Boolean,
|
|
429
|
+
).length;
|
|
430
|
+
if (configured === 0) {
|
|
431
|
+
cachedAppConfig = null;
|
|
432
|
+
return cachedAppConfig;
|
|
433
|
+
}
|
|
434
|
+
if (configured !== GITHUB_APP_KEYS.length) {
|
|
435
|
+
throw new Error(
|
|
436
|
+
"GitHub App configuration is incomplete; configure GITHUB_APP_ID, GITHUB_APP_INSTALLATION_ID, and GITHUB_APP_PRIVATE_KEY",
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
cachedAppConfig = {
|
|
440
|
+
appId: positiveIntegerString(appId, "GITHUB_APP_ID"),
|
|
441
|
+
installationId: positiveIntegerString(
|
|
442
|
+
installationId,
|
|
443
|
+
"GITHUB_APP_INSTALLATION_ID",
|
|
444
|
+
),
|
|
445
|
+
privateKey: normalizePrivateKey(privateKey as string),
|
|
446
|
+
};
|
|
447
|
+
return cachedAppConfig;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
async function token(): Promise<string> {
|
|
451
|
+
const app = await appConfig();
|
|
452
|
+
if (app) {
|
|
453
|
+
const now = Math.floor(Date.now() / 1000);
|
|
454
|
+
if (
|
|
455
|
+
cachedInstallationToken &&
|
|
456
|
+
cachedInstallationToken.expiresAt >
|
|
457
|
+
now * 1000 + INSTALLATION_TOKEN_CACHE_BUFFER_MS
|
|
458
|
+
) {
|
|
459
|
+
return cachedInstallationToken.value;
|
|
460
|
+
}
|
|
461
|
+
const jwt = createGitHubAppJwt(app);
|
|
462
|
+
const response = (await fetchImpl(
|
|
463
|
+
`${baseUrl}/app/installations/${app.installationId}/access_tokens`,
|
|
464
|
+
{
|
|
465
|
+
method: "POST",
|
|
466
|
+
headers: {
|
|
467
|
+
Accept: "application/vnd.github+json",
|
|
468
|
+
Authorization: `Bearer ${jwt}`,
|
|
469
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
470
|
+
},
|
|
471
|
+
},
|
|
472
|
+
)) as JsonResponse;
|
|
473
|
+
if (!response.ok) {
|
|
474
|
+
throw new Error(
|
|
475
|
+
`GitHub App installation token request failed: HTTP ${response.status}`,
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
const body = record(await response.json());
|
|
479
|
+
const value = requiredString(body.token, "GitHub App installation token");
|
|
480
|
+
const expiresAt = Date.parse(
|
|
481
|
+
requiredString(body.expires_at, "GitHub App token expiry"),
|
|
482
|
+
);
|
|
483
|
+
if (!Number.isFinite(expiresAt))
|
|
484
|
+
throw new Error("GitHub App token expiry is invalid");
|
|
485
|
+
cachedInstallationToken = { value, expiresAt };
|
|
486
|
+
return value;
|
|
487
|
+
}
|
|
488
|
+
const value = await connectorSecret("GITHUB_TOKEN");
|
|
379
489
|
if (!value)
|
|
380
490
|
throw new Error("GITHUB_TOKEN is not configured for this workspace");
|
|
381
491
|
return value;
|
|
@@ -603,6 +713,33 @@ export function createGitHubClient(options: GitHubClientOptions) {
|
|
|
603
713
|
},
|
|
604
714
|
|
|
605
715
|
async getAuthenticatedUser() {
|
|
716
|
+
const app = await appConfig();
|
|
717
|
+
if (app) {
|
|
718
|
+
if (!cachedAppBotIdentity) {
|
|
719
|
+
const response = (await fetchImpl(`${baseUrl}/app`, {
|
|
720
|
+
headers: {
|
|
721
|
+
Accept: "application/vnd.github+json",
|
|
722
|
+
Authorization: `Bearer ${createGitHubAppJwt(app)}`,
|
|
723
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
724
|
+
},
|
|
725
|
+
})) as JsonResponse;
|
|
726
|
+
if (!response.ok) {
|
|
727
|
+
throw new Error(
|
|
728
|
+
`GitHub App metadata request failed: HTTP ${response.status}`,
|
|
729
|
+
);
|
|
730
|
+
}
|
|
731
|
+
const metadata = record(await response.json());
|
|
732
|
+
const login = `${requiredString(metadata.slug, "GitHub App slug")}[bot]`;
|
|
733
|
+
const bot = record(
|
|
734
|
+
await request<unknown>(`/users/${encodeURIComponent(login)}`),
|
|
735
|
+
);
|
|
736
|
+
cachedAppBotIdentity = {
|
|
737
|
+
login: requiredString(bot.login, "GitHub App bot login"),
|
|
738
|
+
id: requiredNumber(bot.id, "GitHub App bot id"),
|
|
739
|
+
};
|
|
740
|
+
}
|
|
741
|
+
return cachedAppBotIdentity;
|
|
742
|
+
}
|
|
606
743
|
const item = record(await request<unknown>("/user"));
|
|
607
744
|
return {
|
|
608
745
|
login: requiredString(item.login, "authenticated GitHub user login"),
|
|
@@ -13,21 +13,19 @@ interface FormOgRenderOptions {
|
|
|
13
13
|
|
|
14
14
|
const WIDTH = 1200;
|
|
15
15
|
const HEIGHT = 630;
|
|
16
|
-
const
|
|
17
|
-
const BRAND_MINT = "#48FFE4";
|
|
18
|
-
const BG = "#000000";
|
|
16
|
+
const BG = "#0A0A0A";
|
|
19
17
|
const SURFACE = "#0a0a0a";
|
|
20
18
|
const BORDER = "#1f1f1f";
|
|
21
|
-
const FG = "#
|
|
22
|
-
const MUTED = "#
|
|
19
|
+
const FG = "#FAF9F5";
|
|
20
|
+
const MUTED = "#9A9997";
|
|
23
21
|
const FONT_FAMILY = "Liberation Sans, Arial, system-ui, sans-serif";
|
|
24
22
|
|
|
25
23
|
const BADGE_CX = 996;
|
|
26
24
|
const BADGE_CY = 170;
|
|
27
25
|
|
|
28
26
|
const LOGO_MARK = `
|
|
29
|
-
<path d="
|
|
30
|
-
<path d="
|
|
27
|
+
<path d="M26.8789 71.999H0L16.5146 43.1992L41.2793 0L66.2197 43.1992H43.3945L26.8789 71.999Z" fill="white"/>
|
|
28
|
+
<path d="M97.914 0H124.794L83.5143 72H56.6348L97.914 0Z" fill="white"/>
|
|
31
29
|
`;
|
|
32
30
|
|
|
33
31
|
const AVATAR_DATA_URL_RE =
|
|
@@ -182,28 +180,20 @@ export function renderFormOgImageSvg(input: FormOgImageInput = {}): string {
|
|
|
182
180
|
);
|
|
183
181
|
const avatarContent = profileImageDataUrl
|
|
184
182
|
? `<image x="${BADGE_CX - 86}" y="${BADGE_CY - 86}" width="172" height="172" href="${escapeSvg(profileImageDataUrl)}" preserveAspectRatio="xMidYMid slice" mask="url(#avatarMask)"/>`
|
|
185
|
-
: `<circle cx="${BADGE_CX}" cy="${BADGE_CY}" r="72" fill="
|
|
183
|
+
: `<circle cx="${BADGE_CX}" cy="${BADGE_CY}" r="72" fill="${FG}" fill-opacity="0.12"/>
|
|
186
184
|
<text x="${BADGE_CX}" y="${BADGE_CY + 20}" text-anchor="middle" font-family="${FONT_FAMILY}" font-size="56" font-weight="800" fill="${FG}">${escapeSvg(initials)}</text>`;
|
|
187
185
|
|
|
188
186
|
return `<svg xmlns="http://www.w3.org/2000/svg" width="${WIDTH}" height="${HEIGHT}" viewBox="0 0 ${WIDTH} ${HEIGHT}">
|
|
189
187
|
<title>${escapeSvg(title)} - Agent-Native Forms preview</title>
|
|
190
188
|
<defs>
|
|
191
|
-
<linearGradient id="brand" x1="101.702" y1="67.4791" x2="113.672" y2="-37.4275" gradientUnits="userSpaceOnUse">
|
|
192
|
-
<stop stop-color="${BRAND_BLUE}"/>
|
|
193
|
-
<stop offset="1" stop-color="${BRAND_MINT}"/>
|
|
194
|
-
</linearGradient>
|
|
195
|
-
<pattern id="grid" width="48" height="48" patternUnits="userSpaceOnUse">
|
|
196
|
-
<path d="M 48 0 L 0 0 0 48" fill="none" stroke="#ffffff" stroke-opacity="0.07" stroke-width="1"/>
|
|
197
|
-
</pattern>
|
|
198
189
|
<mask id="avatarMask">
|
|
199
190
|
<rect width="${WIDTH}" height="${HEIGHT}" fill="black"/>
|
|
200
191
|
<circle cx="${BADGE_CX}" cy="${BADGE_CY}" r="78" fill="white"/>
|
|
201
192
|
</mask>
|
|
202
193
|
</defs>
|
|
203
194
|
<rect width="${WIDTH}" height="${HEIGHT}" fill="${BG}"/>
|
|
204
|
-
<rect width="${WIDTH}" height="${HEIGHT}" fill="url(#grid)"/>
|
|
205
195
|
<g transform="translate(80 86)">
|
|
206
|
-
<g transform="scale(0.62)">
|
|
196
|
+
<g transform="translate(0 14) scale(0.62)">
|
|
207
197
|
${LOGO_MARK}
|
|
208
198
|
</g>
|
|
209
199
|
<text x="90" y="31" font-family="${FONT_FAMILY}" font-size="28" font-weight="800" fill="${FG}">Agent-Native</text>
|
|
@@ -98,6 +98,7 @@ function deckCreativeContext(value: unknown): DeckCreativeContext | null {
|
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
export default defineAction({
|
|
101
|
+
title: "Add slide to deck",
|
|
101
102
|
description:
|
|
102
103
|
"Add a single slide to the real editable Agent-Native Slides deck. This is the primary Slides MCP edit action: use it after create-deck instead of creating or publishing a standalone HTML artifact. " +
|
|
103
104
|
"Build decks slide-by-slide — " +
|
|
@@ -101,6 +101,7 @@ function deckDeepLink(deckId: string): string {
|
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
export default defineAction({
|
|
104
|
+
title: "Create Slides deck",
|
|
104
105
|
description:
|
|
105
106
|
"Create the real editable Agent-Native Slides deck, optionally already populated with slides, or atomically replace all slides in an existing deck. This is the primary Slides MCP write action: use it instead of creating or publishing a standalone HTML artifact with the host's file tools. Put slide markup in `slides[].content`; this action persists it and returns an Open in Slides link. " +
|
|
106
107
|
"For short AI-generated decks in MCP app hosts, pass all generated slides in this call so the real deck editor opens inline already populated. " +
|
|
@@ -145,6 +145,7 @@ function deckDeepLink(deckId: string): string {
|
|
|
145
145
|
}
|
|
146
146
|
|
|
147
147
|
export default defineAction({
|
|
148
|
+
title: "Read Slides deck",
|
|
148
149
|
description:
|
|
149
150
|
"Get a specific deck. Pass slideId to return only that slide; targeted agent reads include full HTML by default. In-app agent calls without slideId return compact slide metadata by default; set compact=false when full deck HTML is needed. Frontend and CLI reads remain full unless compact=true. For any continuation or follow-up, call this first and use generationContext as the canonical original brief, references, theme, and target slide count. For source-preserving work, the compact result includes sourceCoverage; do not claim completion until sourceCoverage.complete is true and its expectedSlideIds and actualSlideIds match in order. User-visible slide numbers are 1-based and match the UI: slide 1 is the first slide. Use slideId for edits.",
|
|
150
151
|
timeoutMs: 60_000,
|
|
@@ -4,6 +4,7 @@ import { z } from "zod";
|
|
|
4
4
|
import { writeAppStateForCurrentTab } from "./_tab-state.js";
|
|
5
5
|
|
|
6
6
|
export default defineAction({
|
|
7
|
+
title: "Navigate Slides",
|
|
7
8
|
description:
|
|
8
9
|
"Navigate the UI to a specific deck, slide, or view. Writes a navigate command to application state which the UI reads and auto-deletes.",
|
|
9
10
|
schema: z.object({
|
|
@@ -610,6 +610,7 @@ export function isAgentPatchCaller(caller: string | undefined): boolean {
|
|
|
610
610
|
// ---------------------------------------------------------------------------
|
|
611
611
|
|
|
612
612
|
export default defineAction({
|
|
613
|
+
title: "Patch Slides deck",
|
|
613
614
|
description:
|
|
614
615
|
"Granular deck patch used by the browser editor for concurrent-safe writes. " +
|
|
615
616
|
"Each operation touches only the target slide or field — concurrent writers " +
|
|
@@ -278,6 +278,7 @@ function assertStyleOnlyEdit(
|
|
|
278
278
|
}
|
|
279
279
|
|
|
280
280
|
export default defineAction({
|
|
281
|
+
title: "Edit one Slides slide",
|
|
281
282
|
description:
|
|
282
283
|
"Atomically patch a slide's HTML like a code editor: send several exact edits against the current source, optionally format it with Prettier, and sync the result live to open editors. Use exactly one input mode: edits, legacy find/replace, or fullContent. Mixed modes are rejected and write nothing. Prefer edits over fullContent so unrelated markup is not regenerated, especially for style-only requests, reorders, or changes that must stay consistent across lists, tables, or other representations. For style-only requests, set styleOnly=true and use edits that change only the requested CSS declarations and preserve text and layout properties; the action rejects text or markup changes and fullContent in that mode. Never use unresolved placeholder markers as stand-ins for preserved content. Use baseContentHash from get-deck to reject stale patches, then re-read the targeted slide to verify every affected representation and the requested scope. Content edits clear existing click-reveal metadata; style-only CSS edits preserve it because the HTML structure remains stable. Use patch-deck with the complete animations list when a content edit intentionally changes both content and reveals. Source-imported slides preserve their original images and factual copy by default. The action returns immediately after persistence; layoutFit.status=pending means the open editor will measure the new content asynchronously, and get-layout-overflows can check the returned contentHash plus layoutFitRevision later.",
|
|
283
284
|
schema: z.object({
|
|
@@ -86,6 +86,7 @@ function getCurrentSlideFitMeasurement(
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
export default defineAction({
|
|
89
|
+
title: "Inspect current Slides screen",
|
|
89
90
|
description:
|
|
90
91
|
"See what the user is currently looking at. Returns the CURRENT deck ID, current slide ID, and the full list of slide IDs in the open deck (or the deck list if the user is on the home page). Call this before any slide operation to get the exact IDs you need for add-slide / update-slide / create-deck.",
|
|
91
92
|
schema: z.object({}),
|
|
@@ -132,6 +132,8 @@ export default createAgentChatPlugin({
|
|
|
132
132
|
initialToolNames: INITIAL_TOOL_NAMES,
|
|
133
133
|
mcp: {
|
|
134
134
|
connectorCatalog: INITIAL_TOOL_NAMES,
|
|
135
|
+
instructions:
|
|
136
|
+
"For deck edits, call view-screen first when the active deck or slide ID is unknown. Use get-deck to read the target, update-slide for one-slide edits, and patch-deck for deck-wide or multi-slide changes. Read back with get-deck after writing.",
|
|
135
137
|
},
|
|
136
138
|
durableBackgroundRuns: true,
|
|
137
139
|
runSoftTimeoutMs: SLIDES_BACKGROUND_RUN_SOFT_TIMEOUT_MS,
|
package/dist/action.d.ts
CHANGED
|
@@ -390,6 +390,8 @@ type InferParams<T extends Record<string, ParameterSchema> | undefined> = T exte
|
|
|
390
390
|
*/
|
|
391
391
|
export type ActionOutputErrorStrategy = "strict" | "warn" | "fallback";
|
|
392
392
|
interface DefineActionWithSchema<TSchema extends StandardSchemaV1, TReturn = any, TOutputSchema extends StandardSchemaV1 | undefined = undefined> {
|
|
393
|
+
/** Optional human-facing tool title used by WebMCP and MCP hosts. */
|
|
394
|
+
title?: string;
|
|
393
395
|
description: string;
|
|
394
396
|
/** Standard Schema-compatible schema (Zod, Valibot, ArkType). Provides runtime
|
|
395
397
|
* validation and full TypeScript type inference for `run()` args. The schema is
|
|
@@ -616,6 +618,8 @@ interface DefineActionWithSchema<TSchema extends StandardSchemaV1, TReturn = any
|
|
|
616
618
|
audit?: ActionAuditConfig;
|
|
617
619
|
}
|
|
618
620
|
interface DefineActionWithParams<TParams extends Record<string, ParameterSchema> | undefined = Record<string, ParameterSchema> | undefined, TReturn = any> {
|
|
621
|
+
/** Optional human-facing tool title used by WebMCP and MCP hosts. */
|
|
622
|
+
title?: string;
|
|
619
623
|
description: string;
|
|
620
624
|
/** Flat map of parameter names to their schema. Automatically wrapped in
|
|
621
625
|
* `{ type: "object", properties: ... }` for the Claude API. */
|
package/dist/action.js
CHANGED
|
@@ -214,6 +214,9 @@ export function defineAction(options) {
|
|
|
214
214
|
const chatUI = normalizeActionChatUIConfig(options.chatUI);
|
|
215
215
|
return {
|
|
216
216
|
tool: {
|
|
217
|
+
...(typeof options.title === "string" && options.title.trim()
|
|
218
|
+
? { title: options.title.trim() }
|
|
219
|
+
: {}),
|
|
217
220
|
description: options.description,
|
|
218
221
|
parameters: toolParameters,
|
|
219
222
|
},
|
package/dist/agent/types.d.ts
CHANGED
|
@@ -186,8 +186,8 @@ export declare function createAutomationCallbackHandler(runtime: AutomationRunti
|
|
|
186
186
|
duplicate?: undefined;
|
|
187
187
|
eventId?: undefined;
|
|
188
188
|
} | {
|
|
189
|
-
error?: undefined;
|
|
190
189
|
accepted: boolean;
|
|
191
190
|
duplicate: boolean;
|
|
192
191
|
eventId: string;
|
|
192
|
+
error?: undefined;
|
|
193
193
|
}>;
|
package/dist/client/webmcp.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { agentNativeToolTitle } from "../shared/agent-mcp-metadata.js";
|
|
1
2
|
export class AgentNativeWebMcpUnsupportedError extends Error {
|
|
2
3
|
constructor() {
|
|
3
4
|
super("WebMCP is not supported by this browser or document");
|
|
@@ -254,6 +255,7 @@ export function createAgentNativeServerActionWebMcpRegistration(options) {
|
|
|
254
255
|
}
|
|
255
256
|
return manifest.map((action) => ({
|
|
256
257
|
name: action.name,
|
|
258
|
+
title: agentNativeToolTitle(action.name, action.title),
|
|
257
259
|
description: action.description,
|
|
258
260
|
...(action.inputSchema ? { schema: action.inputSchema } : {}),
|
|
259
261
|
...(action.readOnly ? { readOnly: true } : {}),
|
|
@@ -310,7 +312,10 @@ function sensitiveAction(action) {
|
|
|
310
312
|
Boolean(action.approval));
|
|
311
313
|
}
|
|
312
314
|
function actionManifest(action) {
|
|
313
|
-
const manifest = {
|
|
315
|
+
const manifest = {
|
|
316
|
+
...action,
|
|
317
|
+
title: agentNativeToolTitle(action.name, action.title),
|
|
318
|
+
};
|
|
314
319
|
delete manifest.run;
|
|
315
320
|
return manifest;
|
|
316
321
|
}
|
|
@@ -418,7 +423,7 @@ export function createAgentNativeWebMcpRegistration(options) {
|
|
|
418
423
|
}
|
|
419
424
|
await modelContext.registerTool({
|
|
420
425
|
name: action.name,
|
|
421
|
-
|
|
426
|
+
title: agentNativeToolTitle(action.name, action.title),
|
|
422
427
|
description: action.description,
|
|
423
428
|
inputSchema,
|
|
424
429
|
annotations: {
|
|
@@ -62,11 +62,11 @@ export declare const postAwareness: import("h3").EventHandlerWithFetch<import("h
|
|
|
62
62
|
error: string;
|
|
63
63
|
states?: undefined;
|
|
64
64
|
} | {
|
|
65
|
-
error?: undefined;
|
|
66
65
|
states: {
|
|
67
66
|
clientId: number;
|
|
68
67
|
state: string;
|
|
69
68
|
}[];
|
|
69
|
+
error?: undefined;
|
|
70
70
|
}>>;
|
|
71
71
|
/**
|
|
72
72
|
* GET /_agent-native/collab/:docId/users
|
|
@@ -77,9 +77,9 @@ export declare const getActiveUsers: import("h3").EventHandlerWithFetch<import("
|
|
|
77
77
|
error: string;
|
|
78
78
|
users?: undefined;
|
|
79
79
|
} | {
|
|
80
|
-
error?: undefined;
|
|
81
80
|
users: {
|
|
82
81
|
clientId: number;
|
|
83
82
|
lastSeen: number;
|
|
84
83
|
}[];
|
|
84
|
+
error?: undefined;
|
|
85
85
|
}>>;
|
|
@@ -35,6 +35,8 @@ export interface MCPConfig {
|
|
|
35
35
|
appId?: string;
|
|
36
36
|
/** App description */
|
|
37
37
|
description: string;
|
|
38
|
+
/** Additional host-facing guidance included in the MCP initialize response. */
|
|
39
|
+
instructions?: string;
|
|
38
40
|
/** Optional canonical website URL for hosts that surface MCP app details. */
|
|
39
41
|
websiteUrl?: string;
|
|
40
42
|
/** Optional app icons for MCP hosts that render server branding. */
|
package/dist/mcp/build-server.js
CHANGED
|
@@ -25,6 +25,7 @@ import { writeActionChangeMarker } from "../server/action-change-marker-write.js
|
|
|
25
25
|
import { getConfiguredAppBasePath } from "../server/app-base-path.js";
|
|
26
26
|
import { buildDeepLink, toAbsoluteOpenUrl, toDesktopOpenUrl, toVsCodeOpenUrl, } from "../server/deep-link.js";
|
|
27
27
|
import { getRequestContext, getRequestOrgId, getRequestUserEmail, runWithRequestContext, } from "../server/request-context.js";
|
|
28
|
+
import { agentNativeMcpInstructions, agentNativeToolTitle, } from "../shared/agent-mcp-metadata.js";
|
|
28
29
|
import { isAgentNativeOpenDeepLink, withCollapsedAgentSidebarParam, } from "../shared/agent-sidebar-url.js";
|
|
29
30
|
import { MCP_APP_CHAT_BRIDGE_QUERY_PARAM } from "../shared/embed-auth.js";
|
|
30
31
|
import { describeMcpError, readClientInfoFromRequest, trackMcpResourceRead, trackMcpResourcesList, trackMcpToolCall, trackMcpToolsList, } from "./analytics.js";
|
|
@@ -1315,6 +1316,7 @@ export async function createMCPServerForRequest(config, identity, requestMeta) {
|
|
|
1315
1316
|
const supportsMcpApps = compactMcpAppCatalog ||
|
|
1316
1317
|
Object.values(advertisedActions).some((entry) => Boolean(entry.mcpApp?.resource));
|
|
1317
1318
|
const server = new Server(mcpServerInfo(config, requestMeta), {
|
|
1319
|
+
instructions: agentNativeMcpInstructions(config.instructions),
|
|
1318
1320
|
capabilities: {
|
|
1319
1321
|
tools: {},
|
|
1320
1322
|
...(supportsMcpApps
|
|
@@ -1486,7 +1488,9 @@ export async function createMCPServerForRequest(config, identity, requestMeta) {
|
|
|
1486
1488
|
: {}),
|
|
1487
1489
|
};
|
|
1488
1490
|
const baseDescription = entry.tool.description ?? name;
|
|
1491
|
+
const title = agentNativeToolTitle(name, entry.tool.title);
|
|
1489
1492
|
const annotations = {
|
|
1493
|
+
title,
|
|
1490
1494
|
readOnlyHint: entry.readOnly === true,
|
|
1491
1495
|
destructiveHint: entry.publicAgent?.isConsequential === true ||
|
|
1492
1496
|
entry.needsApproval !== undefined,
|
|
@@ -11,14 +11,14 @@
|
|
|
11
11
|
* DELETE /_agent-native/notifications/:id — delete
|
|
12
12
|
*/
|
|
13
13
|
export declare function createNotificationsHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<"" | import("./types.js").Notification[] | {
|
|
14
|
-
error?: undefined;
|
|
15
14
|
count: number;
|
|
16
15
|
updated?: undefined;
|
|
16
|
+
error?: undefined;
|
|
17
17
|
ok?: undefined;
|
|
18
18
|
} | {
|
|
19
|
-
error?: undefined;
|
|
20
19
|
count?: undefined;
|
|
21
20
|
updated: number;
|
|
21
|
+
error?: undefined;
|
|
22
22
|
ok?: undefined;
|
|
23
23
|
} | {
|
|
24
24
|
count?: undefined;
|
|
@@ -26,8 +26,8 @@ export declare function createNotificationsHandler(): import("h3").EventHandlerW
|
|
|
26
26
|
error: string;
|
|
27
27
|
ok?: undefined;
|
|
28
28
|
} | {
|
|
29
|
-
error?: undefined;
|
|
30
29
|
count?: undefined;
|
|
31
30
|
updated?: undefined;
|
|
31
|
+
error?: undefined;
|
|
32
32
|
ok: boolean;
|
|
33
33
|
}>>;
|
|
@@ -41,27 +41,27 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
|
|
|
41
41
|
thumbsUpRate: number;
|
|
42
42
|
avgEvalScore: number;
|
|
43
43
|
} | {
|
|
44
|
-
error?: undefined;
|
|
45
|
-
ok?: undefined;
|
|
46
44
|
summary: import("./types.js").TraceSummary;
|
|
47
45
|
spans: import("./types.js").TraceSpan[];
|
|
46
|
+
ok?: undefined;
|
|
47
|
+
error?: undefined;
|
|
48
48
|
id?: undefined;
|
|
49
49
|
} | {
|
|
50
|
-
error?: undefined;
|
|
51
|
-
ok?: undefined;
|
|
52
50
|
summary?: undefined;
|
|
53
51
|
spans?: undefined;
|
|
54
52
|
id: string;
|
|
55
|
-
} | {
|
|
56
53
|
ok?: undefined;
|
|
54
|
+
error?: undefined;
|
|
55
|
+
} | {
|
|
57
56
|
summary?: undefined;
|
|
58
57
|
spans?: undefined;
|
|
59
58
|
error: any;
|
|
59
|
+
ok?: undefined;
|
|
60
60
|
id?: undefined;
|
|
61
61
|
} | {
|
|
62
|
-
error?: undefined;
|
|
63
62
|
summary?: undefined;
|
|
64
63
|
spans?: undefined;
|
|
65
64
|
ok: boolean;
|
|
65
|
+
error?: undefined;
|
|
66
66
|
id?: undefined;
|
|
67
67
|
}>>;
|