@bbigbang/core 0.1.0

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 (175) hide show
  1. package/dist/config.js +380 -0
  2. package/dist/execution/executionDispatcher.js +3810 -0
  3. package/dist/main.js +90 -0
  4. package/dist/nodeEventHistory.js +206 -0
  5. package/dist/scheduler/dreamLogic.js +50 -0
  6. package/dist/scheduler/dreamScheduler.js +65 -0
  7. package/dist/services/agentFileAccessService.js +1913 -0
  8. package/dist/services/agentRuntimeCleanupBroker.js +62 -0
  9. package/dist/services/agentSkillsBroker.js +118 -0
  10. package/dist/services/agentSkillsService.js +83 -0
  11. package/dist/services/agentWorkspaceBroker.js +937 -0
  12. package/dist/services/agentWorkspaceService.js +70 -0
  13. package/dist/services/appVersion.js +14 -0
  14. package/dist/services/auth.js +586 -0
  15. package/dist/services/claudeControlBroker.js +154 -0
  16. package/dist/services/claudeTranscriptBroker.js +100 -0
  17. package/dist/services/claudeTranscriptService.js +359 -0
  18. package/dist/services/codexAppServerBroker.js +155 -0
  19. package/dist/services/codexTranscriptBroker.js +98 -0
  20. package/dist/services/codexTranscriptService.js +961 -0
  21. package/dist/services/droidMissionBroker.js +124 -0
  22. package/dist/services/droidMissionImporter.js +630 -0
  23. package/dist/services/droidModelOptions.js +165 -0
  24. package/dist/services/hubServerRegistrationService.js +268 -0
  25. package/dist/services/libraryManifest.js +43 -0
  26. package/dist/services/libraryScaffold.js +26 -0
  27. package/dist/services/libraryService.js +2263 -0
  28. package/dist/services/memoryService.js +386 -0
  29. package/dist/services/missionEvidence.js +377 -0
  30. package/dist/services/missionService.js +2361 -0
  31. package/dist/services/missionTrace.js +158 -0
  32. package/dist/services/nativeMissionBriefParser.js +120 -0
  33. package/dist/services/nativeMissionOrchestrator.js +2045 -0
  34. package/dist/services/nativeMissionReportGenerator.js +227 -0
  35. package/dist/services/nativeMissionValidationRunner.js +452 -0
  36. package/dist/services/nativeMissionWorkerBroker.js +190 -0
  37. package/dist/services/nodeRegistry.js +34 -0
  38. package/dist/services/nodeStateReconciler.js +97 -0
  39. package/dist/services/panelMediaScanner.js +119 -0
  40. package/dist/services/persistentRuntimeJsonlClient.js +153 -0
  41. package/dist/services/platformAgentPolicy.js +180 -0
  42. package/dist/services/platformAgentService.js +2041 -0
  43. package/dist/services/projectAccessResolver.js +93 -0
  44. package/dist/services/projectService.js +392 -0
  45. package/dist/services/resourceSpaceService.js +140 -0
  46. package/dist/services/scenarioRuntimeService.js +1130 -0
  47. package/dist/services/suggestedPlannerService.js +868 -0
  48. package/dist/services/workbenchGitBroker.js +161 -0
  49. package/dist/services/workbenchGitService.js +69 -0
  50. package/dist/services/workbenchInspectBroker.js +65 -0
  51. package/dist/services/workbenchNodePathService.js +79 -0
  52. package/dist/services/workbenchRegistryService.js +240 -0
  53. package/dist/services/workbenchRootService.js +181 -0
  54. package/dist/services/workbenchTerminalBroker.js +378 -0
  55. package/dist/services/workspaceRunOwnership.js +60 -0
  56. package/dist/services/workspaceScaffold.js +105 -0
  57. package/dist/services/workspaceSessionRuntimeService.js +576 -0
  58. package/dist/services/workspaceSessionService.js +245 -0
  59. package/dist/services/workspaceToolActionRunner.js +1582 -0
  60. package/dist/services/workspaceToolErrors.js +10 -0
  61. package/dist/services/workspaceToolExecutionUtils.js +895 -0
  62. package/dist/services/workspaceToolLatestStateProjector.js +91 -0
  63. package/dist/services/workspaceToolManifest.js +572 -0
  64. package/dist/services/workspaceToolMutationQueue.js +43 -0
  65. package/dist/services/workspaceToolPanelProjection.js +460 -0
  66. package/dist/services/workspaceToolPromotion.js +255 -0
  67. package/dist/services/workspaceToolPromotionState.js +224 -0
  68. package/dist/services/workspaceToolPublishDiagnostics.js +189 -0
  69. package/dist/services/workspaceToolPublishIdentityResolver.js +146 -0
  70. package/dist/services/workspaceToolReadModel.js +378 -0
  71. package/dist/services/workspaceToolRunLedger.js +239 -0
  72. package/dist/services/workspaceToolService.js +3067 -0
  73. package/dist/services/workspaceToolSnapshotPanelSync.js +293 -0
  74. package/dist/services/workspaceToolTerminalLifecycle.js +283 -0
  75. package/dist/services/workspaceToolTypes.js +1 -0
  76. package/dist/services/workspaceToolUploadMaterializer.js +228 -0
  77. package/dist/web/actionCardRoutes.js +129 -0
  78. package/dist/web/actionCards.js +469 -0
  79. package/dist/web/activationContext.js +684 -0
  80. package/dist/web/agentChannelGuards.js +48 -0
  81. package/dist/web/agentMentionCooldowns.js +32 -0
  82. package/dist/web/agentReminders.js +1668 -0
  83. package/dist/web/agentRuntimePresence.js +197 -0
  84. package/dist/web/agentSelfState.js +494 -0
  85. package/dist/web/agentTaskLinks.js +26 -0
  86. package/dist/web/agentVisibility.js +79 -0
  87. package/dist/web/assets.js +95 -0
  88. package/dist/web/channelActivationPrompt.js +395 -0
  89. package/dist/web/channelMemoryNotes.js +127 -0
  90. package/dist/web/channelMentions.js +10 -0
  91. package/dist/web/channelMessageSequences.js +19 -0
  92. package/dist/web/channelSubscriptions.js +26 -0
  93. package/dist/web/clearedTaskRoots.js +10 -0
  94. package/dist/web/collaborationPromptGuidance.js +36 -0
  95. package/dist/web/collaborationSurfaceState.js +140 -0
  96. package/dist/web/contextBundleRanking.js +154 -0
  97. package/dist/web/contextBundleResolver.js +488 -0
  98. package/dist/web/conversationBuiltinSkillRoots.js +50 -0
  99. package/dist/web/conversationControls.js +232 -0
  100. package/dist/web/conversationHandoffs.js +612 -0
  101. package/dist/web/conversationManager.js +2511 -0
  102. package/dist/web/conversationSummaries.js +876 -0
  103. package/dist/web/conversationSurfaceKinds.js +17 -0
  104. package/dist/web/conversationTargets.js +173 -0
  105. package/dist/web/directActivationPrompt.js +122 -0
  106. package/dist/web/directReplyTargets.js +69 -0
  107. package/dist/web/directThreadResolver.js +129 -0
  108. package/dist/web/dmTaskHandoffPrompt.js +120 -0
  109. package/dist/web/dmTaskThreadStatusProjection.js +229 -0
  110. package/dist/web/ftsQuery.js +33 -0
  111. package/dist/web/internalAgentRouter.js +11341 -0
  112. package/dist/web/libraryCuratorScheduler.js +58 -0
  113. package/dist/web/libraryDocumentPromptGuidance.js +8 -0
  114. package/dist/web/messageCheckpoints.js +19 -0
  115. package/dist/web/nodeWsHandler.js +2495 -0
  116. package/dist/web/notificationRounds.js +1061 -0
  117. package/dist/web/panelActionMessages.js +108 -0
  118. package/dist/web/panelActivationPrompt.js +18 -0
  119. package/dist/web/panelAudit.js +273 -0
  120. package/dist/web/panelLifecycle.js +222 -0
  121. package/dist/web/panelMediaPolicy.js +43 -0
  122. package/dist/web/panelPathPolicy.js +63 -0
  123. package/dist/web/panelPreviews.js +175 -0
  124. package/dist/web/panelQueryHandles.js +2749 -0
  125. package/dist/web/panelRoutes.js +2147 -0
  126. package/dist/web/panels.js +904 -0
  127. package/dist/web/peerInboxAggregates.js +1247 -0
  128. package/dist/web/planApprovalState.js +92 -0
  129. package/dist/web/platformAgentScheduler.js +66 -0
  130. package/dist/web/proactiveOpportunities.js +452 -0
  131. package/dist/web/promptContextSections.js +242 -0
  132. package/dist/web/promptHistorySanitizer.js +26 -0
  133. package/dist/web/promptSlashCommands.js +158 -0
  134. package/dist/web/rollingConversationSummary.js +453 -0
  135. package/dist/web/routeHelpers.js +11 -0
  136. package/dist/web/routes/handoff.js +288 -0
  137. package/dist/web/routes/history.js +345 -0
  138. package/dist/web/routes/memory.js +258 -0
  139. package/dist/web/routes/selfState.js +171 -0
  140. package/dist/web/routes/workspace.js +154 -0
  141. package/dist/web/runSurfaceWatermarks.js +431 -0
  142. package/dist/web/runtimeCapabilities.js +48 -0
  143. package/dist/web/sameAgentHandoffs.js +494 -0
  144. package/dist/web/server.js +15567 -0
  145. package/dist/web/sharedCollaborationCapsules.js +163 -0
  146. package/dist/web/soloSessionRelay.js +42 -0
  147. package/dist/web/soloWsHandler.js +138 -0
  148. package/dist/web/suggestedPlannerScheduler.js +56 -0
  149. package/dist/web/surfaceActivationPolicy.js +108 -0
  150. package/dist/web/surfaceCollaborators.js +61 -0
  151. package/dist/web/surfaceSystemStatus.js +263 -0
  152. package/dist/web/targetParticipants.js +77 -0
  153. package/dist/web/taskEvents.js +49 -0
  154. package/dist/web/taskLifecycleMessages.js +165 -0
  155. package/dist/web/taskLoops.js +732 -0
  156. package/dist/web/taskMemoryNotes.js +224 -0
  157. package/dist/web/taskNumbers.js +16 -0
  158. package/dist/web/taskOwnerGuards.js +49 -0
  159. package/dist/web/taskParticipantResolver.js +42 -0
  160. package/dist/web/taskParticipants.js +97 -0
  161. package/dist/web/taskSourceDetails.js +20 -0
  162. package/dist/web/taskStateViews.js +210 -0
  163. package/dist/web/taskStatusTransitions.js +9 -0
  164. package/dist/web/taskThreadFollowups.js +599 -0
  165. package/dist/web/taskThreadRuntimeClosure.js +685 -0
  166. package/dist/web/taskUpdateDelivery.js +104 -0
  167. package/dist/web/threadReplyContentHeuristics.js +30 -0
  168. package/dist/web/threadRoots.js +61 -0
  169. package/dist/web/threadTaskBindings.js +365 -0
  170. package/dist/web/uiPanelPromptGuidance.js +27 -0
  171. package/dist/web/workspaceMemoryHints.js +143 -0
  172. package/dist/web/workspaceToolPromptGuidance.js +30 -0
  173. package/dist/web/wsHandler.js +397 -0
  174. package/dist/web/wsSink.js +116 -0
  175. package/package.json +54 -0
@@ -0,0 +1,165 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { readFileSync } from 'node:fs';
3
+ import { homedir } from 'node:os';
4
+ import path from 'node:path';
5
+ const DROID_EXEC_HELP_TIMEOUT_MS = 1500;
6
+ export function listDroidModelOptions(input) {
7
+ const settingsPath = input?.settingsPath ?? process.env.FACTORY_SETTINGS_PATH ?? path.join(homedir(), '.factory', 'settings.json');
8
+ const options = new Map();
9
+ const defaults = {
10
+ orchestratorModel: null,
11
+ workerModel: null,
12
+ validatorModel: null,
13
+ sessionModel: null,
14
+ };
15
+ let loadedFromSettings = false;
16
+ let loadedFromCliHelp = false;
17
+ let error = null;
18
+ try {
19
+ const settings = parseJsonObject(readFileSync(settingsPath, 'utf8'));
20
+ loadedFromSettings = true;
21
+ defaults.orchestratorModel = normalizeModelId(settings.missionOrchestratorModel);
22
+ defaults.workerModel = normalizeModelId(asRecord(settings.missionModelSettings).workerModel);
23
+ defaults.validatorModel = normalizeModelId(asRecord(settings.missionModelSettings).validationWorkerModel);
24
+ defaults.sessionModel = normalizeModelId(asRecord(settings.sessionDefaultSettings).model);
25
+ addOption(options, defaults.orchestratorModel, {
26
+ label: defaults.orchestratorModel,
27
+ source: 'Droid mission orchestrator setting',
28
+ recommendedFor: ['orchestrator'],
29
+ });
30
+ addOption(options, defaults.workerModel, {
31
+ label: defaults.workerModel,
32
+ source: 'Droid mission worker setting',
33
+ recommendedFor: ['worker'],
34
+ });
35
+ addOption(options, defaults.validatorModel, {
36
+ label: defaults.validatorModel,
37
+ source: 'Droid mission validator setting',
38
+ recommendedFor: ['validator'],
39
+ });
40
+ addOption(options, defaults.sessionModel, {
41
+ label: defaults.sessionModel,
42
+ source: 'Droid session default setting',
43
+ recommendedFor: ['session_default'],
44
+ });
45
+ for (const item of asArray(asRecord(settings).customModels)) {
46
+ const record = asRecord(item);
47
+ const id = normalizeModelId(record.id);
48
+ addOption(options, id, {
49
+ label: normalizeDisplayLabel(record.displayName) ?? id,
50
+ source: 'Droid custom model',
51
+ provider: normalizeString(record.provider),
52
+ model: normalizeString(record.model),
53
+ });
54
+ }
55
+ }
56
+ catch (err) {
57
+ error = `Unable to read Droid settings: ${String(err?.message ?? err)}`;
58
+ }
59
+ const shouldReadCliHelp = input?.includeCliHelp !== false
60
+ && (!loadedFromSettings || options.size === 0 || process.env.DROID_MODEL_OPTIONS_INCLUDE_CLI_HELP === '1');
61
+ if (shouldReadCliHelp) {
62
+ try {
63
+ for (const item of parseDroidExecHelpModels(execFileSync('droid', ['exec', '--help'], {
64
+ encoding: 'utf8',
65
+ timeout: DROID_EXEC_HELP_TIMEOUT_MS,
66
+ maxBuffer: 1024 * 1024,
67
+ }))) {
68
+ addOption(options, item.id, {
69
+ label: item.label,
70
+ source: item.custom ? 'Droid CLI custom model' : 'Droid CLI available model',
71
+ });
72
+ }
73
+ loadedFromCliHelp = true;
74
+ }
75
+ catch (err) {
76
+ error = error ?? `Unable to read Droid CLI models: ${String(err?.message ?? err)}`;
77
+ }
78
+ }
79
+ return {
80
+ options: Array.from(options.values())
81
+ .sort((a, b) => a.order - b.order || a.id.localeCompare(b.id))
82
+ .map(({ order: _order, ...option }) => option),
83
+ defaults,
84
+ settingsPath,
85
+ loadedFromSettings,
86
+ loadedFromCliHelp,
87
+ error,
88
+ };
89
+ }
90
+ export function parseDroidExecHelpModels(helpText) {
91
+ const out = [];
92
+ let section = null;
93
+ for (const rawLine of helpText.split(/\r?\n/)) {
94
+ const line = rawLine.trimEnd();
95
+ if (/^Available Models:/i.test(line)) {
96
+ section = 'available';
97
+ continue;
98
+ }
99
+ if (/^Custom Models:/i.test(line)) {
100
+ section = 'custom';
101
+ continue;
102
+ }
103
+ if (/^Model details:/i.test(line))
104
+ break;
105
+ if (!section || !line.trim())
106
+ continue;
107
+ const match = line.match(/^\s{2,}(\S+)\s{2,}(.+?)\s*$/);
108
+ if (!match)
109
+ continue;
110
+ const id = normalizeModelId(match[1]);
111
+ if (!id)
112
+ continue;
113
+ out.push({ id, label: match[2].trim(), custom: section === 'custom' });
114
+ }
115
+ return out;
116
+ }
117
+ function addOption(options, value, input) {
118
+ const id = normalizeModelId(value);
119
+ if (!id)
120
+ return;
121
+ const existing = options.get(id);
122
+ if (existing) {
123
+ existing.recommendedFor = Array.from(new Set([...existing.recommendedFor, ...(input.recommendedFor ?? [])]));
124
+ if (existing.source !== input.source && !existing.source.includes(input.source))
125
+ existing.source = `${existing.source}; ${input.source}`;
126
+ existing.provider = existing.provider ?? input.provider ?? null;
127
+ existing.model = existing.model ?? input.model ?? null;
128
+ return;
129
+ }
130
+ options.set(id, {
131
+ id,
132
+ label: input.label?.trim() || id,
133
+ source: input.source,
134
+ provider: input.provider ?? null,
135
+ model: input.model ?? null,
136
+ recommendedFor: input.recommendedFor ?? [],
137
+ order: options.size,
138
+ });
139
+ }
140
+ function parseJsonObject(value) {
141
+ return asRecord(JSON.parse(value));
142
+ }
143
+ function asRecord(value) {
144
+ return value != null && typeof value === 'object' && !Array.isArray(value) ? value : {};
145
+ }
146
+ function asArray(value) {
147
+ return Array.isArray(value) ? value : [];
148
+ }
149
+ function normalizeString(value) {
150
+ return typeof value === 'string' && value.trim() ? value.trim() : null;
151
+ }
152
+ function normalizeDisplayLabel(value) {
153
+ const label = normalizeString(value);
154
+ if (!label || label === '[redacted]')
155
+ return null;
156
+ return label;
157
+ }
158
+ function normalizeModelId(value) {
159
+ const model = normalizeString(value);
160
+ if (!model || model === '[redacted]' || /\s/.test(model) || model.length > 160)
161
+ return null;
162
+ if (/^(bearer|sk-|pk-|secret|token|key)[-_a-z0-9]*/i.test(model))
163
+ return null;
164
+ return model;
165
+ }
@@ -0,0 +1,268 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { createHash, randomUUID } from 'node:crypto';
4
+ const SLUG_RE = /^[A-Za-z0-9][A-Za-z0-9_-]{0,62}$/u;
5
+ const DEFAULT_TOKEN_TTL_MS = 24 * 60 * 60 * 1000;
6
+ const MIN_TOKEN_TTL_MS = 60 * 1000;
7
+ const MAX_TOKEN_TTL_MS = 7 * 24 * 60 * 60 * 1000;
8
+ export class HubServerRegistrationService {
9
+ params;
10
+ tokenStorePath;
11
+ claimLock = Promise.resolve();
12
+ constructor(params) {
13
+ this.params = params;
14
+ this.tokenStorePath = path.join(path.dirname(params.registryPath), 'hub-server-registration-tokens.json');
15
+ }
16
+ static isConfigured(registryPath) {
17
+ return typeof registryPath === 'string' && registryPath.trim().length > 0;
18
+ }
19
+ createToken(input) {
20
+ const serverSlug = normalizeSlug(input.serverSlug);
21
+ const serverName = normalizeName(input.serverName);
22
+ const version = normalizeVersion(this.params.defaultVersion);
23
+ const expiresAt = Date.now() + normalizeTtlMs(input.expiresInSeconds);
24
+ const serverId = randomUUID();
25
+ const token = `hsr_${randomUUID().replace(/-/gu, '')}${randomUUID().replace(/-/gu, '')}`;
26
+ const tokenHash = hashToken(token);
27
+ const store = this.readTokenStore();
28
+ store.tokens = store.tokens.filter((record) => record.claimedAt == null && record.expiresAt > Date.now());
29
+ store.tokens.push({
30
+ tokenHash,
31
+ serverId,
32
+ serverSlug,
33
+ serverName,
34
+ version,
35
+ expectedVersion: version,
36
+ expiresAt,
37
+ claimedAt: null,
38
+ createdAt: Date.now(),
39
+ });
40
+ this.writeJsonAtomic(this.tokenStorePath, store);
41
+ return {
42
+ token,
43
+ expiresAt,
44
+ installCommand: buildInstallCommand({
45
+ hubUrl: this.params.publicServerUrl,
46
+ token,
47
+ serverId,
48
+ serverSlug,
49
+ serverName,
50
+ version,
51
+ packageSpec: this.params.joinPackageSpec,
52
+ npxPackageSpecs: this.params.joinNpxPackageSpecs,
53
+ installPackages: this.params.joinInstallPackages,
54
+ }),
55
+ serverId,
56
+ serverSlug,
57
+ serverName,
58
+ version,
59
+ };
60
+ }
61
+ async claim(input) {
62
+ return this.withClaimLock(() => this.claimLocked(input));
63
+ }
64
+ async claimLocked(input) {
65
+ const token = readRequired(input.token, 'token');
66
+ const tokenHash = hashToken(token);
67
+ const serverId = readRequired(input.serverId, 'serverId');
68
+ const serverSlug = normalizeSlug(input.serverSlug);
69
+ const serverName = normalizeName(input.serverName);
70
+ const upstream = normalizeHttpUrl(input.upstream, 'upstream');
71
+ const bridgeUrl = normalizeHttpUrl(input.bridgeUrl, 'bridgeUrl');
72
+ const version = normalizeVersion(input.version);
73
+ const now = Date.now();
74
+ const store = this.readTokenStore();
75
+ const record = store.tokens.find((candidate) => candidate.tokenHash === tokenHash);
76
+ if (!record)
77
+ throw new HubServerRegistrationError(401, 'Invalid registration token.');
78
+ if (record.claimedAt != null)
79
+ throw new HubServerRegistrationError(409, 'Registration token has already been claimed.');
80
+ if (record.expiresAt <= now)
81
+ throw new HubServerRegistrationError(410, 'Registration token has expired.');
82
+ if (record.serverId !== serverId || record.serverSlug !== serverSlug || record.serverName !== serverName || record.version !== version) {
83
+ throw new HubServerRegistrationError(409, 'Registration payload does not match the issued token.');
84
+ }
85
+ const identity = await fetchServerIdentity(upstream);
86
+ if (identity.serverId !== serverId || identity.serverSlug !== serverSlug) {
87
+ throw new HubServerRegistrationError(409, 'Upstream server identity does not match the issued token.');
88
+ }
89
+ if (identity.publicBasePath !== `/s/${serverSlug}`) {
90
+ throw new HubServerRegistrationError(409, 'Upstream server publicBasePath does not match its Hub slug.');
91
+ }
92
+ if (identity.version !== version) {
93
+ throw new HubServerRegistrationError(409, 'Upstream server version does not match the issued token.');
94
+ }
95
+ if (bridgeUrl !== upstream) {
96
+ const bridgeIdentity = await fetchServerIdentity(bridgeUrl);
97
+ if (bridgeIdentity.serverId !== serverId || bridgeIdentity.serverSlug !== serverSlug || bridgeIdentity.version !== version) {
98
+ throw new HubServerRegistrationError(409, 'Bridge URL does not reach the claimed server identity.');
99
+ }
100
+ }
101
+ const registry = this.readRegistry();
102
+ const existingIndex = registry.servers.findIndex((server) => server.serverId === serverId || server.serverSlug === serverSlug);
103
+ if (existingIndex >= 0) {
104
+ const existing = registry.servers[existingIndex];
105
+ if (existing.serverId !== serverId || existing.serverSlug !== serverSlug) {
106
+ throw new HubServerRegistrationError(409, 'Hub registry already contains a conflicting server id or slug.');
107
+ }
108
+ registry.servers[existingIndex] = { serverId, serverSlug, serverName, upstream, bridgeUrl, enabled: true, expectedVersion: record.expectedVersion ?? record.version, remoteVersion: identity.version };
109
+ }
110
+ else {
111
+ registry.servers.push({ serverId, serverSlug, serverName, upstream, bridgeUrl, enabled: true, expectedVersion: record.expectedVersion ?? record.version, remoteVersion: identity.version });
112
+ }
113
+ validateRegistry(registry);
114
+ record.claimedAt = now;
115
+ this.writeJsonAtomic(this.tokenStorePath, store);
116
+ this.writeJsonAtomic(this.params.registryPath, registry);
117
+ return {
118
+ ok: true,
119
+ serverId,
120
+ serverSlug,
121
+ serverName,
122
+ upstream,
123
+ replaced: existingIndex >= 0,
124
+ };
125
+ }
126
+ readTokenStore() {
127
+ if (!fs.existsSync(this.tokenStorePath))
128
+ return { tokens: [] };
129
+ try {
130
+ const parsed = JSON.parse(fs.readFileSync(this.tokenStorePath, 'utf8'));
131
+ return { tokens: Array.isArray(parsed.tokens) ? parsed.tokens : [] };
132
+ }
133
+ catch {
134
+ return { tokens: [] };
135
+ }
136
+ }
137
+ readRegistry() {
138
+ if (!fs.existsSync(this.params.registryPath))
139
+ return { servers: [] };
140
+ const parsed = JSON.parse(fs.readFileSync(this.params.registryPath, 'utf8'));
141
+ return { servers: Array.isArray(parsed.servers) ? parsed.servers.map((server) => ({ ...server })) : [] };
142
+ }
143
+ async withClaimLock(fn) {
144
+ const previous = this.claimLock;
145
+ let release;
146
+ this.claimLock = new Promise((resolve) => {
147
+ release = resolve;
148
+ });
149
+ await previous.catch(() => undefined);
150
+ try {
151
+ return await fn();
152
+ }
153
+ finally {
154
+ release();
155
+ }
156
+ }
157
+ writeJsonAtomic(filePath, value) {
158
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
159
+ const tempPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
160
+ fs.writeFileSync(tempPath, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
161
+ fs.renameSync(tempPath, filePath);
162
+ }
163
+ }
164
+ export class HubServerRegistrationError extends Error {
165
+ statusCode;
166
+ constructor(statusCode, message) {
167
+ super(message);
168
+ this.statusCode = statusCode;
169
+ }
170
+ }
171
+ function buildInstallCommand(params) {
172
+ const hubUrl = params.hubUrl.replace(/\/+$/u, '');
173
+ const packageSpec = params.packageSpec?.trim() || `@bbigbang/cli@${params.version}`;
174
+ const installPackages = params.installPackages?.map((item) => item.trim()).filter(Boolean) ?? [];
175
+ const configuredNpxPackageSpecs = params.npxPackageSpecs?.map((item) => item.trim()).filter(Boolean) ?? [];
176
+ const npxPackageSpecs = uniqueStrings(configuredNpxPackageSpecs.length > 0 ? configuredNpxPackageSpecs : [packageSpec]);
177
+ const lines = [
178
+ `npx -y ${npxPackageSpecs.map((spec) => `--package ${shellQuote(spec)}`).join(' ')} bigbang server join`,
179
+ ` --hub-url ${shellQuote(hubUrl)}`,
180
+ ` --token ${shellQuote(params.token)}`,
181
+ ` --server-id ${shellQuote(params.serverId)}`,
182
+ ` --server-slug ${shellQuote(params.serverSlug)}`,
183
+ ` --server-name ${shellQuote(params.serverName)}`,
184
+ ` --version ${shellQuote(params.version)}`,
185
+ ];
186
+ if (installPackages.length > 0) {
187
+ lines.push(` --install-packages ${shellQuote(installPackages.join(','))}`);
188
+ }
189
+ return lines.join(' \\\n');
190
+ }
191
+ function uniqueStrings(values) {
192
+ return Array.from(new Set(values.filter(Boolean)));
193
+ }
194
+ function shellQuote(value) {
195
+ return `'${value.replace(/'/gu, `'\\''`)}'`;
196
+ }
197
+ function hashToken(token) {
198
+ return createHash('sha256').update(token).digest('hex');
199
+ }
200
+ function normalizeTtlMs(seconds) {
201
+ if (typeof seconds !== 'number' || !Number.isFinite(seconds))
202
+ return DEFAULT_TOKEN_TTL_MS;
203
+ return Math.min(Math.max(Math.floor(seconds * 1000), MIN_TOKEN_TTL_MS), MAX_TOKEN_TTL_MS);
204
+ }
205
+ function normalizeSlug(raw) {
206
+ const value = readRequired(raw, 'serverSlug');
207
+ if (!SLUG_RE.test(value))
208
+ throw new HubServerRegistrationError(400, 'serverSlug must be a safe path segment.');
209
+ return value;
210
+ }
211
+ function normalizeName(raw) {
212
+ return readRequired(raw, 'serverName');
213
+ }
214
+ function normalizeVersion(raw) {
215
+ return readRequired(raw, 'version');
216
+ }
217
+ function readRequired(raw, field) {
218
+ if (typeof raw !== 'string' || !raw.trim())
219
+ throw new HubServerRegistrationError(400, `${field} is required.`);
220
+ return raw.trim();
221
+ }
222
+ function normalizeHttpUrl(raw, field) {
223
+ const value = readRequired(raw, field).replace(/\/+$/u, '');
224
+ try {
225
+ const parsed = new URL(value);
226
+ if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:')
227
+ throw new Error('bad protocol');
228
+ return value;
229
+ }
230
+ catch {
231
+ throw new HubServerRegistrationError(400, `${field} must be an http(s) URL.`);
232
+ }
233
+ }
234
+ async function fetchServerIdentity(upstream) {
235
+ let response;
236
+ try {
237
+ response = await fetch(new URL('/api/server/identity', `${upstream}/`));
238
+ }
239
+ catch (error) {
240
+ throw new HubServerRegistrationError(502, `Failed to reach upstream identity: ${String(error?.message ?? error)}`);
241
+ }
242
+ if (!response.ok) {
243
+ throw new HubServerRegistrationError(502, `Upstream identity returned HTTP ${response.status}.`);
244
+ }
245
+ return await response.json();
246
+ }
247
+ function validateRegistry(registry) {
248
+ const ids = new Set();
249
+ const slugs = new Set();
250
+ for (const server of registry.servers) {
251
+ if (!server.serverId || !server.serverSlug || !server.serverName) {
252
+ throw new HubServerRegistrationError(500, 'Hub registry contains an invalid server entry.');
253
+ }
254
+ if (!SLUG_RE.test(server.serverSlug)) {
255
+ throw new HubServerRegistrationError(500, 'Hub registry contains an invalid server slug.');
256
+ }
257
+ if (server.connectionMode !== 'bridge') {
258
+ normalizeHttpUrl(server.upstream, 'upstream');
259
+ }
260
+ if (server.bridgeUrl)
261
+ normalizeHttpUrl(server.bridgeUrl, 'bridgeUrl');
262
+ if (ids.has(server.serverId) || slugs.has(server.serverSlug)) {
263
+ throw new HubServerRegistrationError(500, 'Hub registry contains duplicate server ids or slugs.');
264
+ }
265
+ ids.add(server.serverId);
266
+ slugs.add(server.serverSlug);
267
+ }
268
+ }
@@ -0,0 +1,43 @@
1
+ export function parseLibraryManifest(raw) {
2
+ try {
3
+ const parsed = JSON.parse(raw);
4
+ if (!parsed || typeof parsed !== 'object' || !Array.isArray(parsed.documents))
5
+ return null;
6
+ return {
7
+ version: typeof parsed.version === 'number' ? parsed.version : 1,
8
+ documents: parsed.documents.filter((doc) => Boolean(doc)
9
+ && typeof doc.title === 'string'
10
+ && typeof doc.relativePath === 'string'
11
+ && doc.relativePath.trim().length > 0),
12
+ processedSavedItemIds: Array.isArray(parsed.processedSavedItemIds)
13
+ ? parsed.processedSavedItemIds.filter((id) => typeof id === 'string' && id.trim().length > 0)
14
+ : [],
15
+ };
16
+ }
17
+ catch {
18
+ return null;
19
+ }
20
+ }
21
+ export function manifestDocumentsToLibraryDocuments(params) {
22
+ return params.documents.map((doc) => ({
23
+ userId: params.userId,
24
+ title: doc.title.trim(),
25
+ category: (doc.category ?? '').trim(),
26
+ topicPath: (doc.topicPath ?? '').trim(),
27
+ relativePath: doc.relativePath.trim().replace(/^\/+/, ''),
28
+ summary: (doc.summary ?? '').trim(),
29
+ sourceSavedItemIds: Array.isArray(doc.sourceSavedItemIds)
30
+ ? doc.sourceSavedItemIds.filter((id) => typeof id === 'string' && id.trim())
31
+ : [],
32
+ createdAt: params.now,
33
+ updatedAt: params.now,
34
+ lastRunId: params.runId,
35
+ notionPageId: doc.notionPageId ?? null,
36
+ notionPageUrl: doc.notionPageUrl ?? null,
37
+ }));
38
+ }
39
+ export const LIBRARY_MANIFEST_RELATIVE_PATH = '.meta/manifest.json';
40
+ export const LIBRARY_LAST_RUN_RELATIVE_PATH = '.meta/last-run.json';
41
+ export function buildDefaultLibraryManifest() {
42
+ return { version: 1, documents: [], processedSavedItemIds: [] };
43
+ }
@@ -0,0 +1,26 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { buildDefaultLibraryManifest, LIBRARY_MANIFEST_RELATIVE_PATH } from './libraryManifest.js';
4
+ export function buildDefaultLibraryIndexContent() {
5
+ return [
6
+ '# Library Index',
7
+ '',
8
+ 'Curated knowledge documents organized by category and topic.',
9
+ '',
10
+ 'See `.meta/manifest.json` for the machine-readable index.',
11
+ '',
12
+ ].join('\n');
13
+ }
14
+ /** 初始化用户文库目录结构 */
15
+ export function ensureLocalLibraryScaffold(libraryRoot) {
16
+ fs.mkdirSync(libraryRoot, { recursive: true });
17
+ fs.mkdirSync(path.join(libraryRoot, '.meta'), { recursive: true });
18
+ const indexPath = path.join(libraryRoot, 'INDEX.md');
19
+ if (!fs.existsSync(indexPath)) {
20
+ fs.writeFileSync(indexPath, buildDefaultLibraryIndexContent(), 'utf8');
21
+ }
22
+ const manifestPath = path.join(libraryRoot, LIBRARY_MANIFEST_RELATIVE_PATH);
23
+ if (!fs.existsSync(manifestPath)) {
24
+ fs.writeFileSync(manifestPath, `${JSON.stringify(buildDefaultLibraryManifest(), null, 2)}\n`, 'utf8');
25
+ }
26
+ }