@agent-native/dispatch 0.8.6 → 0.8.8

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.
Files changed (31) hide show
  1. package/dist/actions/list_apps.js +1 -1
  2. package/dist/actions/list_apps.js.map +1 -1
  3. package/dist/actions/open_app.d.ts.map +1 -1
  4. package/dist/actions/open_app.js +7 -4
  5. package/dist/actions/open_app.js.map +1 -1
  6. package/dist/components/workspace-app-card.d.ts.map +1 -1
  7. package/dist/components/workspace-app-card.js +4 -2
  8. package/dist/components/workspace-app-card.js.map +1 -1
  9. package/dist/routes/pages/metrics.d.ts.map +1 -1
  10. package/dist/routes/pages/metrics.js +3 -1
  11. package/dist/routes/pages/metrics.js.map +1 -1
  12. package/dist/server/lib/app-creation-store.d.ts.map +1 -1
  13. package/dist/server/lib/app-creation-store.js +104 -10
  14. package/dist/server/lib/app-creation-store.js.map +1 -1
  15. package/dist/server/lib/mcp-gateway.d.ts.map +1 -1
  16. package/dist/server/lib/mcp-gateway.js +160 -4
  17. package/dist/server/lib/mcp-gateway.js.map +1 -1
  18. package/dist/server/lib/usage-metrics-store.d.ts +1 -0
  19. package/dist/server/lib/usage-metrics-store.d.ts.map +1 -1
  20. package/dist/server/lib/usage-metrics-store.js +1 -0
  21. package/dist/server/lib/usage-metrics-store.js.map +1 -1
  22. package/package.json +1 -1
  23. package/src/actions/list_apps.ts +1 -1
  24. package/src/actions/open_app.ts +11 -4
  25. package/src/components/workspace-app-card.tsx +29 -18
  26. package/src/routes/pages/metrics.tsx +4 -1
  27. package/src/server/lib/app-creation-store.spec.ts +240 -0
  28. package/src/server/lib/app-creation-store.ts +130 -11
  29. package/src/server/lib/mcp-gateway.spec.ts +295 -0
  30. package/src/server/lib/mcp-gateway.ts +187 -4
  31. 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: typeof record.createdAt === "string" && record.createdAt.trim()
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: "Building in Builder",
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((app) => !readyIds.has(app.id))
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 existing = pendingApps.find((app) => app.id === input.appId);
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.id !== input.appId),
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
  }