@agent-native/dispatch 0.40.37 → 0.40.39
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 +24 -14
- package/dist/components/layout/Layout.d.ts +0 -1
- package/dist/components/layout/Layout.d.ts.map +1 -1
- package/dist/components/layout/Layout.js +57 -101
- package/dist/components/layout/Layout.js.map +1 -1
- package/dist/server/lib/app-creation-store.d.ts.map +1 -1
- package/dist/server/lib/app-creation-store.js +9 -5
- 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/package.json +3 -3
- package/src/components/layout/Layout.spec.tsx +348 -26
- package/src/components/layout/Layout.tsx +119 -193
- package/src/server/lib/app-creation-store.spec.ts +26 -5
- package/src/server/lib/app-creation-store.ts +10 -6
- package/src/server/lib/community-apps.ts +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"community-apps.js","sourceRoot":"","sources":["../../../src/server/lib/community-apps.ts"],"names":[],"mappings":"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","sourcesContent":["import { fail } from \"@agent-native/core/action\";\nimport { getUserLabEnabled } from \"@agent-native/core/labs/server\";\nimport {\n getWorkspaceAppIdValidationError,\n normalizeWorkspaceAppId,\n} from \"@agent-native/core/shared\";\nimport {\n BUILTIN_AGENT_CATALOG_IDS,\n HIDDEN_FIRST_PARTY_AGENT_IDS,\n} from \"@agent-native/core/shared/first-party-agents\";\n\nimport { COMMUNITY_APPS_LAB } from \"../../shared/labs.js\";\nimport type {\n StartWorkspaceAppCreationResult,\n WorkspaceAppSummary,\n} from \"./app-creation-store.js\";\nimport {\n getAppCreationSettings,\n getWorkspaceInfo,\n isLocalAppCreationRuntime,\n listWorkspaceApps,\n startWorkspaceAppCreation,\n} from \"./app-creation-store.js\";\nimport {\n loadPinnedCommunitySourceAttachments,\n type PinnedCommunitySource,\n} from \"./community-source.js\";\nimport { CURATED_WORKSPACE_TEMPLATES } from \"./curated-workspace-templates.js\";\nimport {\n currentOrgId,\n currentOwnerEmail,\n recordAudit,\n} from \"./dispatch-store.js\";\n\nexport interface CuratedCommunityAppSource extends PinnedCommunitySource {\n id: string;\n name: string;\n description: string;\n appId: string;\n license: string;\n provenance: string;\n}\n\nexport interface CuratedCommunityApp extends CuratedCommunityAppSource {\n installed: boolean;\n pending: boolean;\n installable: boolean;\n installUnavailableReason?:\n | \"workspace-required\"\n | \"app-id-conflict\"\n | \"builder-install-unverified\";\n}\n\nexport const CURATED_COMMUNITY_APP_SOURCES: readonly CuratedCommunityAppSource[] =\n [\n {\n id: \"community-qr\",\n name: \"QR\",\n description: \"Generate public PNG or SVG QR images from links or text.\",\n appId: \"community-qr\",\n repo: \"BuilderIO/agent-native\",\n ref: \"main\",\n license: \"MIT\",\n provenance:\n \"Reviewed Agent-Native Framework template, sanitized for independent installation.\",\n templatePath: \"templates/community/qr\",\n files: [\n {\n path: \"AGENTS.md\",\n blobSha: \"7dc9e18bc426dfd21526f91225cc615759450816\",\n },\n {\n path: \"LICENSE\",\n blobSha: \"69c7182efb3eb1a8b7f6fd5989f3e4fd737d0645\",\n },\n {\n path: \"README.md\",\n blobSha: \"e05e72bb9213732f56ccbc96f7dcb1c3a064cb19\",\n },\n {\n path: \"actions/generate-qr-code.spec.ts\",\n blobSha: \"3459217b7252c6ee0076c68cfdb83001cc74f29e\",\n },\n {\n path: \"actions/generate-qr-code.ts\",\n blobSha: \"98d30e218bfed796d5e402d5ce19a8626d8da0cf\",\n },\n {\n path: \"actions/run.ts\",\n blobSha: \"b46ab15b667c34741364ba08f010f7aca9d0f02e\",\n },\n {\n path: \"agent-native.json\",\n blobSha: \"accc32476792b84557f69e2b48b317c27b5546dc\",\n },\n {\n path: \"package.json\",\n blobSha: \"dbf733f4e0b751a91398233040e275ec32955a07\",\n },\n {\n path: \"server/db/index.ts\",\n blobSha: \"17007b456530f75a72e7209bdfe9f4bd4c9224e0\",\n },\n {\n path: \"server/db/schema.ts\",\n blobSha: \"2e4cd01c5f46463e78bc7beb6e954f3e526c0b57\",\n },\n {\n path: \"server/plugins/db.ts\",\n blobSha: \"78a5b431c1ad1ea14f593123c090f5376a36df10\",\n },\n {\n path: \"tsconfig.json\",\n blobSha: \"5267473a32dcab8e5dc2c9d9a720afed7f40174c\",\n },\n ],\n },\n ];\n\nconst communitySourceById = new Map(\n CURATED_COMMUNITY_APP_SOURCES.map((source) => [source.id, source]),\n);\n\nfunction requireCommunitySource(sourceId: string): CuratedCommunityAppSource {\n const source = communitySourceById.get(sourceId.trim());\n if (!source) {\n fail(\"That community app is not in Dispatch's reviewed install catalog.\", {\n errorCode: \"unknown_community_app_source\",\n statusCode: 404,\n });\n }\n return source;\n}\n\nfunction communitySourceFailureKind(\n error: unknown,\n): \"unavailable\" | \"integrity\" {\n if (!(error instanceof Error) || !(\"failureKind\" in error)) {\n return \"unavailable\";\n }\n return error.failureKind === \"integrity\" ? \"integrity\" : \"unavailable\";\n}\n\nfunction requireNonShadowingCommunityAppId(appId: string): void {\n const error = getWorkspaceAppIdValidationError(appId);\n if (error) {\n fail(error, { errorCode: \"invalid_community_app_id\", statusCode: 400 });\n }\n if (\n CURATED_WORKSPACE_TEMPLATES.some((template) => template.id === appId) ||\n BUILTIN_AGENT_CATALOG_IDS.includes(appId) ||\n HIDDEN_FIRST_PARTY_AGENT_IDS.has(appId)\n ) {\n fail(\"Community apps cannot use a first-party template app id.\", {\n errorCode: \"community_app_id_shadows_first_party\",\n statusCode: 409,\n });\n }\n}\n\nasync function requireCommunityAppsLab(): Promise<void> {\n const userEmail = currentOwnerEmail().trim();\n const orgId = currentOrgId();\n if (!userEmail || !orgId) {\n fail(\"Sign in to an organization to browse community apps.\", {\n errorCode: \"community_apps_identity_required\",\n statusCode: 403,\n });\n }\n const enabled = await getUserLabEnabled(userEmail, COMMUNITY_APPS_LAB, {\n orgId,\n });\n if (!enabled) {\n fail(\"Community app catalog and installs are disabled for this account.\", {\n errorCode: \"community_apps_lab_disabled\",\n statusCode: 403,\n });\n }\n}\n\nasync function hasAssociatedWorkspace(\n localRuntime = isLocalAppCreationRuntime(),\n): Promise<boolean> {\n if (localRuntime) return Boolean(getWorkspaceInfo().rootPath);\n return Boolean((await getAppCreationSettings()).builderProjectId);\n}\n\nexport async function listCuratedCommunityApps(): Promise<\n CuratedCommunityApp[]\n> {\n await requireCommunityAppsLab();\n const localRuntime = isLocalAppCreationRuntime();\n const [apps, workspaceAvailable] = await Promise.all([\n listWorkspaceApps({ includeAgentCards: false, includeArchived: true }),\n hasAssociatedWorkspace(localRuntime),\n ]);\n return CURATED_COMMUNITY_APP_SOURCES.map((source) => {\n const appWithId = apps.find((app) => app.id === source.appId);\n const installedApp =\n appWithId?.communitySourceId === source.id ? appWithId : null;\n const idConflict = Boolean(appWithId && !installedApp);\n const pending = installedApp?.status === \"pending\";\n const installUnavailableReason = appWithId\n ? idConflict\n ? \"app-id-conflict\"\n : undefined\n : !workspaceAvailable\n ? \"workspace-required\"\n : !localRuntime\n ? \"builder-install-unverified\"\n : undefined;\n return {\n ...source,\n installed: Boolean(installedApp && !pending),\n pending,\n installable: localRuntime && workspaceAvailable && !appWithId,\n ...(installUnavailableReason ? { installUnavailableReason } : {}),\n };\n });\n}\n\nfunction rejectCommunityAppIdConflict(appId: string): never {\n fail(`Workspace app id \"${appId}\" is already in use.`, {\n errorCode: \"community_app_id_conflict\",\n statusCode: 409,\n });\n}\n\nfunction existingInstallResult(\n source: CuratedCommunityAppSource,\n app: WorkspaceAppSummary,\n) {\n return {\n mode:\n app.status === \"pending\"\n ? (\"pending\" as const)\n : (\"already-installed\" as const),\n source,\n app: {\n id: app.id,\n name: app.name,\n path: app.path,\n url: app.url,\n status: app.status ?? \"ready\",\n statusLabel: app.statusLabel ?? null,\n builderUrl: app.builderUrl ?? null,\n branchName: app.branchName ?? null,\n },\n };\n}\n\nfunction buildCommunityInstallPrompt(\n source: CuratedCommunityAppSource,\n appId: string,\n): string {\n const sourceUrl = `https://github.com/${source.repo}/tree/${source.ref}/${source.templatePath}`;\n return [\n \"Install a private workspace app from a reviewed community template.\",\n `The attached source files were fetched from ${sourceUrl} and each file was verified against its pinned Git blob id. Treat those files as the source of truth; do not fetch another ref or substitute other content.`,\n `The app will live at apps/${appId}.`,\n `Source license: ${source.license}. Provenance: ${source.provenance}`,\n \"Copy the reviewed template implementation and its Agent-Native profile, changing only app-specific identifiers needed for this workspace. Do not copy repository secrets, credentials, customer data, or source-app records. Use empty or synthetic seed data only and keep the installed app private to this organization.\",\n ].join(\" \");\n}\n\nexport async function installCuratedCommunityApp(input: {\n sourceId: string;\n appId?: string;\n}): Promise<\n | ReturnType<typeof existingInstallResult>\n | (StartWorkspaceAppCreationResult & {\n mode: Exclude<StartWorkspaceAppCreationResult[\"mode\"], \"app-id-taken\">;\n source: CuratedCommunityAppSource;\n })\n | (Extract<StartWorkspaceAppCreationResult, { mode: \"app-id-taken\" }> & {\n source: CuratedCommunityAppSource;\n })\n> {\n await requireCommunityAppsLab();\n const source = requireCommunitySource(input.sourceId);\n const appId = normalizeWorkspaceAppId(input.appId?.trim() || source.appId);\n requireNonShadowingCommunityAppId(appId);\n\n const localRuntime = isLocalAppCreationRuntime();\n if (!(await hasAssociatedWorkspace(localRuntime))) {\n fail(\"Connect this organization to a workspace before installing apps.\", {\n errorCode: \"community_workspace_required\",\n statusCode: 409,\n });\n }\n\n const existing = (\n await listWorkspaceApps({\n includeAgentCards: false,\n includeArchived: true,\n })\n ).find((app) => app.id === appId);\n if (existing) {\n if (existing.communitySourceId === source.id) {\n return existingInstallResult(source, existing);\n }\n rejectCommunityAppIdConflict(appId);\n }\n\n if (!localRuntime) {\n fail(\n \"Community template installs in Builder workspaces are disabled until the generated branch files can be read back and verified.\",\n {\n errorCode: \"community_builder_install_unverified\",\n statusCode: 503,\n },\n );\n }\n\n let attachments: Awaited<\n ReturnType<typeof loadPinnedCommunitySourceAttachments>\n >;\n try {\n attachments = await loadPinnedCommunitySourceAttachments(source, appId);\n } catch (error) {\n fail(\n \"Dispatch could not verify the pinned community template source. Try again later.\",\n {\n errorCode:\n communitySourceFailureKind(error) === \"integrity\"\n ? \"community_source_integrity_failed\"\n : \"community_source_unavailable\",\n statusCode: 503,\n },\n );\n }\n\n const result = await startWorkspaceAppCreation({\n prompt: buildCommunityInstallPrompt(source, appId),\n appId,\n description: source.description,\n template: source.id,\n communitySourceId: source.id,\n attachments,\n });\n\n if (result.mode === \"app-id-taken\") {\n const raced = (\n await listWorkspaceApps({\n includeAgentCards: false,\n includeArchived: true,\n })\n ).find((app) => app.id === appId);\n if (raced?.communitySourceId === source.id) {\n return existingInstallResult(source, raced);\n }\n if (raced) rejectCommunityAppIdConflict(appId);\n return { ...result, source };\n }\n\n await recordAudit({\n action: \"workspace-app.community-install-requested\",\n targetType: \"workspace-app\",\n targetId: appId,\n summary: `Requested ${source.name} community app installation`,\n metadata: {\n sourceId: source.id,\n repo: source.repo,\n ref: source.ref,\n blobShas: source.files.map((file) => file.blobSha),\n license: source.license,\n },\n });\n return { ...result, source } as typeof result & {\n source: CuratedCommunityAppSource;\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"community-apps.js","sourceRoot":"","sources":["../../../src/server/lib/community-apps.ts"],"names":[],"mappings":"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","sourcesContent":["import { fail } from \"@agent-native/core/action\";\nimport { getUserLabEnabled } from \"@agent-native/core/labs/server\";\nimport {\n getWorkspaceAppIdValidationError,\n normalizeWorkspaceAppId,\n} from \"@agent-native/core/shared\";\nimport {\n BUILTIN_AGENT_CATALOG_IDS,\n HIDDEN_FIRST_PARTY_AGENT_IDS,\n} from \"@agent-native/core/shared/first-party-agents\";\n\nimport { COMMUNITY_APPS_LAB } from \"../../shared/labs.js\";\nimport type {\n StartWorkspaceAppCreationResult,\n WorkspaceAppSummary,\n} from \"./app-creation-store.js\";\nimport {\n getAppCreationSettings,\n getWorkspaceInfo,\n isLocalAppCreationRuntime,\n listWorkspaceApps,\n startWorkspaceAppCreation,\n} from \"./app-creation-store.js\";\nimport {\n loadPinnedCommunitySourceAttachments,\n type PinnedCommunitySource,\n} from \"./community-source.js\";\nimport { CURATED_WORKSPACE_TEMPLATES } from \"./curated-workspace-templates.js\";\nimport {\n currentOrgId,\n currentOwnerEmail,\n recordAudit,\n} from \"./dispatch-store.js\";\n\nexport interface CuratedCommunityAppSource extends PinnedCommunitySource {\n id: string;\n name: string;\n description: string;\n appId: string;\n license: string;\n provenance: string;\n}\n\nexport interface CuratedCommunityApp extends CuratedCommunityAppSource {\n installed: boolean;\n pending: boolean;\n installable: boolean;\n installUnavailableReason?:\n | \"workspace-required\"\n | \"app-id-conflict\"\n | \"builder-install-unverified\";\n}\n\nexport const CURATED_COMMUNITY_APP_SOURCES: readonly CuratedCommunityAppSource[] =\n [\n {\n id: \"community-qr\",\n name: \"QR\",\n description: \"Generate public PNG or SVG QR images from links or text.\",\n appId: \"community-qr\",\n repo: \"BuilderIO/agent-native\",\n ref: \"main\",\n license: \"MIT\",\n provenance:\n \"Reviewed Agent-Native Framework template, sanitized for independent installation.\",\n templatePath: \"templates/community/qr\",\n files: [\n {\n path: \"AGENTS.md\",\n blobSha: \"7dc9e18bc426dfd21526f91225cc615759450816\",\n },\n {\n path: \"LICENSE\",\n blobSha: \"69c7182efb3eb1a8b7f6fd5989f3e4fd737d0645\",\n },\n {\n path: \"README.md\",\n blobSha: \"e05e72bb9213732f56ccbc96f7dcb1c3a064cb19\",\n },\n {\n path: \"actions/generate-qr-code.spec.ts\",\n blobSha: \"3459217b7252c6ee0076c68cfdb83001cc74f29e\",\n },\n {\n path: \"actions/generate-qr-code.ts\",\n blobSha: \"98d30e218bfed796d5e402d5ce19a8626d8da0cf\",\n },\n {\n path: \"actions/run.ts\",\n blobSha: \"b46ab15b667c34741364ba08f010f7aca9d0f02e\",\n },\n {\n path: \"agent-native.json\",\n blobSha: \"accc32476792b84557f69e2b48b317c27b5546dc\",\n },\n {\n path: \"package.json\",\n blobSha: \"5815e6665e06176df9954183e89b234acf5c5c37\",\n },\n {\n path: \"server/db/index.ts\",\n blobSha: \"17007b456530f75a72e7209bdfe9f4bd4c9224e0\",\n },\n {\n path: \"server/db/schema.ts\",\n blobSha: \"2e4cd01c5f46463e78bc7beb6e954f3e526c0b57\",\n },\n {\n path: \"server/plugins/db.ts\",\n blobSha: \"78a5b431c1ad1ea14f593123c090f5376a36df10\",\n },\n {\n path: \"tsconfig.json\",\n blobSha: \"5267473a32dcab8e5dc2c9d9a720afed7f40174c\",\n },\n ],\n },\n ];\n\nconst communitySourceById = new Map(\n CURATED_COMMUNITY_APP_SOURCES.map((source) => [source.id, source]),\n);\n\nfunction requireCommunitySource(sourceId: string): CuratedCommunityAppSource {\n const source = communitySourceById.get(sourceId.trim());\n if (!source) {\n fail(\"That community app is not in Dispatch's reviewed install catalog.\", {\n errorCode: \"unknown_community_app_source\",\n statusCode: 404,\n });\n }\n return source;\n}\n\nfunction communitySourceFailureKind(\n error: unknown,\n): \"unavailable\" | \"integrity\" {\n if (!(error instanceof Error) || !(\"failureKind\" in error)) {\n return \"unavailable\";\n }\n return error.failureKind === \"integrity\" ? \"integrity\" : \"unavailable\";\n}\n\nfunction requireNonShadowingCommunityAppId(appId: string): void {\n const error = getWorkspaceAppIdValidationError(appId);\n if (error) {\n fail(error, { errorCode: \"invalid_community_app_id\", statusCode: 400 });\n }\n if (\n CURATED_WORKSPACE_TEMPLATES.some((template) => template.id === appId) ||\n BUILTIN_AGENT_CATALOG_IDS.includes(appId) ||\n HIDDEN_FIRST_PARTY_AGENT_IDS.has(appId)\n ) {\n fail(\"Community apps cannot use a first-party template app id.\", {\n errorCode: \"community_app_id_shadows_first_party\",\n statusCode: 409,\n });\n }\n}\n\nasync function requireCommunityAppsLab(): Promise<void> {\n const userEmail = currentOwnerEmail().trim();\n const orgId = currentOrgId();\n if (!userEmail || !orgId) {\n fail(\"Sign in to an organization to browse community apps.\", {\n errorCode: \"community_apps_identity_required\",\n statusCode: 403,\n });\n }\n const enabled = await getUserLabEnabled(userEmail, COMMUNITY_APPS_LAB, {\n orgId,\n });\n if (!enabled) {\n fail(\"Community app catalog and installs are disabled for this account.\", {\n errorCode: \"community_apps_lab_disabled\",\n statusCode: 403,\n });\n }\n}\n\nasync function hasAssociatedWorkspace(\n localRuntime = isLocalAppCreationRuntime(),\n): Promise<boolean> {\n if (localRuntime) return Boolean(getWorkspaceInfo().rootPath);\n return Boolean((await getAppCreationSettings()).builderProjectId);\n}\n\nexport async function listCuratedCommunityApps(): Promise<\n CuratedCommunityApp[]\n> {\n await requireCommunityAppsLab();\n const localRuntime = isLocalAppCreationRuntime();\n const [apps, workspaceAvailable] = await Promise.all([\n listWorkspaceApps({ includeAgentCards: false, includeArchived: true }),\n hasAssociatedWorkspace(localRuntime),\n ]);\n return CURATED_COMMUNITY_APP_SOURCES.map((source) => {\n const appWithId = apps.find((app) => app.id === source.appId);\n const installedApp =\n appWithId?.communitySourceId === source.id ? appWithId : null;\n const idConflict = Boolean(appWithId && !installedApp);\n const pending = installedApp?.status === \"pending\";\n const installUnavailableReason = appWithId\n ? idConflict\n ? \"app-id-conflict\"\n : undefined\n : !workspaceAvailable\n ? \"workspace-required\"\n : !localRuntime\n ? \"builder-install-unverified\"\n : undefined;\n return {\n ...source,\n installed: Boolean(installedApp && !pending),\n pending,\n installable: localRuntime && workspaceAvailable && !appWithId,\n ...(installUnavailableReason ? { installUnavailableReason } : {}),\n };\n });\n}\n\nfunction rejectCommunityAppIdConflict(appId: string): never {\n fail(`Workspace app id \"${appId}\" is already in use.`, {\n errorCode: \"community_app_id_conflict\",\n statusCode: 409,\n });\n}\n\nfunction existingInstallResult(\n source: CuratedCommunityAppSource,\n app: WorkspaceAppSummary,\n) {\n return {\n mode:\n app.status === \"pending\"\n ? (\"pending\" as const)\n : (\"already-installed\" as const),\n source,\n app: {\n id: app.id,\n name: app.name,\n path: app.path,\n url: app.url,\n status: app.status ?? \"ready\",\n statusLabel: app.statusLabel ?? null,\n builderUrl: app.builderUrl ?? null,\n branchName: app.branchName ?? null,\n },\n };\n}\n\nfunction buildCommunityInstallPrompt(\n source: CuratedCommunityAppSource,\n appId: string,\n): string {\n const sourceUrl = `https://github.com/${source.repo}/tree/${source.ref}/${source.templatePath}`;\n return [\n \"Install a private workspace app from a reviewed community template.\",\n `The attached source files were fetched from ${sourceUrl} and each file was verified against its pinned Git blob id. Treat those files as the source of truth; do not fetch another ref or substitute other content.`,\n `The app will live at apps/${appId}.`,\n `Source license: ${source.license}. Provenance: ${source.provenance}`,\n \"Copy the reviewed template implementation and its Agent-Native profile, changing only app-specific identifiers needed for this workspace. Do not copy repository secrets, credentials, customer data, or source-app records. Use empty or synthetic seed data only and keep the installed app private to this organization.\",\n ].join(\" \");\n}\n\nexport async function installCuratedCommunityApp(input: {\n sourceId: string;\n appId?: string;\n}): Promise<\n | ReturnType<typeof existingInstallResult>\n | (StartWorkspaceAppCreationResult & {\n mode: Exclude<StartWorkspaceAppCreationResult[\"mode\"], \"app-id-taken\">;\n source: CuratedCommunityAppSource;\n })\n | (Extract<StartWorkspaceAppCreationResult, { mode: \"app-id-taken\" }> & {\n source: CuratedCommunityAppSource;\n })\n> {\n await requireCommunityAppsLab();\n const source = requireCommunitySource(input.sourceId);\n const appId = normalizeWorkspaceAppId(input.appId?.trim() || source.appId);\n requireNonShadowingCommunityAppId(appId);\n\n const localRuntime = isLocalAppCreationRuntime();\n if (!(await hasAssociatedWorkspace(localRuntime))) {\n fail(\"Connect this organization to a workspace before installing apps.\", {\n errorCode: \"community_workspace_required\",\n statusCode: 409,\n });\n }\n\n const existing = (\n await listWorkspaceApps({\n includeAgentCards: false,\n includeArchived: true,\n })\n ).find((app) => app.id === appId);\n if (existing) {\n if (existing.communitySourceId === source.id) {\n return existingInstallResult(source, existing);\n }\n rejectCommunityAppIdConflict(appId);\n }\n\n if (!localRuntime) {\n fail(\n \"Community template installs in Builder workspaces are disabled until the generated branch files can be read back and verified.\",\n {\n errorCode: \"community_builder_install_unverified\",\n statusCode: 503,\n },\n );\n }\n\n let attachments: Awaited<\n ReturnType<typeof loadPinnedCommunitySourceAttachments>\n >;\n try {\n attachments = await loadPinnedCommunitySourceAttachments(source, appId);\n } catch (error) {\n fail(\n \"Dispatch could not verify the pinned community template source. Try again later.\",\n {\n errorCode:\n communitySourceFailureKind(error) === \"integrity\"\n ? \"community_source_integrity_failed\"\n : \"community_source_unavailable\",\n statusCode: 503,\n },\n );\n }\n\n const result = await startWorkspaceAppCreation({\n prompt: buildCommunityInstallPrompt(source, appId),\n appId,\n description: source.description,\n template: source.id,\n communitySourceId: source.id,\n attachments,\n });\n\n if (result.mode === \"app-id-taken\") {\n const raced = (\n await listWorkspaceApps({\n includeAgentCards: false,\n includeArchived: true,\n })\n ).find((app) => app.id === appId);\n if (raced?.communitySourceId === source.id) {\n return existingInstallResult(source, raced);\n }\n if (raced) rejectCommunityAppIdConflict(appId);\n return { ...result, source };\n }\n\n await recordAudit({\n action: \"workspace-app.community-install-requested\",\n targetType: \"workspace-app\",\n targetId: appId,\n summary: `Requested ${source.name} community app installation`,\n metadata: {\n sourceId: source.id,\n repo: source.repo,\n ref: source.ref,\n blobShas: source.files.map((file) => file.blobSha),\n license: source.license,\n },\n });\n return { ...result, source } as typeof result & {\n source: CuratedCommunityAppSource;\n };\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/dispatch",
|
|
3
|
-
"version": "0.40.
|
|
3
|
+
"version": "0.40.39",
|
|
4
4
|
"description": "Dispatch — workspace control plane for agent-native apps. Vault, integrations, destinations, scheduled jobs, and cross-app delegation, shipped as a single drop-in package.",
|
|
5
5
|
"homepage": "https://github.com/BuilderIO/agent-native#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
"tailwind-merge": "^3.5.0",
|
|
91
91
|
"vaul": "^1.1.2",
|
|
92
92
|
"zod": "^4.3.6",
|
|
93
|
-
"@agent-native/toolkit": "0.
|
|
93
|
+
"@agent-native/toolkit": "0.219.0"
|
|
94
94
|
},
|
|
95
95
|
"devDependencies": {
|
|
96
96
|
"@react-router/dev": "^8.1.0",
|
|
@@ -104,7 +104,7 @@
|
|
|
104
104
|
"typescript-7": "npm:typescript@^7.0.2",
|
|
105
105
|
"vite": "8.1.0",
|
|
106
106
|
"vitest": "^4.1.5",
|
|
107
|
-
"@agent-native/core": "0.
|
|
107
|
+
"@agent-native/core": "0.219.0"
|
|
108
108
|
},
|
|
109
109
|
"peerDependencies": {
|
|
110
110
|
"@agent-native/core": ">=0.209.1",
|
|
@@ -4,6 +4,7 @@ import { isSettingsPathname } from "@agent-native/toolkit/app/settings";
|
|
|
4
4
|
import React, { act } from "react";
|
|
5
5
|
import { createRoot, type Root } from "react-dom/client";
|
|
6
6
|
import { MemoryRouter, useLocation, useNavigate } from "react-router";
|
|
7
|
+
import { toast } from "sonner";
|
|
7
8
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
8
9
|
|
|
9
10
|
import { CHAT_FIRST_PANE_STATE_KEY } from "../../shared/chat-first-pane";
|
|
@@ -13,7 +14,6 @@ import {
|
|
|
13
14
|
buildChatFirstEmbedSessionInput,
|
|
14
15
|
CHAT_FIRST_SURFACE_PANEL_TOGGLE_CLASS_NAME,
|
|
15
16
|
dispatchNavLinkTarget,
|
|
16
|
-
formatThreadAge,
|
|
17
17
|
isElectronEmbeddedSearch,
|
|
18
18
|
isRedesignedSettingsPath,
|
|
19
19
|
Layout,
|
|
@@ -26,6 +26,16 @@ import {
|
|
|
26
26
|
const clientState = vi.hoisted(() => ({
|
|
27
27
|
createThread: vi.fn<() => Promise<string | null>>(),
|
|
28
28
|
switchThread: vi.fn(),
|
|
29
|
+
renameThread: vi.fn().mockResolvedValue(true),
|
|
30
|
+
pinThread: vi.fn().mockResolvedValue(true),
|
|
31
|
+
archiveThread: vi.fn().mockResolvedValue(true),
|
|
32
|
+
isNewThread: () => false,
|
|
33
|
+
mineThreads: null as Array<Record<string, unknown>> | null,
|
|
34
|
+
sharedThreads: [] as Array<Record<string, unknown>>,
|
|
35
|
+
sharedHasMore: false,
|
|
36
|
+
loadMore: vi.fn().mockResolvedValue(undefined),
|
|
37
|
+
listQueries: vi.fn(),
|
|
38
|
+
lifecycleOptions: vi.fn(),
|
|
29
39
|
threads: [] as Array<Record<string, unknown>>,
|
|
30
40
|
workspaceApps: [] as Array<Record<string, unknown>>,
|
|
31
41
|
workspaceAppsError: null as Error | null,
|
|
@@ -70,15 +80,40 @@ vi.mock("@agent-native/core/client/agent-chat", async (importOriginal) => ({
|
|
|
70
80
|
onEffortChange: vi.fn(),
|
|
71
81
|
refreshEngines: vi.fn(),
|
|
72
82
|
}),
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
83
|
+
useChatThreadList: (options: {
|
|
84
|
+
ownership: string;
|
|
85
|
+
includeExternal?: boolean;
|
|
86
|
+
}) => {
|
|
87
|
+
clientState.listQueries(options);
|
|
88
|
+
return {
|
|
89
|
+
threads:
|
|
90
|
+
options.ownership === "shared"
|
|
91
|
+
? clientState.sharedThreads
|
|
92
|
+
: (clientState.mineThreads ?? clientState.threads),
|
|
93
|
+
loading: false,
|
|
94
|
+
loadingMore: false,
|
|
95
|
+
error: null,
|
|
96
|
+
hasMore: options.ownership === "shared" && clientState.sharedHasMore,
|
|
97
|
+
loadMore: clientState.loadMore,
|
|
98
|
+
refresh: async () => {},
|
|
99
|
+
refreshWithResult: async () => "success",
|
|
100
|
+
};
|
|
101
|
+
},
|
|
102
|
+
useChatThreads: (...args: unknown[]) => {
|
|
103
|
+
clientState.lifecycleOptions(args[3]);
|
|
104
|
+
return {
|
|
105
|
+
threads: clientState.threads,
|
|
106
|
+
activeThreadId: "active-thread",
|
|
107
|
+
isLoading: false,
|
|
108
|
+
createThread: clientState.createThread,
|
|
109
|
+
switchThread: clientState.switchThread,
|
|
110
|
+
renameThread: clientState.renameThread,
|
|
111
|
+
pinThread: clientState.pinThread,
|
|
112
|
+
archiveThread: clientState.archiveThread,
|
|
113
|
+
isNewThread: clientState.isNewThread,
|
|
114
|
+
refreshThreads: vi.fn(),
|
|
115
|
+
};
|
|
116
|
+
},
|
|
82
117
|
useChatModels: () => ({
|
|
83
118
|
availableModels: [],
|
|
84
119
|
defaultModel: "auto",
|
|
@@ -89,6 +124,14 @@ vi.mock("@agent-native/core/client/agent-chat", async (importOriginal) => ({
|
|
|
89
124
|
}),
|
|
90
125
|
}));
|
|
91
126
|
|
|
127
|
+
vi.mock(
|
|
128
|
+
"@agent-native/toolkit/app/chat/ownership-history",
|
|
129
|
+
async () =>
|
|
130
|
+
import("../../../../toolkit/src/app/chat/ownership-history/index.tsx"),
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
vi.mock("sonner", () => ({ toast: { error: vi.fn(), success: vi.fn() } }));
|
|
134
|
+
|
|
92
135
|
vi.mock("@agent-native/toolkit/app/chat/AgentSidebar", () => ({
|
|
93
136
|
AgentToggleButton: () => null,
|
|
94
137
|
AgentSidebar: ({ children }: { children: React.ReactNode }) => (
|
|
@@ -100,6 +143,9 @@ vi.mock("@agent-native/toolkit/app/chat/AgentSidebar", () => ({
|
|
|
100
143
|
vi.mock("../deferred-chat-components.js", async (importOriginal) => ({
|
|
101
144
|
...(await importOriginal<typeof import("../deferred-chat-components.js")>()),
|
|
102
145
|
AgentChatSurface: () => null,
|
|
146
|
+
ChatFirstChatHistory: (
|
|
147
|
+
await import("../../../../toolkit/src/app/chat/chat-first/chat-history.tsx")
|
|
148
|
+
).ChatFirstChatHistory,
|
|
103
149
|
}));
|
|
104
150
|
|
|
105
151
|
vi.mock("@agent-native/core/client/api-path", () => ({
|
|
@@ -173,6 +219,8 @@ vi.mock("next-themes", () => ({
|
|
|
173
219
|
}));
|
|
174
220
|
|
|
175
221
|
vi.mock("@agent-native/core/client/i18n", () => ({
|
|
222
|
+
DEFAULT_LOCALE: "en-US",
|
|
223
|
+
useOptionalLocale: () => ({ locale: "en-US" }),
|
|
176
224
|
useFormatters: () => ({ formatNumber: (value: number) => String(value) }),
|
|
177
225
|
useT: () => (key: string, values?: Record<string, unknown>) => {
|
|
178
226
|
const messages: Record<string, string> = {
|
|
@@ -183,6 +231,24 @@ vi.mock("@agent-native/core/client/i18n", () => ({
|
|
|
183
231
|
"dispatch.pages.workspaceApps": "Workspace apps",
|
|
184
232
|
"dispatch.nav.operate": "Operate",
|
|
185
233
|
"dispatch.nav.advanced": "Advanced",
|
|
234
|
+
"agentChat.history.mine": "My chats",
|
|
235
|
+
"agentChat.history.sharedWithYou": "Shared with you",
|
|
236
|
+
"agentChat.history.ownedBy": `Owner: ${values?.name ?? ""}`,
|
|
237
|
+
"agentChat.history.accessDirect": "Shared with you",
|
|
238
|
+
"agentChat.history.accessOrganization": "Shared with your organization",
|
|
239
|
+
"agentChat.history.copyLink": "Copy link",
|
|
240
|
+
"agentChat.history.showAll": "Show all",
|
|
241
|
+
"agentChat.history.showLess": "Show less",
|
|
242
|
+
"agentChat.history.loadMore": "Load more",
|
|
243
|
+
"agentChat.history.loadingMore": "Loading...",
|
|
244
|
+
"agentChat.history.emptyMine": "No chats yet",
|
|
245
|
+
"dispatch.sidebar.chats": "Chats",
|
|
246
|
+
"dispatch.sidebar.showLocalChats": "Show local chats",
|
|
247
|
+
"dispatch.sidebar.showAllChats": "Show all chats",
|
|
248
|
+
"agentChat.contextXray.pin": "Pin",
|
|
249
|
+
"agentChat.contextXray.unpin": "Unpin",
|
|
250
|
+
"extensions.delete": "Archive",
|
|
251
|
+
"dispatch.integrations.failedToSave": "Failed to save",
|
|
186
252
|
"dispatch.sidebar.newChat": "New chat",
|
|
187
253
|
"dispatch.sidebar.newDispatchChat": "New Dispatch chat",
|
|
188
254
|
"dispatch.sidebar.renameChat": "Rename chat",
|
|
@@ -248,20 +314,6 @@ describe("Dispatch navigation paths", () => {
|
|
|
248
314
|
});
|
|
249
315
|
});
|
|
250
316
|
|
|
251
|
-
describe("formatThreadAge", () => {
|
|
252
|
-
const now = 2_000_000_000_000;
|
|
253
|
-
|
|
254
|
-
it.each([
|
|
255
|
-
[0, "now"],
|
|
256
|
-
[2 * 60 * 60_000, "2h"],
|
|
257
|
-
[7 * 24 * 60 * 60_000, "7d"],
|
|
258
|
-
[21 * 24 * 60 * 60_000, "3w"],
|
|
259
|
-
[365 * 24 * 60 * 60_000, "1y"],
|
|
260
|
-
])("formats %i milliseconds as %s", (elapsed, expected) => {
|
|
261
|
-
expect(formatThreadAge(now - elapsed, now)).toBe(expected);
|
|
262
|
-
});
|
|
263
|
-
});
|
|
264
|
-
|
|
265
317
|
describe("chat-first embed sessions", () => {
|
|
266
318
|
it("keeps the granted app id on app-relative embed requests", () => {
|
|
267
319
|
expect(buildChatFirstEmbedSessionInput("mail", "/mail/inbox")).toEqual({
|
|
@@ -347,8 +399,26 @@ describe("Dispatch NavContent", () => {
|
|
|
347
399
|
});
|
|
348
400
|
clientState.createThread.mockResolvedValue("new-thread");
|
|
349
401
|
clientState.switchThread.mockReset();
|
|
402
|
+
clientState.createThread.mockClear();
|
|
403
|
+
clientState.renameThread.mockReset().mockResolvedValue(true);
|
|
404
|
+
clientState.pinThread.mockReset().mockResolvedValue(true);
|
|
405
|
+
clientState.archiveThread.mockReset().mockResolvedValue(true);
|
|
406
|
+
clientState.sharedThreads = [];
|
|
407
|
+
clientState.mineThreads = null;
|
|
408
|
+
clientState.sharedHasMore = false;
|
|
409
|
+
clientState.loadMore.mockClear();
|
|
410
|
+
clientState.listQueries.mockClear();
|
|
411
|
+
clientState.lifecycleOptions.mockClear();
|
|
412
|
+
localStorage.clear();
|
|
413
|
+
vi.stubGlobal(
|
|
414
|
+
"fetch",
|
|
415
|
+
vi.fn().mockResolvedValue(new Response("{}", { status: 200 })),
|
|
416
|
+
);
|
|
350
417
|
clientState.threads = [
|
|
351
418
|
{
|
|
419
|
+
ownerEmail: "probe@example.com",
|
|
420
|
+
accessBasis: "owner",
|
|
421
|
+
visibility: "private",
|
|
352
422
|
id: "active-thread",
|
|
353
423
|
title: "Current Dispatch work",
|
|
354
424
|
messageCount: 2,
|
|
@@ -356,6 +426,9 @@ describe("Dispatch NavContent", () => {
|
|
|
356
426
|
createdAt: Date.now(),
|
|
357
427
|
},
|
|
358
428
|
{
|
|
429
|
+
ownerEmail: "probe@example.com",
|
|
430
|
+
accessBasis: "owner",
|
|
431
|
+
visibility: "private",
|
|
359
432
|
id: "older-thread",
|
|
360
433
|
title: "Earlier Dispatch work",
|
|
361
434
|
messageCount: 1,
|
|
@@ -1166,6 +1239,255 @@ describe("Dispatch NavContent", () => {
|
|
|
1166
1239
|
expect(paths).toEqual(["/apps/mail"]);
|
|
1167
1240
|
});
|
|
1168
1241
|
|
|
1242
|
+
async function renderHistory(
|
|
1243
|
+
chatFirstMode = false,
|
|
1244
|
+
path = "/chat/active-thread",
|
|
1245
|
+
) {
|
|
1246
|
+
await act(async () => {
|
|
1247
|
+
root.render(
|
|
1248
|
+
<MemoryRouter initialEntries={[path]}>
|
|
1249
|
+
<TooltipProvider>
|
|
1250
|
+
<NavContent chatFirstMode={chatFirstMode} />
|
|
1251
|
+
</TooltipProvider>
|
|
1252
|
+
</MemoryRouter>,
|
|
1253
|
+
);
|
|
1254
|
+
});
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
async function clickText(scope: ParentNode, text: string) {
|
|
1258
|
+
const button = [
|
|
1259
|
+
...scope.querySelectorAll<HTMLButtonElement>("button"),
|
|
1260
|
+
].find((button) => button.textContent?.trim() === text);
|
|
1261
|
+
expect(button, text).toBeDefined();
|
|
1262
|
+
await act(async () => {
|
|
1263
|
+
button!.click();
|
|
1264
|
+
});
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
async function openThreadMenu(title: string) {
|
|
1268
|
+
const row = [
|
|
1269
|
+
...container.querySelectorAll<HTMLElement>(".an-chat-history-row"),
|
|
1270
|
+
].find((row) => row.textContent?.includes(title));
|
|
1271
|
+
expect(row).toBeDefined();
|
|
1272
|
+
await act(async () => {
|
|
1273
|
+
row!.dispatchEvent(new MouseEvent("contextmenu", { bubbles: true }));
|
|
1274
|
+
});
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
it.each([false, true])(
|
|
1278
|
+
"keeps the proven-owned active empty chat and hides inactive blanks (chat-first %s)",
|
|
1279
|
+
async (chatFirstMode) => {
|
|
1280
|
+
clientState.mineThreads = [];
|
|
1281
|
+
clientState.threads = clientState.threads.map((thread) => ({
|
|
1282
|
+
...thread,
|
|
1283
|
+
messageCount: 0,
|
|
1284
|
+
}));
|
|
1285
|
+
await renderHistory(chatFirstMode, "/chat");
|
|
1286
|
+
|
|
1287
|
+
expect(container.textContent).toContain("Current Dispatch work");
|
|
1288
|
+
expect(container.textContent).not.toContain("Earlier Dispatch work");
|
|
1289
|
+
expect(container.querySelectorAll(".an-chat-history-row")).toHaveLength(
|
|
1290
|
+
1,
|
|
1291
|
+
);
|
|
1292
|
+
expect(clientState.switchThread).not.toHaveBeenCalled();
|
|
1293
|
+
expect(clientState.lifecycleOptions).toHaveBeenLastCalledWith({
|
|
1294
|
+
autoCreate: false,
|
|
1295
|
+
includeExternal: false,
|
|
1296
|
+
});
|
|
1297
|
+
},
|
|
1298
|
+
);
|
|
1299
|
+
|
|
1300
|
+
it.each(
|
|
1301
|
+
[false, true].flatMap((chatFirstMode) =>
|
|
1302
|
+
["direct", "organization", "unproven"].map(
|
|
1303
|
+
(basis) => [chatFirstMode, basis] as const,
|
|
1304
|
+
),
|
|
1305
|
+
),
|
|
1306
|
+
)(
|
|
1307
|
+
"does not promote an active empty %s/%s lifecycle row into Mine",
|
|
1308
|
+
async (chatFirstMode, basis) => {
|
|
1309
|
+
clientState.mineThreads = [];
|
|
1310
|
+
const active = clientState.threads[0];
|
|
1311
|
+
clientState.threads = [
|
|
1312
|
+
{
|
|
1313
|
+
...active,
|
|
1314
|
+
messageCount: 0,
|
|
1315
|
+
accessBasis: basis === "unproven" ? undefined : basis,
|
|
1316
|
+
ownerEmail:
|
|
1317
|
+
basis === "unproven" ? undefined : "colleague@example.com",
|
|
1318
|
+
visibility: basis === "unproven" ? undefined : "organization",
|
|
1319
|
+
},
|
|
1320
|
+
];
|
|
1321
|
+
await renderHistory(chatFirstMode);
|
|
1322
|
+
|
|
1323
|
+
expect(container.textContent).not.toContain("Current Dispatch work");
|
|
1324
|
+
expect(container.querySelector(".an-chat-history-row")).toBeNull();
|
|
1325
|
+
expect(clientState.switchThread).not.toHaveBeenCalled();
|
|
1326
|
+
},
|
|
1327
|
+
);
|
|
1328
|
+
|
|
1329
|
+
it.each([false, true])(
|
|
1330
|
+
"renders server Shared attribution, five-row disclosure and paging (chat-first %s)",
|
|
1331
|
+
async (chatFirstMode) => {
|
|
1332
|
+
clientState.sharedThreads = Array.from({ length: 6 }, (_, index) => ({
|
|
1333
|
+
id: `shared-${index}`,
|
|
1334
|
+
title: `Shared work ${index}`,
|
|
1335
|
+
preview: "",
|
|
1336
|
+
messageCount: 1,
|
|
1337
|
+
createdAt: Date.now(),
|
|
1338
|
+
updatedAt: Date.now(),
|
|
1339
|
+
ownerEmail: "colleague@example.com",
|
|
1340
|
+
accessBasis: index === 0 ? "direct" : "organization",
|
|
1341
|
+
visibility: "organization",
|
|
1342
|
+
pinnedAt: Date.now(),
|
|
1343
|
+
}));
|
|
1344
|
+
clientState.sharedHasMore = true;
|
|
1345
|
+
await renderHistory(chatFirstMode, "/chat/shared-0");
|
|
1346
|
+
const shared = container.querySelector(
|
|
1347
|
+
'[data-section-id="shared"].an-chat-history__section',
|
|
1348
|
+
)!;
|
|
1349
|
+
expect(shared).not.toBeNull();
|
|
1350
|
+
expect(shared.querySelectorAll(".an-chat-history-row")).toHaveLength(5);
|
|
1351
|
+
expect(
|
|
1352
|
+
shared.querySelector('[role="img"]')?.getAttribute("aria-label"),
|
|
1353
|
+
).toBe("Owner: colleague@example.com");
|
|
1354
|
+
for (const basis of ["direct", "organization"]) {
|
|
1355
|
+
expect(
|
|
1356
|
+
shared.querySelector(
|
|
1357
|
+
`.an-chat-history-row__owner-mark[data-access-basis="${basis}"]`,
|
|
1358
|
+
),
|
|
1359
|
+
).not.toBeNull();
|
|
1360
|
+
}
|
|
1361
|
+
expect(shared.querySelector(".an-chat-history-row--pinned")).toBeNull();
|
|
1362
|
+
expect(
|
|
1363
|
+
shared.querySelector(".an-chat-history-row--active")?.textContent,
|
|
1364
|
+
).toContain("Shared work 0");
|
|
1365
|
+
await clickText(shared, "Show all");
|
|
1366
|
+
expect(shared.querySelectorAll(".an-chat-history-row")).toHaveLength(6);
|
|
1367
|
+
clientState.sharedThreads = clientState.sharedThreads.map((row) => ({
|
|
1368
|
+
...row,
|
|
1369
|
+
}));
|
|
1370
|
+
await renderHistory(chatFirstMode, "/chat/shared-0");
|
|
1371
|
+
expect(shared.querySelectorAll(".an-chat-history-row")).toHaveLength(6);
|
|
1372
|
+
expect(
|
|
1373
|
+
shared.querySelector(".an-chat-history-row--active")?.textContent,
|
|
1374
|
+
).toContain("Shared work 0");
|
|
1375
|
+
expect(clientState.switchThread).not.toHaveBeenCalled();
|
|
1376
|
+
await clickText(shared, "Load more");
|
|
1377
|
+
expect(clientState.loadMore).toHaveBeenCalledOnce();
|
|
1378
|
+
await clickText(shared, "Show less");
|
|
1379
|
+
expect(shared.querySelectorAll(".an-chat-history-row")).toHaveLength(5);
|
|
1380
|
+
await act(async () => {
|
|
1381
|
+
shared
|
|
1382
|
+
.querySelector<HTMLButtonElement>(".an-chat-history-row__button")!
|
|
1383
|
+
.click();
|
|
1384
|
+
});
|
|
1385
|
+
expect(clientState.switchThread).toHaveBeenCalledWith("shared-0");
|
|
1386
|
+
expect(clientState.lifecycleOptions).toHaveBeenLastCalledWith({
|
|
1387
|
+
autoCreate: false,
|
|
1388
|
+
includeExternal: false,
|
|
1389
|
+
});
|
|
1390
|
+
await openThreadMenu("Shared work 0");
|
|
1391
|
+
expect(
|
|
1392
|
+
document.body.querySelector(".an-chat-history-row__menu-owner")
|
|
1393
|
+
?.textContent,
|
|
1394
|
+
).toContain("colleague@example.comShared with you");
|
|
1395
|
+
const actions = [
|
|
1396
|
+
...document.body.querySelectorAll(".an-chat-history-row__menu-item"),
|
|
1397
|
+
].map((item) => item.textContent);
|
|
1398
|
+
expect(actions).toContain("Copy link");
|
|
1399
|
+
for (const ownerAction of ["Rename chat", "Pin", "Unpin", "Archive"]) {
|
|
1400
|
+
expect(actions).not.toContain(ownerAction);
|
|
1401
|
+
}
|
|
1402
|
+
},
|
|
1403
|
+
);
|
|
1404
|
+
|
|
1405
|
+
it.each([false, true])(
|
|
1406
|
+
"passes the persisted external source toggle to both ownership queries (chat-first %s)",
|
|
1407
|
+
async (chatFirstMode) => {
|
|
1408
|
+
await renderHistory(chatFirstMode);
|
|
1409
|
+
const toggle = container.querySelector<HTMLButtonElement>(
|
|
1410
|
+
"[data-dispatch-chat-source-toggle]",
|
|
1411
|
+
)!;
|
|
1412
|
+
expect(toggle.getAttribute("aria-pressed")).toBe("false");
|
|
1413
|
+
await act(async () => toggle.click());
|
|
1414
|
+
for (const ownership of ["mine", "shared"]) {
|
|
1415
|
+
expect(clientState.listQueries).toHaveBeenCalledWith(
|
|
1416
|
+
expect.objectContaining({ ownership, includeExternal: true }),
|
|
1417
|
+
);
|
|
1418
|
+
}
|
|
1419
|
+
expect(localStorage.getItem("dispatch.chat-history.source")).toBe("all");
|
|
1420
|
+
},
|
|
1421
|
+
);
|
|
1422
|
+
|
|
1423
|
+
it.each([false, true])(
|
|
1424
|
+
"archives the active owned chat before opening New chat (chat-first %s)",
|
|
1425
|
+
async (chatFirstMode) => {
|
|
1426
|
+
let settle!: (value: boolean) => void;
|
|
1427
|
+
clientState.archiveThread.mockReturnValue(
|
|
1428
|
+
new Promise<boolean>((resolve) => {
|
|
1429
|
+
settle = resolve;
|
|
1430
|
+
}),
|
|
1431
|
+
);
|
|
1432
|
+
await renderHistory(chatFirstMode);
|
|
1433
|
+
await openThreadMenu("Current Dispatch work");
|
|
1434
|
+
await clickText(document.body, "Archive");
|
|
1435
|
+
expect(clientState.archiveThread).toHaveBeenCalledWith("active-thread");
|
|
1436
|
+
expect(clientState.createThread).not.toHaveBeenCalled();
|
|
1437
|
+
await act(async () => settle(true));
|
|
1438
|
+
expect(clientState.createThread).toHaveBeenCalledOnce();
|
|
1439
|
+
expect(clientState.switchThread).toHaveBeenCalledWith("new-thread");
|
|
1440
|
+
},
|
|
1441
|
+
);
|
|
1442
|
+
|
|
1443
|
+
it.each([false, true])(
|
|
1444
|
+
"rolls back a failed archive, toasts once and keeps the active chat (chat-first %s)",
|
|
1445
|
+
async (chatFirstMode) => {
|
|
1446
|
+
vi.mocked(toast.error).mockClear();
|
|
1447
|
+
clientState.archiveThread.mockResolvedValue(false);
|
|
1448
|
+
await renderHistory(chatFirstMode);
|
|
1449
|
+
await openThreadMenu("Current Dispatch work");
|
|
1450
|
+
await clickText(document.body, "Archive");
|
|
1451
|
+
expect(container.textContent).toContain("Current Dispatch work");
|
|
1452
|
+
expect(clientState.createThread).not.toHaveBeenCalled();
|
|
1453
|
+
expect(clientState.switchThread).not.toHaveBeenCalled();
|
|
1454
|
+
expect(toast.error).toHaveBeenCalledExactlyOnceWith("Failed to save");
|
|
1455
|
+
},
|
|
1456
|
+
);
|
|
1457
|
+
|
|
1458
|
+
it.each([false, true])(
|
|
1459
|
+
"pins and renames owned rows through the adapter (chat-first %s)",
|
|
1460
|
+
async (chatFirstMode) => {
|
|
1461
|
+
await renderHistory(chatFirstMode);
|
|
1462
|
+
await openThreadMenu("Earlier Dispatch work");
|
|
1463
|
+
await clickText(document.body, "Pin");
|
|
1464
|
+
expect(clientState.pinThread).toHaveBeenCalledWith("older-thread", true);
|
|
1465
|
+
await openThreadMenu("Earlier Dispatch work");
|
|
1466
|
+
await clickText(document.body, "Rename chat");
|
|
1467
|
+
const input = container.querySelector<HTMLInputElement>(
|
|
1468
|
+
".an-chat-history-row__rename-input",
|
|
1469
|
+
)!;
|
|
1470
|
+
await act(async () => {
|
|
1471
|
+
Object.getOwnPropertyDescriptor(
|
|
1472
|
+
HTMLInputElement.prototype,
|
|
1473
|
+
"value",
|
|
1474
|
+
)!.set!.call(input, "Revised work");
|
|
1475
|
+
input.dispatchEvent(new Event("input", { bubbles: true }));
|
|
1476
|
+
input.dispatchEvent(new Event("change", { bubbles: true }));
|
|
1477
|
+
});
|
|
1478
|
+
await act(async () => {
|
|
1479
|
+
input.dispatchEvent(
|
|
1480
|
+
new KeyboardEvent("keydown", { key: "Enter", bubbles: true }),
|
|
1481
|
+
);
|
|
1482
|
+
});
|
|
1483
|
+
expect(clientState.renameThread).toHaveBeenCalledWith(
|
|
1484
|
+
"older-thread",
|
|
1485
|
+
"Revised work",
|
|
1486
|
+
);
|
|
1487
|
+
expect(clientState.createThread).not.toHaveBeenCalled();
|
|
1488
|
+
},
|
|
1489
|
+
);
|
|
1490
|
+
|
|
1169
1491
|
it("uses the shared chat history rail and retains thread actions", async () => {
|
|
1170
1492
|
await act(async () => {
|
|
1171
1493
|
root.render(
|
|
@@ -1181,7 +1503,7 @@ describe("Dispatch NavContent", () => {
|
|
|
1181
1503
|
expect(container.textContent).toContain("Current Dispatch work");
|
|
1182
1504
|
expect(container.textContent).toContain("Earlier Dispatch work");
|
|
1183
1505
|
expect(container.textContent).toContain("New chat");
|
|
1184
|
-
expect(container.textContent).toContain("5m");
|
|
1506
|
+
expect(container.textContent).toContain("5m ago");
|
|
1185
1507
|
expect(container.querySelector('[aria-label="Slack"]')).not.toBeNull();
|
|
1186
1508
|
const sourceToggle = container.querySelector(
|
|
1187
1509
|
"[data-dispatch-chat-source-toggle]",
|
|
@@ -1192,7 +1514,7 @@ describe("Dispatch NavContent", () => {
|
|
|
1192
1514
|
});
|
|
1193
1515
|
expect(sourceToggle.getAttribute("aria-pressed")).toBe("true");
|
|
1194
1516
|
const age = [...container.querySelectorAll("span")].find(
|
|
1195
|
-
(element) => element.textContent === "5m",
|
|
1517
|
+
(element) => element.textContent === "5m ago",
|
|
1196
1518
|
);
|
|
1197
1519
|
expect(age?.className).toContain("an-chat-history-row__timestamp");
|
|
1198
1520
|
const historyList = container.querySelector(
|