@agent-native/dispatch 0.40.40 → 0.40.41-beta-dispatch-embed-01a15dc9-20261027050558
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/CHANGELOG.md +42 -0
- package/dist/actions/create_embed_session.js +1 -0
- package/dist/actions/create_embed_session.js.map +1 -1
- package/dist/actions/delete-destination.d.ts +12 -12
- package/dist/actions/provider-api-register.d.ts +13 -13
- package/dist/components/layout/Layout.d.ts.map +1 -1
- package/dist/components/layout/Layout.js +8 -11
- package/dist/components/layout/Layout.js.map +1 -1
- package/dist/components/workspace-app-host.d.ts.map +1 -1
- package/dist/components/workspace-app-host.js +23 -10
- package/dist/components/workspace-app-host.js.map +1 -1
- package/dist/lib/workspace-apps.d.ts +2 -2
- package/dist/lib/workspace-apps.d.ts.map +1 -1
- package/dist/lib/workspace-apps.js +35 -5
- package/dist/lib/workspace-apps.js.map +1 -1
- package/dist/server/lib/app-creation-store.d.ts.map +1 -1
- package/dist/server/lib/app-creation-store.js +173 -48
- package/dist/server/lib/app-creation-store.js.map +1 -1
- package/dist/server/lib/community-apps.js +1 -1
- package/dist/server/lib/community-apps.js.map +1 -1
- package/dist/server/lib/mcp-gateway.d.ts +1 -0
- package/dist/server/lib/mcp-gateway.d.ts.map +1 -1
- package/dist/server/lib/mcp-gateway.js +170 -38
- package/dist/server/lib/mcp-gateway.js.map +1 -1
- package/dist/server/lib/provider-api.d.ts +2 -2
- package/dist/shared/workspace-app-route.d.ts +2 -0
- package/dist/shared/workspace-app-route.d.ts.map +1 -0
- package/dist/shared/workspace-app-route.js +4 -0
- package/dist/shared/workspace-app-route.js.map +1 -0
- package/package.json +4 -4
- package/src/actions/create_embed_session.ts +1 -0
- package/src/actions/index.spec.ts +3 -0
- package/src/components/layout/Layout.spec.tsx +39 -9
- package/src/components/layout/Layout.tsx +19 -40
- package/src/components/workspace-app-host.spec.tsx +88 -1
- package/src/components/workspace-app-host.tsx +30 -9
- package/src/lib/workspace-apps.spec.ts +64 -5
- package/src/lib/workspace-apps.ts +36 -6
- package/src/routes/pages/apps.$appId.spec.tsx +21 -3
- package/src/server/lib/app-creation-store.spec.ts +502 -11
- package/src/server/lib/app-creation-store.ts +198 -50
- package/src/server/lib/community-apps.ts +1 -1
- package/src/server/lib/mcp-gateway.spec.ts +396 -9
- package/src/server/lib/mcp-gateway.ts +209 -45
- package/src/shared/workspace-app-route.ts +3 -0
|
@@ -472,6 +472,194 @@ describe("listWorkspaceApps", () => {
|
|
|
472
472
|
]);
|
|
473
473
|
});
|
|
474
474
|
|
|
475
|
+
it("keeps healthy gateway registry apps when one app URL is malformed", async () => {
|
|
476
|
+
const fetchMock = vi.fn(async () => {
|
|
477
|
+
return new Response(
|
|
478
|
+
JSON.stringify({
|
|
479
|
+
apps: [
|
|
480
|
+
{
|
|
481
|
+
id: "invalid-url",
|
|
482
|
+
name: "Invalid URL",
|
|
483
|
+
path: "/invalid-url",
|
|
484
|
+
url: "not-a-url",
|
|
485
|
+
},
|
|
486
|
+
{
|
|
487
|
+
id: "healthy-app",
|
|
488
|
+
name: "Healthy App",
|
|
489
|
+
path: "/healthy-app",
|
|
490
|
+
url: "https://agent-workspace.builder.io/healthy-app",
|
|
491
|
+
},
|
|
492
|
+
],
|
|
493
|
+
}),
|
|
494
|
+
{ headers: { "content-type": "application/json" } },
|
|
495
|
+
);
|
|
496
|
+
});
|
|
497
|
+
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
498
|
+
try {
|
|
499
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
500
|
+
vi.stubEnv("A2A_SECRET", "test-a2a-secret");
|
|
501
|
+
vi.stubEnv("WORKSPACE_GATEWAY_URL", "https://agent-workspace.builder.io");
|
|
502
|
+
|
|
503
|
+
const apps = await runWithRequestContext(
|
|
504
|
+
{
|
|
505
|
+
userEmail: "dev@example.test",
|
|
506
|
+
requestOrigin: "https://beta.dispatch.agent-native.com",
|
|
507
|
+
},
|
|
508
|
+
() => listWorkspaceApps({ includeAgentCards: false }),
|
|
509
|
+
);
|
|
510
|
+
|
|
511
|
+
expect(apps.map((app) => app.id)).toEqual(["healthy-app"]);
|
|
512
|
+
expect(warnSpy).toHaveBeenCalledWith(
|
|
513
|
+
"[dispatch] Could not resolve Workspace app invalid-url; skipping app",
|
|
514
|
+
expect.any(Error),
|
|
515
|
+
);
|
|
516
|
+
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
517
|
+
} finally {
|
|
518
|
+
warnSpy.mockRestore();
|
|
519
|
+
}
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
it("rejects a gateway registry when every app URL is invalid", async () => {
|
|
523
|
+
const fetchMock = vi.fn(async () => {
|
|
524
|
+
return new Response(
|
|
525
|
+
JSON.stringify({
|
|
526
|
+
apps: [
|
|
527
|
+
{
|
|
528
|
+
id: "invalid-url",
|
|
529
|
+
name: "Invalid URL",
|
|
530
|
+
path: "/invalid-url",
|
|
531
|
+
url: "not-a-url",
|
|
532
|
+
},
|
|
533
|
+
],
|
|
534
|
+
}),
|
|
535
|
+
{ headers: { "content-type": "application/json" } },
|
|
536
|
+
);
|
|
537
|
+
});
|
|
538
|
+
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
539
|
+
try {
|
|
540
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
541
|
+
vi.stubEnv("A2A_SECRET", "test-a2a-secret");
|
|
542
|
+
vi.stubEnv("WORKSPACE_GATEWAY_URL", "https://agent-workspace.builder.io");
|
|
543
|
+
|
|
544
|
+
await expect(
|
|
545
|
+
runWithRequestContext(
|
|
546
|
+
{
|
|
547
|
+
userEmail: "dev@example.test",
|
|
548
|
+
requestOrigin: "https://beta.dispatch.agent-native.com",
|
|
549
|
+
},
|
|
550
|
+
() => listWorkspaceApps({ includeAgentCards: false }),
|
|
551
|
+
),
|
|
552
|
+
).rejects.toMatchObject({
|
|
553
|
+
statusCode: 503,
|
|
554
|
+
errorCode: "workspace_gateway_invalid_response",
|
|
555
|
+
});
|
|
556
|
+
expect(warnSpy).toHaveBeenCalledWith(
|
|
557
|
+
"[dispatch] Could not resolve Workspace app invalid-url; skipping app",
|
|
558
|
+
expect.any(Error),
|
|
559
|
+
);
|
|
560
|
+
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
561
|
+
} finally {
|
|
562
|
+
warnSpy.mockRestore();
|
|
563
|
+
}
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
it("accepts a genuinely empty gateway registry", async () => {
|
|
567
|
+
const fetchMock = vi.fn(async () => {
|
|
568
|
+
return new Response(JSON.stringify({ apps: [] }), {
|
|
569
|
+
headers: { "content-type": "application/json" },
|
|
570
|
+
});
|
|
571
|
+
});
|
|
572
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
573
|
+
vi.stubEnv("A2A_SECRET", "test-a2a-secret");
|
|
574
|
+
vi.stubEnv("WORKSPACE_GATEWAY_URL", "https://agent-workspace.builder.io");
|
|
575
|
+
|
|
576
|
+
const apps = await runWithRequestContext(
|
|
577
|
+
{
|
|
578
|
+
userEmail: "dev@example.test",
|
|
579
|
+
requestOrigin: "https://beta.dispatch.agent-native.com",
|
|
580
|
+
},
|
|
581
|
+
() => listWorkspaceApps({ includeAgentCards: false }),
|
|
582
|
+
);
|
|
583
|
+
|
|
584
|
+
expect(apps).toEqual([]);
|
|
585
|
+
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
it.each([
|
|
589
|
+
[
|
|
590
|
+
"gateway mount path",
|
|
591
|
+
"https://beta.agent-workspace.builder.io/dispatch",
|
|
592
|
+
"",
|
|
593
|
+
],
|
|
594
|
+
["APP_BASE_PATH", "https://beta.agent-workspace.builder.io", "/dispatch"],
|
|
595
|
+
] as const)(
|
|
596
|
+
"keeps mounted Workspace prefixes on gateway app URLs with %s",
|
|
597
|
+
async (_mountConfig, gatewayUrl, appBasePath) => {
|
|
598
|
+
const fetchMock = vi.fn(async () => {
|
|
599
|
+
return new Response(
|
|
600
|
+
JSON.stringify({
|
|
601
|
+
apps: [
|
|
602
|
+
{
|
|
603
|
+
id: "customer-ticket-triage",
|
|
604
|
+
name: "Customer Ticket Triage",
|
|
605
|
+
path: "/customer-ticket-triage",
|
|
606
|
+
url: "https://beta.agent-workspace.builder.io/customer-ticket-triage",
|
|
607
|
+
},
|
|
608
|
+
],
|
|
609
|
+
}),
|
|
610
|
+
{ headers: { "content-type": "application/json" } },
|
|
611
|
+
);
|
|
612
|
+
});
|
|
613
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
614
|
+
vi.stubEnv("A2A_SECRET", "test-a2a-secret");
|
|
615
|
+
vi.stubEnv("WORKSPACE_GATEWAY_URL", gatewayUrl);
|
|
616
|
+
vi.stubEnv("APP_BASE_PATH", appBasePath);
|
|
617
|
+
vi.stubEnv("VITE_APP_BASE_PATH", "");
|
|
618
|
+
|
|
619
|
+
const apps = await runWithRequestContext(
|
|
620
|
+
{
|
|
621
|
+
userEmail: "dev@example.test",
|
|
622
|
+
requestOrigin: "https://beta.dispatch.agent-native.com",
|
|
623
|
+
},
|
|
624
|
+
() => listWorkspaceApps({ includeAgentCards: false }),
|
|
625
|
+
);
|
|
626
|
+
|
|
627
|
+
expect(apps.find((app) => app.id === "customer-ticket-triage")?.url).toBe(
|
|
628
|
+
"https://beta.agent-workspace.builder.io/dispatch/apps/customer-ticket-triage",
|
|
629
|
+
);
|
|
630
|
+
},
|
|
631
|
+
);
|
|
632
|
+
|
|
633
|
+
it("applies APP_BASE_PATH to locally resolved workspace app URLs", async () => {
|
|
634
|
+
stubNoPendingContext();
|
|
635
|
+
stubManifest([
|
|
636
|
+
{
|
|
637
|
+
id: "customer-ticket-triage",
|
|
638
|
+
name: "Customer Ticket Triage",
|
|
639
|
+
path: "/customer-ticket-triage",
|
|
640
|
+
},
|
|
641
|
+
]);
|
|
642
|
+
vi.stubEnv("APP_URL", "https://beta.agent-workspace.builder.io");
|
|
643
|
+
vi.stubEnv("APP_BASE_PATH", "/dispatch");
|
|
644
|
+
vi.stubEnv("VITE_APP_BASE_PATH", "");
|
|
645
|
+
vi.stubEnv(
|
|
646
|
+
"WORKSPACE_GATEWAY_URL",
|
|
647
|
+
"https://beta.agent-workspace.builder.io",
|
|
648
|
+
);
|
|
649
|
+
|
|
650
|
+
const apps = await runWithRequestContext(
|
|
651
|
+
{
|
|
652
|
+
userEmail: "dev@example.test",
|
|
653
|
+
requestOrigin: "https://beta.agent-workspace.builder.io",
|
|
654
|
+
},
|
|
655
|
+
() => listWorkspaceApps({ includeAgentCards: false }),
|
|
656
|
+
);
|
|
657
|
+
|
|
658
|
+
expect(apps.find((app) => app.id === "customer-ticket-triage")?.url).toBe(
|
|
659
|
+
"https://beta.agent-workspace.builder.io/dispatch/apps/customer-ticket-triage",
|
|
660
|
+
);
|
|
661
|
+
});
|
|
662
|
+
|
|
475
663
|
it("lists only ready org-visible apps for an organization principal", async () => {
|
|
476
664
|
stubNoPendingContext();
|
|
477
665
|
stubManifest([
|
|
@@ -581,7 +769,7 @@ describe("listWorkspaceApps", () => {
|
|
|
581
769
|
);
|
|
582
770
|
|
|
583
771
|
expect(apps.find((app) => app.id === "todo")?.url).toBe(
|
|
584
|
-
"https://workspace-preview.vercel.app/todo",
|
|
772
|
+
"https://workspace-preview.vercel.app/apps/todo",
|
|
585
773
|
);
|
|
586
774
|
});
|
|
587
775
|
|
|
@@ -633,7 +821,7 @@ describe("listWorkspaceApps", () => {
|
|
|
633
821
|
}),
|
|
634
822
|
);
|
|
635
823
|
expect(apps.map((app) => app.id)).toEqual(["atlas"]);
|
|
636
|
-
expect(apps[0]?.url).toBe("https://agent-workspace.builder.io/atlas");
|
|
824
|
+
expect(apps[0]?.url).toBe("https://agent-workspace.builder.io/apps/atlas");
|
|
637
825
|
});
|
|
638
826
|
|
|
639
827
|
it("signs the hosted registry request as the member with the deployment secret", async () => {
|
|
@@ -1152,7 +1340,7 @@ describe("listWorkspaceApps", () => {
|
|
|
1152
1340
|
"https://beta.agent-workspace.builder.io/_agent-native/actions/list-workspace-apps?includeAgentCards=false&audience=all",
|
|
1153
1341
|
);
|
|
1154
1342
|
expect(apps[0]?.url).toBe(
|
|
1155
|
-
"https://beta.agent-workspace.builder.io/private-app",
|
|
1343
|
+
"https://beta.agent-workspace.builder.io/apps/private-app",
|
|
1156
1344
|
);
|
|
1157
1345
|
});
|
|
1158
1346
|
|
|
@@ -1229,19 +1417,277 @@ describe("listWorkspaceApps", () => {
|
|
|
1229
1417
|
expect(tokenPayload).not.toHaveProperty("org_id");
|
|
1230
1418
|
});
|
|
1231
1419
|
|
|
1232
|
-
it("
|
|
1420
|
+
it("surfaces malformed gateway config while resolving manifest app URLs", async () => {
|
|
1233
1421
|
stubManifest();
|
|
1234
|
-
vi.stubEnv("WORKSPACE_GATEWAY_URL", "
|
|
1235
|
-
const fetchMock = vi.fn()
|
|
1422
|
+
vi.stubEnv("WORKSPACE_GATEWAY_URL", "http://127.0.0.1:8080");
|
|
1423
|
+
const fetchMock = vi.fn(async () => {
|
|
1424
|
+
vi.stubEnv("WORKSPACE_GATEWAY_URL", "not-a-url");
|
|
1425
|
+
return new Response("not found", { status: 404 });
|
|
1426
|
+
});
|
|
1236
1427
|
vi.stubGlobal("fetch", fetchMock);
|
|
1237
1428
|
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1429
|
+
await expect(
|
|
1430
|
+
runWithRequestContext(
|
|
1431
|
+
{
|
|
1432
|
+
userEmail: "dev@example.test",
|
|
1433
|
+
requestOrigin: "http://127.0.0.1:8080",
|
|
1434
|
+
},
|
|
1435
|
+
() => listWorkspaceApps({ includeAgentCards: false }),
|
|
1436
|
+
),
|
|
1437
|
+
).rejects.toMatchObject({ name: "WorkspaceGatewayConfigurationError" });
|
|
1438
|
+
|
|
1439
|
+
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
1440
|
+
});
|
|
1441
|
+
|
|
1442
|
+
it("rejects unsupported gateway schemes before beta environment rewriting", async () => {
|
|
1443
|
+
vi.stubEnv("A2A_SECRET", "test-a2a-secret");
|
|
1444
|
+
vi.stubEnv(
|
|
1445
|
+
"WORKSPACE_GATEWAY_URL",
|
|
1446
|
+
"ftp://agent-workspace.builder.io/dispatch",
|
|
1241
1447
|
);
|
|
1448
|
+
const fetchMock = vi.fn();
|
|
1449
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
1450
|
+
|
|
1451
|
+
await expect(
|
|
1452
|
+
runWithRequestContext(
|
|
1453
|
+
{
|
|
1454
|
+
userEmail: "dev@example.test",
|
|
1455
|
+
requestOrigin: "https://beta.dispatch.agent-native.com",
|
|
1456
|
+
},
|
|
1457
|
+
() => listWorkspaceApps({ includeAgentCards: false }),
|
|
1458
|
+
),
|
|
1459
|
+
).rejects.toMatchObject({ name: "WorkspaceGatewayConfigurationError" });
|
|
1242
1460
|
|
|
1243
1461
|
expect(fetchMock).not.toHaveBeenCalled();
|
|
1244
|
-
|
|
1462
|
+
});
|
|
1463
|
+
|
|
1464
|
+
it("surfaces malformed gateway config when normalizing absolute app URLs", async () => {
|
|
1465
|
+
const workspaceRoot = fs.mkdtempSync(
|
|
1466
|
+
path.join(os.tmpdir(), "dispatch-workspace-"),
|
|
1467
|
+
);
|
|
1468
|
+
const cwdSpy = vi.spyOn(process, "cwd").mockReturnValue(workspaceRoot);
|
|
1469
|
+
try {
|
|
1470
|
+
vi.stubEnv("WORKSPACE_GATEWAY_URL", "http://127.0.0.1:8080");
|
|
1471
|
+
vi.stubEnv("AGENT_NATIVE_WORKSPACE_APPS_JSON", "");
|
|
1472
|
+
const fetchMock = vi.fn(async () => {
|
|
1473
|
+
vi.stubEnv("WORKSPACE_GATEWAY_URL", "not-a-url");
|
|
1474
|
+
return new Response("not found", { status: 404 });
|
|
1475
|
+
});
|
|
1476
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
1477
|
+
const manifestDirectory = path.join(workspaceRoot, ".agent-native");
|
|
1478
|
+
fs.mkdirSync(manifestDirectory, { recursive: true });
|
|
1479
|
+
fs.writeFileSync(
|
|
1480
|
+
path.join(manifestDirectory, "workspace-apps.json"),
|
|
1481
|
+
JSON.stringify([
|
|
1482
|
+
{
|
|
1483
|
+
id: "customer-ticket-triage",
|
|
1484
|
+
name: "Customer Ticket Triage",
|
|
1485
|
+
path: "/customer-ticket-triage",
|
|
1486
|
+
url: "https://beta.agent-workspace.builder.io/customer-ticket-triage",
|
|
1487
|
+
},
|
|
1488
|
+
]),
|
|
1489
|
+
);
|
|
1490
|
+
|
|
1491
|
+
await expect(
|
|
1492
|
+
runWithRequestContext(
|
|
1493
|
+
{
|
|
1494
|
+
userEmail: "dev@example.test",
|
|
1495
|
+
requestOrigin: "http://127.0.0.1:8080",
|
|
1496
|
+
},
|
|
1497
|
+
() => listWorkspaceApps({ includeAgentCards: false }),
|
|
1498
|
+
),
|
|
1499
|
+
).rejects.toMatchObject({ name: "WorkspaceGatewayConfigurationError" });
|
|
1500
|
+
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
1501
|
+
} finally {
|
|
1502
|
+
cwdSpy.mockRestore();
|
|
1503
|
+
fs.rmSync(workspaceRoot, { recursive: true, force: true });
|
|
1504
|
+
}
|
|
1505
|
+
});
|
|
1506
|
+
|
|
1507
|
+
it("surfaces malformed gateway config for filesystem-discovered apps", async () => {
|
|
1508
|
+
const workspaceRoot = fs.mkdtempSync(
|
|
1509
|
+
path.join(os.tmpdir(), "dispatch-workspace-"),
|
|
1510
|
+
);
|
|
1511
|
+
const cwdSpy = vi.spyOn(process, "cwd").mockReturnValue(workspaceRoot);
|
|
1512
|
+
try {
|
|
1513
|
+
stubNoPendingContext();
|
|
1514
|
+
vi.stubEnv("WORKSPACE_GATEWAY_URL", "http://127.0.0.1:8080");
|
|
1515
|
+
vi.stubEnv("AGENT_NATIVE_WORKSPACE_APPS_JSON", "");
|
|
1516
|
+
const fetchMock = vi.fn(async () => {
|
|
1517
|
+
vi.stubEnv("WORKSPACE_GATEWAY_URL", "not-a-url");
|
|
1518
|
+
return new Response("not found", { status: 404 });
|
|
1519
|
+
});
|
|
1520
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
1521
|
+
fs.writeFileSync(
|
|
1522
|
+
path.join(workspaceRoot, "package.json"),
|
|
1523
|
+
JSON.stringify({
|
|
1524
|
+
name: "test-workspace",
|
|
1525
|
+
"agent-native": { workspaceCore: "workspace-core" },
|
|
1526
|
+
}),
|
|
1527
|
+
);
|
|
1528
|
+
const appDir = path.join(workspaceRoot, "apps", "healthy");
|
|
1529
|
+
fs.mkdirSync(appDir, { recursive: true });
|
|
1530
|
+
fs.writeFileSync(
|
|
1531
|
+
path.join(appDir, "package.json"),
|
|
1532
|
+
JSON.stringify({ name: "healthy", displayName: "Healthy" }),
|
|
1533
|
+
);
|
|
1534
|
+
|
|
1535
|
+
await expect(
|
|
1536
|
+
runWithRequestContext(
|
|
1537
|
+
{
|
|
1538
|
+
userEmail: "dev@example.test",
|
|
1539
|
+
requestOrigin: "http://127.0.0.1:8080",
|
|
1540
|
+
},
|
|
1541
|
+
() => listWorkspaceApps({ includeAgentCards: false }),
|
|
1542
|
+
),
|
|
1543
|
+
).rejects.toMatchObject({ name: "WorkspaceGatewayConfigurationError" });
|
|
1544
|
+
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
1545
|
+
} finally {
|
|
1546
|
+
cwdSpy.mockRestore();
|
|
1547
|
+
fs.rmSync(workspaceRoot, { recursive: true, force: true });
|
|
1548
|
+
}
|
|
1549
|
+
});
|
|
1550
|
+
|
|
1551
|
+
it("skips an invalid absolute app URL without discarding healthy manifest apps", async () => {
|
|
1552
|
+
const workspaceRoot = fs.mkdtempSync(
|
|
1553
|
+
path.join(os.tmpdir(), "dispatch-workspace-"),
|
|
1554
|
+
);
|
|
1555
|
+
const cwdSpy = vi.spyOn(process, "cwd").mockReturnValue(workspaceRoot);
|
|
1556
|
+
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
1557
|
+
try {
|
|
1558
|
+
vi.stubEnv(
|
|
1559
|
+
"WORKSPACE_GATEWAY_URL",
|
|
1560
|
+
"https://beta.agent-workspace.builder.io",
|
|
1561
|
+
);
|
|
1562
|
+
vi.stubEnv("AGENT_NATIVE_WORKSPACE_APPS_JSON", "");
|
|
1563
|
+
vi.stubEnv("A2A_SECRET", "test-a2a-secret");
|
|
1564
|
+
vi.stubGlobal(
|
|
1565
|
+
"fetch",
|
|
1566
|
+
vi.fn(async () => new Response("not found", { status: 404 })),
|
|
1567
|
+
);
|
|
1568
|
+
const manifestDirectory = path.join(workspaceRoot, ".agent-native");
|
|
1569
|
+
fs.mkdirSync(manifestDirectory, { recursive: true });
|
|
1570
|
+
fs.writeFileSync(
|
|
1571
|
+
path.join(manifestDirectory, "workspace-apps.json"),
|
|
1572
|
+
JSON.stringify([
|
|
1573
|
+
{
|
|
1574
|
+
id: "invalid-url",
|
|
1575
|
+
name: "Invalid URL",
|
|
1576
|
+
path: "/invalid-url",
|
|
1577
|
+
url: "not-a-url",
|
|
1578
|
+
},
|
|
1579
|
+
{
|
|
1580
|
+
id: "healthy-app",
|
|
1581
|
+
name: "Healthy App",
|
|
1582
|
+
path: "/healthy-app",
|
|
1583
|
+
url: "https://apps.example.test/healthy-app",
|
|
1584
|
+
},
|
|
1585
|
+
]),
|
|
1586
|
+
);
|
|
1587
|
+
|
|
1588
|
+
const apps = await runWithRequestContext(
|
|
1589
|
+
{
|
|
1590
|
+
userEmail: "dev@example.test",
|
|
1591
|
+
requestOrigin: "https://beta.dispatch.agent-native.com",
|
|
1592
|
+
},
|
|
1593
|
+
() => listWorkspaceApps({ includeAgentCards: false }),
|
|
1594
|
+
);
|
|
1595
|
+
|
|
1596
|
+
expect(apps.map((app) => app.id)).toContain("healthy-app");
|
|
1597
|
+
expect(apps.map((app) => app.id)).not.toContain("invalid-url");
|
|
1598
|
+
expect(warnSpy).toHaveBeenCalledWith(
|
|
1599
|
+
"[dispatch] Could not resolve Workspace app invalid-url; skipping app",
|
|
1600
|
+
expect.any(Error),
|
|
1601
|
+
);
|
|
1602
|
+
} finally {
|
|
1603
|
+
warnSpy.mockRestore();
|
|
1604
|
+
cwdSpy.mockRestore();
|
|
1605
|
+
fs.rmSync(workspaceRoot, { recursive: true, force: true });
|
|
1606
|
+
}
|
|
1607
|
+
});
|
|
1608
|
+
|
|
1609
|
+
it("reports a malformed env manifest and continues to a file manifest", async () => {
|
|
1610
|
+
const workspaceRoot = fs.mkdtempSync(
|
|
1611
|
+
path.join(os.tmpdir(), "dispatch-workspace-"),
|
|
1612
|
+
);
|
|
1613
|
+
const cwdSpy = vi.spyOn(process, "cwd").mockReturnValue(workspaceRoot);
|
|
1614
|
+
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
1615
|
+
try {
|
|
1616
|
+
vi.stubEnv("WORKSPACE_GATEWAY_URL", "");
|
|
1617
|
+
vi.stubEnv("AGENT_NATIVE_WORKSPACE_APPS_JSON", "{");
|
|
1618
|
+
const manifestPath = path.join(
|
|
1619
|
+
workspaceRoot,
|
|
1620
|
+
".agent-native",
|
|
1621
|
+
"workspace-apps.json",
|
|
1622
|
+
);
|
|
1623
|
+
fs.mkdirSync(path.dirname(manifestPath), { recursive: true });
|
|
1624
|
+
fs.writeFileSync(
|
|
1625
|
+
manifestPath,
|
|
1626
|
+
JSON.stringify([
|
|
1627
|
+
{
|
|
1628
|
+
id: "healthy-app",
|
|
1629
|
+
name: "Healthy App",
|
|
1630
|
+
path: "/healthy-app",
|
|
1631
|
+
},
|
|
1632
|
+
]),
|
|
1633
|
+
);
|
|
1634
|
+
|
|
1635
|
+
const apps = await runWithRequestContext(
|
|
1636
|
+
{ userEmail: "dev@example.test" },
|
|
1637
|
+
() => listWorkspaceApps({ includeAgentCards: false }),
|
|
1638
|
+
);
|
|
1639
|
+
|
|
1640
|
+
expect(apps.map((app) => app.id)).toContain("healthy-app");
|
|
1641
|
+
expect(warnSpy).toHaveBeenCalledWith(
|
|
1642
|
+
"[dispatch] Could not read Workspace app manifest from AGENT_NATIVE_WORKSPACE_APPS_JSON; skipping source",
|
|
1643
|
+
);
|
|
1644
|
+
} finally {
|
|
1645
|
+
warnSpy.mockRestore();
|
|
1646
|
+
cwdSpy.mockRestore();
|
|
1647
|
+
fs.rmSync(workspaceRoot, { recursive: true, force: true });
|
|
1648
|
+
}
|
|
1649
|
+
});
|
|
1650
|
+
|
|
1651
|
+
it("skips a malformed manifest file and continues to the next candidate", async () => {
|
|
1652
|
+
const workspaceRoot = fs.mkdtempSync(
|
|
1653
|
+
path.join(os.tmpdir(), "dispatch-workspace-"),
|
|
1654
|
+
);
|
|
1655
|
+
const cwdSpy = vi.spyOn(process, "cwd").mockReturnValue(workspaceRoot);
|
|
1656
|
+
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
1657
|
+
const malformedManifest = path.join(
|
|
1658
|
+
workspaceRoot,
|
|
1659
|
+
".agent-native",
|
|
1660
|
+
"workspace-apps.json",
|
|
1661
|
+
);
|
|
1662
|
+
try {
|
|
1663
|
+
vi.stubEnv("AGENT_NATIVE_WORKSPACE_APPS_JSON", "");
|
|
1664
|
+
fs.mkdirSync(path.dirname(malformedManifest), { recursive: true });
|
|
1665
|
+
fs.writeFileSync(malformedManifest, "{");
|
|
1666
|
+
fs.writeFileSync(
|
|
1667
|
+
path.join(workspaceRoot, "workspace-apps.json"),
|
|
1668
|
+
JSON.stringify([
|
|
1669
|
+
{
|
|
1670
|
+
id: "healthy-app",
|
|
1671
|
+
name: "Healthy App",
|
|
1672
|
+
path: "/healthy-app",
|
|
1673
|
+
},
|
|
1674
|
+
]),
|
|
1675
|
+
);
|
|
1676
|
+
|
|
1677
|
+
const apps = await runWithRequestContext(
|
|
1678
|
+
{ userEmail: "dev@example.test" },
|
|
1679
|
+
() => listWorkspaceApps({ includeAgentCards: false }),
|
|
1680
|
+
);
|
|
1681
|
+
|
|
1682
|
+
expect(apps.map((app) => app.id)).toContain("healthy-app");
|
|
1683
|
+
expect(warnSpy).toHaveBeenCalledWith(
|
|
1684
|
+
`[dispatch] Could not read Workspace app manifest ${malformedManifest}; skipping source`,
|
|
1685
|
+
);
|
|
1686
|
+
} finally {
|
|
1687
|
+
warnSpy.mockRestore();
|
|
1688
|
+
cwdSpy.mockRestore();
|
|
1689
|
+
fs.rmSync(workspaceRoot, { recursive: true, force: true });
|
|
1690
|
+
}
|
|
1245
1691
|
});
|
|
1246
1692
|
|
|
1247
1693
|
it("keeps healthy filesystem apps discoverable when config or routes cannot load", async () => {
|
|
@@ -1262,12 +1708,15 @@ describe("listWorkspaceApps", () => {
|
|
|
1262
1708
|
"healthy",
|
|
1263
1709
|
"config-broken",
|
|
1264
1710
|
"routes-broken",
|
|
1711
|
+
"package-broken",
|
|
1265
1712
|
]) {
|
|
1266
1713
|
const appDir = path.join(workspaceRoot, "apps", app);
|
|
1267
1714
|
fs.mkdirSync(appDir, { recursive: true });
|
|
1268
1715
|
fs.writeFileSync(
|
|
1269
1716
|
path.join(appDir, "package.json"),
|
|
1270
|
-
|
|
1717
|
+
app === "package-broken"
|
|
1718
|
+
? "{"
|
|
1719
|
+
: JSON.stringify({ name: app, displayName: app }),
|
|
1271
1720
|
);
|
|
1272
1721
|
if (app === "routes-broken") {
|
|
1273
1722
|
const brokenRoutes = path.join(appDir, "app", "routes");
|
|
@@ -1299,6 +1748,48 @@ describe("listWorkspaceApps", () => {
|
|
|
1299
1748
|
}
|
|
1300
1749
|
});
|
|
1301
1750
|
|
|
1751
|
+
it("continues workspace root discovery past malformed ancestor package metadata", async () => {
|
|
1752
|
+
const workspaceRoot = fs.mkdtempSync(
|
|
1753
|
+
path.join(os.tmpdir(), "dispatch-workspace-"),
|
|
1754
|
+
);
|
|
1755
|
+
const cwd = path.join(workspaceRoot, "worker");
|
|
1756
|
+
fs.mkdirSync(cwd);
|
|
1757
|
+
const cwdSpy = vi.spyOn(process, "cwd").mockReturnValue(cwd);
|
|
1758
|
+
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
1759
|
+
try {
|
|
1760
|
+
stubNoPendingContext();
|
|
1761
|
+
fs.writeFileSync(
|
|
1762
|
+
path.join(workspaceRoot, "package.json"),
|
|
1763
|
+
JSON.stringify({
|
|
1764
|
+
name: "test-workspace",
|
|
1765
|
+
"agent-native": { workspaceCore: "workspace-core" },
|
|
1766
|
+
}),
|
|
1767
|
+
);
|
|
1768
|
+
fs.writeFileSync(path.join(cwd, "package.json"), "{");
|
|
1769
|
+
const appDir = path.join(workspaceRoot, "apps", "healthy");
|
|
1770
|
+
fs.mkdirSync(appDir, { recursive: true });
|
|
1771
|
+
fs.writeFileSync(
|
|
1772
|
+
path.join(appDir, "package.json"),
|
|
1773
|
+
JSON.stringify({ name: "healthy", displayName: "Healthy" }),
|
|
1774
|
+
);
|
|
1775
|
+
|
|
1776
|
+
const apps = await runWithRequestContext(
|
|
1777
|
+
{ userEmail: "dev@example.test" },
|
|
1778
|
+
() => listWorkspaceApps({ includeAgentCards: false }),
|
|
1779
|
+
);
|
|
1780
|
+
|
|
1781
|
+
expect(apps.map((app) => app.id)).toContain("healthy");
|
|
1782
|
+
expect(warnSpy).toHaveBeenCalledWith(
|
|
1783
|
+
`[dispatch] Could not read workspace package metadata in ${cwd}; continuing parent search`,
|
|
1784
|
+
expect.any(Error),
|
|
1785
|
+
);
|
|
1786
|
+
} finally {
|
|
1787
|
+
warnSpy.mockRestore();
|
|
1788
|
+
cwdSpy.mockRestore();
|
|
1789
|
+
fs.rmSync(workspaceRoot, { recursive: true, force: true });
|
|
1790
|
+
}
|
|
1791
|
+
});
|
|
1792
|
+
|
|
1302
1793
|
it("keeps legacy apps organization-visible when the org default cannot be read", async () => {
|
|
1303
1794
|
stubManifest([
|
|
1304
1795
|
{ id: "dispatch", name: "Dispatch", path: "/dispatch" },
|