@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,154 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ export class ClaudeControlBroker {
3
+ controlsPending = new Map();
4
+ modePending = new Map();
5
+ modelPending = new Map();
6
+ commandPending = new Map();
7
+ nodeRegistry;
8
+ timeoutMs;
9
+ constructor(params) {
10
+ this.nodeRegistry = params.nodeRegistry;
11
+ this.timeoutMs = params.timeoutMs ?? 15_000;
12
+ }
13
+ getControls(nodeId, target) {
14
+ const requestId = randomUUID();
15
+ return new Promise((resolve, reject) => {
16
+ const timer = setTimeout(() => {
17
+ this.controlsPending.delete(requestId);
18
+ reject(new Error('Claude controls request timed out.'));
19
+ }, this.timeoutMs);
20
+ this.controlsPending.set(requestId, { nodeId, resolve, reject, timer });
21
+ const sent = this.nodeRegistry.send(nodeId, {
22
+ type: 'claude.controls.request',
23
+ requestId,
24
+ target,
25
+ });
26
+ if (!sent) {
27
+ clearTimeout(timer);
28
+ this.controlsPending.delete(requestId);
29
+ reject(new Error('Agent node is offline.'));
30
+ }
31
+ });
32
+ }
33
+ setMode(nodeId, target, modeId) {
34
+ const requestId = randomUUID();
35
+ return new Promise((resolve, reject) => {
36
+ const timer = setTimeout(() => {
37
+ this.modePending.delete(requestId);
38
+ reject(new Error('Claude set-mode request timed out.'));
39
+ }, this.timeoutMs);
40
+ this.modePending.set(requestId, { nodeId, resolve, reject, timer });
41
+ const sent = this.nodeRegistry.send(nodeId, {
42
+ type: 'claude.set_mode.request',
43
+ requestId,
44
+ target,
45
+ modeId,
46
+ });
47
+ if (!sent) {
48
+ clearTimeout(timer);
49
+ this.modePending.delete(requestId);
50
+ reject(new Error('Agent node is offline.'));
51
+ }
52
+ });
53
+ }
54
+ setModel(nodeId, target, modelId) {
55
+ const requestId = randomUUID();
56
+ return new Promise((resolve, reject) => {
57
+ const timer = setTimeout(() => {
58
+ this.modelPending.delete(requestId);
59
+ reject(new Error('Claude set-model request timed out.'));
60
+ }, this.timeoutMs);
61
+ this.modelPending.set(requestId, { nodeId, resolve, reject, timer });
62
+ const sent = this.nodeRegistry.send(nodeId, {
63
+ type: 'claude.set_model.request',
64
+ requestId,
65
+ target,
66
+ modelId,
67
+ });
68
+ if (!sent) {
69
+ clearTimeout(timer);
70
+ this.modelPending.delete(requestId);
71
+ reject(new Error('Agent node is offline.'));
72
+ }
73
+ });
74
+ }
75
+ executeCommand(nodeId, target, commandName, args) {
76
+ const requestId = randomUUID();
77
+ return new Promise((resolve, reject) => {
78
+ const timer = setTimeout(() => {
79
+ this.commandPending.delete(requestId);
80
+ reject(new Error('Claude command request timed out.'));
81
+ }, this.timeoutMs);
82
+ this.commandPending.set(requestId, { nodeId, resolve, reject, timer });
83
+ const sent = this.nodeRegistry.send(nodeId, {
84
+ type: 'claude.command.request',
85
+ requestId,
86
+ target,
87
+ commandName,
88
+ args,
89
+ });
90
+ if (!sent) {
91
+ clearTimeout(timer);
92
+ this.commandPending.delete(requestId);
93
+ reject(new Error('Agent node is offline.'));
94
+ }
95
+ });
96
+ }
97
+ handleControlsResponse(msg) {
98
+ this.resolveControlsPending(this.controlsPending, msg, 'Claude controls request failed.');
99
+ }
100
+ handleSetModeResponse(msg) {
101
+ this.resolveControlsPending(this.modePending, msg, 'Claude set-mode request failed.');
102
+ }
103
+ handleSetModelResponse(msg) {
104
+ this.resolveControlsPending(this.modelPending, msg, 'Claude set-model request failed.');
105
+ }
106
+ handleCommandResponse(msg) {
107
+ const pending = this.commandPending.get(msg.requestId);
108
+ if (!pending)
109
+ return;
110
+ this.commandPending.delete(msg.requestId);
111
+ clearTimeout(pending.timer);
112
+ if (msg.error || !msg.controls || !msg.result) {
113
+ pending.reject(new Error(msg.error ?? 'Claude command request failed.'));
114
+ return;
115
+ }
116
+ pending.resolve({
117
+ controls: msg.controls,
118
+ result: msg.result,
119
+ });
120
+ }
121
+ rejectPendingForNode(nodeId) {
122
+ this.rejectControlsMap(this.controlsPending, nodeId);
123
+ this.rejectControlsMap(this.modePending, nodeId);
124
+ this.rejectControlsMap(this.modelPending, nodeId);
125
+ for (const [requestId, pending] of this.commandPending.entries()) {
126
+ if (pending.nodeId !== nodeId)
127
+ continue;
128
+ clearTimeout(pending.timer);
129
+ pending.reject(new Error(`Agent node disconnected: ${nodeId}`));
130
+ this.commandPending.delete(requestId);
131
+ }
132
+ }
133
+ resolveControlsPending(pendingMap, msg, fallback) {
134
+ const pending = pendingMap.get(msg.requestId);
135
+ if (!pending)
136
+ return;
137
+ pendingMap.delete(msg.requestId);
138
+ clearTimeout(pending.timer);
139
+ if (msg.error || !msg.controls) {
140
+ pending.reject(new Error(msg.error ?? fallback));
141
+ return;
142
+ }
143
+ pending.resolve(msg.controls);
144
+ }
145
+ rejectControlsMap(pendingMap, nodeId) {
146
+ for (const [requestId, pending] of pendingMap.entries()) {
147
+ if (pending.nodeId !== nodeId)
148
+ continue;
149
+ clearTimeout(pending.timer);
150
+ pending.reject(new Error(`Agent node disconnected: ${nodeId}`));
151
+ pendingMap.delete(requestId);
152
+ }
153
+ }
154
+ }
@@ -0,0 +1,100 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ export class ClaudeTranscriptBroker {
3
+ pending = new Map();
4
+ nodeRegistry;
5
+ timeoutMs;
6
+ constructor(params) {
7
+ this.nodeRegistry = params.nodeRegistry;
8
+ this.timeoutMs = params.timeoutMs ?? 15_000;
9
+ }
10
+ listFiles(nodeId, workspaceRoot, maxFiles = 1000) {
11
+ const requestId = randomUUID();
12
+ return new Promise((resolve, reject) => {
13
+ const timer = setTimeout(() => {
14
+ this.pending.delete(requestId);
15
+ reject(new Error('Claude transcript request timed out.'));
16
+ }, this.timeoutMs);
17
+ this.pending.set(requestId, { nodeId, kind: 'list', resolve, reject, timer });
18
+ const sent = this.nodeRegistry.send(nodeId, {
19
+ type: 'claude.transcript.list.request',
20
+ requestId,
21
+ workspaceRoot,
22
+ maxFiles,
23
+ });
24
+ if (!sent) {
25
+ clearTimeout(timer);
26
+ this.pending.delete(requestId);
27
+ reject(new Error('Agent node is offline.'));
28
+ }
29
+ });
30
+ }
31
+ readFile(nodeId, workspaceRoot, transcriptPath) {
32
+ const requestId = randomUUID();
33
+ return new Promise((resolve, reject) => {
34
+ const timer = setTimeout(() => {
35
+ this.pending.delete(requestId);
36
+ reject(new Error('Claude transcript request timed out.'));
37
+ }, this.timeoutMs);
38
+ this.pending.set(requestId, { nodeId, kind: 'read', resolve, reject, timer });
39
+ const sent = this.nodeRegistry.send(nodeId, {
40
+ type: 'claude.transcript.read.request',
41
+ requestId,
42
+ workspaceRoot,
43
+ path: transcriptPath,
44
+ });
45
+ if (!sent) {
46
+ clearTimeout(timer);
47
+ this.pending.delete(requestId);
48
+ reject(new Error('Agent node is offline.'));
49
+ }
50
+ });
51
+ }
52
+ handleListResponse(msg) {
53
+ const pending = this.pending.get(msg.requestId);
54
+ if (!pending || pending.kind !== 'list')
55
+ return;
56
+ this.pending.delete(msg.requestId);
57
+ clearTimeout(pending.timer);
58
+ if (msg.error || !msg.rootPath || !msg.files) {
59
+ pending.reject(new Error(formatErrorMessage(msg.errorCode, msg.error)));
60
+ return;
61
+ }
62
+ pending.resolve({
63
+ rootPath: msg.rootPath,
64
+ files: msg.files,
65
+ truncated: Boolean(msg.truncated),
66
+ });
67
+ }
68
+ handleReadResponse(msg) {
69
+ const pending = this.pending.get(msg.requestId);
70
+ if (!pending || pending.kind !== 'read')
71
+ return;
72
+ this.pending.delete(msg.requestId);
73
+ clearTimeout(pending.timer);
74
+ if (msg.error || !msg.rootPath || msg.content === undefined || msg.size === undefined) {
75
+ pending.reject(new Error(formatErrorMessage(msg.errorCode, msg.error)));
76
+ return;
77
+ }
78
+ pending.resolve({
79
+ rootPath: msg.rootPath,
80
+ path: msg.path,
81
+ content: msg.content,
82
+ size: msg.size,
83
+ modifiedAt: msg.modifiedAt ?? null,
84
+ });
85
+ }
86
+ rejectPendingForNode(nodeId) {
87
+ for (const [requestId, pending] of this.pending.entries()) {
88
+ if (pending.nodeId !== nodeId)
89
+ continue;
90
+ clearTimeout(pending.timer);
91
+ pending.reject(new Error(`Agent node disconnected: ${nodeId}`));
92
+ this.pending.delete(requestId);
93
+ }
94
+ }
95
+ }
96
+ function formatErrorMessage(errorCode, error) {
97
+ if (errorCode)
98
+ return `${errorCode}:${error ?? 'claude transcript request failed'}`;
99
+ return error ?? 'claude transcript request failed';
100
+ }
@@ -0,0 +1,359 @@
1
+ import { log } from '@bbigbang/runtime-acp';
2
+ import { attachPlatformInputsToTurns, getRolloutSortTime, listPlatformInputsForConversation, parseReplyTarget, parseTriggerTarget, } from './codexTranscriptService.js';
3
+ const MAX_INLINE_TRANSCRIPT_BYTES = 2 * 1024 * 1024;
4
+ const HEURISTIC_TRANSCRIPT_LOOKBACK_MS = 24 * 60 * 60 * 1000;
5
+ const MAX_SESSION_MATCH_TRANSCRIPT_FILES = 8;
6
+ const MAX_HEURISTIC_TRANSCRIPT_FILES = 6;
7
+ export class ClaudeTranscriptService {
8
+ rolloutCache = new Map();
9
+ db;
10
+ broker;
11
+ getConversationById;
12
+ getAgentById;
13
+ getAcpSessionIdByConversationId;
14
+ constructor(params) {
15
+ this.db = params.db;
16
+ this.broker = params.broker;
17
+ this.getConversationById = params.getConversationById;
18
+ this.getAgentById = params.getAgentById;
19
+ this.getAcpSessionIdByConversationId = params.getAcpSessionIdByConversationId ?? (() => null);
20
+ }
21
+ async getConversationDebug(conversationId) {
22
+ const conversation = this.getConversationById(conversationId);
23
+ if (!conversation)
24
+ throw new Error('Conversation not found.');
25
+ if (conversation.agentType !== 'claude_acp' && conversation.agentType !== 'claude_sdk') {
26
+ throw new Error('Claude debug is only supported for Claude conversations.');
27
+ }
28
+ if (!conversation.nodeId)
29
+ throw new Error('Conversation is not assigned to a remote node.');
30
+ if (!conversation.workspacePath)
31
+ throw new Error('Conversation has no workspace path.');
32
+ const replyTarget = (conversation.replyTarget ?? '').trim();
33
+ if (!replyTarget)
34
+ throw new Error('Conversation has no reply target.');
35
+ const acpSessionId = this.getAcpSessionIdByConversationId(conversationId)?.trim() || undefined;
36
+ const heuristicTranscriptCutoff = Date.now() - HEURISTIC_TRANSCRIPT_LOOKBACK_MS;
37
+ const listing = await this.broker.listFiles(conversation.nodeId, conversation.workspacePath).catch((error) => {
38
+ log.warn('[claude-debug] failed to list transcripts', {
39
+ conversationId,
40
+ nodeId: conversation.nodeId,
41
+ workspacePath: conversation.workspacePath,
42
+ error: String(error?.message ?? error),
43
+ });
44
+ return {
45
+ rootPath: '',
46
+ files: [],
47
+ truncated: false,
48
+ };
49
+ });
50
+ const exactSessionRollouts = [];
51
+ const heuristicRollouts = [];
52
+ const workspaceCandidates = listing.files.filter((file) => {
53
+ if (!acpSessionId && file.modifiedAt < heuristicTranscriptCutoff)
54
+ return false;
55
+ if (file.size > MAX_INLINE_TRANSCRIPT_BYTES)
56
+ return false;
57
+ if (file.cwd && file.cwd.trim() !== conversation.workspacePath)
58
+ return false;
59
+ return true;
60
+ });
61
+ const filesToScan = acpSessionId
62
+ ? selectTranscriptCandidatesForSession(workspaceCandidates, acpSessionId, heuristicTranscriptCutoff)
63
+ : workspaceCandidates.slice(0, MAX_HEURISTIC_TRANSCRIPT_FILES);
64
+ for (const file of filesToScan) {
65
+ const loaded = await this.loadParsedRollout(conversation.nodeId, conversation.workspacePath, file);
66
+ if (loaded.timedOut)
67
+ break;
68
+ const parsed = loaded.rollout;
69
+ if (!parsed)
70
+ continue;
71
+ const sessionCwd = parsed.cwd?.trim();
72
+ if (!sessionCwd || sessionCwd !== conversation.workspacePath)
73
+ continue;
74
+ const exactSessionMatch = acpSessionId != null
75
+ && (parsed.sessionId === acpSessionId || file.path.includes(acpSessionId));
76
+ let matchedTurns = parsed.turns.filter((turn) => (turn.replyTarget ?? '').trim() === replyTarget);
77
+ if (matchedTurns.length === 0 && exactSessionMatch) {
78
+ matchedTurns = parsed.turns.filter((turn) => !(turn.replyTarget ?? '').trim());
79
+ if (matchedTurns.length === 0) {
80
+ matchedTurns = parsed.turns;
81
+ }
82
+ }
83
+ if (matchedTurns.length === 0)
84
+ continue;
85
+ const matchedRollout = {
86
+ ...parsed,
87
+ turns: matchedTurns,
88
+ };
89
+ if (exactSessionMatch) {
90
+ exactSessionRollouts.push(matchedRollout);
91
+ }
92
+ else {
93
+ heuristicRollouts.push(matchedRollout);
94
+ }
95
+ }
96
+ const sessionMatchMissed = Boolean(acpSessionId) && exactSessionRollouts.length === 0;
97
+ const usingExactSession = Boolean(acpSessionId) && exactSessionRollouts.length > 0;
98
+ const rollouts = usingExactSession ? exactSessionRollouts : heuristicRollouts;
99
+ rollouts.sort((a, b) => {
100
+ const aTime = getRolloutSortTime(a);
101
+ const bTime = getRolloutSortTime(b);
102
+ return usingExactSession ? aTime - bTime : bTime - aTime;
103
+ });
104
+ const platformInputs = listPlatformInputsForConversation(this.db, {
105
+ conversation,
106
+ getAgentById: this.getAgentById,
107
+ includePlatformTrace: false,
108
+ newestFirst: !usingExactSession,
109
+ });
110
+ const unmatchedPlatformInputs = attachPlatformInputsToTurns(rollouts, platformInputs);
111
+ return {
112
+ provider: 'claude',
113
+ conversationId: conversation.id,
114
+ agentType: conversation.agentType,
115
+ workspacePath: conversation.workspacePath,
116
+ replyTarget,
117
+ acpSessionId,
118
+ matchMode: usingExactSession ? 'acp_session_id' : 'heuristic',
119
+ sessionMatchMissed,
120
+ truncated: listing.truncated,
121
+ rollouts,
122
+ unmatchedPlatformInputs,
123
+ notificationDeliveries: [],
124
+ };
125
+ }
126
+ async loadParsedRollout(nodeId, workspacePath, file) {
127
+ const cacheKey = `${nodeId}:${workspacePath}:${file.path}:${file.modifiedAt}:${file.size}`;
128
+ const cached = this.rolloutCache.get(cacheKey);
129
+ if (cached) {
130
+ this.rolloutCache.delete(cacheKey);
131
+ this.rolloutCache.set(cacheKey, cached);
132
+ return { rollout: cached, timedOut: false };
133
+ }
134
+ let result;
135
+ try {
136
+ result = await this.broker.readFile(nodeId, workspacePath, file.path);
137
+ }
138
+ catch (error) {
139
+ const message = String(error?.message ?? error);
140
+ log.warn('[claude-debug] failed to read transcript', {
141
+ nodeId,
142
+ workspacePath,
143
+ path: file.path,
144
+ error: message,
145
+ });
146
+ return { rollout: null, timedOut: isTranscriptTimeoutError(message) };
147
+ }
148
+ const parsed = parseClaudeRollout(result.content, {
149
+ path: file.path,
150
+ modifiedAt: file.modifiedAt,
151
+ size: file.size,
152
+ });
153
+ setCachedRollout(this.rolloutCache, cacheKey, parsed);
154
+ return { rollout: parsed, timedOut: false };
155
+ }
156
+ }
157
+ function parseClaudeRollout(content, meta) {
158
+ const lines = content.split(/\r?\n/).filter((line) => line.trim().length > 0);
159
+ let sessionId;
160
+ let cwd;
161
+ const turns = [];
162
+ let currentTurn = null;
163
+ const functionCallById = new Map();
164
+ const pushCurrentTurn = () => {
165
+ if (!currentTurn)
166
+ return;
167
+ turns.push(currentTurn);
168
+ currentTurn = null;
169
+ functionCallById.clear();
170
+ };
171
+ for (const line of lines) {
172
+ let parsedLine;
173
+ try {
174
+ parsedLine = JSON.parse(line);
175
+ }
176
+ catch {
177
+ continue;
178
+ }
179
+ const lineType = stringValue(parsedLine.type);
180
+ const timestamp = stringValue(parsedLine.timestamp) ?? new Date(meta.modifiedAt).toISOString();
181
+ sessionId = sessionId ?? stringValue(parsedLine.sessionId);
182
+ cwd = cwd ?? stringValue(parsedLine.cwd);
183
+ if (lineType === 'user') {
184
+ const message = objectValue(parsedLine.message);
185
+ const role = stringValue(message?.role);
186
+ const textBlocks = extractClaudeTextBlocks(message?.content);
187
+ const toolResults = extractClaudeToolResults(message?.content);
188
+ if (role === 'user' && textBlocks.length > 0) {
189
+ pushCurrentTurn();
190
+ const combined = textBlocks.join('\n\n');
191
+ currentTurn = {
192
+ turnId: stringValue(parsedLine.uuid) ?? `turn-${turns.length + 1}`,
193
+ timestamp,
194
+ cwd: stringValue(parsedLine.cwd) ?? cwd,
195
+ replyTarget: parseReplyTarget(combined),
196
+ triggerTarget: parseTriggerTarget(combined),
197
+ inputBlocks: textBlocks,
198
+ combinedUserMessage: combined,
199
+ functionCalls: [],
200
+ assistantOutputs: [],
201
+ reasoningSummaries: [],
202
+ hasEncryptedReasoning: false,
203
+ };
204
+ }
205
+ else if (currentTurn && toolResults.length > 0) {
206
+ const toolUseId = stringValueArray(message?.content, 'tool_use_id')[0];
207
+ const outputText = toolResults.join('\n\n');
208
+ if (toolUseId) {
209
+ const existing = functionCallById.get(toolUseId);
210
+ if (existing) {
211
+ existing.output = outputText;
212
+ existing.outputTimestamp = timestamp;
213
+ }
214
+ }
215
+ }
216
+ continue;
217
+ }
218
+ if (lineType === 'assistant') {
219
+ if (!currentTurn)
220
+ continue;
221
+ const message = objectValue(parsedLine.message);
222
+ const assistantTexts = extractClaudeTextBlocks(message?.content);
223
+ for (const text of assistantTexts) {
224
+ currentTurn.assistantOutputs.push({
225
+ text,
226
+ phase: stringValue(message?.stop_reason) ?? undefined,
227
+ timestamp,
228
+ });
229
+ }
230
+ const toolUses = extractClaudeToolUses(message?.content);
231
+ for (const toolUse of toolUses) {
232
+ const call = {
233
+ callId: toolUse.id,
234
+ name: toolUse.name,
235
+ arguments: toolUse.input,
236
+ timestamp,
237
+ };
238
+ functionCallById.set(call.callId, call);
239
+ currentTurn.functionCalls.push(call);
240
+ }
241
+ const usage = objectValue(message?.usage);
242
+ if (usage) {
243
+ currentTurn.tokenUsage = parseClaudeTokenUsage(usage);
244
+ }
245
+ }
246
+ }
247
+ pushCurrentTurn();
248
+ return {
249
+ path: meta.path,
250
+ modifiedAt: meta.modifiedAt,
251
+ size: meta.size,
252
+ sessionId,
253
+ cwd,
254
+ preludeDeveloperMessages: [],
255
+ preludeUserMessages: [],
256
+ turns,
257
+ };
258
+ }
259
+ function extractClaudeTextBlocks(content) {
260
+ const blocks = Array.isArray(content) ? content : [];
261
+ return blocks.flatMap((block) => {
262
+ const typed = objectValue(block);
263
+ if (!typed)
264
+ return [];
265
+ if (stringValue(typed.type) !== 'text')
266
+ return [];
267
+ const text = stringValue(typed.text);
268
+ return text ? [text] : [];
269
+ });
270
+ }
271
+ function extractClaudeToolResults(content) {
272
+ const blocks = Array.isArray(content) ? content : [];
273
+ return blocks.flatMap((block) => {
274
+ const typed = objectValue(block);
275
+ if (!typed)
276
+ return [];
277
+ if (stringValue(typed.type) !== 'tool_result')
278
+ return [];
279
+ return extractClaudeTextBlocks(typed.content);
280
+ });
281
+ }
282
+ function selectTranscriptCandidatesForSession(files, acpSessionId, heuristicCutoffMs) {
283
+ const exactCandidates = files.filter((file) => file.sessionId === acpSessionId);
284
+ if (exactCandidates.length > 0)
285
+ return exactCandidates.slice(0, MAX_SESSION_MATCH_TRANSCRIPT_FILES);
286
+ const hintedCandidates = files.filter((file) => file.path.includes(acpSessionId));
287
+ if (hintedCandidates.length > 0)
288
+ return hintedCandidates.slice(0, MAX_SESSION_MATCH_TRANSCRIPT_FILES);
289
+ const heuristicCandidates = files.filter((file) => file.modifiedAt >= heuristicCutoffMs);
290
+ if (heuristicCandidates.length > 0)
291
+ return heuristicCandidates.slice(0, MAX_HEURISTIC_TRANSCRIPT_FILES);
292
+ return files.slice(0, MAX_HEURISTIC_TRANSCRIPT_FILES);
293
+ }
294
+ function setCachedRollout(cache, key, rollout, maxEntries = 64) {
295
+ cache.set(key, rollout);
296
+ while (cache.size > maxEntries) {
297
+ const oldestKey = cache.keys().next().value;
298
+ if (!oldestKey)
299
+ break;
300
+ cache.delete(oldestKey);
301
+ }
302
+ }
303
+ function extractClaudeToolUses(content) {
304
+ const blocks = Array.isArray(content) ? content : [];
305
+ return blocks.flatMap((block) => {
306
+ const typed = objectValue(block);
307
+ if (!typed)
308
+ return [];
309
+ if (stringValue(typed.type) !== 'tool_use')
310
+ return [];
311
+ const id = stringValue(typed.id);
312
+ const name = stringValue(typed.name);
313
+ if (!id || !name)
314
+ return [];
315
+ return [{
316
+ id,
317
+ name,
318
+ input: JSON.stringify(typed.input ?? {}, null, 2),
319
+ }];
320
+ });
321
+ }
322
+ function parseClaudeTokenUsage(usage) {
323
+ const inputTokens = numberValue(usage.input_tokens) ?? numberValue(usage.prompt_tokens);
324
+ const cachedInputTokens = numberValue(usage.cache_read_input_tokens) ?? numberValue(usage.cached_tokens);
325
+ return {
326
+ currentInputTokens: inputTokens,
327
+ inputTokens,
328
+ cachedInputTokens,
329
+ currentCachedInputTokens: cachedInputTokens,
330
+ outputTokens: numberValue(usage.output_tokens),
331
+ totalTokens: addNumbers(inputTokens, numberValue(usage.output_tokens)),
332
+ modelContextWindow: 256000,
333
+ };
334
+ }
335
+ function objectValue(value) {
336
+ return value && typeof value === 'object' ? value : null;
337
+ }
338
+ function stringValue(value) {
339
+ return typeof value === 'string' && value.trim() ? value : undefined;
340
+ }
341
+ function numberValue(value) {
342
+ return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
343
+ }
344
+ function addNumbers(a, b) {
345
+ if (a == null && b == null)
346
+ return undefined;
347
+ return (a ?? 0) + (b ?? 0);
348
+ }
349
+ function stringValueArray(content, key) {
350
+ const blocks = Array.isArray(content) ? content : [];
351
+ return blocks.flatMap((block) => {
352
+ const typed = objectValue(block);
353
+ const value = stringValue(typed?.[key]);
354
+ return value ? [value] : [];
355
+ });
356
+ }
357
+ function isTranscriptTimeoutError(message) {
358
+ return message.toLowerCase().includes('timed out');
359
+ }