@promptbook/cli 0.112.0 → 0.113.0-1

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 (109) hide show
  1. package/README.md +45 -29
  2. package/agents/default/developer.book +2 -1
  3. package/apps/agents-server/next.config.ts +21 -0
  4. package/apps/agents-server/src/app/admin/limits/LimitsClient.tsx +1 -1
  5. package/apps/agents-server/src/app/admin/task-manager/TaskManagerTaskRow.tsx +7 -2
  6. package/apps/agents-server/src/app/admin/update/UpdateClient.tsx +52 -33
  7. package/apps/agents-server/src/app/api/agent-folders/[folderId]/visibility/route.ts +19 -7
  8. package/apps/agents-server/src/app/api/agents/[agentName]/route.ts +8 -18
  9. package/apps/agents-server/src/app/api/internal/agent-runner-limits/route.ts +1 -1
  10. package/apps/agents-server/src/app/api/page-preview/check/route.ts +5 -20
  11. package/apps/agents-server/src/app/api/page-preview/interact/route.ts +179 -0
  12. package/apps/agents-server/src/app/api/page-preview/live/route.ts +67 -0
  13. package/apps/agents-server/src/app/api/page-preview/screenshot/route.ts +13 -21
  14. package/apps/agents-server/src/app/api/v1/agents/[agentId]/route.ts +13 -8
  15. package/apps/agents-server/src/components/AgentProfile/AgentCapabilityChips.tsx +39 -5
  16. package/apps/agents-server/src/components/Homepage/AgentCard.tsx +7 -1
  17. package/apps/agents-server/src/components/Homepage/AgentsList.tsx +3 -0
  18. package/apps/agents-server/src/components/Homepage/AgentsListHeader.tsx +41 -0
  19. package/apps/agents-server/src/components/Homepage/hiddenFolders.ts +104 -0
  20. package/apps/agents-server/src/components/Homepage/useAgentsListQueryState.ts +35 -0
  21. package/apps/agents-server/src/components/Homepage/useAgentsListState.ts +24 -4
  22. package/apps/agents-server/src/constants/chatVisualMode.ts +1 -1
  23. package/apps/agents-server/src/constants/serverLimits.ts +19 -0
  24. package/apps/agents-server/src/constants/themeMode.ts +1 -1
  25. package/apps/agents-server/src/database/migrations/2026-05-2600-default-theme.sql +2 -2
  26. package/apps/agents-server/src/database/seedCoreAgents.ts +0 -2
  27. package/apps/agents-server/src/database/seedDefaultAgents.ts +0 -2
  28. package/apps/agents-server/src/tools/agent_progress.ts +4 -10
  29. package/apps/agents-server/src/utils/agentVisibility.ts +25 -62
  30. package/apps/agents-server/src/utils/chatTasksAdmin.ts +3 -1
  31. package/apps/agents-server/src/utils/createAgentWithDefaultVisibility.ts +3 -1
  32. package/apps/agents-server/src/utils/externalChatRunner/processExternalUserChatJob.ts +9 -17
  33. package/apps/agents-server/src/utils/getAdminChatTasksResponse/mapVpsSelfUpdateJobToAdminChatTask.ts +84 -0
  34. package/apps/agents-server/src/utils/getAdminChatTasksResponse.ts +195 -5
  35. package/apps/agents-server/src/utils/localChatRunner/LocalUserChatJobMetadata.ts +15 -5
  36. package/apps/agents-server/src/utils/localChatRunner/processLocalUserChatJob.ts +17 -26
  37. package/apps/agents-server/src/utils/pagePreview/livePagePreviewSessions.ts +365 -0
  38. package/apps/agents-server/src/utils/pagePreview/resolvePagePreviewRequestUrl.ts +74 -0
  39. package/apps/agents-server/src/utils/serverLimits.ts +2 -0
  40. package/apps/agents-server/src/utils/serverManagement/createManagedServer/seedServerCoreAgents.ts +2 -2
  41. package/apps/agents-server/src/utils/serverManagement/createManagedServer/seedServerDefaultAgents.ts +1 -1
  42. package/apps/agents-server/src/utils/userChat/createUserChatHarnessProgressCard.ts +0 -6
  43. package/apps/agents-server/src/utils/userChat/createUserChatRunnerProgressCard.ts +0 -6
  44. package/apps/agents-server/src/utils/userChat/userChatMessageLifecycle.ts +69 -0
  45. package/apps/agents-server/src/utils/userChat/userChatProgressCard.ts +0 -6
  46. package/apps/agents-server/src/utils/vpsSelfUpdate.ts +150 -24
  47. package/esm/apps/agents-server/src/constants/serverLimits.d.ts +17 -0
  48. package/esm/index.es.js +581 -93
  49. package/esm/index.es.js.map +1 -1
  50. package/esm/scripts/run-agent-messages/main/runMultipleAgentMessages.d.ts +1 -0
  51. package/esm/scripts/run-agent-messages/main/tickAgentMessages.d.ts +1 -0
  52. package/esm/src/_packages/core.index.d.ts +22 -0
  53. package/esm/src/_packages/types.index.d.ts +2 -0
  54. package/esm/src/book-2.0/agent-source/AgentBasicInformation.d.ts +2 -0
  55. package/esm/src/book-2.0/agent-source/agentSourceVisibility.d.ts +97 -0
  56. package/esm/src/book-2.0/agent-source/agentSourceVisibility.test.d.ts +1 -0
  57. package/esm/src/book-components/BookEditor/BookEditorMonacoTokenization.d.ts +1 -0
  58. package/esm/src/book-components/Chat/Chat/CitationIframePreview.d.ts +2 -2
  59. package/esm/src/book-components/Chat/utils/livePagePreviewConstants.d.ts +12 -0
  60. package/esm/src/cli/cli-commands/agents-server/buildAgentsServer.d.ts +6 -0
  61. package/esm/src/collection/agent-collection/CreateAgentInput.d.ts +2 -1
  62. package/esm/src/collection/agent-collection/constructors/agent-collection-in-supabase/prepareAgentSourceForPersistence.d.ts +21 -1
  63. package/esm/src/commitments/META_VISIBILITY/META_VISIBILITY.d.ts +27 -0
  64. package/esm/src/commitments/index.d.ts +2 -1
  65. package/esm/src/version.d.ts +1 -1
  66. package/package.json +1 -1
  67. package/src/_packages/core.index.ts +22 -0
  68. package/src/_packages/types.index.ts +2 -0
  69. package/src/book-2.0/agent-source/AgentBasicInformation.ts +2 -0
  70. package/src/book-2.0/agent-source/agentSourceVisibility.ts +214 -0
  71. package/src/book-2.0/agent-source/parseAgentSource/applyMetaCommitment.ts +17 -0
  72. package/src/book-2.0/book-language-documentation/createStandaloneBookLanguageMarkdown.ts +1 -0
  73. package/src/book-components/BookEditor/BookEditorMonacoTokenization.ts +11 -0
  74. package/src/book-components/BookEditor/useBookEditorMonacoLanguage.ts +7 -3
  75. package/src/book-components/Chat/Chat/Chat.module.css +14 -5
  76. package/src/book-components/Chat/Chat/CitationIframePreview.tsx +197 -22
  77. package/src/book-components/Chat/utils/livePagePreviewConstants.ts +17 -0
  78. package/src/cli/cli-commands/agents-server/buildAgentsServer.ts +144 -16
  79. package/src/cli/cli-commands/agents-server/startAgentsServer.ts +22 -2
  80. package/src/cli/cli-commands/coder/getDefaultCoderPackageJsonScripts.ts +1 -1
  81. package/src/cli/cli-commands/coder/run.ts +30 -0
  82. package/src/collection/agent-collection/CreateAgentInput.ts +10 -5
  83. package/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentCollectionInSupabase.ts +7 -3
  84. package/src/collection/agent-collection/constructors/agent-collection-in-supabase/createAgentPersistenceRecords.ts +7 -4
  85. package/src/collection/agent-collection/constructors/agent-collection-in-supabase/prepareAgentSourceForPersistence.ts +44 -8
  86. package/src/commitments/META_VISIBILITY/META_VISIBILITY.ts +78 -0
  87. package/src/commitments/index.ts +2 -0
  88. package/src/other/templates/getTemplatesPipelineCollection.ts +826 -650
  89. package/src/version.ts +2 -2
  90. package/src/versions.txt +2 -0
  91. package/umd/apps/agents-server/src/constants/serverLimits.d.ts +17 -0
  92. package/umd/index.umd.js +581 -93
  93. package/umd/index.umd.js.map +1 -1
  94. package/umd/scripts/run-agent-messages/main/runMultipleAgentMessages.d.ts +1 -0
  95. package/umd/scripts/run-agent-messages/main/tickAgentMessages.d.ts +1 -0
  96. package/umd/src/_packages/core.index.d.ts +22 -0
  97. package/umd/src/_packages/types.index.d.ts +2 -0
  98. package/umd/src/book-2.0/agent-source/AgentBasicInformation.d.ts +2 -0
  99. package/umd/src/book-2.0/agent-source/agentSourceVisibility.d.ts +97 -0
  100. package/umd/src/book-2.0/agent-source/agentSourceVisibility.test.d.ts +1 -0
  101. package/umd/src/book-components/BookEditor/BookEditorMonacoTokenization.d.ts +1 -0
  102. package/umd/src/book-components/Chat/Chat/CitationIframePreview.d.ts +2 -2
  103. package/umd/src/book-components/Chat/utils/livePagePreviewConstants.d.ts +12 -0
  104. package/umd/src/cli/cli-commands/agents-server/buildAgentsServer.d.ts +6 -0
  105. package/umd/src/collection/agent-collection/CreateAgentInput.d.ts +2 -1
  106. package/umd/src/collection/agent-collection/constructors/agent-collection-in-supabase/prepareAgentSourceForPersistence.d.ts +21 -1
  107. package/umd/src/commitments/META_VISIBILITY/META_VISIBILITY.d.ts +27 -0
  108. package/umd/src/commitments/index.d.ts +2 -1
  109. package/umd/src/version.d.ts +1 -1
@@ -0,0 +1,179 @@
1
+ import { serializeError } from '@promptbook-local/utils';
2
+ import type { NextRequest } from 'next/server';
3
+ import { NextResponse } from 'next/server';
4
+ import { assertsError } from '../../../../../../../src/errors/assertsError';
5
+ import { getCurrentUser } from '../../../../utils/getCurrentUser';
6
+ import {
7
+ applyLivePagePreviewInteraction,
8
+ isLivePagePreviewSessionId,
9
+ type LivePagePreviewInteraction,
10
+ } from '../../../../utils/pagePreview/livePagePreviewSessions';
11
+
12
+ /**
13
+ * Maximum wheel delta accepted from one live-preview UI event.
14
+ */
15
+ const MAX_LIVE_PAGE_PREVIEW_WHEEL_DELTA = 4_000;
16
+
17
+ /**
18
+ * Maximum keyboard key name length accepted from one live-preview UI event.
19
+ */
20
+ const MAX_LIVE_PAGE_PREVIEW_KEY_LENGTH = 40;
21
+
22
+ /**
23
+ * Parsed live-preview interaction request body.
24
+ */
25
+ type ParsedLivePagePreviewInteractionRequest = {
26
+ readonly sessionId: string;
27
+ readonly interaction: LivePagePreviewInteraction;
28
+ };
29
+
30
+ /**
31
+ * Constant for dynamic route rendering.
32
+ */
33
+ export const dynamic = 'force-dynamic';
34
+
35
+ /**
36
+ * Applies one UI interaction to an active live browser preview.
37
+ *
38
+ * Requires authentication because interaction targets are opened by the authenticated preview route.
39
+ */
40
+ export async function POST(request: NextRequest): Promise<NextResponse> {
41
+ const currentUser = await getCurrentUser();
42
+ if (!currentUser) {
43
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
44
+ }
45
+
46
+ const parsedRequest = await parseLivePagePreviewInteractionRequest(request);
47
+ if (!parsedRequest) {
48
+ return NextResponse.json({ error: 'Invalid live preview interaction request.' }, { status: 400 });
49
+ }
50
+
51
+ try {
52
+ const isApplied = await applyLivePagePreviewInteraction(parsedRequest);
53
+ if (!isApplied) {
54
+ return NextResponse.json({ error: 'Live preview session not found.' }, { status: 404 });
55
+ }
56
+
57
+ return NextResponse.json({ ok: true });
58
+ } catch (error) {
59
+ assertsError(error);
60
+ console.error('Error applying live page preview interaction:', error);
61
+ return NextResponse.json({ error: serializeError(error) }, { status: 500 });
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Parses and validates a live-preview interaction request.
67
+ *
68
+ * @param request - Incoming interaction request.
69
+ * @returns Parsed request or null when the payload is invalid.
70
+ */
71
+ async function parseLivePagePreviewInteractionRequest(
72
+ request: NextRequest,
73
+ ): Promise<ParsedLivePagePreviewInteractionRequest | null> {
74
+ let body: unknown;
75
+
76
+ try {
77
+ body = await request.json();
78
+ } catch {
79
+ return null;
80
+ }
81
+
82
+ if (!isRecord(body)) {
83
+ return null;
84
+ }
85
+
86
+ const sessionId = typeof body.sessionId === 'string' ? body.sessionId : null;
87
+ if (!sessionId || !isLivePagePreviewSessionId(sessionId)) {
88
+ return null;
89
+ }
90
+
91
+ const interaction = parseLivePagePreviewInteraction(body);
92
+ if (!interaction) {
93
+ return null;
94
+ }
95
+
96
+ return {
97
+ sessionId,
98
+ interaction,
99
+ };
100
+ }
101
+
102
+ /**
103
+ * Parses one normalized live-preview interaction from an object payload.
104
+ *
105
+ * @param body - Request body record.
106
+ * @returns Interaction payload or null when invalid.
107
+ */
108
+ function parseLivePagePreviewInteraction(body: Record<string, unknown>): LivePagePreviewInteraction | null {
109
+ if (body.type === 'click') {
110
+ const x = readFiniteNumber(body.x);
111
+ const y = readFiniteNumber(body.y);
112
+
113
+ if (x === null || y === null) {
114
+ return null;
115
+ }
116
+
117
+ return { type: 'click', x, y };
118
+ }
119
+
120
+ if (body.type === 'wheel') {
121
+ const deltaX = readFiniteNumber(body.deltaX);
122
+ const deltaY = readFiniteNumber(body.deltaY);
123
+
124
+ if (deltaX === null || deltaY === null) {
125
+ return null;
126
+ }
127
+
128
+ return {
129
+ type: 'wheel',
130
+ deltaX: clampWheelDelta(deltaX),
131
+ deltaY: clampWheelDelta(deltaY),
132
+ };
133
+ }
134
+
135
+ if (body.type === 'keyDown') {
136
+ const key = typeof body.key === 'string' ? body.key : null;
137
+ if (!key || key.length > MAX_LIVE_PAGE_PREVIEW_KEY_LENGTH) {
138
+ return null;
139
+ }
140
+
141
+ return { type: 'keyDown', key };
142
+ }
143
+
144
+ return null;
145
+ }
146
+
147
+ /**
148
+ * Checks whether a value is a plain object record.
149
+ *
150
+ * @param value - Candidate value.
151
+ * @returns True when the value can be read as a JSON object.
152
+ */
153
+ function isRecord(value: unknown): value is Record<string, unknown> {
154
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
155
+ }
156
+
157
+ /**
158
+ * Reads one finite number from a JSON value.
159
+ *
160
+ * @param value - Candidate JSON value.
161
+ * @returns Number or null when invalid.
162
+ */
163
+ function readFiniteNumber(value: unknown): number | null {
164
+ if (typeof value !== 'number' || !Number.isFinite(value)) {
165
+ return null;
166
+ }
167
+
168
+ return value;
169
+ }
170
+
171
+ /**
172
+ * Clamps one wheel delta to a bounded value.
173
+ *
174
+ * @param value - Raw wheel delta.
175
+ * @returns Delta safe to forward to Playwright.
176
+ */
177
+ function clampWheelDelta(value: number): number {
178
+ return Math.min(Math.max(value, -MAX_LIVE_PAGE_PREVIEW_WHEEL_DELTA), MAX_LIVE_PAGE_PREVIEW_WHEEL_DELTA);
179
+ }
@@ -0,0 +1,67 @@
1
+ import { serializeError } from '@promptbook-local/utils';
2
+ import type { NextRequest } from 'next/server';
3
+ import { NextResponse } from 'next/server';
4
+ import { assertsError } from '../../../../../../../src/errors/assertsError';
5
+ import {
6
+ createLivePagePreviewStream,
7
+ getOrCreateLivePagePreviewSession,
8
+ isLivePagePreviewSessionId,
9
+ LIVE_PAGE_PREVIEW_STREAM_CONTENT_TYPE,
10
+ } from '../../../../utils/pagePreview/livePagePreviewSessions';
11
+ import { resolvePagePreviewRequestUrl } from '../../../../utils/pagePreview/resolvePagePreviewRequestUrl';
12
+
13
+ /**
14
+ * Query parameter containing the browser-side live preview session id.
15
+ */
16
+ const LIVE_PAGE_PREVIEW_SESSION_ID_QUERY_PARAMETER = 'sessionId';
17
+
18
+ /**
19
+ * Constant for dynamic route rendering.
20
+ */
21
+ export const dynamic = 'force-dynamic';
22
+
23
+ /**
24
+ * Streams a live browser preview for a knowledge source URL.
25
+ *
26
+ * Query parameters:
27
+ * - `url` — fully-qualified HTTP(S) URL to preview
28
+ * - `sessionId` — client-generated id used for forwarding interactions
29
+ *
30
+ * Requires authentication to prevent unauthenticated SSRF abuse.
31
+ */
32
+ export async function GET(request: NextRequest): Promise<NextResponse> {
33
+ const requestUrlResolution = await resolvePagePreviewRequestUrl(request);
34
+ if (requestUrlResolution.errorResponse !== null) {
35
+ return requestUrlResolution.errorResponse;
36
+ }
37
+
38
+ const sessionId = request.nextUrl.searchParams.get(LIVE_PAGE_PREVIEW_SESSION_ID_QUERY_PARAMETER);
39
+ if (!sessionId || !isLivePagePreviewSessionId(sessionId)) {
40
+ return NextResponse.json({ error: 'Missing or invalid live preview session id.' }, { status: 400 });
41
+ }
42
+
43
+ try {
44
+ const page = await getOrCreateLivePagePreviewSession({
45
+ sessionId,
46
+ url: requestUrlResolution.url,
47
+ signal: request.signal,
48
+ });
49
+ const stream = createLivePagePreviewStream({
50
+ sessionId,
51
+ page,
52
+ signal: request.signal,
53
+ });
54
+
55
+ return new NextResponse(stream, {
56
+ headers: {
57
+ 'Content-Type': LIVE_PAGE_PREVIEW_STREAM_CONTENT_TYPE,
58
+ 'Cache-Control': 'no-cache, no-store, max-age=0',
59
+ Connection: 'keep-alive',
60
+ },
61
+ });
62
+ } catch (error) {
63
+ assertsError(error);
64
+ console.error('Error streaming live page preview:', error);
65
+ return NextResponse.json({ error: serializeError(error) }, { status: 500 });
66
+ }
67
+ }
@@ -2,9 +2,12 @@ import { $provideBrowserForServer } from '@/src/tools/$provideBrowserForServer';
2
2
  import { serializeError } from '@promptbook-local/utils';
3
3
  import type { NextRequest } from 'next/server';
4
4
  import { NextResponse } from 'next/server';
5
+ import {
6
+ LIVE_PAGE_PREVIEW_VIEWPORT_HEIGHT,
7
+ LIVE_PAGE_PREVIEW_VIEWPORT_WIDTH,
8
+ } from '../../../../../../../src/book-components/Chat/utils/livePagePreviewConstants';
5
9
  import { assertsError } from '../../../../../../../src/errors/assertsError';
6
- import { assertSafeUrl } from '../../../../utils/assertSafeUrl';
7
- import { getCurrentUser } from '../../../../utils/getCurrentUser';
10
+ import { resolvePagePreviewRequestUrl } from '../../../../utils/pagePreview/resolvePagePreviewRequestUrl';
8
11
 
9
12
  /**
10
13
  * Takes a screenshot of the given URL using a headless browser.
@@ -18,23 +21,9 @@ import { getCurrentUser } from '../../../../utils/getCurrentUser';
18
21
  * The destination URL is validated against private/internal IP ranges before fetching.
19
22
  */
20
23
  export async function GET(request: NextRequest): Promise<NextResponse> {
21
- const currentUser = await getCurrentUser();
22
- if (!currentUser) {
23
- return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
24
- }
25
-
26
- const url = request.nextUrl.searchParams.get('url');
27
-
28
- if (!url) {
29
- return NextResponse.json({ error: 'Missing required query parameter: url' }, { status: 400 });
30
- }
31
-
32
- // Guard against SSRF: reject private/internal IPs and non-HTTP(S) schemes
33
- try {
34
- assertSafeUrl(url);
35
- } catch (error) {
36
- assertsError(error);
37
- return NextResponse.json({ error: error.message }, { status: 400 });
24
+ const requestUrlResolution = await resolvePagePreviewRequestUrl(request);
25
+ if (requestUrlResolution.errorResponse !== null) {
26
+ return requestUrlResolution.errorResponse;
38
27
  }
39
28
 
40
29
  try {
@@ -42,8 +31,11 @@ export async function GET(request: NextRequest): Promise<NextResponse> {
42
31
  const page = await browserContext.newPage();
43
32
 
44
33
  try {
45
- await page.setViewportSize({ width: 1280, height: 800 });
46
- await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30_000 });
34
+ await page.setViewportSize({
35
+ width: LIVE_PAGE_PREVIEW_VIEWPORT_WIDTH,
36
+ height: LIVE_PAGE_PREVIEW_VIEWPORT_HEIGHT,
37
+ });
38
+ await page.goto(requestUrlResolution.url, { waitUntil: 'domcontentloaded', timeout: 30_000 });
47
39
  const screenshotBuffer = await page.screenshot({ type: 'png' });
48
40
 
49
41
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -18,6 +18,7 @@ import {
18
18
  ManagementAgentPathParamsSchema,
19
19
  ManagementAgentUpdateRequestSchema,
20
20
  } from '@/src/utils/managementApi/managementApiSchemas';
21
+ import { setAgentSourceVisibility } from '@/src/utils/agentVisibility';
21
22
  import {
22
23
  createManagementApiErrorResponse,
23
24
  createManagementApiJsonResponse,
@@ -34,7 +35,6 @@ type ManagementAgentUpdateRequest = z.infer<typeof ManagementAgentUpdateRequestS
34
35
  * Agent metadata fields updated directly in the database during `PATCH`.
35
36
  */
36
37
  type ManagementAgentMetadataUpdate = {
37
- visibility?: ManagementAgentUpdateRequest['visibility'];
38
38
  folderId?: ManagementAgentUpdateRequest['folderId'];
39
39
  sortOrder?: ManagementAgentUpdateRequest['sortOrder'];
40
40
  updatedAt?: string;
@@ -259,7 +259,11 @@ async function updateOwnedAgentSourceIfNeeded(
259
259
  existingAgent: OwnedAgentRow,
260
260
  updateRequest: ManagementAgentUpdateRequest,
261
261
  ): Promise<void> {
262
- if (updateRequest.name === undefined && updateRequest.source === undefined) {
262
+ if (
263
+ updateRequest.name === undefined &&
264
+ updateRequest.source === undefined &&
265
+ updateRequest.visibility === undefined
266
+ ) {
263
267
  return;
264
268
  }
265
269
 
@@ -280,11 +284,15 @@ function createUpdatedOwnedAgentSource(
280
284
  updateRequest: ManagementAgentUpdateRequest,
281
285
  ): string_book {
282
286
  const sourceWithRequestedContent = (updateRequest.source || existingAgent.agentSource) as string_book;
283
- if (!updateRequest.name) {
284
- return sourceWithRequestedContent;
287
+ const sourceWithRequestedName = updateRequest.name
288
+ ? renameAgentSource(sourceWithRequestedContent, updateRequest.name)
289
+ : sourceWithRequestedContent;
290
+
291
+ if (updateRequest.visibility === undefined) {
292
+ return sourceWithRequestedName;
285
293
  }
286
294
 
287
- return renameAgentSource(sourceWithRequestedContent, updateRequest.name);
295
+ return setAgentSourceVisibility(sourceWithRequestedName, updateRequest.visibility);
288
296
  }
289
297
 
290
298
  /**
@@ -302,9 +310,6 @@ async function createOwnedAgentMetadataUpdate(
302
310
  ): Promise<ManagementAgentMetadataUpdate> {
303
311
  const metadataUpdate: ManagementAgentMetadataUpdate = {};
304
312
 
305
- if (updateRequest.visibility !== undefined) {
306
- metadataUpdate.visibility = updateRequest.visibility;
307
- }
308
313
  if (updateRequest.folderId !== undefined) {
309
314
  metadataUpdate.folderId = updateRequest.folderId;
310
315
  }
@@ -18,6 +18,7 @@ import {
18
18
  Users,
19
19
  } from 'lucide-react';
20
20
  import NextLink from 'next/link';
21
+ import { isVoidPseudoAgentReference } from '../../../../../src/book-2.0/agent-source/pseudoAgentReferences';
21
22
  import { simplifyKnowledgeLabel } from '@/src/utils/knowledge/simplifyKnowledgeLabel';
22
23
  import { buildFreshAgentChatHrefFromAgentUrl } from '../../utils/agentRouting/agentRouteHrefs';
23
24
  import { TeamCommitmentChip } from './TeamCommitmentChip';
@@ -32,6 +33,11 @@ export const HOMEPAGE_CAPABILITY_CHIPS_LIMIT = 3;
32
33
  */
33
34
  export const AGENT_PROFILE_CAPABILITY_CHIPS_LIMIT = 7;
34
35
 
36
+ /**
37
+ * Capability types omitted from homepage agent cards.
38
+ */
39
+ export const AGENT_CARD_HIDDEN_CAPABILITY_TYPES: ReadonlyArray<AgentCapability['type']> = ['time'];
40
+
35
41
  /**
36
42
  * Constant for max individual knowledge chips.
37
43
  */
@@ -82,6 +88,11 @@ type AgentCapabilityChipsProps = {
82
88
  * Visual size preset for chip rendering.
83
89
  */
84
90
  readonly size?: AgentCapabilityChipsSize;
91
+
92
+ /**
93
+ * Capability types that should be omitted from this chip list.
94
+ */
95
+ readonly hiddenCapabilityTypes?: ReadonlyArray<AgentCapability['type']>;
85
96
  };
86
97
 
87
98
  /**
@@ -107,13 +118,23 @@ type OrderedCapability = {
107
118
  /**
108
119
  * Render capability chips for an agent with priority, grouping, and limits.
109
120
  */
110
- export function AgentCapabilityChips({ agent, className, maxChips, size = 'default' }: AgentCapabilityChipsProps) {
121
+ export function AgentCapabilityChips({
122
+ agent,
123
+ className,
124
+ maxChips,
125
+ size = 'default',
126
+ hiddenCapabilityTypes = [],
127
+ }: AgentCapabilityChipsProps) {
111
128
  if (!agent.capabilities || agent.capabilities.length === 0) {
112
129
  return null;
113
130
  }
114
131
 
115
132
  const maxChipsToDisplay = maxChips ?? AGENT_PROFILE_CAPABILITY_CHIPS_LIMIT;
116
- const displayedCapabilities = selectCapabilitiesForDisplay(agent.capabilities, maxChipsToDisplay);
133
+ const displayedCapabilities = selectCapabilitiesForDisplay(
134
+ agent.capabilities,
135
+ maxChipsToDisplay,
136
+ hiddenCapabilityTypes,
137
+ );
117
138
 
118
139
  if (displayedCapabilities.length === 0) {
119
140
  return null;
@@ -228,12 +249,13 @@ export function AgentCapabilityChips({ agent, className, maxChips, size = 'defau
228
249
  function selectCapabilitiesForDisplay(
229
250
  capabilities: Array<AgentCapability>,
230
251
  maxChips: number,
252
+ hiddenCapabilityTypes: ReadonlyArray<AgentCapability['type']>,
231
253
  ): Array<AgentCapability> {
232
254
  if (maxChips <= 0) {
233
255
  return [];
234
256
  }
235
257
 
236
- const normalizedCapabilities = normalizeCapabilities(capabilities);
258
+ const normalizedCapabilities = normalizeCapabilities(capabilities, hiddenCapabilityTypes);
237
259
  const groupedCapabilities = groupKnowledgeCapabilities(normalizedCapabilities);
238
260
  const prioritizedCapabilities = groupedCapabilities
239
261
  .map((item, index) => ({
@@ -263,11 +285,23 @@ function selectCapabilitiesForDisplay(
263
285
  * @param capabilities - Raw capability list from the agent.
264
286
  * @returns Normalized capabilities with ordering metadata.
265
287
  */
266
- function normalizeCapabilities(capabilities: Array<AgentCapability>): Array<OrderedCapability> {
288
+ function normalizeCapabilities(
289
+ capabilities: Array<AgentCapability>,
290
+ hiddenCapabilityTypes: ReadonlyArray<AgentCapability['type']>,
291
+ ): Array<OrderedCapability> {
292
+ const hiddenCapabilityTypeSet = new Set(hiddenCapabilityTypes);
293
+
267
294
  return capabilities
268
295
  .map((capability, index) => ({ capability, order: index }))
269
296
  .filter((item) => {
270
- if (item.capability.type === 'inheritance' && item.capability.agentUrl === 'VOID') {
297
+ if (hiddenCapabilityTypeSet.has(item.capability.type)) {
298
+ return false;
299
+ }
300
+
301
+ if (
302
+ item.capability.type === 'inheritance' &&
303
+ isVoidPseudoAgentReference(item.capability.agentUrl || item.capability.label)
304
+ ) {
271
305
  return false;
272
306
  }
273
307
 
@@ -5,7 +5,11 @@ import { CheckCircle2Icon, EyeIcon, EyeOffIcon, LockIcon, RotateCcwIcon } from '
5
5
  import Link from 'next/link';
6
6
  import { AgentBasicInformation } from '../../../../../src/book-2.0/agent-source/AgentBasicInformation';
7
7
  import type { AgentVisibility } from '../../utils/agentVisibility';
8
- import { AgentCapabilityChips, HOMEPAGE_CAPABILITY_CHIPS_LIMIT } from '../AgentProfile/AgentCapabilityChips';
8
+ import {
9
+ AGENT_CARD_HIDDEN_CAPABILITY_TYPES,
10
+ AgentCapabilityChips,
11
+ HOMEPAGE_CAPABILITY_CHIPS_LIMIT,
12
+ } from '../AgentProfile/AgentCapabilityChips';
9
13
  import { useAgentBackground } from '../AgentProfile/useAgentBackground';
10
14
  import { useAgentNaming } from '../AgentNaming/AgentNamingContext';
11
15
  import { AgentAvatar } from '../AgentAvatar/AgentAvatar';
@@ -153,6 +157,7 @@ export function AgentCard({
153
157
  <AgentCapabilityChips
154
158
  agent={agent}
155
159
  className="mt-2"
160
+ hiddenCapabilityTypes={AGENT_CARD_HIDDEN_CAPABILITY_TYPES}
156
161
  maxChips={HOMEPAGE_CAPABILITY_CHIPS_LIMIT}
157
162
  size="compact"
158
163
  />
@@ -202,6 +207,7 @@ export function AgentCard({
202
207
  <AgentCapabilityChips
203
208
  agent={agent}
204
209
  className="mt-2"
210
+ hiddenCapabilityTypes={AGENT_CARD_HIDDEN_CAPABILITY_TYPES}
205
211
  maxChips={HOMEPAGE_CAPABILITY_CHIPS_LIMIT}
206
212
  size="compact"
207
213
  />
@@ -91,13 +91,16 @@ export function AgentsList(props: AgentsListProps) {
91
91
  allAgentsLabel={state.allAgentsLabel}
92
92
  breadcrumbFolders={state.breadcrumbFolders}
93
93
  canOrganize={canOrganize}
94
+ hasHiddenFolders={state.hasHiddenFolders}
94
95
  headingTitle={state.headingTitle}
95
96
  isAdmin={isAdmin}
96
97
  isAgentsExporting={state.isAgentsExporting}
97
98
  isAgentsImporting={state.isAgentsImporting}
99
+ isHiddenFoldersVisible={state.isHiddenFoldersVisible}
98
100
  onCreateFolder={state.handleCreateFolder}
99
101
  onExportAgents={() => void state.handleAgentsExport()}
100
102
  onNavigateToFolder={state.navigateToFolder}
103
+ onSetHiddenFoldersVisible={state.setHiddenFoldersVisible}
101
104
  onSetViewMode={state.setViewMode}
102
105
  viewMode={state.viewMode}
103
106
  />
@@ -3,6 +3,8 @@
3
3
  import {
4
4
  Building2,
5
5
  DownloadIcon,
6
+ Eye,
7
+ EyeOff,
6
8
  FolderPlusIcon,
7
9
  Gamepad2,
8
10
  Grid,
@@ -60,6 +62,10 @@ type AgentsListHeaderProps = {
60
62
  * Whether folder-management controls are available.
61
63
  */
62
64
  readonly canOrganize: boolean;
65
+ /**
66
+ * Whether the unfiltered organization snapshot contains at least one hidden folder.
67
+ */
68
+ readonly hasHiddenFolders: boolean;
63
69
  /**
64
70
  * Current heading title.
65
71
  */
@@ -68,6 +74,10 @@ type AgentsListHeaderProps = {
68
74
  * Whether agents import/export controls are available.
69
75
  */
70
76
  readonly isAdmin: boolean;
77
+ /**
78
+ * Whether hidden folders are currently rendered in the agents list.
79
+ */
80
+ readonly isHiddenFoldersVisible: boolean;
71
81
  /**
72
82
  * Downloads an agents archive.
73
83
  */
@@ -80,6 +90,10 @@ type AgentsListHeaderProps = {
80
90
  * Navigates to the selected folder scope.
81
91
  */
82
92
  readonly onNavigateToFolder: (folderId: number | null) => void;
93
+ /**
94
+ * Toggles whether hidden folders are visible in the agents list.
95
+ */
96
+ readonly onSetHiddenFoldersVisible: (isVisible: boolean) => void;
83
97
  /**
84
98
  * Switches the homepage view mode.
85
99
  */
@@ -124,13 +138,16 @@ export function AgentsListHeader({
124
138
  allAgentsLabel,
125
139
  breadcrumbFolders,
126
140
  canOrganize,
141
+ hasHiddenFolders,
127
142
  headingTitle,
128
143
  isAdmin,
129
144
  isAgentsExporting,
130
145
  isAgentsImporting,
146
+ isHiddenFoldersVisible,
131
147
  onCreateFolder,
132
148
  onExportAgents,
133
149
  onNavigateToFolder,
150
+ onSetHiddenFoldersVisible,
134
151
  onSetViewMode,
135
152
  viewMode,
136
153
  }: AgentsListHeaderProps) {
@@ -192,6 +209,30 @@ export function AgentsListHeader({
192
209
  New Folder
193
210
  </button>
194
211
  )}
212
+ {hasHiddenFolders && (
213
+ <button
214
+ type="button"
215
+ onClick={() => onSetHiddenFoldersVisible(!isHiddenFoldersVisible)}
216
+ className={`flex items-center gap-2 px-3 py-2 rounded-lg border text-sm transition-colors ${
217
+ isHiddenFoldersVisible
218
+ ? 'border-blue-200 bg-blue-50 text-blue-700 hover:bg-blue-100'
219
+ : 'border-gray-200 bg-white text-gray-700 hover:bg-gray-50'
220
+ }`}
221
+ title={
222
+ isHiddenFoldersVisible
223
+ ? 'Hide folders whose name starts with "."'
224
+ : 'Show folders whose name starts with "."'
225
+ }
226
+ aria-pressed={isHiddenFoldersVisible}
227
+ >
228
+ {isHiddenFoldersVisible ? (
229
+ <EyeOff className="w-4 h-4" />
230
+ ) : (
231
+ <Eye className="w-4 h-4" />
232
+ )}
233
+ {isHiddenFoldersVisible ? 'Hide hidden' : 'Show hidden'}
234
+ </button>
235
+ )}
195
236
  <div className="flex items-center gap-2 bg-gray-100 p-1 rounded-lg ml-4">
196
237
  {VIEW_MODE_BUTTONS.map(({ mode, label, title, icon: Icon }) => (
197
238
  <button