@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.
- package/README.md +45 -29
- package/agents/default/developer.book +2 -1
- package/apps/agents-server/next.config.ts +21 -0
- package/apps/agents-server/src/app/admin/limits/LimitsClient.tsx +1 -1
- package/apps/agents-server/src/app/admin/task-manager/TaskManagerTaskRow.tsx +7 -2
- package/apps/agents-server/src/app/admin/update/UpdateClient.tsx +52 -33
- package/apps/agents-server/src/app/api/agent-folders/[folderId]/visibility/route.ts +19 -7
- package/apps/agents-server/src/app/api/agents/[agentName]/route.ts +8 -18
- package/apps/agents-server/src/app/api/internal/agent-runner-limits/route.ts +1 -1
- package/apps/agents-server/src/app/api/page-preview/check/route.ts +5 -20
- package/apps/agents-server/src/app/api/page-preview/interact/route.ts +179 -0
- package/apps/agents-server/src/app/api/page-preview/live/route.ts +67 -0
- package/apps/agents-server/src/app/api/page-preview/screenshot/route.ts +13 -21
- package/apps/agents-server/src/app/api/v1/agents/[agentId]/route.ts +13 -8
- package/apps/agents-server/src/components/AgentProfile/AgentCapabilityChips.tsx +39 -5
- package/apps/agents-server/src/components/Homepage/AgentCard.tsx +7 -1
- package/apps/agents-server/src/components/Homepage/AgentsList.tsx +3 -0
- package/apps/agents-server/src/components/Homepage/AgentsListHeader.tsx +41 -0
- package/apps/agents-server/src/components/Homepage/hiddenFolders.ts +104 -0
- package/apps/agents-server/src/components/Homepage/useAgentsListQueryState.ts +35 -0
- package/apps/agents-server/src/components/Homepage/useAgentsListState.ts +24 -4
- package/apps/agents-server/src/constants/chatVisualMode.ts +1 -1
- package/apps/agents-server/src/constants/serverLimits.ts +19 -0
- package/apps/agents-server/src/constants/themeMode.ts +1 -1
- package/apps/agents-server/src/database/migrations/2026-05-2600-default-theme.sql +2 -2
- package/apps/agents-server/src/database/seedCoreAgents.ts +0 -2
- package/apps/agents-server/src/database/seedDefaultAgents.ts +0 -2
- package/apps/agents-server/src/tools/agent_progress.ts +4 -10
- package/apps/agents-server/src/utils/agentVisibility.ts +25 -62
- package/apps/agents-server/src/utils/chatTasksAdmin.ts +3 -1
- package/apps/agents-server/src/utils/createAgentWithDefaultVisibility.ts +3 -1
- package/apps/agents-server/src/utils/externalChatRunner/processExternalUserChatJob.ts +9 -17
- package/apps/agents-server/src/utils/getAdminChatTasksResponse/mapVpsSelfUpdateJobToAdminChatTask.ts +84 -0
- package/apps/agents-server/src/utils/getAdminChatTasksResponse.ts +195 -5
- package/apps/agents-server/src/utils/localChatRunner/LocalUserChatJobMetadata.ts +15 -5
- package/apps/agents-server/src/utils/localChatRunner/processLocalUserChatJob.ts +17 -26
- package/apps/agents-server/src/utils/pagePreview/livePagePreviewSessions.ts +365 -0
- package/apps/agents-server/src/utils/pagePreview/resolvePagePreviewRequestUrl.ts +74 -0
- package/apps/agents-server/src/utils/serverLimits.ts +2 -0
- package/apps/agents-server/src/utils/serverManagement/createManagedServer/seedServerCoreAgents.ts +2 -2
- package/apps/agents-server/src/utils/serverManagement/createManagedServer/seedServerDefaultAgents.ts +1 -1
- package/apps/agents-server/src/utils/userChat/createUserChatHarnessProgressCard.ts +0 -6
- package/apps/agents-server/src/utils/userChat/createUserChatRunnerProgressCard.ts +0 -6
- package/apps/agents-server/src/utils/userChat/userChatMessageLifecycle.ts +69 -0
- package/apps/agents-server/src/utils/userChat/userChatProgressCard.ts +0 -6
- package/apps/agents-server/src/utils/vpsSelfUpdate.ts +150 -24
- package/esm/apps/agents-server/src/constants/serverLimits.d.ts +17 -0
- package/esm/index.es.js +581 -93
- package/esm/index.es.js.map +1 -1
- package/esm/scripts/run-agent-messages/main/runMultipleAgentMessages.d.ts +1 -0
- package/esm/scripts/run-agent-messages/main/tickAgentMessages.d.ts +1 -0
- package/esm/src/_packages/core.index.d.ts +22 -0
- package/esm/src/_packages/types.index.d.ts +2 -0
- package/esm/src/book-2.0/agent-source/AgentBasicInformation.d.ts +2 -0
- package/esm/src/book-2.0/agent-source/agentSourceVisibility.d.ts +97 -0
- package/esm/src/book-2.0/agent-source/agentSourceVisibility.test.d.ts +1 -0
- package/esm/src/book-components/BookEditor/BookEditorMonacoTokenization.d.ts +1 -0
- package/esm/src/book-components/Chat/Chat/CitationIframePreview.d.ts +2 -2
- package/esm/src/book-components/Chat/utils/livePagePreviewConstants.d.ts +12 -0
- package/esm/src/cli/cli-commands/agents-server/buildAgentsServer.d.ts +6 -0
- package/esm/src/collection/agent-collection/CreateAgentInput.d.ts +2 -1
- package/esm/src/collection/agent-collection/constructors/agent-collection-in-supabase/prepareAgentSourceForPersistence.d.ts +21 -1
- package/esm/src/commitments/META_VISIBILITY/META_VISIBILITY.d.ts +27 -0
- package/esm/src/commitments/index.d.ts +2 -1
- package/esm/src/version.d.ts +1 -1
- package/package.json +1 -1
- package/src/_packages/core.index.ts +22 -0
- package/src/_packages/types.index.ts +2 -0
- package/src/book-2.0/agent-source/AgentBasicInformation.ts +2 -0
- package/src/book-2.0/agent-source/agentSourceVisibility.ts +214 -0
- package/src/book-2.0/agent-source/parseAgentSource/applyMetaCommitment.ts +17 -0
- package/src/book-2.0/book-language-documentation/createStandaloneBookLanguageMarkdown.ts +1 -0
- package/src/book-components/BookEditor/BookEditorMonacoTokenization.ts +11 -0
- package/src/book-components/BookEditor/useBookEditorMonacoLanguage.ts +7 -3
- package/src/book-components/Chat/Chat/Chat.module.css +14 -5
- package/src/book-components/Chat/Chat/CitationIframePreview.tsx +197 -22
- package/src/book-components/Chat/utils/livePagePreviewConstants.ts +17 -0
- package/src/cli/cli-commands/agents-server/buildAgentsServer.ts +144 -16
- package/src/cli/cli-commands/agents-server/startAgentsServer.ts +22 -2
- package/src/cli/cli-commands/coder/getDefaultCoderPackageJsonScripts.ts +1 -1
- package/src/cli/cli-commands/coder/run.ts +30 -0
- package/src/collection/agent-collection/CreateAgentInput.ts +10 -5
- package/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentCollectionInSupabase.ts +7 -3
- package/src/collection/agent-collection/constructors/agent-collection-in-supabase/createAgentPersistenceRecords.ts +7 -4
- package/src/collection/agent-collection/constructors/agent-collection-in-supabase/prepareAgentSourceForPersistence.ts +44 -8
- package/src/commitments/META_VISIBILITY/META_VISIBILITY.ts +78 -0
- package/src/commitments/index.ts +2 -0
- package/src/other/templates/getTemplatesPipelineCollection.ts +826 -650
- package/src/version.ts +2 -2
- package/src/versions.txt +2 -0
- package/umd/apps/agents-server/src/constants/serverLimits.d.ts +17 -0
- package/umd/index.umd.js +581 -93
- package/umd/index.umd.js.map +1 -1
- package/umd/scripts/run-agent-messages/main/runMultipleAgentMessages.d.ts +1 -0
- package/umd/scripts/run-agent-messages/main/tickAgentMessages.d.ts +1 -0
- package/umd/src/_packages/core.index.d.ts +22 -0
- package/umd/src/_packages/types.index.d.ts +2 -0
- package/umd/src/book-2.0/agent-source/AgentBasicInformation.d.ts +2 -0
- package/umd/src/book-2.0/agent-source/agentSourceVisibility.d.ts +97 -0
- package/umd/src/book-2.0/agent-source/agentSourceVisibility.test.d.ts +1 -0
- package/umd/src/book-components/BookEditor/BookEditorMonacoTokenization.d.ts +1 -0
- package/umd/src/book-components/Chat/Chat/CitationIframePreview.d.ts +2 -2
- package/umd/src/book-components/Chat/utils/livePagePreviewConstants.d.ts +12 -0
- package/umd/src/cli/cli-commands/agents-server/buildAgentsServer.d.ts +6 -0
- package/umd/src/collection/agent-collection/CreateAgentInput.d.ts +2 -1
- package/umd/src/collection/agent-collection/constructors/agent-collection-in-supabase/prepareAgentSourceForPersistence.d.ts +21 -1
- package/umd/src/commitments/META_VISIBILITY/META_VISIBILITY.d.ts +27 -0
- package/umd/src/commitments/index.d.ts +2 -1
- package/umd/src/version.d.ts +1 -1
|
@@ -3,6 +3,7 @@ import { resolveAvatarVisualId } from '../../../avatars/visuals/avatarVisualRegi
|
|
|
3
3
|
import { normalizeTo_camelCase } from '../../../utils/normalization/normalizeTo_camelCase';
|
|
4
4
|
import { normalizeDomainForMatching } from '../../../utils/validators/url/normalizeDomainForMatching';
|
|
5
5
|
import type { ParsedCommitment } from '../../../commitments/_base/ParsedCommitment';
|
|
6
|
+
import { normalizeAgentVisibility } from '../agentSourceVisibility';
|
|
6
7
|
import type { ParseAgentSourceState } from './ParseAgentSourceState';
|
|
7
8
|
|
|
8
9
|
/**
|
|
@@ -25,6 +26,7 @@ const META_COMMITMENT_APPLIERS: Readonly<Record<string, MetaCommitmentApplier |
|
|
|
25
26
|
'META COLOR': applyMetaColorContent,
|
|
26
27
|
'META FONT': applyMetaFontContent,
|
|
27
28
|
'META VOICE': applyMetaVoiceContent,
|
|
29
|
+
'META VISIBILITY': applyMetaVisibilityContent,
|
|
28
30
|
};
|
|
29
31
|
|
|
30
32
|
/**
|
|
@@ -60,6 +62,11 @@ function applyGenericMetaCommitment(state: ParseAgentSourceState, content: strin
|
|
|
60
62
|
return;
|
|
61
63
|
}
|
|
62
64
|
|
|
65
|
+
if (metaTypeRaw.toUpperCase() === 'VISIBILITY') {
|
|
66
|
+
applyMetaVisibilityContent(state, metaValue);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
63
70
|
const metaType = normalizeTo_camelCase(metaTypeRaw);
|
|
64
71
|
state.meta[metaType] = metaValue;
|
|
65
72
|
}
|
|
@@ -149,6 +156,16 @@ function applyMetaVoiceContent(state: ParseAgentSourceState, content: string): v
|
|
|
149
156
|
state.meta.voice = spaceTrim(content);
|
|
150
157
|
}
|
|
151
158
|
|
|
159
|
+
/**
|
|
160
|
+
* Applies META VISIBILITY content into the normalized `meta.visibility` field.
|
|
161
|
+
*/
|
|
162
|
+
function applyMetaVisibilityContent(state: ParseAgentSourceState, content: string): void {
|
|
163
|
+
const visibility = normalizeAgentVisibility(content);
|
|
164
|
+
if (visibility) {
|
|
165
|
+
state.meta.visibility = visibility;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
152
169
|
/**
|
|
153
170
|
* Normalizes the separator in the content
|
|
154
171
|
*
|
|
@@ -21,6 +21,16 @@ const AGENT_REFERENCE_AT_PATTERN = '(?<!\\S)@[A-Za-z0-9_-]+';
|
|
|
21
21
|
*/
|
|
22
22
|
const AGENT_REFERENCE_BRACED_PATTERN = '\\{[^{}\\r\\n]+\\}';
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Pattern matching plain inline `@` tokens, for example email addresses.
|
|
26
|
+
*
|
|
27
|
+
* Monaco tokenizes from the current offset, so `team@foo.bar` can otherwise reach
|
|
28
|
+
* the `@foo` suffix and look like a standalone compact reference.
|
|
29
|
+
*
|
|
30
|
+
* @private function of BookEditorMonaco
|
|
31
|
+
*/
|
|
32
|
+
const NON_AGENT_REFERENCE_INLINE_AT_TOKEN_REGEX = /[^\s{}]*[^\s@{}]@[^\s{}]+/;
|
|
33
|
+
|
|
24
34
|
/**
|
|
25
35
|
* Commitment types where compact agent references are supported.
|
|
26
36
|
*
|
|
@@ -334,6 +344,7 @@ export const BookEditorMonacoTokenization = {
|
|
|
334
344
|
AGENT_URL_REFERENCE_REGEX,
|
|
335
345
|
AGENT_REFERENCE_TOKEN_REGEX,
|
|
336
346
|
AGENT_REFERENCE_BRACED_REGEX,
|
|
347
|
+
NON_AGENT_REFERENCE_INLINE_AT_TOKEN_REGEX,
|
|
337
348
|
AGENT_REFERENCE_HIGHLIGHT_REGEXES,
|
|
338
349
|
extractAgentReferenceValue,
|
|
339
350
|
resolveAgentReferenceToUrl,
|
|
@@ -312,14 +312,17 @@ export function ensureBookEditorMonacoLanguage(
|
|
|
312
312
|
commitmentRegex,
|
|
313
313
|
);
|
|
314
314
|
|
|
315
|
-
const
|
|
315
|
+
const PARAMETER_REGEX = /@([a-zA-Z0-9_á-žÁ-Žč-řČ-Řš-žŠ-Žа-яА-ЯёЁ]+)/;
|
|
316
|
+
const NON_AGENT_REFERENCE_INLINE_AT_TOKEN_REGEX =
|
|
317
|
+
BookEditorMonacoTokenization.NON_AGENT_REFERENCE_INLINE_AT_TOKEN_REGEX;
|
|
316
318
|
|
|
317
319
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
318
320
|
const defaultBodyRules: any = [
|
|
319
321
|
[/^---[-]*$/, ''],
|
|
320
322
|
[CODE_BLOCK_FENCE_REGEX, 'code-block', '@codeblock'],
|
|
321
323
|
...commitmentTransitionRules,
|
|
322
|
-
[
|
|
324
|
+
[NON_AGENT_REFERENCE_INLINE_AT_TOKEN_REGEX, ''],
|
|
325
|
+
[PARAMETER_REGEX, 'parameter'],
|
|
323
326
|
[/\{[^}]+\}/, 'parameter'],
|
|
324
327
|
];
|
|
325
328
|
|
|
@@ -329,7 +332,8 @@ export function ensureBookEditorMonacoLanguage(
|
|
|
329
332
|
[CODE_BLOCK_FENCE_REGEX, 'code-block', '@codeblock'],
|
|
330
333
|
...commitmentTransitionRules,
|
|
331
334
|
...BookEditorMonacoTokenization.AGENT_REFERENCE_HIGHLIGHT_REGEXES.map((regex) => [regex, 'agent-reference']),
|
|
332
|
-
[
|
|
335
|
+
[NON_AGENT_REFERENCE_INLINE_AT_TOKEN_REGEX, ''],
|
|
336
|
+
[PARAMETER_REGEX, 'parameter'],
|
|
333
337
|
[/\{[^}]+\}/, 'parameter'],
|
|
334
338
|
];
|
|
335
339
|
|
|
@@ -3322,23 +3322,32 @@
|
|
|
3322
3322
|
font-size: 0.9em;
|
|
3323
3323
|
}
|
|
3324
3324
|
|
|
3325
|
-
.
|
|
3325
|
+
.citationBrowserPreviewFallback {
|
|
3326
3326
|
width: 100%;
|
|
3327
3327
|
position: relative;
|
|
3328
3328
|
display: flex;
|
|
3329
3329
|
flex-direction: column;
|
|
3330
3330
|
}
|
|
3331
3331
|
|
|
3332
|
-
.
|
|
3332
|
+
.citationBrowserPreviewImage {
|
|
3333
3333
|
width: 100%;
|
|
3334
|
+
aspect-ratio: 16 / 10;
|
|
3334
3335
|
max-height: 60vh;
|
|
3335
3336
|
min-height: 400px;
|
|
3336
|
-
object-fit:
|
|
3337
|
+
object-fit: contain;
|
|
3337
3338
|
object-position: top;
|
|
3338
3339
|
display: block;
|
|
3340
|
+
background: #fff;
|
|
3341
|
+
cursor: crosshair;
|
|
3342
|
+
outline: none;
|
|
3343
|
+
touch-action: none;
|
|
3344
|
+
}
|
|
3345
|
+
|
|
3346
|
+
.citationBrowserPreviewImage:focus-visible {
|
|
3347
|
+
box-shadow: inset 0 0 0 3px rgba(0, 112, 243, 0.35);
|
|
3339
3348
|
}
|
|
3340
3349
|
|
|
3341
|
-
.
|
|
3350
|
+
.citationBrowserPreviewLink {
|
|
3342
3351
|
display: block;
|
|
3343
3352
|
text-align: center;
|
|
3344
3353
|
padding: 10px 16px;
|
|
@@ -3351,7 +3360,7 @@
|
|
|
3351
3360
|
transition: background 0.15s;
|
|
3352
3361
|
}
|
|
3353
3362
|
|
|
3354
|
-
.
|
|
3363
|
+
.citationBrowserPreviewLink:hover {
|
|
3355
3364
|
background: #e0e0e0;
|
|
3356
3365
|
text-decoration: underline;
|
|
3357
3366
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { useEffect, useState } from 'react';
|
|
3
|
+
import { useCallback, useEffect, useId, useMemo, useState } from 'react';
|
|
4
|
+
import type { KeyboardEvent, MouseEvent, WheelEvent } from 'react';
|
|
5
|
+
import { LIVE_PAGE_PREVIEW_VIEWPORT_HEIGHT, LIVE_PAGE_PREVIEW_VIEWPORT_WIDTH } from '../utils/livePagePreviewConstants';
|
|
4
6
|
import styles from './Chat.module.css';
|
|
5
7
|
|
|
6
8
|
/**
|
|
@@ -18,12 +20,48 @@ export type CitationIframePreviewProps = {
|
|
|
18
20
|
*
|
|
19
21
|
* @private type of `<CitationIframePreview/>`
|
|
20
22
|
*/
|
|
21
|
-
type EmbedStatus = 'loading' | 'embed' | '
|
|
23
|
+
type EmbedStatus = 'loading' | 'embed' | 'browser';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Browser-preview interaction sent to the Agents Server preview route.
|
|
27
|
+
*
|
|
28
|
+
* @private type of `<CitationIframePreview/>`
|
|
29
|
+
*/
|
|
30
|
+
type LivePagePreviewInteraction =
|
|
31
|
+
| {
|
|
32
|
+
readonly type: 'click';
|
|
33
|
+
readonly x: number;
|
|
34
|
+
readonly y: number;
|
|
35
|
+
}
|
|
36
|
+
| {
|
|
37
|
+
readonly type: 'wheel';
|
|
38
|
+
readonly deltaX: number;
|
|
39
|
+
readonly deltaY: number;
|
|
40
|
+
}
|
|
41
|
+
| {
|
|
42
|
+
readonly type: 'keyDown';
|
|
43
|
+
readonly key: string;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Pointer position translated into the live-preview browser viewport.
|
|
48
|
+
*
|
|
49
|
+
* @private type of `<CitationIframePreview/>`
|
|
50
|
+
*/
|
|
51
|
+
type LivePagePreviewPointerPosition = {
|
|
52
|
+
readonly x: number;
|
|
53
|
+
readonly y: number;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Keyboard keys that should not be forwarded as standalone browser actions.
|
|
58
|
+
*/
|
|
59
|
+
const LIVE_PAGE_PREVIEW_IGNORED_KEYS = new Set(['Alt', 'CapsLock', 'Control', 'Meta', 'Shift']);
|
|
22
60
|
|
|
23
61
|
/**
|
|
24
62
|
* Renders a citation URL preview as an iframe when the target page allows embedding,
|
|
25
|
-
* or falls back to a server-side
|
|
26
|
-
* does not (e.g. X-Frame-Options: DENY / SAMEORIGIN).
|
|
63
|
+
* or falls back to a live server-side browser preview with an "Open in new tab" link
|
|
64
|
+
* when it does not (e.g. X-Frame-Options: DENY / SAMEORIGIN).
|
|
27
65
|
*
|
|
28
66
|
* Embedding capability is determined by `GET /api/page-preview/check?url=<url>`.
|
|
29
67
|
* If that endpoint is unavailable the component falls back to the iframe directly.
|
|
@@ -34,24 +72,24 @@ export function CitationIframePreview({ src, title }: CitationIframePreviewProps
|
|
|
34
72
|
const [status, setStatus] = useState<EmbedStatus>('loading');
|
|
35
73
|
|
|
36
74
|
useEffect(() => {
|
|
37
|
-
let
|
|
75
|
+
let isCancelled = false;
|
|
38
76
|
|
|
39
77
|
fetch(`/api/page-preview/check?url=${encodeURIComponent(src)}`)
|
|
40
78
|
.then((response) => response.json())
|
|
41
79
|
.then((data: { canEmbed: boolean }) => {
|
|
42
|
-
if (!
|
|
43
|
-
setStatus(data.canEmbed ? 'embed' : '
|
|
80
|
+
if (!isCancelled) {
|
|
81
|
+
setStatus(data.canEmbed ? 'embed' : 'browser');
|
|
44
82
|
}
|
|
45
83
|
})
|
|
46
84
|
.catch(() => {
|
|
47
85
|
// API not available — fall back to iframe (e.g. outside agents-server)
|
|
48
|
-
if (!
|
|
86
|
+
if (!isCancelled) {
|
|
49
87
|
setStatus('embed');
|
|
50
88
|
}
|
|
51
89
|
});
|
|
52
90
|
|
|
53
91
|
return () => {
|
|
54
|
-
|
|
92
|
+
isCancelled = true;
|
|
55
93
|
};
|
|
56
94
|
}, [src]);
|
|
57
95
|
|
|
@@ -59,20 +97,157 @@ export function CitationIframePreview({ src, title }: CitationIframePreviewProps
|
|
|
59
97
|
return <div className={styles.citationIframeLoading}>Loading preview…</div>;
|
|
60
98
|
}
|
|
61
99
|
|
|
62
|
-
if (status === '
|
|
63
|
-
return
|
|
64
|
-
<div className={styles.citationScreenshotFallback}>
|
|
65
|
-
<img
|
|
66
|
-
src={`/api/page-preview/screenshot?url=${encodeURIComponent(src)}`}
|
|
67
|
-
alt={`Screenshot of ${title}`}
|
|
68
|
-
className={styles.citationScreenshotImage}
|
|
69
|
-
/>
|
|
70
|
-
<a href={src} target="_blank" rel="noopener noreferrer" className={styles.citationScreenshotLink}>
|
|
71
|
-
Open in new tab ↗
|
|
72
|
-
</a>
|
|
73
|
-
</div>
|
|
74
|
-
);
|
|
100
|
+
if (status === 'browser') {
|
|
101
|
+
return <LiveBrowserPreview src={src} title={title} />;
|
|
75
102
|
}
|
|
76
103
|
|
|
77
104
|
return <iframe src={src} className={styles.citationIframe} title={title} />;
|
|
78
105
|
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Renders and controls a streamed browser preview for non-embeddable citation URLs.
|
|
109
|
+
*
|
|
110
|
+
* @private component of `<CitationIframePreview/>`
|
|
111
|
+
*/
|
|
112
|
+
function LiveBrowserPreview({ src, title }: CitationIframePreviewProps) {
|
|
113
|
+
const reactSessionId = useId();
|
|
114
|
+
const sessionId = useMemo(() => createLivePagePreviewSessionId(reactSessionId), [reactSessionId]);
|
|
115
|
+
const streamSrc = useMemo(
|
|
116
|
+
() => `/api/page-preview/live?url=${encodeURIComponent(src)}&sessionId=${encodeURIComponent(sessionId)}`,
|
|
117
|
+
[sessionId, src],
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
const handleClick = useCallback(
|
|
121
|
+
(event: MouseEvent<HTMLImageElement>) => {
|
|
122
|
+
event.currentTarget.focus();
|
|
123
|
+
const position = resolveLivePagePreviewPointerPosition(event);
|
|
124
|
+
if (!position) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
void postLivePagePreviewInteraction(sessionId, {
|
|
129
|
+
type: 'click',
|
|
130
|
+
x: position.x,
|
|
131
|
+
y: position.y,
|
|
132
|
+
});
|
|
133
|
+
},
|
|
134
|
+
[sessionId],
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
const handleWheel = useCallback(
|
|
138
|
+
(event: WheelEvent<HTMLImageElement>) => {
|
|
139
|
+
event.preventDefault();
|
|
140
|
+
void postLivePagePreviewInteraction(sessionId, {
|
|
141
|
+
type: 'wheel',
|
|
142
|
+
deltaX: event.deltaX,
|
|
143
|
+
deltaY: event.deltaY,
|
|
144
|
+
});
|
|
145
|
+
},
|
|
146
|
+
[sessionId],
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
const handleKeyDown = useCallback(
|
|
150
|
+
(event: KeyboardEvent<HTMLImageElement>) => {
|
|
151
|
+
if (event.altKey || event.ctrlKey || event.metaKey) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const key = normalizeLivePagePreviewKey(event.key);
|
|
156
|
+
if (!key) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
event.preventDefault();
|
|
161
|
+
void postLivePagePreviewInteraction(sessionId, {
|
|
162
|
+
type: 'keyDown',
|
|
163
|
+
key,
|
|
164
|
+
});
|
|
165
|
+
},
|
|
166
|
+
[sessionId],
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
return (
|
|
170
|
+
<div className={styles.citationBrowserPreviewFallback}>
|
|
171
|
+
<img
|
|
172
|
+
src={streamSrc}
|
|
173
|
+
alt={`Live browser preview of ${title}`}
|
|
174
|
+
className={styles.citationBrowserPreviewImage}
|
|
175
|
+
onClick={handleClick}
|
|
176
|
+
onWheel={handleWheel}
|
|
177
|
+
onKeyDown={handleKeyDown}
|
|
178
|
+
tabIndex={0}
|
|
179
|
+
/>
|
|
180
|
+
<a href={src} target="_blank" rel="noopener noreferrer" className={styles.citationBrowserPreviewLink}>
|
|
181
|
+
Open in new tab ↗
|
|
182
|
+
</a>
|
|
183
|
+
</div>
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Creates one browser-preview session id from React's hydration-stable id.
|
|
189
|
+
*
|
|
190
|
+
* @param reactSessionId - React-generated id for the component instance.
|
|
191
|
+
* @returns Session id shared by the stream and interaction endpoints.
|
|
192
|
+
*/
|
|
193
|
+
function createLivePagePreviewSessionId(reactSessionId: string): string {
|
|
194
|
+
return `preview-${reactSessionId.replace(/[^a-zA-Z0-9_-]/g, '_')}`;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Converts one pointer event over the stream image to browser viewport coordinates.
|
|
199
|
+
*
|
|
200
|
+
* @param event - Pointer event from the stream image.
|
|
201
|
+
* @returns Browser viewport position or null when the image has no layout box.
|
|
202
|
+
*/
|
|
203
|
+
function resolveLivePagePreviewPointerPosition(
|
|
204
|
+
event: MouseEvent<HTMLImageElement>,
|
|
205
|
+
): LivePagePreviewPointerPosition | null {
|
|
206
|
+
const rect = event.currentTarget.getBoundingClientRect();
|
|
207
|
+
if (rect.width <= 0 || rect.height <= 0) {
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
x: ((event.clientX - rect.left) / rect.width) * LIVE_PAGE_PREVIEW_VIEWPORT_WIDTH,
|
|
213
|
+
y: ((event.clientY - rect.top) / rect.height) * LIVE_PAGE_PREVIEW_VIEWPORT_HEIGHT,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Normalizes one browser keyboard key before forwarding it to Playwright.
|
|
219
|
+
*
|
|
220
|
+
* @param key - Browser KeyboardEvent key.
|
|
221
|
+
* @returns Playwright-compatible key name or null when the key should be ignored.
|
|
222
|
+
*/
|
|
223
|
+
function normalizeLivePagePreviewKey(key: string): string | null {
|
|
224
|
+
if (!key || LIVE_PAGE_PREVIEW_IGNORED_KEYS.has(key)) {
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (key === ' ') {
|
|
229
|
+
return 'Space';
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return key;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Sends one live-preview interaction to the Agents Server.
|
|
237
|
+
*
|
|
238
|
+
* @param sessionId - Active live-preview session id.
|
|
239
|
+
* @param interaction - Interaction to apply to the browser page.
|
|
240
|
+
*/
|
|
241
|
+
async function postLivePagePreviewInteraction(
|
|
242
|
+
sessionId: string,
|
|
243
|
+
interaction: LivePagePreviewInteraction,
|
|
244
|
+
): Promise<void> {
|
|
245
|
+
await fetch('/api/page-preview/interact', {
|
|
246
|
+
method: 'POST',
|
|
247
|
+
headers: { 'Content-Type': 'application/json' },
|
|
248
|
+
body: JSON.stringify({
|
|
249
|
+
sessionId,
|
|
250
|
+
...interaction,
|
|
251
|
+
}),
|
|
252
|
+
}).catch(() => undefined);
|
|
253
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/* eslint-disable no-magic-numbers */
|
|
2
|
+
|
|
3
|
+
// Note: [💞] Ignore a discrepancy between file name and exported helper names
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Width of the browser viewport used for live knowledge previews.
|
|
7
|
+
*
|
|
8
|
+
* @private shared constant of Agents Server knowledge previews
|
|
9
|
+
*/
|
|
10
|
+
export const LIVE_PAGE_PREVIEW_VIEWPORT_WIDTH = 1280;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Height of the browser viewport used for live knowledge previews.
|
|
14
|
+
*
|
|
15
|
+
* @private shared constant of Agents Server knowledge previews
|
|
16
|
+
*/
|
|
17
|
+
export const LIVE_PAGE_PREVIEW_VIEWPORT_HEIGHT = 800;
|
|
@@ -45,6 +45,31 @@ const DEFAULT_AGENTS_SERVER_NEXT_DIST_DIRECTORY_NAME = '.next';
|
|
|
45
45
|
*/
|
|
46
46
|
const AGENTS_SERVER_BUILD_MAX_OLD_SPACE_MIB = 4096;
|
|
47
47
|
|
|
48
|
+
/**
|
|
49
|
+
* Conservative Next.js build worker count used by CLI-owned Agents Server production builds.
|
|
50
|
+
*
|
|
51
|
+
* Standalone VPS self-updates build the replacement server while the current pm2 process is
|
|
52
|
+
* still serving traffic. Keeping Next's static workers serial avoids CPU-count-based memory
|
|
53
|
+
* spikes that can make the OS kill the build child with no normal exit code.
|
|
54
|
+
*
|
|
55
|
+
* @private internal constant of `ptbk agents-server`
|
|
56
|
+
*/
|
|
57
|
+
const AGENTS_SERVER_BUILD_WORKER_COUNT = 1;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Maximum attempts for a Next.js build that was killed by the operating system.
|
|
61
|
+
*
|
|
62
|
+
* @private internal constant of `ptbk agents-server`
|
|
63
|
+
*/
|
|
64
|
+
const AGENTS_SERVER_BUILD_MAX_ATTEMPTS = 2;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Signals that indicate the Next.js build may have been terminated by resource pressure.
|
|
68
|
+
*
|
|
69
|
+
* @private internal constant of `ptbk agents-server`
|
|
70
|
+
*/
|
|
71
|
+
const RETRYABLE_NEXT_BUILD_SIGNALS = new Set<NodeJS.Signals | null>(['SIGKILL', null]);
|
|
72
|
+
|
|
48
73
|
/**
|
|
49
74
|
* Environment variable passed to the bundled Next app so webpack can resolve dependencies
|
|
50
75
|
* installed beside `ptbk` even when the app sources are materialized into a project cache.
|
|
@@ -53,6 +78,13 @@ const AGENTS_SERVER_BUILD_MAX_OLD_SPACE_MIB = 4096;
|
|
|
53
78
|
*/
|
|
54
79
|
export const PTBK_AGENTS_SERVER_NODE_MODULES_PATH_ENV = 'PTBK_AGENTS_SERVER_NODE_MODULES_PATH';
|
|
55
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Environment variable consumed by `apps/agents-server/next.config.ts` to throttle build workers.
|
|
83
|
+
*
|
|
84
|
+
* @private internal constant of `ptbk agents-server`
|
|
85
|
+
*/
|
|
86
|
+
export const PTBK_AGENTS_SERVER_BUILD_WORKER_COUNT_ENV = 'PTBK_AGENTS_SERVER_BUILD_WORKER_COUNT';
|
|
87
|
+
|
|
56
88
|
/**
|
|
57
89
|
* Environment variable used only by the CLI-owned production build.
|
|
58
90
|
*
|
|
@@ -206,6 +238,16 @@ type AgentsServerBuildCacheOptions = {
|
|
|
206
238
|
readonly environment?: NodeJS.ProcessEnv;
|
|
207
239
|
};
|
|
208
240
|
|
|
241
|
+
/**
|
|
242
|
+
* Exit status reported by one spawned Next.js build child process.
|
|
243
|
+
*
|
|
244
|
+
* @private internal type of `ptbk agents-server`
|
|
245
|
+
*/
|
|
246
|
+
type NextBuildExitStatus = {
|
|
247
|
+
readonly code: number | null;
|
|
248
|
+
readonly signal: NodeJS.Signals | null;
|
|
249
|
+
};
|
|
250
|
+
|
|
209
251
|
/**
|
|
210
252
|
* Ensures that the local Agents Server production build exists and matches its source fingerprint.
|
|
211
253
|
*
|
|
@@ -358,6 +400,8 @@ export function createAgentsServerRuntimeEnvironment(
|
|
|
358
400
|
...environment,
|
|
359
401
|
NODE_PATH: mergeNodePath(nodeModulesPath, environment.NODE_PATH),
|
|
360
402
|
[PTBK_AGENTS_SERVER_NODE_MODULES_PATH_ENV]: nodeModulesPath,
|
|
403
|
+
[PTBK_AGENTS_SERVER_BUILD_WORKER_COUNT_ENV]:
|
|
404
|
+
environment[PTBK_AGENTS_SERVER_BUILD_WORKER_COUNT_ENV] || String(AGENTS_SERVER_BUILD_WORKER_COUNT),
|
|
361
405
|
...(options.isNextValidationIgnored
|
|
362
406
|
? {
|
|
363
407
|
[PTBK_AGENTS_SERVER_IGNORE_NEXT_VALIDATION_ENV]: 'true',
|
|
@@ -626,16 +670,35 @@ async function runNextBuild(options: {
|
|
|
626
670
|
readonly nextCliPath: string;
|
|
627
671
|
readonly onBuildOutput?: (chunk: string) => void;
|
|
628
672
|
}): Promise<void> {
|
|
629
|
-
|
|
673
|
+
for (let attempt = 1; attempt <= AGENTS_SERVER_BUILD_MAX_ATTEMPTS; attempt++) {
|
|
674
|
+
const exitStatus = await runNextBuildAttempt(options);
|
|
675
|
+
|
|
676
|
+
if (exitStatus.code === 0) {
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
if (attempt < AGENTS_SERVER_BUILD_MAX_ATTEMPTS && isNextBuildTerminationRetryable(exitStatus)) {
|
|
681
|
+
forwardBuildOutput(createNextBuildRetryMessage(exitStatus, attempt + 1), options.onBuildOutput);
|
|
682
|
+
continue;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
throw createNextBuildExitError(exitStatus, attempt);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* Runs one Next.js production build attempt and returns its exit status.
|
|
691
|
+
*/
|
|
692
|
+
async function runNextBuildAttempt(options: {
|
|
693
|
+
readonly appPath: string;
|
|
694
|
+
readonly environment: NodeJS.ProcessEnv;
|
|
695
|
+
readonly nextCliPath: string;
|
|
696
|
+
readonly onBuildOutput?: (chunk: string) => void;
|
|
697
|
+
}): Promise<NextBuildExitStatus> {
|
|
698
|
+
return await new Promise<NextBuildExitStatus>((resolveBuild, rejectBuild) => {
|
|
630
699
|
const buildProcess = spawn(process.execPath, [options.nextCliPath, 'build'], {
|
|
631
700
|
cwd: options.appPath,
|
|
632
|
-
env:
|
|
633
|
-
...options.environment,
|
|
634
|
-
NODE_OPTIONS: mergeNodeOptionsWithHeapSize(
|
|
635
|
-
options.environment.NODE_OPTIONS,
|
|
636
|
-
AGENTS_SERVER_BUILD_MAX_OLD_SPACE_MIB,
|
|
637
|
-
),
|
|
638
|
-
},
|
|
701
|
+
env: createNextBuildProcessEnvironment(options.environment),
|
|
639
702
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
640
703
|
});
|
|
641
704
|
|
|
@@ -646,18 +709,83 @@ async function runNextBuild(options: {
|
|
|
646
709
|
forwardBuildOutput(chunk.toString(), options.onBuildOutput);
|
|
647
710
|
});
|
|
648
711
|
|
|
649
|
-
buildProcess.once('error',
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
rejectBuild(new Error(`next-build exited with code ${String(code)}.`));
|
|
712
|
+
buildProcess.once('error', (error) => {
|
|
713
|
+
rejectBuild(createNextBuildSpawnError(error));
|
|
714
|
+
});
|
|
715
|
+
buildProcess.once('close', (code, signal) => {
|
|
716
|
+
resolveBuild({ code, signal });
|
|
657
717
|
});
|
|
658
718
|
});
|
|
659
719
|
}
|
|
660
720
|
|
|
721
|
+
/**
|
|
722
|
+
* Creates the environment passed to the spawned Next.js build process.
|
|
723
|
+
*/
|
|
724
|
+
function createNextBuildProcessEnvironment(environment: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
|
|
725
|
+
return {
|
|
726
|
+
...environment,
|
|
727
|
+
NODE_OPTIONS: mergeNodeOptionsWithHeapSize(environment.NODE_OPTIONS, AGENTS_SERVER_BUILD_MAX_OLD_SPACE_MIB),
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
/**
|
|
732
|
+
* Returns true when one failed build attempt is worth retrying.
|
|
733
|
+
*/
|
|
734
|
+
function isNextBuildTerminationRetryable(exitStatus: NextBuildExitStatus): boolean {
|
|
735
|
+
return exitStatus.code === null && RETRYABLE_NEXT_BUILD_SIGNALS.has(exitStatus.signal);
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/**
|
|
739
|
+
* Creates one visible retry message for a killed Next.js build attempt.
|
|
740
|
+
*/
|
|
741
|
+
function createNextBuildRetryMessage(exitStatus: NextBuildExitStatus, nextAttempt: number): string {
|
|
742
|
+
return `\nAgents Server Next build was terminated by ${describeNextBuildExitStatus(exitStatus)}. Retrying attempt ${nextAttempt}/${AGENTS_SERVER_BUILD_MAX_ATTEMPTS}.\n`;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
/**
|
|
746
|
+
* Creates a branded error for a failed Next.js build child process.
|
|
747
|
+
*/
|
|
748
|
+
function createNextBuildExitError(exitStatus: NextBuildExitStatus, attemptCount: number): NotAllowed {
|
|
749
|
+
const lowMemoryHint = isNextBuildTerminationRetryable(exitStatus)
|
|
750
|
+
? '\n\nThe build process was terminated by the operating system. On standalone VPS self-updates this usually means the host ran out of memory while Next.js was compiling or prerendering the app.'
|
|
751
|
+
: '';
|
|
752
|
+
|
|
753
|
+
return new NotAllowed(
|
|
754
|
+
spaceTrim(`
|
|
755
|
+
Agents Server Next production build failed.
|
|
756
|
+
|
|
757
|
+
- Exit code: \`${String(exitStatus.code)}\`
|
|
758
|
+
- Signal: \`${String(exitStatus.signal)}\`
|
|
759
|
+
- Attempts: \`${String(attemptCount)}\`
|
|
760
|
+
${lowMemoryHint}
|
|
761
|
+
`),
|
|
762
|
+
);
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
/**
|
|
766
|
+
* Creates a branded error for failures to start the Next.js build child process.
|
|
767
|
+
*/
|
|
768
|
+
function createNextBuildSpawnError(error: Error): NotAllowed {
|
|
769
|
+
return new NotAllowed(
|
|
770
|
+
spaceTrim(`
|
|
771
|
+
Cannot start the Agents Server Next production build.
|
|
772
|
+
|
|
773
|
+
${error.message}
|
|
774
|
+
`),
|
|
775
|
+
);
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
/**
|
|
779
|
+
* Formats one Next.js build exit status for foreground logs.
|
|
780
|
+
*/
|
|
781
|
+
function describeNextBuildExitStatus(exitStatus: NextBuildExitStatus): string {
|
|
782
|
+
if (exitStatus.signal) {
|
|
783
|
+
return `signal \`${exitStatus.signal}\``;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
return `exit code \`${String(exitStatus.code)}\``;
|
|
787
|
+
}
|
|
788
|
+
|
|
661
789
|
/**
|
|
662
790
|
* Prepends `--max-old-space-size=<mib>` to `NODE_OPTIONS` unless the caller already set one.
|
|
663
791
|
*/
|