@promptbook/cli 0.112.0-126 → 0.112.0-128
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 +5 -5
- package/apps/agents-server/README.md +3 -0
- package/apps/agents-server/playwright.config.ts +1 -0
- package/apps/agents-server/public/promptbook-logo-blue.png +0 -0
- package/apps/agents-server/public/promptbook-logo-white.png +0 -0
- package/apps/agents-server/src/app/[agentName]/layout.tsx +1 -0
- package/apps/agents-server/src/app/actions.ts +2 -2
- package/apps/agents-server/src/app/admin/update/UpdateClient.tsx +68 -36
- package/apps/agents-server/src/app/agents/[agentName]/ActiveAgentBreadcrumbBinder.tsx +30 -0
- package/apps/agents-server/src/app/agents/[agentName]/AgentChatWrapper.tsx +2 -0
- package/apps/agents-server/src/app/agents/[agentName]/chat/AgentChatHistoryPayloadState.ts +38 -4
- package/apps/agents-server/src/app/agents/[agentName]/chat/AgentChatHistorySyncOperations.ts +4 -1
- package/apps/agents-server/src/app/agents/[agentName]/chat/CanonicalAgentChatSurface.tsx +19 -15
- package/apps/agents-server/src/app/agents/[agentName]/chat/useCanonicalAgentChatPanelState.ts +58 -7
- package/apps/agents-server/src/app/agents/[agentName]/layout.tsx +47 -1
- package/apps/agents-server/src/app/api/chat/citation-label/route.ts +133 -0
- package/apps/agents-server/src/app/globals.css +78 -0
- package/apps/agents-server/src/components/Header/ActiveAgentBreadcrumbContext.tsx +82 -0
- package/apps/agents-server/src/components/Header/useHeaderActiveAgent.ts +26 -7
- package/apps/agents-server/src/components/LayoutWrapper/LayoutWrapper.tsx +35 -32
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/ManGoNewAgentWizard.tsx +151 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/ManGoOnboardingNavigation.tsx +50 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/BookLanguagePanel.tsx +125 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/DropZone.tsx +83 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/EmailTestRun.tsx +216 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/KnowledgeList.tsx +79 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/Logo.tsx +20 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/MarkdownBookEditor.tsx +154 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/MarkdownPreview.tsx +188 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/RailStepper.tsx +115 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/StepFrame.tsx +47 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/TestChat.tsx +159 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/WizardShell.tsx +114 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/brand/RailArtwork.tsx +60 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/steps/BookStep.tsx +135 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/steps/DoneStep.tsx +215 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/steps/KnowledgeStep.tsx +152 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/steps/TestStep.tsx +262 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/steps/ZadaniStep.tsx +64 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/ui/Badge.tsx +41 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/ui/Banner.tsx +69 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/ui/Button.tsx +77 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/ui/Card.tsx +33 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/ui/Field.tsx +103 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/ui/IconButton.tsx +40 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/ui/Spinner.tsx +14 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/components/ui/tokens.ts +37 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/config/bookSections.ts +62 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/config/emailScenarios.ts +22 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/config/steps.ts +46 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/index.ts +11 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/lib/cn.ts +4 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/lib/format.ts +9 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/lib/id.ts +11 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/services/agentEvalService.ts +30 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/services/agentTestService.ts +69 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/services/bookService.ts +21 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/services/createManGoAgentSource.ts +107 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/services/draftService.ts +45 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/services/uploadService.ts +27 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/state/OnboardingProvider.tsx +121 -0
- package/apps/agents-server/src/components/NewAgentDialog/ManGoNewAgentWizard/types.ts +52 -0
- package/apps/agents-server/src/components/NewAgentDialog/createNewAgentWizardSource.ts +1 -1
- package/apps/agents-server/src/components/NewAgentDialog/trackNewAgentCreationEvent.ts +1 -1
- package/apps/agents-server/src/components/NewAgentDialog/useNewAgentDialog.tsx +76 -0
- package/apps/agents-server/src/constants/newAgentWizard.ts +17 -8
- package/apps/agents-server/src/database/getMetadata.ts +37 -1
- package/apps/agents-server/src/database/metadataDefaults.ts +18 -7
- package/apps/agents-server/src/database/migrations/2026-06-2200-new-agent-wizard-metadata-key.sql +27 -0
- package/apps/agents-server/src/database/migrations/2026-06-2201-new-agent-wizard-mango-default.sql +11 -0
- package/apps/agents-server/src/generated/reservedPaths.ts +2 -0
- package/apps/agents-server/src/languages/translations/english.yaml +1 -1
- package/apps/agents-server/src/tools/createChatAttachmentToolFunctions.ts +8 -4
- package/apps/agents-server/src/utils/chat/resolveAgentsServerCitationLabel.ts +130 -0
- package/apps/agents-server/src/utils/chat/resolveCitationSourceLabel.ts +436 -0
- package/apps/agents-server/src/utils/knowledge/resolveWebsiteKnowledgeSourcesForServer.ts +10 -1
- package/apps/agents-server/src/utils/session.ts +62 -5
- package/apps/agents-server/src/utils/transpilers/resolveTranspiledTeamExport.ts +10 -10
- package/apps/agents-server/src/utils/userChat/userChatProgressCard.ts +198 -63
- package/apps/agents-server/src/utils/vpsConfiguration.ts +1 -0
- package/apps/agents-server/src/utils/vpsSelfUpdate.ts +81 -8
- package/esm/index.es.js +1052 -455
- package/esm/index.es.js.map +1 -1
- package/esm/scripts/run-codex-prompts/git/commitChanges.d.ts +3 -1
- package/esm/scripts/run-codex-prompts/main/runPromptRound.d.ts +2 -2
- package/esm/scripts/run-codex-prompts/server/buildCoderServerPromptResponse.d.ts +50 -0
- package/esm/scripts/run-codex-prompts/server/buildCoderServerRunState.d.ts +23 -0
- package/esm/scripts/run-codex-prompts/server/coderServerHtml.d.ts +1 -1
- package/esm/scripts/run-codex-prompts/server/runCoderHttpServer.d.ts +13 -1
- package/esm/scripts/run-codex-prompts/server/updatePromptSection.d.ts +1 -1
- package/esm/scripts/run-codex-prompts/ui/CoderRunUiState.d.ts +1 -0
- package/esm/scripts/run-codex-prompts/ui/buildRunUiFrameShared.d.ts +4 -0
- package/esm/scripts/run-codex-prompts/ui/renderCoderRunUi.d.ts +1 -0
- package/esm/src/_packages/components.index.d.ts +2 -0
- package/esm/src/_packages/types.index.d.ts +2 -0
- package/esm/src/avatars/avatarAnimationScheduler.d.ts +4 -0
- package/esm/src/book-components/Chat/Chat/ChatCitationModal.d.ts +2 -0
- package/esm/src/book-components/Chat/Chat/ChatMessageItem.d.ts +4 -0
- package/esm/src/book-components/Chat/Chat/ChatMessageList.d.ts +4 -0
- package/esm/src/book-components/Chat/Chat/ChatProps.d.ts +5 -0
- package/esm/src/book-components/Chat/Chat/ChatToolCallModal.d.ts +2 -0
- package/esm/src/book-components/Chat/Chat/ChatToolCallModalContent.d.ts +3 -1
- package/esm/src/book-components/Chat/Chat/TeamToolCallModalContent.d.ts +2 -0
- package/esm/src/book-components/Chat/SourceChip/SourceChip.d.ts +6 -1
- package/esm/src/book-components/Chat/hooks/useResolvedCitationLabel.d.ts +12 -0
- package/esm/src/book-components/Chat/types/ChatMessage.d.ts +4 -0
- package/esm/src/book-components/Chat/types/ChatParticipant.d.ts +1 -1
- package/esm/src/book-components/Chat/types/CitationLabelResolver.d.ts +8 -0
- package/esm/src/book-components/Chat/utils/citationHelpers.d.ts +9 -0
- package/esm/src/book-components/Chat/utils/decodeJsonUnicodeEscapesInMarkdownText.d.ts +14 -0
- package/esm/src/book-components/Chat/utils/decodeJsonUnicodeEscapesInMarkdownText.test.d.ts +1 -0
- package/esm/src/book-components/Chat/utils/parseCitationsFromContent.d.ts +4 -0
- package/esm/src/version.d.ts +1 -1
- package/package.json +1 -1
- package/src/_packages/components.index.ts +2 -0
- package/src/_packages/types.index.ts +2 -0
- package/src/avatars/avatarAnimationScheduler.ts +61 -20
- package/src/avatars/visuals/octopus3d2AvatarVisual.ts +106 -21
- package/src/avatars/visuals/octopus3d3AvatarVisual.ts +131 -28
- package/src/book-2.0/agent-source/createAgentModelRequirementsWithCommitments/augmentAgentModelRequirementsFromSource.ts +18 -2
- package/src/book-2.0/book-language-documentation/renderGroupedCommitmentDocumentationMarkdown.ts +7 -1
- package/src/book-components/Chat/Chat/Chat.tsx +4 -0
- package/src/book-components/Chat/Chat/ChatCitationModal.tsx +17 -3
- package/src/book-components/Chat/Chat/ChatMessageItem.tsx +22 -2
- package/src/book-components/Chat/Chat/ChatMessageList.tsx +6 -0
- package/src/book-components/Chat/Chat/ChatProps.tsx +6 -0
- package/src/book-components/Chat/Chat/ChatToolCallModal.tsx +4 -0
- package/src/book-components/Chat/Chat/ChatToolCallModalContent.tsx +4 -0
- package/src/book-components/Chat/Chat/TeamToolCallModalContent.tsx +16 -3
- package/src/book-components/Chat/Chat/useChatPostprocessedMessages.ts +13 -2
- package/src/book-components/Chat/SourceChip/SourceChip.module.css +8 -0
- package/src/book-components/Chat/SourceChip/SourceChip.tsx +19 -5
- package/src/book-components/Chat/hooks/useResolvedCitationLabel.ts +65 -0
- package/src/book-components/Chat/save/html/htmlSaveFormatDefinition.ts +315 -88
- package/src/book-components/Chat/save/react/reactSaveFormatDefinition.ts +7 -1
- package/src/book-components/Chat/types/ChatMessage.ts +5 -0
- package/src/book-components/Chat/types/ChatParticipant.ts +1 -1
- package/src/book-components/Chat/types/CitationLabelResolver.ts +9 -0
- package/src/book-components/Chat/utils/citationHelpers.ts +90 -2
- package/src/book-components/Chat/utils/createCitationFootnoteRenderModel.ts +3 -0
- package/src/book-components/Chat/utils/decodeJsonUnicodeEscapesInMarkdownText.ts +178 -0
- package/src/book-components/Chat/utils/parseCitationsFromContent.ts +6 -0
- package/src/cli/cli-commands/agents-server/ensureAgentsServerEnvFile.ts +13 -2
- package/src/cli/cli-commands/coder/getDefaultCoderPackageJsonScripts.ts +1 -1
- package/src/cli/cli-commands/coder/run.ts +17 -20
- package/src/cli/cli-commands/coder/server.ts +13 -16
- package/src/cli/common/$deprecateCliCommand.ts +10 -1
- package/src/commitments/GOAL/GOAL.ts +7 -2
- package/src/commitments/LANGUAGE/LANGUAGE.ts +7 -2
- package/src/commitments/TEAM/TEAM.ts +12 -1
- package/src/commitments/USE_PROJECT/createUseProjectToolFunctions.ts +8 -1
- package/src/import-plugins/JsonFileImportPlugin.ts +15 -2
- package/src/import-plugins/TextFileImportPlugin.ts +8 -1
- package/src/llm-providers/_common/register/$registeredLlmToolsMessage.ts +6 -1
- package/src/other/templates/getTemplatesPipelineCollection.ts +713 -785
- package/src/utils/chat/chatAttachments/resolveChatAttachmentContent.ts +8 -1
- package/src/version.ts +2 -2
- package/src/versions.txt +1 -0
- package/umd/index.umd.js +1055 -458
- package/umd/index.umd.js.map +1 -1
- package/umd/scripts/run-codex-prompts/git/commitChanges.d.ts +3 -1
- package/umd/scripts/run-codex-prompts/main/runPromptRound.d.ts +2 -2
- package/umd/scripts/run-codex-prompts/server/buildCoderServerPromptResponse.d.ts +50 -0
- package/umd/scripts/run-codex-prompts/server/buildCoderServerRunState.d.ts +23 -0
- package/umd/scripts/run-codex-prompts/server/coderServerHtml.d.ts +1 -1
- package/umd/scripts/run-codex-prompts/server/runCoderHttpServer.d.ts +13 -1
- package/umd/scripts/run-codex-prompts/server/updatePromptSection.d.ts +1 -1
- package/umd/scripts/run-codex-prompts/ui/CoderRunUiState.d.ts +1 -0
- package/umd/scripts/run-codex-prompts/ui/buildRunUiFrameShared.d.ts +4 -0
- package/umd/scripts/run-codex-prompts/ui/renderCoderRunUi.d.ts +1 -0
- package/umd/src/_packages/components.index.d.ts +2 -0
- package/umd/src/_packages/types.index.d.ts +2 -0
- package/umd/src/avatars/avatarAnimationScheduler.d.ts +4 -0
- package/umd/src/book-components/Chat/Chat/ChatCitationModal.d.ts +2 -0
- package/umd/src/book-components/Chat/Chat/ChatMessageItem.d.ts +4 -0
- package/umd/src/book-components/Chat/Chat/ChatMessageList.d.ts +4 -0
- package/umd/src/book-components/Chat/Chat/ChatProps.d.ts +5 -0
- package/umd/src/book-components/Chat/Chat/ChatToolCallModal.d.ts +2 -0
- package/umd/src/book-components/Chat/Chat/ChatToolCallModalContent.d.ts +3 -1
- package/umd/src/book-components/Chat/Chat/TeamToolCallModalContent.d.ts +2 -0
- package/umd/src/book-components/Chat/SourceChip/SourceChip.d.ts +6 -1
- package/umd/src/book-components/Chat/hooks/useResolvedCitationLabel.d.ts +12 -0
- package/umd/src/book-components/Chat/types/ChatMessage.d.ts +4 -0
- package/umd/src/book-components/Chat/types/ChatParticipant.d.ts +1 -1
- package/umd/src/book-components/Chat/types/CitationLabelResolver.d.ts +8 -0
- package/umd/src/book-components/Chat/utils/citationHelpers.d.ts +9 -0
- package/umd/src/book-components/Chat/utils/decodeJsonUnicodeEscapesInMarkdownText.d.ts +14 -0
- package/umd/src/book-components/Chat/utils/decodeJsonUnicodeEscapesInMarkdownText.test.d.ts +1 -0
- package/umd/src/book-components/Chat/utils/parseCitationsFromContent.d.ts +4 -0
- package/umd/src/version.d.ts +1 -1
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import type { ChatProps } from '@promptbook-local/components';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Citation label resolver type accepted by the shared Chat component.
|
|
7
|
+
*
|
|
8
|
+
* @private utility type of Agents Server chat citations
|
|
9
|
+
*/
|
|
10
|
+
type AgentsServerCitationLabelResolver = NonNullable<ChatProps['resolveCitationLabel']>;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Cache of in-flight and completed citation label lookups.
|
|
14
|
+
*
|
|
15
|
+
* @private utility constant of Agents Server chat citations
|
|
16
|
+
*/
|
|
17
|
+
const citationLabelCache = new Map<string, Promise<string | null>>();
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Pattern matching HTTP(S) URLs that the server can fetch.
|
|
21
|
+
*
|
|
22
|
+
* @private utility constant of Agents Server chat citations
|
|
23
|
+
*/
|
|
24
|
+
const HTTP_URL_REGEX = /^https?:\/\//i;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Resolves nicer citation labels for Agents Server chat sources.
|
|
28
|
+
*
|
|
29
|
+
* @private utility of Agents Server chat citations
|
|
30
|
+
*/
|
|
31
|
+
export const resolveAgentsServerCitationLabel: AgentsServerCitationLabelResolver = async (citation) => {
|
|
32
|
+
const title = normalizeCitationLabelResponse(citation.title);
|
|
33
|
+
if (title) {
|
|
34
|
+
return title;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!hasResolvableCitationTarget(citation.source, citation.url)) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const cacheKey = createCitationLabelCacheKey(citation.source, citation.url, citation.title);
|
|
42
|
+
const cachedLabel = citationLabelCache.get(cacheKey);
|
|
43
|
+
if (cachedLabel) {
|
|
44
|
+
return cachedLabel;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const labelPromise = requestCitationLabel({
|
|
48
|
+
source: citation.source,
|
|
49
|
+
url: citation.url,
|
|
50
|
+
title: citation.title,
|
|
51
|
+
});
|
|
52
|
+
citationLabelCache.set(cacheKey, labelPromise);
|
|
53
|
+
|
|
54
|
+
return labelPromise;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Requests one citation label from the server.
|
|
59
|
+
*
|
|
60
|
+
* @param payload - Citation metadata sent to the server resolver.
|
|
61
|
+
* @returns Resolved label or null.
|
|
62
|
+
*
|
|
63
|
+
* @private utility of Agents Server chat citations
|
|
64
|
+
*/
|
|
65
|
+
async function requestCitationLabel(payload: {
|
|
66
|
+
readonly source: string;
|
|
67
|
+
readonly url?: string;
|
|
68
|
+
readonly title?: string;
|
|
69
|
+
}): Promise<string | null> {
|
|
70
|
+
const response = await fetch('/api/chat/citation-label', {
|
|
71
|
+
method: 'POST',
|
|
72
|
+
headers: {
|
|
73
|
+
'Content-Type': 'application/json',
|
|
74
|
+
},
|
|
75
|
+
body: JSON.stringify(payload),
|
|
76
|
+
}).catch(() => null);
|
|
77
|
+
|
|
78
|
+
if (!response?.ok) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const body = (await response.json().catch(() => null)) as { label?: unknown } | null;
|
|
83
|
+
|
|
84
|
+
return normalizeCitationLabelResponse(body?.label);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Creates a stable cache key for one citation lookup.
|
|
89
|
+
*
|
|
90
|
+
* @param source - Citation source.
|
|
91
|
+
* @param url - Optional resolved citation URL.
|
|
92
|
+
* @param title - Optional title.
|
|
93
|
+
* @returns Cache key.
|
|
94
|
+
*
|
|
95
|
+
* @private utility of Agents Server chat citations
|
|
96
|
+
*/
|
|
97
|
+
function createCitationLabelCacheKey(source: string, url?: string, title?: string): string {
|
|
98
|
+
return [source, url || '', title || ''].join('\n');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Checks whether a citation has a URL target worth resolving on the server.
|
|
103
|
+
*
|
|
104
|
+
* @param source - Citation source.
|
|
105
|
+
* @param url - Optional resolved citation URL.
|
|
106
|
+
* @returns True when the server can attempt a metadata lookup.
|
|
107
|
+
*
|
|
108
|
+
* @private utility of Agents Server chat citations
|
|
109
|
+
*/
|
|
110
|
+
function hasResolvableCitationTarget(source: string, url?: string): boolean {
|
|
111
|
+
return HTTP_URL_REGEX.test(url || '') || HTTP_URL_REGEX.test(source);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Normalizes one label value received from the server or structured citation metadata.
|
|
116
|
+
*
|
|
117
|
+
* @param value - Raw label value.
|
|
118
|
+
* @returns Trimmed label or null.
|
|
119
|
+
*
|
|
120
|
+
* @private utility of Agents Server chat citations
|
|
121
|
+
*/
|
|
122
|
+
function normalizeCitationLabelResponse(value: unknown): string | null {
|
|
123
|
+
if (typeof value !== 'string') {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const label = value.replace(/\s+/g, ' ').trim();
|
|
128
|
+
|
|
129
|
+
return label || null;
|
|
130
|
+
}
|
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
import { JSDOM } from 'jsdom';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Citation metadata accepted by the Agents Server source-label resolver.
|
|
5
|
+
*
|
|
6
|
+
* @private utility type of Agents Server chat citation labels
|
|
7
|
+
*/
|
|
8
|
+
export type CitationSourceLabelPayload = {
|
|
9
|
+
/**
|
|
10
|
+
* Raw citation source from the model response.
|
|
11
|
+
*/
|
|
12
|
+
readonly source: string;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Optional resolved URL for the citation source.
|
|
16
|
+
*/
|
|
17
|
+
readonly url?: string;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Optional title already attached to the citation.
|
|
21
|
+
*/
|
|
22
|
+
readonly title?: string;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Maximum number of bytes fetched when resolving one citation title.
|
|
27
|
+
*
|
|
28
|
+
* @private utility constant of Agents Server chat citation labels
|
|
29
|
+
*/
|
|
30
|
+
const MAX_SOURCE_LABEL_BYTES = 1_000_000;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Timeout for metadata fetches so the chat UI is not blocked by slow sources.
|
|
34
|
+
*
|
|
35
|
+
* @private utility constant of Agents Server chat citation labels
|
|
36
|
+
*/
|
|
37
|
+
const SOURCE_LABEL_FETCH_TIMEOUT_MS = 4_000;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* User agent used for lightweight source metadata requests.
|
|
41
|
+
*
|
|
42
|
+
* @private utility constant of Agents Server chat citation labels
|
|
43
|
+
*/
|
|
44
|
+
const SOURCE_LABEL_USER_AGENT = 'Promptbook Agents Server citation label resolver';
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Pattern matching markdown headings.
|
|
48
|
+
*
|
|
49
|
+
* @private utility constant of Agents Server chat citation labels
|
|
50
|
+
*/
|
|
51
|
+
const MARKDOWN_HEADING_REGEX = /^\s{0,3}#{1,6}\s+(.+?)\s*#*\s*$/m;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Pattern matching common PDF/XMP title metadata.
|
|
55
|
+
*
|
|
56
|
+
* @private utility constant of Agents Server chat citation labels
|
|
57
|
+
*/
|
|
58
|
+
const PDF_XMP_TITLE_REGEX = /<dc:title\b[^>]*>[\s\S]*?<rdf:li\b[^>]*>([\s\S]*?)<\/rdf:li>[\s\S]*?<\/dc:title>/i;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Pattern matching simple PDF/XMP title metadata without `rdf:li`.
|
|
62
|
+
*
|
|
63
|
+
* @private utility constant of Agents Server chat citation labels
|
|
64
|
+
*/
|
|
65
|
+
const PDF_XMP_SIMPLE_TITLE_REGEX = /<dc:title\b[^>]*>([\s\S]*?)<\/dc:title>/i;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Pattern matching PDF literal title metadata.
|
|
69
|
+
*
|
|
70
|
+
* @private utility constant of Agents Server chat citation labels
|
|
71
|
+
*/
|
|
72
|
+
const PDF_LITERAL_TITLE_REGEX = /\/Title\s*\(((?:\\.|[^\\)]){1,500})\)/s;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Pattern matching PDF hexadecimal title metadata.
|
|
76
|
+
*
|
|
77
|
+
* @private utility constant of Agents Server chat citation labels
|
|
78
|
+
*/
|
|
79
|
+
const PDF_HEX_TITLE_REGEX = /\/Title\s*<([0-9a-fA-F\s]{2,1000})>/;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Returns the HTTP(S) URL that can be fetched for richer citation metadata.
|
|
83
|
+
*
|
|
84
|
+
* @param citation - Citation payload received from the chat UI.
|
|
85
|
+
* @returns URL string or null when the citation has no fetchable target.
|
|
86
|
+
*
|
|
87
|
+
* @private utility of Agents Server chat citation labels
|
|
88
|
+
*/
|
|
89
|
+
export function resolveCitationLabelTargetUrl(citation: CitationSourceLabelPayload): string | null {
|
|
90
|
+
return normalizeHttpUrl(citation.url) || normalizeHttpUrl(citation.source);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Resolves a human-readable source label from page or document metadata.
|
|
95
|
+
*
|
|
96
|
+
* @param citation - Citation metadata from the chat UI.
|
|
97
|
+
* @returns Resolved label or null when no useful title metadata is available.
|
|
98
|
+
*
|
|
99
|
+
* @private utility of Agents Server chat citation labels
|
|
100
|
+
*/
|
|
101
|
+
export async function resolveCitationSourceLabel(citation: CitationSourceLabelPayload): Promise<string | null> {
|
|
102
|
+
const explicitTitle = normalizeResolvedCitationLabel(citation.title);
|
|
103
|
+
if (explicitTitle) {
|
|
104
|
+
return explicitTitle;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const targetUrl = resolveCitationLabelTargetUrl(citation);
|
|
108
|
+
if (!targetUrl) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const response = await fetch(targetUrl, {
|
|
113
|
+
headers: {
|
|
114
|
+
'User-Agent': SOURCE_LABEL_USER_AGENT,
|
|
115
|
+
Accept: 'text/html,application/xhtml+xml,application/pdf,text/markdown,text/plain;q=0.9,*/*;q=0.1',
|
|
116
|
+
},
|
|
117
|
+
signal: AbortSignal.timeout(SOURCE_LABEL_FETCH_TIMEOUT_MS),
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
if (!response.ok) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const bytes = await readResponseSnippet(response, MAX_SOURCE_LABEL_BYTES);
|
|
125
|
+
const contentType = response.headers.get('content-type')?.toLowerCase() || '';
|
|
126
|
+
const isPdf = contentType.includes('application/pdf') || targetUrl.toLowerCase().split('?')[0]?.endsWith('.pdf');
|
|
127
|
+
|
|
128
|
+
if (isPdf || startsWithPdfSignature(bytes)) {
|
|
129
|
+
return extractCitationLabelFromPdfBytes(bytes);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const text = new TextDecoder('utf-8', { fatal: false }).decode(bytes);
|
|
133
|
+
if (contentType.includes('markdown') || contentType.includes('text/plain')) {
|
|
134
|
+
return extractCitationLabelFromPlainText(text);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return extractCitationLabelFromHtml(text) || extractCitationLabelFromPlainText(text);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Extracts a title from an HTML document.
|
|
142
|
+
*
|
|
143
|
+
* @param html - HTML source snippet.
|
|
144
|
+
* @returns Metadata title, heading, or null.
|
|
145
|
+
*
|
|
146
|
+
* @private utility of Agents Server chat citation labels
|
|
147
|
+
*/
|
|
148
|
+
export function extractCitationLabelFromHtml(html: string): string | null {
|
|
149
|
+
const dom = new JSDOM(html);
|
|
150
|
+
const document = dom.window.document;
|
|
151
|
+
const candidates = [
|
|
152
|
+
getMetaContent(document, 'property', 'og:title'),
|
|
153
|
+
getMetaContent(document, 'name', 'twitter:title'),
|
|
154
|
+
document.querySelector('title')?.textContent,
|
|
155
|
+
document.querySelector('h1')?.textContent,
|
|
156
|
+
];
|
|
157
|
+
|
|
158
|
+
return candidates.map(normalizeResolvedCitationLabel).find((label): label is string => Boolean(label)) || null;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Extracts a title from plain text or markdown-like document snippets.
|
|
163
|
+
*
|
|
164
|
+
* @param text - Text source snippet.
|
|
165
|
+
* @returns Heading or first useful line.
|
|
166
|
+
*
|
|
167
|
+
* @private utility of Agents Server chat citation labels
|
|
168
|
+
*/
|
|
169
|
+
export function extractCitationLabelFromPlainText(text: string): string | null {
|
|
170
|
+
const headingMatch = text.match(MARKDOWN_HEADING_REGEX);
|
|
171
|
+
const heading = normalizeResolvedCitationLabel(headingMatch?.[1]);
|
|
172
|
+
if (heading) {
|
|
173
|
+
return heading;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const firstLine = text
|
|
177
|
+
.split(/\r?\n/)
|
|
178
|
+
.map(normalizeResolvedCitationLabel)
|
|
179
|
+
.find((line): line is string => Boolean(line));
|
|
180
|
+
|
|
181
|
+
return firstLine || null;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Extracts a title from PDF metadata bytes.
|
|
186
|
+
*
|
|
187
|
+
* @param bytes - PDF byte snippet.
|
|
188
|
+
* @returns PDF metadata title or null.
|
|
189
|
+
*
|
|
190
|
+
* @private utility of Agents Server chat citation labels
|
|
191
|
+
*/
|
|
192
|
+
export function extractCitationLabelFromPdfBytes(bytes: Uint8Array): string | null {
|
|
193
|
+
const latin1Text = Buffer.from(bytes).toString('latin1');
|
|
194
|
+
const utf8Text = new TextDecoder('utf-8', { fatal: false }).decode(bytes);
|
|
195
|
+
const xmpTitle = extractPdfXmpTitle(utf8Text);
|
|
196
|
+
if (xmpTitle) {
|
|
197
|
+
return xmpTitle;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const literalTitleMatch = latin1Text.match(PDF_LITERAL_TITLE_REGEX);
|
|
201
|
+
const literalTitle = normalizeResolvedCitationLabel(
|
|
202
|
+
literalTitleMatch?.[1] ? decodePdfLiteralString(literalTitleMatch[1]) : undefined,
|
|
203
|
+
);
|
|
204
|
+
if (literalTitle) {
|
|
205
|
+
return literalTitle;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const hexTitleMatch = latin1Text.match(PDF_HEX_TITLE_REGEX);
|
|
209
|
+
return normalizeResolvedCitationLabel(hexTitleMatch?.[1] ? decodePdfHexString(hexTitleMatch[1]) : undefined);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Reads at most `maxBytes` bytes from one fetch response.
|
|
214
|
+
*
|
|
215
|
+
* @param response - Fetch response.
|
|
216
|
+
* @param maxBytes - Maximum number of bytes to keep.
|
|
217
|
+
* @returns Collected response bytes.
|
|
218
|
+
*
|
|
219
|
+
* @private utility of Agents Server chat citation labels
|
|
220
|
+
*/
|
|
221
|
+
async function readResponseSnippet(response: Response, maxBytes: number): Promise<Uint8Array> {
|
|
222
|
+
if (!response.body) {
|
|
223
|
+
return new Uint8Array(await response.arrayBuffer()).slice(0, maxBytes);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const reader = response.body.getReader();
|
|
227
|
+
const chunks: Uint8Array[] = [];
|
|
228
|
+
let collectedBytes = 0;
|
|
229
|
+
|
|
230
|
+
try {
|
|
231
|
+
while (collectedBytes < maxBytes) {
|
|
232
|
+
const { done, value } = await reader.read();
|
|
233
|
+
if (done || !value) {
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const remainingBytes = maxBytes - collectedBytes;
|
|
238
|
+
const chunk = value.length > remainingBytes ? value.slice(0, remainingBytes) : value;
|
|
239
|
+
chunks.push(chunk);
|
|
240
|
+
collectedBytes += chunk.length;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (collectedBytes >= maxBytes) {
|
|
244
|
+
await reader.cancel();
|
|
245
|
+
}
|
|
246
|
+
} finally {
|
|
247
|
+
reader.releaseLock();
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const bytes = new Uint8Array(collectedBytes);
|
|
251
|
+
let offset = 0;
|
|
252
|
+
for (const chunk of chunks) {
|
|
253
|
+
bytes.set(chunk, offset);
|
|
254
|
+
offset += chunk.length;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return bytes;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Extracts the content attribute from a matching metadata tag.
|
|
262
|
+
*
|
|
263
|
+
* @param document - Parsed DOM document.
|
|
264
|
+
* @param attribute - Metadata selector attribute.
|
|
265
|
+
* @param value - Metadata selector value.
|
|
266
|
+
* @returns Metadata content or undefined.
|
|
267
|
+
*
|
|
268
|
+
* @private utility of Agents Server chat citation labels
|
|
269
|
+
*/
|
|
270
|
+
function getMetaContent(document: Document, attribute: 'name' | 'property', value: string): string | undefined {
|
|
271
|
+
return document.querySelector(`meta[${attribute}="${value}"]`)?.getAttribute('content') || undefined;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Normalizes a title candidate into a compact display label.
|
|
276
|
+
*
|
|
277
|
+
* @param label - Raw label candidate.
|
|
278
|
+
* @returns Trimmed label or null.
|
|
279
|
+
*
|
|
280
|
+
* @private utility of Agents Server chat citation labels
|
|
281
|
+
*/
|
|
282
|
+
function normalizeResolvedCitationLabel(label: string | null | undefined): string | null {
|
|
283
|
+
const normalized = (label || '').replace(/\s+/g, ' ').trim();
|
|
284
|
+
|
|
285
|
+
return normalized || null;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Returns a normalized HTTP(S) URL candidate.
|
|
290
|
+
*
|
|
291
|
+
* @param value - URL candidate.
|
|
292
|
+
* @returns URL href or null.
|
|
293
|
+
*
|
|
294
|
+
* @private utility of Agents Server chat citation labels
|
|
295
|
+
*/
|
|
296
|
+
function normalizeHttpUrl(value: string | undefined): string | null {
|
|
297
|
+
if (!value) {
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
try {
|
|
302
|
+
const url = new URL(value.trim());
|
|
303
|
+
return url.protocol === 'http:' || url.protocol === 'https:' ? url.href : null;
|
|
304
|
+
} catch {
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Checks whether bytes start with a PDF file signature.
|
|
311
|
+
*
|
|
312
|
+
* @param bytes - Response bytes.
|
|
313
|
+
* @returns True when the snippet looks like a PDF.
|
|
314
|
+
*
|
|
315
|
+
* @private utility of Agents Server chat citation labels
|
|
316
|
+
*/
|
|
317
|
+
function startsWithPdfSignature(bytes: Uint8Array): boolean {
|
|
318
|
+
return bytes.length >= 4 && bytes[0] === 0x25 && bytes[1] === 0x50 && bytes[2] === 0x44 && bytes[3] === 0x46;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Extracts an XMP title from a PDF text snippet.
|
|
323
|
+
*
|
|
324
|
+
* @param text - UTF-8 decoded PDF snippet.
|
|
325
|
+
* @returns XMP title or null.
|
|
326
|
+
*
|
|
327
|
+
* @private utility of Agents Server chat citation labels
|
|
328
|
+
*/
|
|
329
|
+
function extractPdfXmpTitle(text: string): string | null {
|
|
330
|
+
const title =
|
|
331
|
+
text.match(PDF_XMP_TITLE_REGEX)?.[1] || text.match(PDF_XMP_SIMPLE_TITLE_REGEX)?.[1]?.replace(/<[^>]+>/g, ' ');
|
|
332
|
+
|
|
333
|
+
return normalizeResolvedCitationLabel(title ? decodeHtmlText(title) : undefined);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Decodes text entities using the DOM parser already used for HTML titles.
|
|
338
|
+
*
|
|
339
|
+
* @param value - Raw HTML text.
|
|
340
|
+
* @returns Decoded text.
|
|
341
|
+
*
|
|
342
|
+
* @private utility of Agents Server chat citation labels
|
|
343
|
+
*/
|
|
344
|
+
function decodeHtmlText(value: string): string {
|
|
345
|
+
return new JSDOM(`<span>${value}</span>`).window.document.querySelector('span')?.textContent || value;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Decodes a PDF literal string.
|
|
350
|
+
*
|
|
351
|
+
* @param value - Escaped PDF literal value.
|
|
352
|
+
* @returns Decoded text.
|
|
353
|
+
*
|
|
354
|
+
* @private utility of Agents Server chat citation labels
|
|
355
|
+
*/
|
|
356
|
+
function decodePdfLiteralString(value: string): string {
|
|
357
|
+
const decoded = value
|
|
358
|
+
.replace(/\\\r?\n/g, '')
|
|
359
|
+
.replace(/\\([nrtbf()\\])/g, (_match, escaped: string) => {
|
|
360
|
+
const replacements: Record<string, string> = {
|
|
361
|
+
n: '\n',
|
|
362
|
+
r: '\r',
|
|
363
|
+
t: '\t',
|
|
364
|
+
b: '\b',
|
|
365
|
+
f: '\f',
|
|
366
|
+
'(': '(',
|
|
367
|
+
')': ')',
|
|
368
|
+
'\\': '\\',
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
return replacements[escaped] || escaped;
|
|
372
|
+
})
|
|
373
|
+
.replace(/\\([0-7]{1,3})/g, (_match, octal: string) => String.fromCharCode(parseInt(octal, 8)));
|
|
374
|
+
|
|
375
|
+
return decodePdfBinaryString(decoded);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Decodes a PDF hexadecimal string.
|
|
380
|
+
*
|
|
381
|
+
* @param value - Hex encoded PDF string.
|
|
382
|
+
* @returns Decoded text.
|
|
383
|
+
*
|
|
384
|
+
* @private utility of Agents Server chat citation labels
|
|
385
|
+
*/
|
|
386
|
+
function decodePdfHexString(value: string): string {
|
|
387
|
+
const normalizedHex = value.replace(/\s+/g, '');
|
|
388
|
+
const evenHex = normalizedHex.length % 2 === 0 ? normalizedHex : `${normalizedHex}0`;
|
|
389
|
+
const bytes = new Uint8Array(evenHex.length / 2);
|
|
390
|
+
|
|
391
|
+
for (let index = 0; index < evenHex.length; index += 2) {
|
|
392
|
+
bytes[index / 2] = parseInt(evenHex.slice(index, index + 2), 16);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (bytes[0] === 0xfe && bytes[1] === 0xff) {
|
|
396
|
+
return decodeUtf16BeBytes(bytes.slice(2));
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return new TextDecoder('utf-8', { fatal: false }).decode(bytes);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Decodes a binary string that may use a UTF-16BE byte-order mark.
|
|
404
|
+
*
|
|
405
|
+
* @param value - Binary string with one byte per character.
|
|
406
|
+
* @returns Decoded text.
|
|
407
|
+
*
|
|
408
|
+
* @private utility of Agents Server chat citation labels
|
|
409
|
+
*/
|
|
410
|
+
function decodePdfBinaryString(value: string): string {
|
|
411
|
+
if (value.charCodeAt(0) === 0xfe && value.charCodeAt(1) === 0xff) {
|
|
412
|
+
const bytes = Uint8Array.from(value.slice(2), (character) => character.charCodeAt(0));
|
|
413
|
+
return decodeUtf16BeBytes(bytes);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
return value;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Decodes UTF-16BE bytes into a JavaScript string.
|
|
421
|
+
*
|
|
422
|
+
* @param bytes - Big-endian UTF-16 bytes.
|
|
423
|
+
* @returns Decoded text.
|
|
424
|
+
*
|
|
425
|
+
* @private utility of Agents Server chat citation labels
|
|
426
|
+
*/
|
|
427
|
+
function decodeUtf16BeBytes(bytes: Uint8Array): string {
|
|
428
|
+
let text = '';
|
|
429
|
+
for (let index = 0; index + 1 < bytes.length; index += 2) {
|
|
430
|
+
text += String.fromCharCode((bytes[index]! << 8) | bytes[index + 1]!);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
return text;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Note: [🟢] Code for Agents Server chat citation label resolver should never be published into packages that could be imported into browser environment
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { spaceTrim } from 'spacetrim';
|
|
1
2
|
import type { string_knowledge_source_link } from '../../../../../src/types/typeAliases';
|
|
2
3
|
import { $provideFilesystemForNode } from '../../../../../src/scrapers/_common/register/$provideFilesystemForNode';
|
|
3
4
|
import type { ScraperSourceHandler } from '../../../../../src/scrapers/_common/Scraper';
|
|
@@ -121,7 +122,15 @@ export async function resolveWebsiteKnowledgeSourcesForServer(
|
|
|
121
122
|
continue;
|
|
122
123
|
}
|
|
123
124
|
|
|
124
|
-
const inlineSource = createInlineKnowledgeSourceFile(
|
|
125
|
+
const inlineSource = createInlineKnowledgeSourceFile(
|
|
126
|
+
spaceTrim(
|
|
127
|
+
(block) => `
|
|
128
|
+
Source URL: ${source}
|
|
129
|
+
|
|
130
|
+
${block(markdown)}
|
|
131
|
+
`,
|
|
132
|
+
),
|
|
133
|
+
);
|
|
125
134
|
const uploadedSource = await uploadInlineKnowledgeSource(inlineSource);
|
|
126
135
|
resolvedKnowledgeSources.push(uploadedSource);
|
|
127
136
|
} catch (error) {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { createHmac } from 'crypto';
|
|
1
|
+
import { createHmac, randomBytes } from 'crypto';
|
|
2
2
|
import { cookies, headers } from 'next/headers';
|
|
3
3
|
import { cache } from 'react';
|
|
4
|
+
import { spaceTrim } from 'spacetrim';
|
|
5
|
+
import { EnvironmentMismatchError } from '../../../../src/errors/EnvironmentMismatchError';
|
|
4
6
|
import { isStandaloneVpsRawIpBootstrapActive } from './standaloneVpsRawIpBootstrap';
|
|
5
7
|
|
|
6
8
|
/**
|
|
@@ -9,9 +11,64 @@ import { isStandaloneVpsRawIpBootstrapActive } from './standaloneVpsRawIpBootstr
|
|
|
9
11
|
export const SESSION_COOKIE_NAME = 'sessionToken';
|
|
10
12
|
|
|
11
13
|
/**
|
|
12
|
-
*
|
|
14
|
+
* Cached HMAC signing key resolved from `SESSION_SECRET` on first use.
|
|
15
|
+
*
|
|
16
|
+
* Computed lazily so module imports never fail eagerly at build time and the
|
|
17
|
+
* environment can still be configured before the first authenticated request.
|
|
18
|
+
*/
|
|
19
|
+
let resolvedSessionSigningKey: string | null = null;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Resolves the HMAC signing key used for session payloads.
|
|
23
|
+
*
|
|
24
|
+
* In production, `SESSION_SECRET` must be configured explicitly — falling back
|
|
25
|
+
* to any shared credential (in particular `ADMIN_PASSWORD`) or a hardcoded
|
|
26
|
+
* literal would let anyone who learns that value forge session tokens for any
|
|
27
|
+
* user (including `admin`). Outside production, a per-process random key is
|
|
28
|
+
* generated on first use so local development works without configuration
|
|
29
|
+
* while still rejecting the unsafe hardcoded default.
|
|
30
|
+
*
|
|
31
|
+
* @returns HMAC signing key.
|
|
32
|
+
* @throws {EnvironmentMismatchError} When `SESSION_SECRET` is missing in production.
|
|
13
33
|
*/
|
|
14
|
-
|
|
34
|
+
function getSessionSigningKey(): string {
|
|
35
|
+
if (resolvedSessionSigningKey !== null) {
|
|
36
|
+
return resolvedSessionSigningKey;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const configuredSecret = process.env.SESSION_SECRET?.trim();
|
|
40
|
+
if (configuredSecret) {
|
|
41
|
+
resolvedSessionSigningKey = configuredSecret;
|
|
42
|
+
return resolvedSessionSigningKey;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (process.env.NODE_ENV === 'production') {
|
|
46
|
+
throw new EnvironmentMismatchError(
|
|
47
|
+
spaceTrim(`
|
|
48
|
+
Missing required \`SESSION_SECRET\` environment variable in production.
|
|
49
|
+
|
|
50
|
+
The Agents Server signs session cookies with HMAC-SHA256 keyed by
|
|
51
|
+
\`SESSION_SECRET\`. Reusing \`ADMIN_PASSWORD\` or a hardcoded fallback
|
|
52
|
+
would let anyone who learns that value forge session tokens for any
|
|
53
|
+
user (including \`admin\`).
|
|
54
|
+
|
|
55
|
+
**Fix:** set \`SESSION_SECRET\` to a long random string (for example
|
|
56
|
+
the output of \`openssl rand -hex 32\`) in the deployment environment
|
|
57
|
+
and restart the server.
|
|
58
|
+
`),
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
resolvedSessionSigningKey = randomBytes(32).toString('hex');
|
|
63
|
+
console.warn(
|
|
64
|
+
spaceTrim(`
|
|
65
|
+
\`SESSION_SECRET\` is not configured — generated a random per-process
|
|
66
|
+
signing key for this non-production run. Existing session cookies will
|
|
67
|
+
be invalidated on every restart until \`SESSION_SECRET\` is set.
|
|
68
|
+
`),
|
|
69
|
+
);
|
|
70
|
+
return resolvedSessionSigningKey;
|
|
71
|
+
}
|
|
15
72
|
|
|
16
73
|
/**
|
|
17
74
|
* Signed session payload persisted in the authentication cookie.
|
|
@@ -73,7 +130,7 @@ export type SessionCookieSecurityContext = {
|
|
|
73
130
|
*/
|
|
74
131
|
export function serializeSessionToken(user: SessionUser): string {
|
|
75
132
|
const payload = JSON.stringify(user);
|
|
76
|
-
const signature = createHmac('sha256',
|
|
133
|
+
const signature = createHmac('sha256', getSessionSigningKey()).update(payload).digest('hex');
|
|
77
134
|
return `${Buffer.from(payload).toString('base64')}.${signature}`;
|
|
78
135
|
}
|
|
79
136
|
|
|
@@ -94,7 +151,7 @@ export function parseSessionToken(token: string | null | undefined): SessionUser
|
|
|
94
151
|
}
|
|
95
152
|
|
|
96
153
|
const payload = Buffer.from(payloadBase64, 'base64').toString('utf-8');
|
|
97
|
-
const expectedSignature = createHmac('sha256',
|
|
154
|
+
const expectedSignature = createHmac('sha256', getSessionSigningKey()).update(payload).digest('hex');
|
|
98
155
|
|
|
99
156
|
if (signature !== expectedSignature) {
|
|
100
157
|
return null;
|