@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.
- package/dist/config.js +380 -0
- package/dist/execution/executionDispatcher.js +3810 -0
- package/dist/main.js +90 -0
- package/dist/nodeEventHistory.js +206 -0
- package/dist/scheduler/dreamLogic.js +50 -0
- package/dist/scheduler/dreamScheduler.js +65 -0
- package/dist/services/agentFileAccessService.js +1913 -0
- package/dist/services/agentRuntimeCleanupBroker.js +62 -0
- package/dist/services/agentSkillsBroker.js +118 -0
- package/dist/services/agentSkillsService.js +83 -0
- package/dist/services/agentWorkspaceBroker.js +937 -0
- package/dist/services/agentWorkspaceService.js +70 -0
- package/dist/services/appVersion.js +14 -0
- package/dist/services/auth.js +586 -0
- package/dist/services/claudeControlBroker.js +154 -0
- package/dist/services/claudeTranscriptBroker.js +100 -0
- package/dist/services/claudeTranscriptService.js +359 -0
- package/dist/services/codexAppServerBroker.js +155 -0
- package/dist/services/codexTranscriptBroker.js +98 -0
- package/dist/services/codexTranscriptService.js +961 -0
- package/dist/services/droidMissionBroker.js +124 -0
- package/dist/services/droidMissionImporter.js +630 -0
- package/dist/services/droidModelOptions.js +165 -0
- package/dist/services/hubServerRegistrationService.js +268 -0
- package/dist/services/libraryManifest.js +43 -0
- package/dist/services/libraryScaffold.js +26 -0
- package/dist/services/libraryService.js +2263 -0
- package/dist/services/memoryService.js +386 -0
- package/dist/services/missionEvidence.js +377 -0
- package/dist/services/missionService.js +2361 -0
- package/dist/services/missionTrace.js +158 -0
- package/dist/services/nativeMissionBriefParser.js +120 -0
- package/dist/services/nativeMissionOrchestrator.js +2045 -0
- package/dist/services/nativeMissionReportGenerator.js +227 -0
- package/dist/services/nativeMissionValidationRunner.js +452 -0
- package/dist/services/nativeMissionWorkerBroker.js +190 -0
- package/dist/services/nodeRegistry.js +34 -0
- package/dist/services/nodeStateReconciler.js +97 -0
- package/dist/services/panelMediaScanner.js +119 -0
- package/dist/services/persistentRuntimeJsonlClient.js +153 -0
- package/dist/services/platformAgentPolicy.js +180 -0
- package/dist/services/platformAgentService.js +2041 -0
- package/dist/services/projectAccessResolver.js +93 -0
- package/dist/services/projectService.js +392 -0
- package/dist/services/resourceSpaceService.js +140 -0
- package/dist/services/scenarioRuntimeService.js +1130 -0
- package/dist/services/suggestedPlannerService.js +868 -0
- package/dist/services/workbenchGitBroker.js +161 -0
- package/dist/services/workbenchGitService.js +69 -0
- package/dist/services/workbenchInspectBroker.js +65 -0
- package/dist/services/workbenchNodePathService.js +79 -0
- package/dist/services/workbenchRegistryService.js +240 -0
- package/dist/services/workbenchRootService.js +181 -0
- package/dist/services/workbenchTerminalBroker.js +378 -0
- package/dist/services/workspaceRunOwnership.js +60 -0
- package/dist/services/workspaceScaffold.js +105 -0
- package/dist/services/workspaceSessionRuntimeService.js +576 -0
- package/dist/services/workspaceSessionService.js +245 -0
- package/dist/services/workspaceToolActionRunner.js +1582 -0
- package/dist/services/workspaceToolErrors.js +10 -0
- package/dist/services/workspaceToolExecutionUtils.js +895 -0
- package/dist/services/workspaceToolLatestStateProjector.js +91 -0
- package/dist/services/workspaceToolManifest.js +572 -0
- package/dist/services/workspaceToolMutationQueue.js +43 -0
- package/dist/services/workspaceToolPanelProjection.js +460 -0
- package/dist/services/workspaceToolPromotion.js +255 -0
- package/dist/services/workspaceToolPromotionState.js +224 -0
- package/dist/services/workspaceToolPublishDiagnostics.js +189 -0
- package/dist/services/workspaceToolPublishIdentityResolver.js +146 -0
- package/dist/services/workspaceToolReadModel.js +378 -0
- package/dist/services/workspaceToolRunLedger.js +239 -0
- package/dist/services/workspaceToolService.js +3067 -0
- package/dist/services/workspaceToolSnapshotPanelSync.js +293 -0
- package/dist/services/workspaceToolTerminalLifecycle.js +283 -0
- package/dist/services/workspaceToolTypes.js +1 -0
- package/dist/services/workspaceToolUploadMaterializer.js +228 -0
- package/dist/web/actionCardRoutes.js +129 -0
- package/dist/web/actionCards.js +469 -0
- package/dist/web/activationContext.js +684 -0
- package/dist/web/agentChannelGuards.js +48 -0
- package/dist/web/agentMentionCooldowns.js +32 -0
- package/dist/web/agentReminders.js +1668 -0
- package/dist/web/agentRuntimePresence.js +197 -0
- package/dist/web/agentSelfState.js +494 -0
- package/dist/web/agentTaskLinks.js +26 -0
- package/dist/web/agentVisibility.js +79 -0
- package/dist/web/assets.js +95 -0
- package/dist/web/channelActivationPrompt.js +395 -0
- package/dist/web/channelMemoryNotes.js +127 -0
- package/dist/web/channelMentions.js +10 -0
- package/dist/web/channelMessageSequences.js +19 -0
- package/dist/web/channelSubscriptions.js +26 -0
- package/dist/web/clearedTaskRoots.js +10 -0
- package/dist/web/collaborationPromptGuidance.js +36 -0
- package/dist/web/collaborationSurfaceState.js +140 -0
- package/dist/web/contextBundleRanking.js +154 -0
- package/dist/web/contextBundleResolver.js +488 -0
- package/dist/web/conversationBuiltinSkillRoots.js +50 -0
- package/dist/web/conversationControls.js +232 -0
- package/dist/web/conversationHandoffs.js +612 -0
- package/dist/web/conversationManager.js +2511 -0
- package/dist/web/conversationSummaries.js +876 -0
- package/dist/web/conversationSurfaceKinds.js +17 -0
- package/dist/web/conversationTargets.js +173 -0
- package/dist/web/directActivationPrompt.js +122 -0
- package/dist/web/directReplyTargets.js +69 -0
- package/dist/web/directThreadResolver.js +129 -0
- package/dist/web/dmTaskHandoffPrompt.js +120 -0
- package/dist/web/dmTaskThreadStatusProjection.js +229 -0
- package/dist/web/ftsQuery.js +33 -0
- package/dist/web/internalAgentRouter.js +11341 -0
- package/dist/web/libraryCuratorScheduler.js +58 -0
- package/dist/web/libraryDocumentPromptGuidance.js +8 -0
- package/dist/web/messageCheckpoints.js +19 -0
- package/dist/web/nodeWsHandler.js +2495 -0
- package/dist/web/notificationRounds.js +1061 -0
- package/dist/web/panelActionMessages.js +108 -0
- package/dist/web/panelActivationPrompt.js +18 -0
- package/dist/web/panelAudit.js +273 -0
- package/dist/web/panelLifecycle.js +222 -0
- package/dist/web/panelMediaPolicy.js +43 -0
- package/dist/web/panelPathPolicy.js +63 -0
- package/dist/web/panelPreviews.js +175 -0
- package/dist/web/panelQueryHandles.js +2749 -0
- package/dist/web/panelRoutes.js +2147 -0
- package/dist/web/panels.js +904 -0
- package/dist/web/peerInboxAggregates.js +1247 -0
- package/dist/web/planApprovalState.js +92 -0
- package/dist/web/platformAgentScheduler.js +66 -0
- package/dist/web/proactiveOpportunities.js +452 -0
- package/dist/web/promptContextSections.js +242 -0
- package/dist/web/promptHistorySanitizer.js +26 -0
- package/dist/web/promptSlashCommands.js +158 -0
- package/dist/web/rollingConversationSummary.js +453 -0
- package/dist/web/routeHelpers.js +11 -0
- package/dist/web/routes/handoff.js +288 -0
- package/dist/web/routes/history.js +345 -0
- package/dist/web/routes/memory.js +258 -0
- package/dist/web/routes/selfState.js +171 -0
- package/dist/web/routes/workspace.js +154 -0
- package/dist/web/runSurfaceWatermarks.js +431 -0
- package/dist/web/runtimeCapabilities.js +48 -0
- package/dist/web/sameAgentHandoffs.js +494 -0
- package/dist/web/server.js +15567 -0
- package/dist/web/sharedCollaborationCapsules.js +163 -0
- package/dist/web/soloSessionRelay.js +42 -0
- package/dist/web/soloWsHandler.js +138 -0
- package/dist/web/suggestedPlannerScheduler.js +56 -0
- package/dist/web/surfaceActivationPolicy.js +108 -0
- package/dist/web/surfaceCollaborators.js +61 -0
- package/dist/web/surfaceSystemStatus.js +263 -0
- package/dist/web/targetParticipants.js +77 -0
- package/dist/web/taskEvents.js +49 -0
- package/dist/web/taskLifecycleMessages.js +165 -0
- package/dist/web/taskLoops.js +732 -0
- package/dist/web/taskMemoryNotes.js +224 -0
- package/dist/web/taskNumbers.js +16 -0
- package/dist/web/taskOwnerGuards.js +49 -0
- package/dist/web/taskParticipantResolver.js +42 -0
- package/dist/web/taskParticipants.js +97 -0
- package/dist/web/taskSourceDetails.js +20 -0
- package/dist/web/taskStateViews.js +210 -0
- package/dist/web/taskStatusTransitions.js +9 -0
- package/dist/web/taskThreadFollowups.js +599 -0
- package/dist/web/taskThreadRuntimeClosure.js +685 -0
- package/dist/web/taskUpdateDelivery.js +104 -0
- package/dist/web/threadReplyContentHeuristics.js +30 -0
- package/dist/web/threadRoots.js +61 -0
- package/dist/web/threadTaskBindings.js +365 -0
- package/dist/web/uiPanelPromptGuidance.js +27 -0
- package/dist/web/workspaceMemoryHints.js +143 -0
- package/dist/web/workspaceToolPromptGuidance.js +30 -0
- package/dist/web/wsHandler.js +397 -0
- package/dist/web/wsSink.js +116 -0
- package/package.json +54 -0
|
@@ -0,0 +1,630 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
2
|
+
import { basename, join } from 'node:path';
|
|
3
|
+
const JSON_FILES = [
|
|
4
|
+
'state.json',
|
|
5
|
+
'features.json',
|
|
6
|
+
'validation-state.json',
|
|
7
|
+
'model-settings.json',
|
|
8
|
+
'runtime-custom-models.json',
|
|
9
|
+
];
|
|
10
|
+
const SENSITIVE_KEY_RE = /(api[_-]?key|secret|token|password|authorization|credential|bearer)/i;
|
|
11
|
+
const SECRET_VALUE_PATTERNS = [
|
|
12
|
+
/\bBearer\s+[A-Za-z0-9._~+/=-]{8,}/gi,
|
|
13
|
+
/\b(?:sk|sk-ant|sk-proj|ghp|github_pat|xox[baprs])[-_A-Za-z0-9]{8,}\b/g,
|
|
14
|
+
/\b[A-Za-z0-9._%+-]+:[A-Za-z0-9._~+/=-]{12,}@/g,
|
|
15
|
+
];
|
|
16
|
+
const REPORT_TEXT_LIMIT = 120_000;
|
|
17
|
+
const TRANSCRIPT_TEXT_LIMIT = 40_000;
|
|
18
|
+
export function importDroidMissionArtifacts(missionDir, now = Date.now(), options = {}) {
|
|
19
|
+
if (!existsSync(missionDir) || !statSync(missionDir).isDirectory()) {
|
|
20
|
+
throw new Error(`Droid mission dir not found: ${missionDir}`);
|
|
21
|
+
}
|
|
22
|
+
const anomalies = [];
|
|
23
|
+
const state = readJsonObject(join(missionDir, 'state.json'), anomalies, false);
|
|
24
|
+
const featuresJson = readJsonObject(join(missionDir, 'features.json'), anomalies, false);
|
|
25
|
+
const validationJson = readJsonObject(join(missionDir, 'validation-state.json'), anomalies, false);
|
|
26
|
+
const modelSettings = readJsonObject(join(missionDir, 'model-settings.json'), anomalies, false);
|
|
27
|
+
const runtimeCustomModels = readJsonObject(join(missionDir, 'runtime-custom-models.json'), anomalies, true);
|
|
28
|
+
const timelineEvents = readJsonLines(join(missionDir, 'progress_log.jsonl'), anomalies);
|
|
29
|
+
const handoffItems = readHandoffs(join(missionDir, 'handoffs'), anomalies);
|
|
30
|
+
const featureItems = normalizeFeatures(featuresJson);
|
|
31
|
+
const assertions = normalizeValidationAssertions(validationJson);
|
|
32
|
+
const reports = readMarkdownReports(missionDir, anomalies);
|
|
33
|
+
const transcripts = readWorkerTranscripts(missionDir, anomalies);
|
|
34
|
+
const reportOnly = shouldUseReportOnlyMode(state, reports, options);
|
|
35
|
+
const artifactFiles = listArtifactFiles(missionDir, anomalies, { reportOnly });
|
|
36
|
+
const normalizedAnomalies = normalizeArtifactAnomalies(anomalies, missionDir, { reportOnly });
|
|
37
|
+
return {
|
|
38
|
+
schemaVersion: 1,
|
|
39
|
+
importedAt: now,
|
|
40
|
+
missionDir,
|
|
41
|
+
...(options.platformMissionDir ? { platformMissionDir: options.platformMissionDir } : {}),
|
|
42
|
+
...(options.factorySessionId ? { factorySessionId: options.factorySessionId } : {}),
|
|
43
|
+
overview: buildOverview(state, featureItems, timelineEvents, handoffItems, assertions, {
|
|
44
|
+
fallbackMissionId: options.fallbackMissionId,
|
|
45
|
+
fallbackState: options.fallbackState,
|
|
46
|
+
reportOnly,
|
|
47
|
+
reportCount: reports.length,
|
|
48
|
+
}),
|
|
49
|
+
features: {
|
|
50
|
+
summary: summarizeByStatusAndMilestone(featureItems),
|
|
51
|
+
items: featureItems,
|
|
52
|
+
},
|
|
53
|
+
timeline: {
|
|
54
|
+
summary: summarizeTimeline(timelineEvents),
|
|
55
|
+
events: timelineEvents,
|
|
56
|
+
},
|
|
57
|
+
handoffs: {
|
|
58
|
+
summary: summarizeHandoffs(handoffItems),
|
|
59
|
+
items: handoffItems,
|
|
60
|
+
},
|
|
61
|
+
validation: {
|
|
62
|
+
summary: summarizeValidation(assertions),
|
|
63
|
+
assertions,
|
|
64
|
+
raw: validationJson ? redactSecrets(validationJson) : undefined,
|
|
65
|
+
},
|
|
66
|
+
artifacts: {
|
|
67
|
+
files: artifactFiles,
|
|
68
|
+
reports,
|
|
69
|
+
transcripts,
|
|
70
|
+
diagnostics: summarizeAnomalies(normalizedAnomalies),
|
|
71
|
+
},
|
|
72
|
+
settings: {
|
|
73
|
+
modelSettings: modelSettings ? redactSecrets(modelSettings) : null,
|
|
74
|
+
runtimeCustomModels: runtimeCustomModels ? redactSecrets(runtimeCustomModels) : null,
|
|
75
|
+
},
|
|
76
|
+
anomalies: normalizedAnomalies,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function readWorkerTranscripts(missionDir, anomalies) {
|
|
80
|
+
const filePath = join(missionDir, 'worker-transcripts.jsonl');
|
|
81
|
+
if (!existsSync(filePath))
|
|
82
|
+
return [];
|
|
83
|
+
const lines = readFileSync(filePath, 'utf8').split(/\r?\n/);
|
|
84
|
+
const transcripts = [];
|
|
85
|
+
lines.forEach((line, index) => {
|
|
86
|
+
if (!line.trim())
|
|
87
|
+
return;
|
|
88
|
+
try {
|
|
89
|
+
const parsed = JSON.parse(line);
|
|
90
|
+
if (!isRecord(parsed)) {
|
|
91
|
+
anomalies.push({ severity: 'warning', type: 'invalid_jsonl_record', path: 'worker-transcripts.jsonl', line: index + 1 });
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const skeleton = stringValue(parsed.skeleton) ?? stringValue(parsed.transcript) ?? stringValue(parsed.text) ?? '';
|
|
95
|
+
const truncated = skeleton.length > TRANSCRIPT_TEXT_LIMIT;
|
|
96
|
+
const text = truncated ? `${skeleton.slice(0, TRANSCRIPT_TEXT_LIMIT)}\n[truncated]` : skeleton;
|
|
97
|
+
transcripts.push(redactSecrets({
|
|
98
|
+
lineNumber: index + 1,
|
|
99
|
+
featureId: stringValue(parsed.featureId),
|
|
100
|
+
milestone: stringValue(parsed.milestone),
|
|
101
|
+
workerSessionId: stringValue(parsed.workerSessionId),
|
|
102
|
+
role: inferTranscriptRole(parsed),
|
|
103
|
+
timestamp: stringValue(parsed.timestamp),
|
|
104
|
+
timestampMs: parseTimestamp(stringValue(parsed.timestamp)) ?? numberValue(parsed.timestampMs),
|
|
105
|
+
preview: firstMeaningfulLine(skeleton),
|
|
106
|
+
text,
|
|
107
|
+
truncated,
|
|
108
|
+
raw: transcriptMetadata(parsed, { textChars: skeleton.length, truncated }),
|
|
109
|
+
}));
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
anomalies.push({
|
|
113
|
+
severity: 'warning',
|
|
114
|
+
type: 'invalid_jsonl',
|
|
115
|
+
path: 'worker-transcripts.jsonl',
|
|
116
|
+
line: index + 1,
|
|
117
|
+
message: String(error.message ?? error),
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
return transcripts.sort((left, right) => {
|
|
122
|
+
const leftTime = numberValue(left.timestampMs) ?? numberValue(left.lineNumber) ?? 0;
|
|
123
|
+
const rightTime = numberValue(right.timestampMs) ?? numberValue(right.lineNumber) ?? 0;
|
|
124
|
+
return leftTime - rightTime;
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
function transcriptMetadata(record, details) {
|
|
128
|
+
const raw = {};
|
|
129
|
+
for (const [key, value] of Object.entries(record)) {
|
|
130
|
+
if (key === 'skeleton' || key === 'transcript' || key === 'text')
|
|
131
|
+
continue;
|
|
132
|
+
raw[key] = value;
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
...raw,
|
|
136
|
+
transcriptTextChars: details.textChars,
|
|
137
|
+
transcriptTextStoredIn: 'text',
|
|
138
|
+
transcriptTruncated: details.truncated,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
function buildOverview(state, features, events, handoffs, assertions, fallback = {}) {
|
|
142
|
+
const featureSummary = summarizeByStatusAndMilestone(features);
|
|
143
|
+
const validationSummary = summarizeValidation(assertions);
|
|
144
|
+
return {
|
|
145
|
+
missionId: stringValue(state?.missionId) ?? stringValue(fallback.fallbackMissionId),
|
|
146
|
+
state: stringValue(state?.state) ?? inferStateFromEvents(events) ?? stringValue(fallback.fallbackState),
|
|
147
|
+
workingDirectory: stringValue(state?.workingDirectory),
|
|
148
|
+
createdAt: stringValue(state?.createdAt),
|
|
149
|
+
updatedAt: stringValue(state?.updatedAt),
|
|
150
|
+
reportOnly: Boolean(fallback.reportOnly),
|
|
151
|
+
reportCount: fallback.reportCount ?? 0,
|
|
152
|
+
lastReviewedHandoffCount: numberValue(state?.lastReviewedHandoffCount),
|
|
153
|
+
featureCount: features.length,
|
|
154
|
+
handoffCount: handoffs.length,
|
|
155
|
+
timelineEventCount: events.length,
|
|
156
|
+
validationAssertionCount: assertions.length,
|
|
157
|
+
featureStatusCounts: featureSummary.statusCounts,
|
|
158
|
+
validationStatusCounts: validationSummary.statusCounts,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
function readMarkdownReports(missionDir, anomalies) {
|
|
162
|
+
return readdirSync(missionDir)
|
|
163
|
+
.filter((name) => name.toLowerCase().endsWith('.md'))
|
|
164
|
+
.sort()
|
|
165
|
+
.map((name) => {
|
|
166
|
+
const path = join(missionDir, name);
|
|
167
|
+
try {
|
|
168
|
+
const stat = statSync(path);
|
|
169
|
+
if (!stat.isFile())
|
|
170
|
+
return null;
|
|
171
|
+
const raw = readFileSync(path, 'utf8');
|
|
172
|
+
const truncated = raw.length > REPORT_TEXT_LIMIT;
|
|
173
|
+
return redactSecrets({
|
|
174
|
+
path: name,
|
|
175
|
+
sizeBytes: stat.size,
|
|
176
|
+
truncated,
|
|
177
|
+
text: truncated ? `${raw.slice(0, REPORT_TEXT_LIMIT)}\n[truncated]` : raw,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
anomalies.push({
|
|
182
|
+
severity: 'warning',
|
|
183
|
+
type: 'unreadable_report',
|
|
184
|
+
path: name,
|
|
185
|
+
message: String(error.message ?? error),
|
|
186
|
+
});
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
})
|
|
190
|
+
.filter((item) => Boolean(item));
|
|
191
|
+
}
|
|
192
|
+
function shouldUseReportOnlyMode(state, reports, options) {
|
|
193
|
+
return !state && reports.length > 0 && Boolean(stringValue(options.factorySessionId));
|
|
194
|
+
}
|
|
195
|
+
function normalizeFeatures(value) {
|
|
196
|
+
const features = Array.isArray(value?.features) ? value.features : [];
|
|
197
|
+
return features
|
|
198
|
+
.filter(isRecord)
|
|
199
|
+
.map((feature) => {
|
|
200
|
+
const workerSessionIds = Array.isArray(feature.workerSessionIds)
|
|
201
|
+
? feature.workerSessionIds.filter((item) => typeof item === 'string')
|
|
202
|
+
: [];
|
|
203
|
+
return redactSecrets({
|
|
204
|
+
id: stringValue(feature.id),
|
|
205
|
+
description: stringValue(feature.description),
|
|
206
|
+
skillName: stringValue(feature.skillName),
|
|
207
|
+
milestone: stringValue(feature.milestone),
|
|
208
|
+
status: stringValue(feature.status) ?? 'unknown',
|
|
209
|
+
workerSessionIds,
|
|
210
|
+
currentWorkerSessionId: stringValue(feature.currentWorkerSessionId),
|
|
211
|
+
completedWorkerSessionId: stringValue(feature.completedWorkerSessionId),
|
|
212
|
+
preconditions: stringArray(feature.preconditions),
|
|
213
|
+
expectedBehavior: stringArray(feature.expectedBehavior),
|
|
214
|
+
verificationSteps: stringArray(feature.verificationSteps),
|
|
215
|
+
raw: feature,
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
function readJsonLines(filePath, anomalies) {
|
|
220
|
+
if (!existsSync(filePath)) {
|
|
221
|
+
anomalies.push({ severity: 'warning', type: 'missing_artifact', path: basename(filePath) });
|
|
222
|
+
return [];
|
|
223
|
+
}
|
|
224
|
+
const lines = readFileSync(filePath, 'utf8').split(/\r?\n/);
|
|
225
|
+
const events = [];
|
|
226
|
+
lines.forEach((line, index) => {
|
|
227
|
+
if (!line.trim())
|
|
228
|
+
return;
|
|
229
|
+
try {
|
|
230
|
+
const parsed = JSON.parse(line);
|
|
231
|
+
if (isRecord(parsed)) {
|
|
232
|
+
events.push(redactSecrets({
|
|
233
|
+
...parsed,
|
|
234
|
+
timestampMs: parseTimestamp(stringValue(parsed.timestamp)),
|
|
235
|
+
lineNumber: index + 1,
|
|
236
|
+
}));
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
anomalies.push({ severity: 'warning', type: 'invalid_jsonl_record', path: basename(filePath), line: index + 1 });
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
anomalies.push({
|
|
244
|
+
severity: 'warning',
|
|
245
|
+
type: 'invalid_jsonl',
|
|
246
|
+
path: basename(filePath),
|
|
247
|
+
line: index + 1,
|
|
248
|
+
message: String(error.message ?? error),
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
return events.sort((left, right) => {
|
|
253
|
+
const leftTime = numberValue(left.timestampMs) ?? 0;
|
|
254
|
+
const rightTime = numberValue(right.timestampMs) ?? 0;
|
|
255
|
+
return leftTime - rightTime;
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
function readHandoffs(handoffsDir, anomalies) {
|
|
259
|
+
if (!existsSync(handoffsDir)) {
|
|
260
|
+
anomalies.push({ severity: 'warning', type: 'missing_artifact', path: 'handoffs' });
|
|
261
|
+
return [];
|
|
262
|
+
}
|
|
263
|
+
return readdirSync(handoffsDir)
|
|
264
|
+
.filter((name) => name.endsWith('.json'))
|
|
265
|
+
.sort()
|
|
266
|
+
.map((name) => {
|
|
267
|
+
const path = join(handoffsDir, name);
|
|
268
|
+
const parsed = readJsonObject(path, anomalies, false);
|
|
269
|
+
if (!parsed)
|
|
270
|
+
return null;
|
|
271
|
+
const handoff = isRecord(parsed.handoff) ? parsed.handoff : {};
|
|
272
|
+
return redactSecrets({
|
|
273
|
+
fileName: name,
|
|
274
|
+
timestamp: stringValue(parsed.timestamp),
|
|
275
|
+
timestampMs: parseTimestamp(stringValue(parsed.timestamp)),
|
|
276
|
+
workerSessionId: stringValue(parsed.workerSessionId),
|
|
277
|
+
featureId: stringValue(parsed.featureId),
|
|
278
|
+
milestone: stringValue(parsed.milestone),
|
|
279
|
+
commitId: stringValue(parsed.commitId),
|
|
280
|
+
repoPath: stringValue(parsed.repoPath),
|
|
281
|
+
successState: stringValue(parsed.successState),
|
|
282
|
+
returnToOrchestrator: Boolean(parsed.returnToOrchestrator),
|
|
283
|
+
salientSummary: stringValue(handoff.salientSummary),
|
|
284
|
+
verification: isRecord(handoff.verification) ? handoff.verification : null,
|
|
285
|
+
discoveredIssues: Array.isArray(handoff.discoveredIssues) ? handoff.discoveredIssues : [],
|
|
286
|
+
raw: parsed,
|
|
287
|
+
});
|
|
288
|
+
})
|
|
289
|
+
.filter((item) => Boolean(item));
|
|
290
|
+
}
|
|
291
|
+
function normalizeValidationAssertions(value) {
|
|
292
|
+
const assertions = isRecord(value?.assertions) ? value.assertions : {};
|
|
293
|
+
return Object.entries(assertions)
|
|
294
|
+
.filter((entry) => isRecord(entry[1]))
|
|
295
|
+
.map(([id, assertion]) => redactSecrets({
|
|
296
|
+
id,
|
|
297
|
+
status: stringValue(assertion.status) ?? 'unknown',
|
|
298
|
+
note: stringValue(assertion.note),
|
|
299
|
+
raw: assertion,
|
|
300
|
+
}))
|
|
301
|
+
.sort((left, right) => String(left.id).localeCompare(String(right.id)));
|
|
302
|
+
}
|
|
303
|
+
function summarizeByStatusAndMilestone(items) {
|
|
304
|
+
return {
|
|
305
|
+
total: items.length,
|
|
306
|
+
statusCounts: countBy(items, 'status'),
|
|
307
|
+
milestoneCounts: countBy(items, 'milestone'),
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
function summarizeTimeline(events) {
|
|
311
|
+
return {
|
|
312
|
+
total: events.length,
|
|
313
|
+
typeCounts: countBy(events, 'type'),
|
|
314
|
+
firstTimestamp: stringValue(events[0]?.timestamp),
|
|
315
|
+
lastTimestamp: stringValue(events[events.length - 1]?.timestamp),
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
function summarizeHandoffs(items) {
|
|
319
|
+
return {
|
|
320
|
+
total: items.length,
|
|
321
|
+
successStateCounts: countBy(items, 'successState'),
|
|
322
|
+
milestoneCounts: countBy(items, 'milestone'),
|
|
323
|
+
latestTimestamp: stringValue(items[items.length - 1]?.timestamp),
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
function summarizeValidation(assertions) {
|
|
327
|
+
return {
|
|
328
|
+
total: assertions.length,
|
|
329
|
+
statusCounts: countBy(assertions, 'status'),
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
function listArtifactFiles(missionDir, anomalies, options = {}) {
|
|
333
|
+
const files = [];
|
|
334
|
+
for (const name of JSON_FILES) {
|
|
335
|
+
const path = join(missionDir, name);
|
|
336
|
+
files.push({
|
|
337
|
+
path: name,
|
|
338
|
+
exists: existsSync(path),
|
|
339
|
+
sizeBytes: existsSync(path) ? statSync(path).size : null,
|
|
340
|
+
kind: 'json',
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
const progressPath = join(missionDir, 'progress_log.jsonl');
|
|
344
|
+
files.push({
|
|
345
|
+
path: 'progress_log.jsonl',
|
|
346
|
+
exists: existsSync(progressPath),
|
|
347
|
+
sizeBytes: existsSync(progressPath) ? statSync(progressPath).size : null,
|
|
348
|
+
kind: 'jsonl',
|
|
349
|
+
});
|
|
350
|
+
const transcriptsPath = join(missionDir, 'worker-transcripts.jsonl');
|
|
351
|
+
files.push({
|
|
352
|
+
path: 'worker-transcripts.jsonl',
|
|
353
|
+
exists: existsSync(transcriptsPath),
|
|
354
|
+
sizeBytes: existsSync(transcriptsPath) ? statSync(transcriptsPath).size : null,
|
|
355
|
+
kind: 'jsonl_transcript',
|
|
356
|
+
});
|
|
357
|
+
const handoffsDir = join(missionDir, 'handoffs');
|
|
358
|
+
files.push({
|
|
359
|
+
path: 'handoffs',
|
|
360
|
+
exists: existsSync(handoffsDir),
|
|
361
|
+
count: existsSync(handoffsDir) ? readdirSync(handoffsDir).filter((name) => name.endsWith('.json')).length : 0,
|
|
362
|
+
kind: 'directory',
|
|
363
|
+
});
|
|
364
|
+
for (const name of readdirSync(missionDir).filter((entry) => entry.toLowerCase().endsWith('.md')).sort()) {
|
|
365
|
+
const reportPath = join(missionDir, name);
|
|
366
|
+
try {
|
|
367
|
+
const stat = statSync(reportPath);
|
|
368
|
+
if (stat.isFile()) {
|
|
369
|
+
files.push({
|
|
370
|
+
path: name,
|
|
371
|
+
exists: true,
|
|
372
|
+
sizeBytes: stat.size,
|
|
373
|
+
kind: 'markdown_report',
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
catch {
|
|
378
|
+
files.push({
|
|
379
|
+
path: name,
|
|
380
|
+
exists: false,
|
|
381
|
+
sizeBytes: null,
|
|
382
|
+
kind: 'markdown_report',
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
const missingRequired = ['state.json', 'features.json', 'progress_log.jsonl'].filter((name) => {
|
|
387
|
+
return !existsSync(join(missionDir, name));
|
|
388
|
+
});
|
|
389
|
+
for (const path of missingRequired) {
|
|
390
|
+
anomalies.push({
|
|
391
|
+
severity: options.reportOnly ? 'warning' : 'error',
|
|
392
|
+
type: options.reportOnly ? 'missing_structured_artifact' : 'missing_required_artifact',
|
|
393
|
+
path,
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
return files;
|
|
397
|
+
}
|
|
398
|
+
function readJsonObject(filePath, anomalies, optional) {
|
|
399
|
+
if (!existsSync(filePath)) {
|
|
400
|
+
if (!optional) {
|
|
401
|
+
anomalies.push({
|
|
402
|
+
severity: 'warning',
|
|
403
|
+
type: 'missing_artifact',
|
|
404
|
+
path: basename(filePath),
|
|
405
|
+
});
|
|
406
|
+
if (isRequiredArtifact(basename(filePath))) {
|
|
407
|
+
anomalies.push({
|
|
408
|
+
severity: 'error',
|
|
409
|
+
type: 'missing_required_artifact',
|
|
410
|
+
path: basename(filePath),
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
return null;
|
|
415
|
+
}
|
|
416
|
+
try {
|
|
417
|
+
const parsed = JSON.parse(readFileSync(filePath, 'utf8'));
|
|
418
|
+
if (isRecord(parsed))
|
|
419
|
+
return parsed;
|
|
420
|
+
anomalies.push({ severity: 'warning', type: 'invalid_json_object', path: basename(filePath) });
|
|
421
|
+
return null;
|
|
422
|
+
}
|
|
423
|
+
catch (error) {
|
|
424
|
+
anomalies.push({
|
|
425
|
+
severity: isRequiredArtifact(basename(filePath)) ? 'error' : 'warning',
|
|
426
|
+
type: isRequiredArtifact(basename(filePath)) ? 'invalid_required_artifact' : 'invalid_json',
|
|
427
|
+
path: basename(filePath),
|
|
428
|
+
message: String(error.message ?? error),
|
|
429
|
+
});
|
|
430
|
+
return null;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
function countBy(items, field) {
|
|
434
|
+
const counts = {};
|
|
435
|
+
for (const item of items) {
|
|
436
|
+
const key = stringValue(item[field]) ?? 'unknown';
|
|
437
|
+
counts[key] = (counts[key] ?? 0) + 1;
|
|
438
|
+
}
|
|
439
|
+
return counts;
|
|
440
|
+
}
|
|
441
|
+
function inferTranscriptRole(record) {
|
|
442
|
+
const searchable = [
|
|
443
|
+
stringValue(record.role),
|
|
444
|
+
stringValue(record.featureId),
|
|
445
|
+
stringValue(record.skillName),
|
|
446
|
+
stringValue(record.workerSessionId),
|
|
447
|
+
].filter(Boolean).join(' ').toLowerCase();
|
|
448
|
+
if (searchable.includes('validat'))
|
|
449
|
+
return 'validator';
|
|
450
|
+
if (searchable.includes('worker'))
|
|
451
|
+
return 'worker';
|
|
452
|
+
if (searchable.includes('orchestrator'))
|
|
453
|
+
return 'orchestrator';
|
|
454
|
+
return 'worker';
|
|
455
|
+
}
|
|
456
|
+
function firstMeaningfulLine(value) {
|
|
457
|
+
const line = value.split(/\r?\n/).map((entry) => entry.trim()).find((entry) => entry && !entry.startsWith('---'));
|
|
458
|
+
return line ? (line.length > 240 ? `${line.slice(0, 240)}...` : line) : 'Transcript captured';
|
|
459
|
+
}
|
|
460
|
+
function redactSecrets(value) {
|
|
461
|
+
if (Array.isArray(value))
|
|
462
|
+
return value.map((item) => redactSecrets(item));
|
|
463
|
+
if (typeof value === 'string')
|
|
464
|
+
return redactSecretString(value);
|
|
465
|
+
if (!isRecord(value))
|
|
466
|
+
return value;
|
|
467
|
+
const result = {};
|
|
468
|
+
for (const [key, child] of Object.entries(value)) {
|
|
469
|
+
result[key] = SENSITIVE_KEY_RE.test(key) ? '[redacted]' : redactSecrets(child);
|
|
470
|
+
}
|
|
471
|
+
return result;
|
|
472
|
+
}
|
|
473
|
+
function redactSecretString(value) {
|
|
474
|
+
return SECRET_VALUE_PATTERNS.reduce((current, pattern) => current.replace(pattern, '[redacted]'), value);
|
|
475
|
+
}
|
|
476
|
+
function isRequiredArtifact(name) {
|
|
477
|
+
return name === 'state.json' || name === 'features.json' || name === 'progress_log.jsonl';
|
|
478
|
+
}
|
|
479
|
+
function normalizeArtifactAnomalies(anomalies, missionDir, options = {}) {
|
|
480
|
+
const byKey = new Map();
|
|
481
|
+
for (const anomaly of anomalies) {
|
|
482
|
+
const normalized = enrichArtifactAnomaly(anomaly, missionDir, options);
|
|
483
|
+
const key = [
|
|
484
|
+
stringValue(normalized.type) ?? 'unknown',
|
|
485
|
+
stringValue(normalized.path) ?? '',
|
|
486
|
+
numberValue(normalized.line) ?? '',
|
|
487
|
+
stringValue(normalized.severity) ?? '',
|
|
488
|
+
].join(':');
|
|
489
|
+
byKey.set(key, { ...byKey.get(key), ...normalized });
|
|
490
|
+
}
|
|
491
|
+
return Array.from(byKey.values()).sort((left, right) => {
|
|
492
|
+
const leftSeverity = severityRank(stringValue(left.severity));
|
|
493
|
+
const rightSeverity = severityRank(stringValue(right.severity));
|
|
494
|
+
if (leftSeverity !== rightSeverity)
|
|
495
|
+
return leftSeverity - rightSeverity;
|
|
496
|
+
return String(left.path ?? '').localeCompare(String(right.path ?? ''));
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
function enrichArtifactAnomaly(anomaly, missionDir, options) {
|
|
500
|
+
let severity = stringValue(anomaly.severity) ?? 'warning';
|
|
501
|
+
let type = stringValue(anomaly.type) ?? 'unknown_artifact_anomaly';
|
|
502
|
+
const path = stringValue(anomaly.path);
|
|
503
|
+
if (path && isRequiredArtifact(path) && (type === 'missing_artifact' || type === 'missing_required_artifact')) {
|
|
504
|
+
type = options.reportOnly ? 'missing_structured_artifact' : 'missing_required_artifact';
|
|
505
|
+
severity = options.reportOnly ? 'warning' : 'error';
|
|
506
|
+
}
|
|
507
|
+
else if (options.reportOnly && type === 'missing_required_artifact') {
|
|
508
|
+
severity = 'warning';
|
|
509
|
+
type = 'missing_structured_artifact';
|
|
510
|
+
}
|
|
511
|
+
const preset = anomalyPreset(type, path, options);
|
|
512
|
+
return {
|
|
513
|
+
severity,
|
|
514
|
+
type,
|
|
515
|
+
category: preset.category,
|
|
516
|
+
title: preset.title,
|
|
517
|
+
message: stringValue(anomaly.message) ?? preset.message,
|
|
518
|
+
guidance: preset.guidance,
|
|
519
|
+
blocking: severity === 'error',
|
|
520
|
+
...(path ? { path, checkedPath: join(missionDir, path) } : {}),
|
|
521
|
+
...(numberValue(anomaly.line) != null ? { line: numberValue(anomaly.line) } : {}),
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
function anomalyPreset(type, path, options) {
|
|
525
|
+
const label = path ?? 'artifact';
|
|
526
|
+
if (type === 'missing_required_artifact') {
|
|
527
|
+
return {
|
|
528
|
+
category: 'missing',
|
|
529
|
+
title: `Missing required artifact: ${label}`,
|
|
530
|
+
message: `${label} was not found in the Droid mission artifact directory.`,
|
|
531
|
+
guidance: 'Import may be incomplete. Re-run the mission or inspect the Droid/Factory artifact directory used for this snapshot.',
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
if (type === 'missing_structured_artifact') {
|
|
535
|
+
return {
|
|
536
|
+
category: 'missing',
|
|
537
|
+
title: `Missing structured artifact: ${label}`,
|
|
538
|
+
message: options.reportOnly
|
|
539
|
+
? `${label} was not found, but Markdown report output was imported.`
|
|
540
|
+
: `${label} was not found in the Droid mission artifact directory.`,
|
|
541
|
+
guidance: options.reportOnly
|
|
542
|
+
? 'Review the imported Markdown report; structured Features, Timeline, Handoffs, or Validation may remain sparse.'
|
|
543
|
+
: 'Re-run the mission or inspect the Droid/Factory artifact directory.',
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
if (type === 'missing_artifact') {
|
|
547
|
+
return {
|
|
548
|
+
category: 'missing',
|
|
549
|
+
title: `Missing optional artifact: ${label}`,
|
|
550
|
+
message: `${label} was not found in the Droid mission artifact directory.`,
|
|
551
|
+
guidance: 'Some sections may be empty. This is expected for partial or report-only Droid output.',
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
if (type === 'invalid_required_artifact' || type === 'invalid_json_object' || type === 'invalid_json') {
|
|
555
|
+
return {
|
|
556
|
+
category: 'parse',
|
|
557
|
+
title: `Invalid JSON artifact: ${label}`,
|
|
558
|
+
message: `${label} could not be parsed as a JSON object.`,
|
|
559
|
+
guidance: 'Open the artifact file and check whether Droid wrote partial or malformed JSON.',
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
if (type === 'invalid_jsonl' || type === 'invalid_jsonl_record') {
|
|
563
|
+
return {
|
|
564
|
+
category: 'parse',
|
|
565
|
+
title: `Invalid JSONL record: ${label}`,
|
|
566
|
+
message: `${label} contains at least one line that could not be parsed as a JSON object.`,
|
|
567
|
+
guidance: 'The importer skipped malformed lines and kept valid timeline events.',
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
if (type === 'unreadable_report') {
|
|
571
|
+
return {
|
|
572
|
+
category: 'report',
|
|
573
|
+
title: `Unreadable report: ${label}`,
|
|
574
|
+
message: `${label} could not be read as Markdown report text.`,
|
|
575
|
+
guidance: 'Check file permissions and whether the report still exists in the artifact directory.',
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
return {
|
|
579
|
+
category: 'unknown',
|
|
580
|
+
title: `Artifact anomaly: ${type}`,
|
|
581
|
+
message: `${type} was reported while importing Droid artifacts.`,
|
|
582
|
+
guidance: 'Inspect raw anomaly details and the artifact directory.',
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
function summarizeAnomalies(anomalies) {
|
|
586
|
+
return {
|
|
587
|
+
total: anomalies.length,
|
|
588
|
+
errorCount: anomalies.filter((item) => stringValue(item.severity) === 'error').length,
|
|
589
|
+
warningCount: anomalies.filter((item) => stringValue(item.severity) === 'warning').length,
|
|
590
|
+
blockingCount: anomalies.filter((item) => item.blocking === true).length,
|
|
591
|
+
categoryCounts: countBy(anomalies, 'category'),
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
function severityRank(value) {
|
|
595
|
+
if (value === 'error')
|
|
596
|
+
return 0;
|
|
597
|
+
if (value === 'warning')
|
|
598
|
+
return 1;
|
|
599
|
+
return 2;
|
|
600
|
+
}
|
|
601
|
+
function inferStateFromEvents(events) {
|
|
602
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
603
|
+
const type = stringValue(events[index]?.type);
|
|
604
|
+
if (type === 'mission_paused')
|
|
605
|
+
return 'paused';
|
|
606
|
+
if (type === 'mission_completed')
|
|
607
|
+
return 'completed';
|
|
608
|
+
if (type === 'mission_run_started' || type === 'mission_resumed')
|
|
609
|
+
return 'running';
|
|
610
|
+
}
|
|
611
|
+
return null;
|
|
612
|
+
}
|
|
613
|
+
function stringArray(value) {
|
|
614
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === 'string') : [];
|
|
615
|
+
}
|
|
616
|
+
function parseTimestamp(value) {
|
|
617
|
+
if (!value)
|
|
618
|
+
return null;
|
|
619
|
+
const time = Date.parse(value);
|
|
620
|
+
return Number.isFinite(time) ? time : null;
|
|
621
|
+
}
|
|
622
|
+
function stringValue(value) {
|
|
623
|
+
return typeof value === 'string' && value.trim() ? value : null;
|
|
624
|
+
}
|
|
625
|
+
function numberValue(value) {
|
|
626
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : null;
|
|
627
|
+
}
|
|
628
|
+
function isRecord(value) {
|
|
629
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
630
|
+
}
|