@agent-native/dispatch 0.8.6 → 0.8.7
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/dist/actions/list_apps.js +1 -1
- package/dist/actions/list_apps.js.map +1 -1
- package/dist/actions/open_app.d.ts.map +1 -1
- package/dist/actions/open_app.js +7 -4
- package/dist/actions/open_app.js.map +1 -1
- package/dist/components/workspace-app-card.d.ts.map +1 -1
- package/dist/components/workspace-app-card.js +2 -1
- package/dist/components/workspace-app-card.js.map +1 -1
- package/dist/routes/pages/metrics.d.ts.map +1 -1
- package/dist/routes/pages/metrics.js +3 -1
- package/dist/routes/pages/metrics.js.map +1 -1
- package/dist/server/lib/app-creation-store.d.ts.map +1 -1
- package/dist/server/lib/app-creation-store.js +104 -10
- package/dist/server/lib/app-creation-store.js.map +1 -1
- package/dist/server/lib/mcp-gateway.d.ts.map +1 -1
- package/dist/server/lib/mcp-gateway.js +160 -4
- package/dist/server/lib/mcp-gateway.js.map +1 -1
- package/dist/server/lib/usage-metrics-store.d.ts +1 -0
- package/dist/server/lib/usage-metrics-store.d.ts.map +1 -1
- package/dist/server/lib/usage-metrics-store.js +1 -0
- package/dist/server/lib/usage-metrics-store.js.map +1 -1
- package/package.json +1 -1
- package/src/actions/list_apps.ts +1 -1
- package/src/actions/open_app.ts +11 -4
- package/src/components/workspace-app-card.tsx +3 -2
- package/src/routes/pages/metrics.tsx +4 -1
- package/src/server/lib/app-creation-store.spec.ts +240 -0
- package/src/server/lib/app-creation-store.ts +130 -11
- package/src/server/lib/mcp-gateway.spec.ts +295 -0
- package/src/server/lib/mcp-gateway.ts +187 -4
- package/src/server/lib/usage-metrics-store.ts +2 -0
|
@@ -17,6 +17,7 @@ const WORKSPACE_APPS_MANIFEST_FILE = "workspace-apps.json";
|
|
|
17
17
|
const WORKSPACE_APPS_GATEWAY_PATH = "/_workspace/apps";
|
|
18
18
|
const WORKSPACE_APPS_GATEWAY_TIMEOUT_MS = 1_000;
|
|
19
19
|
const MAX_PENDING_APPS = 50;
|
|
20
|
+
const PENDING_WORKSPACE_APP_TTL_MS = 7 * 24 * 60 * 60 * 1_000;
|
|
20
21
|
const AGENT_CARD_PATH = "/.well-known/agent-card.json";
|
|
21
22
|
const AGENT_CARD_FETCH_TIMEOUT_MS = 1_500;
|
|
22
23
|
const DEFAULT_WORKSPACE_APP_AUDIENCE = "internal";
|
|
@@ -88,6 +89,43 @@ function workspaceAppMetadataSettingsKey() {
|
|
|
88
89
|
return `${WORKSPACE_APP_METADATA_SETTINGS_KEY}:org:${orgId}`;
|
|
89
90
|
return `${WORKSPACE_APP_METADATA_SETTINGS_KEY}:user:${currentOwnerEmail()}`;
|
|
90
91
|
}
|
|
92
|
+
function cleanOptionalString(value) {
|
|
93
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
94
|
+
}
|
|
95
|
+
function normalizeContextPart(value) {
|
|
96
|
+
return value.trim().replace(/\s+/g, " ");
|
|
97
|
+
}
|
|
98
|
+
function pendingWorkspaceAppContext() {
|
|
99
|
+
const branch = cleanOptionalString(process.env.BRANCH) ??
|
|
100
|
+
cleanOptionalString(process.env.HEAD) ??
|
|
101
|
+
cleanOptionalString(process.env.VERCEL_GIT_COMMIT_REF) ??
|
|
102
|
+
cleanOptionalString(process.env.CF_PAGES_BRANCH) ??
|
|
103
|
+
cleanOptionalString(process.env.RENDER_GIT_BRANCH) ??
|
|
104
|
+
cleanOptionalString(process.env.FLY_BRANCH);
|
|
105
|
+
if (branch) {
|
|
106
|
+
const normalized = normalizeContextPart(branch);
|
|
107
|
+
return { id: `branch:${normalized}`, label: `Branch: ${normalized}` };
|
|
108
|
+
}
|
|
109
|
+
const origin = cleanOptionalString(process.env.DEPLOY_PRIME_URL) ??
|
|
110
|
+
cleanOptionalString(process.env.DEPLOY_URL) ??
|
|
111
|
+
cleanOptionalString(process.env.URL) ??
|
|
112
|
+
cleanOptionalString(process.env.APP_URL) ??
|
|
113
|
+
cleanOptionalString(process.env.BETTER_AUTH_URL) ??
|
|
114
|
+
cleanOptionalString(process.env.WORKSPACE_GATEWAY_URL);
|
|
115
|
+
if (!origin)
|
|
116
|
+
return null;
|
|
117
|
+
try {
|
|
118
|
+
const parsed = new URL(origin);
|
|
119
|
+
return {
|
|
120
|
+
id: `origin:${parsed.origin}`,
|
|
121
|
+
label: parsed.hostname,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
const normalized = normalizeContextPart(origin);
|
|
126
|
+
return { id: `origin:${normalized}`, label: normalized };
|
|
127
|
+
}
|
|
128
|
+
}
|
|
91
129
|
async function readSettingsRecord() {
|
|
92
130
|
const raw = await getSetting(scopedSettingsKey()).catch(() => null);
|
|
93
131
|
return raw && typeof raw === "object" && !Array.isArray(raw)
|
|
@@ -321,6 +359,49 @@ function parseWorkspaceAppsManifest(parsed) {
|
|
|
321
359
|
.sort(sortWorkspaceApps);
|
|
322
360
|
return apps.length ? apps : null;
|
|
323
361
|
}
|
|
362
|
+
function parseDateMs(value) {
|
|
363
|
+
if (!value)
|
|
364
|
+
return null;
|
|
365
|
+
const ms = Date.parse(value);
|
|
366
|
+
return Number.isFinite(ms) ? ms : null;
|
|
367
|
+
}
|
|
368
|
+
function pendingWorkspaceAppExpiresAt(createdAt) {
|
|
369
|
+
const createdMs = parseDateMs(createdAt) ?? Date.now();
|
|
370
|
+
return new Date(createdMs + PENDING_WORKSPACE_APP_TTL_MS).toISOString();
|
|
371
|
+
}
|
|
372
|
+
function isPendingWorkspaceAppExpired(app) {
|
|
373
|
+
const expiresMs = parseDateMs(app.expiresAt) ??
|
|
374
|
+
(parseDateMs(app.createdAt) ?? Date.now()) + PENDING_WORKSPACE_APP_TTL_MS;
|
|
375
|
+
return expiresMs <= Date.now();
|
|
376
|
+
}
|
|
377
|
+
function pendingWorkspaceAppMatchesCurrentContext(app) {
|
|
378
|
+
if (isPendingWorkspaceAppExpired(app))
|
|
379
|
+
return false;
|
|
380
|
+
const currentContext = pendingWorkspaceAppContext();
|
|
381
|
+
if (!app.contextId)
|
|
382
|
+
return true;
|
|
383
|
+
return app.contextId === currentContext?.id;
|
|
384
|
+
}
|
|
385
|
+
function pendingWorkspaceAppContextRank(app) {
|
|
386
|
+
const currentContext = pendingWorkspaceAppContext();
|
|
387
|
+
if (app.contextId && app.contextId === currentContext?.id)
|
|
388
|
+
return 2;
|
|
389
|
+
if (!app.contextId)
|
|
390
|
+
return 1;
|
|
391
|
+
return 0;
|
|
392
|
+
}
|
|
393
|
+
function dedupePendingWorkspaceAppsForCurrentContext(apps) {
|
|
394
|
+
const byId = new Map();
|
|
395
|
+
for (const app of apps) {
|
|
396
|
+
const existing = byId.get(app.id);
|
|
397
|
+
if (!existing ||
|
|
398
|
+
pendingWorkspaceAppContextRank(app) >
|
|
399
|
+
pendingWorkspaceAppContextRank(existing)) {
|
|
400
|
+
byId.set(app.id, app);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
return Array.from(byId.values());
|
|
404
|
+
}
|
|
324
405
|
function sortWorkspaceApps(a, b) {
|
|
325
406
|
if (a.id === "dispatch")
|
|
326
407
|
return -1;
|
|
@@ -345,6 +426,7 @@ function parsePendingWorkspaceApps(value) {
|
|
|
345
426
|
if (!id || !pathValue.startsWith("/"))
|
|
346
427
|
return null;
|
|
347
428
|
const now = new Date().toISOString();
|
|
429
|
+
const createdAt = cleanOptionalString(record.createdAt) ?? now;
|
|
348
430
|
return {
|
|
349
431
|
id,
|
|
350
432
|
name: typeof record.name === "string" && record.name.trim()
|
|
@@ -363,15 +445,17 @@ function parsePendingWorkspaceApps(value) {
|
|
|
363
445
|
projectId: typeof record.projectId === "string" && record.projectId.trim()
|
|
364
446
|
? record.projectId.trim()
|
|
365
447
|
: null,
|
|
448
|
+
contextId: cleanOptionalString(record.contextId),
|
|
449
|
+
contextLabel: cleanOptionalString(record.contextLabel),
|
|
366
450
|
...(record.audience === undefined
|
|
367
451
|
? {}
|
|
368
452
|
: { audience: normalizeWorkspaceAppAudience(record.audience) }),
|
|
369
|
-
createdAt
|
|
370
|
-
? record.createdAt.trim()
|
|
371
|
-
: now,
|
|
453
|
+
createdAt,
|
|
372
454
|
updatedAt: typeof record.updatedAt === "string" && record.updatedAt.trim()
|
|
373
455
|
? record.updatedAt.trim()
|
|
374
456
|
: now,
|
|
457
|
+
expiresAt: cleanOptionalString(record.expiresAt) ??
|
|
458
|
+
pendingWorkspaceAppExpiresAt(createdAt),
|
|
375
459
|
};
|
|
376
460
|
})
|
|
377
461
|
.filter((app) => !!app)
|
|
@@ -431,7 +515,7 @@ export async function removePendingWorkspaceApp(input) {
|
|
|
431
515
|
throw new Error("appId is required");
|
|
432
516
|
const raw = await readSettingsRecord();
|
|
433
517
|
const pending = parsePendingWorkspaceApps(raw.pendingApps);
|
|
434
|
-
const next = pending.filter((app) => app.id !== appId);
|
|
518
|
+
const next = pending.filter((app) => app.id !== appId || !pendingWorkspaceAppMatchesCurrentContext(app));
|
|
435
519
|
const removed = next.length !== pending.length;
|
|
436
520
|
if (!removed)
|
|
437
521
|
return { removed: false };
|
|
@@ -456,7 +540,7 @@ function pendingAppToSummary(app) {
|
|
|
456
540
|
publicPaths: [],
|
|
457
541
|
protectedPaths: [],
|
|
458
542
|
status: "pending",
|
|
459
|
-
statusLabel: "
|
|
543
|
+
statusLabel: "Pending Builder branch",
|
|
460
544
|
builderUrl: app.builderUrl,
|
|
461
545
|
branchName: app.branchName,
|
|
462
546
|
createdAt: app.createdAt,
|
|
@@ -464,9 +548,9 @@ function pendingAppToSummary(app) {
|
|
|
464
548
|
}
|
|
465
549
|
async function appendPendingWorkspaceApps(apps) {
|
|
466
550
|
const readyIds = new Set(apps.map((app) => app.id));
|
|
467
|
-
const pendingApps = (await listPendingWorkspaceApps())
|
|
468
|
-
.filter(
|
|
469
|
-
.map(pendingAppToSummary);
|
|
551
|
+
const pendingApps = dedupePendingWorkspaceAppsForCurrentContext((await listPendingWorkspaceApps())
|
|
552
|
+
.filter(pendingWorkspaceAppMatchesCurrentContext)
|
|
553
|
+
.filter((app) => !readyIds.has(app.id))).map(pendingAppToSummary);
|
|
470
554
|
return [...apps, ...pendingApps].sort(sortWorkspaceApps);
|
|
471
555
|
}
|
|
472
556
|
function agentCardUrlForApp(appUrl) {
|
|
@@ -562,9 +646,15 @@ async function maybeIncludeAgentCards(apps, options) {
|
|
|
562
646
|
}
|
|
563
647
|
async function recordPendingWorkspaceApp(input) {
|
|
564
648
|
const now = new Date().toISOString();
|
|
649
|
+
const context = pendingWorkspaceAppContext();
|
|
565
650
|
const raw = await readSettingsRecord();
|
|
566
651
|
const pendingApps = parsePendingWorkspaceApps(raw.pendingApps);
|
|
567
|
-
const
|
|
652
|
+
const contextId = context?.id ?? null;
|
|
653
|
+
const samePendingEntry = (app) => app.id === input.appId &&
|
|
654
|
+
(app.contextId === contextId || (!!contextId && !app.contextId));
|
|
655
|
+
const existing = pendingApps
|
|
656
|
+
.filter((app) => !isPendingWorkspaceAppExpired(app))
|
|
657
|
+
.find(samePendingEntry);
|
|
568
658
|
const next = {
|
|
569
659
|
id: input.appId,
|
|
570
660
|
name: titleCase(input.appId),
|
|
@@ -574,14 +664,17 @@ async function recordPendingWorkspaceApp(input) {
|
|
|
574
664
|
builderUrl: input.builderUrl?.trim() || null,
|
|
575
665
|
branchName: input.branchName?.trim() || null,
|
|
576
666
|
projectId: input.projectId,
|
|
667
|
+
contextId: context?.id ?? null,
|
|
668
|
+
contextLabel: context?.label ?? null,
|
|
577
669
|
createdAt: existing?.createdAt || now,
|
|
578
670
|
updatedAt: now,
|
|
671
|
+
expiresAt: pendingWorkspaceAppExpiresAt(existing?.createdAt || now),
|
|
579
672
|
};
|
|
580
673
|
await putSetting(scopedSettingsKey(), {
|
|
581
674
|
...raw,
|
|
582
675
|
pendingApps: [
|
|
583
676
|
next,
|
|
584
|
-
...pendingApps.filter((app) => app
|
|
677
|
+
...pendingApps.filter((app) => !samePendingEntry(app)),
|
|
585
678
|
].slice(0, MAX_PENDING_APPS),
|
|
586
679
|
});
|
|
587
680
|
await writeWorkspaceAppMetadataOverride({
|
|
@@ -600,6 +693,7 @@ async function recordPendingWorkspaceApp(input) {
|
|
|
600
693
|
builderBranchUrlConfigured: !!next.builderUrl,
|
|
601
694
|
branchName: next.branchName,
|
|
602
695
|
projectIdConfigured: !!next.projectId,
|
|
696
|
+
contextLabel: next.contextLabel,
|
|
603
697
|
},
|
|
604
698
|
});
|
|
605
699
|
}
|