@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,227 @@
1
+ import { writeFileSync, existsSync, mkdirSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ export const NATIVE_MISSION_REPORT_FILE_NAME = 'mission-report.md';
4
+ /**
5
+ * Generates a Markdown mission report from native mission data and writes it
6
+ * to the mission directory. The report is intended to be human-readable and
7
+ * includes a mission summary, feature list, validation results, handoffs, and
8
+ * a timeline of key events.
9
+ */
10
+ export function generateNativeMissionReport(missionDir, input) {
11
+ if (!existsSync(missionDir)) {
12
+ mkdirSync(missionDir, { recursive: true });
13
+ }
14
+ const reportText = renderNativeMissionReport(input);
15
+ const reportPath = join(missionDir, NATIVE_MISSION_REPORT_FILE_NAME);
16
+ writeFileSync(reportPath, reportText, 'utf8');
17
+ return { reportPath, sizeBytes: Buffer.byteLength(reportText, 'utf8') };
18
+ }
19
+ function renderNativeMissionReport(input) {
20
+ const { mission, features, assertions, events } = input;
21
+ const featureStatusCounts = countBy(features.map((f) => f.status));
22
+ const assertionStatusCounts = countBy(assertions.map((a) => a.status));
23
+ const handoffFeatures = features.filter((f) => f.handoff != null);
24
+ const lines = [];
25
+ lines.push(`# Mission Report: ${mission.title}`);
26
+ lines.push('');
27
+ lines.push(`- **Mission ID:** \`${mission.missionId}\``);
28
+ lines.push(`- **State:** ${mission.state}`);
29
+ lines.push(`- **Runtime Provider:** ${mission.runtimeProvider}`);
30
+ lines.push(`- **Workspace:** \`${mission.workspacePath}\``);
31
+ lines.push(`- **Model Mode:** ${mission.modelMode}`);
32
+ if (mission.startedAt) {
33
+ lines.push(`- **Started At:** ${formatTimestamp(mission.startedAt)}`);
34
+ }
35
+ if (mission.completedAt) {
36
+ lines.push(`- **Completed At:** ${formatTimestamp(mission.completedAt)}`);
37
+ }
38
+ lines.push('');
39
+ lines.push('## Summary');
40
+ lines.push('');
41
+ lines.push(`- **Features:** ${features.length} total`);
42
+ lines.push(` - Completed: ${featureStatusCounts.completed ?? 0}`);
43
+ lines.push(` - Failed: ${featureStatusCounts.failed ?? 0}`);
44
+ lines.push(` - Skipped: ${featureStatusCounts.skipped ?? 0}`);
45
+ lines.push(` - Cancelled: ${featureStatusCounts.cancelled ?? 0}`);
46
+ lines.push(` - Pending: ${featureStatusCounts.pending ?? 0}`);
47
+ lines.push(`- **Validation Assertions:** ${assertions.length} total`);
48
+ lines.push(` - Passed: ${assertionStatusCounts.passed ?? 0}`);
49
+ lines.push(` - Failed: ${assertionStatusCounts.failed ?? 0}`);
50
+ lines.push(` - Blocked: ${assertionStatusCounts.blocked ?? 0}`);
51
+ lines.push(` - Pending: ${assertionStatusCounts.pending ?? 0}`);
52
+ lines.push(`- **Handoffs:** ${handoffFeatures.length}`);
53
+ lines.push('');
54
+ if (mission.description) {
55
+ lines.push('## Mission Brief');
56
+ lines.push('');
57
+ lines.push(mission.description);
58
+ lines.push('');
59
+ }
60
+ lines.push('## Features');
61
+ lines.push('');
62
+ if (features.length === 0) {
63
+ lines.push('*No features were parsed from the mission brief.*');
64
+ lines.push('');
65
+ }
66
+ else {
67
+ const featuresByMilestone = groupBy(features, (f) => f.milestone);
68
+ for (const [milestone, milestoneFeatures] of featuresByMilestone) {
69
+ lines.push(`### ${milestone}`);
70
+ lines.push('');
71
+ for (const feature of milestoneFeatures.sort((a, b) => a.ordering - b.ordering)) {
72
+ lines.push(`#### ${feature.description.split(/\r?\n/)[0] ?? feature.description}`);
73
+ lines.push('');
74
+ lines.push(`- **Status:** ${feature.status}`);
75
+ lines.push(`- **Feature ID:** \`${feature.featureId}\``);
76
+ if (feature.skillName) {
77
+ lines.push(`- **Skill:** ${feature.skillName}`);
78
+ }
79
+ if (feature.preconditions.length > 0) {
80
+ lines.push(`- **Preconditions:**`);
81
+ for (const precondition of feature.preconditions) {
82
+ lines.push(` - ${precondition}`);
83
+ }
84
+ }
85
+ if (feature.expectedBehavior.length > 0) {
86
+ lines.push(`- **Expected Behavior:**`);
87
+ for (const expected of feature.expectedBehavior) {
88
+ lines.push(` - ${expected}`);
89
+ }
90
+ }
91
+ lines.push('');
92
+ }
93
+ }
94
+ }
95
+ lines.push('## Validation Results');
96
+ lines.push('');
97
+ if (assertions.length === 0) {
98
+ lines.push('*No validation assertions were generated.*');
99
+ lines.push('');
100
+ }
101
+ else {
102
+ const assertionsByFeature = groupBy(assertions, (a) => a.featureId ?? 'unknown');
103
+ for (const [featureId, featureAssertions] of assertionsByFeature) {
104
+ const feature = features.find((f) => f.featureId === featureId);
105
+ if (feature) {
106
+ lines.push(`### ${feature.description.split(/\r?\n/)[0] ?? feature.description}`);
107
+ }
108
+ else {
109
+ lines.push(`### Feature ID: \`${featureId}\``);
110
+ }
111
+ lines.push('');
112
+ for (const assertion of featureAssertions) {
113
+ lines.push(`- **${assertion.status.toUpperCase()}:** ${assertion.assertionText}`);
114
+ if (assertion.note) {
115
+ lines.push(` - *Note:* ${assertion.note}`);
116
+ }
117
+ if (assertion.evidence) {
118
+ lines.push(` - *Evidence:* ${assertion.evidence}`);
119
+ }
120
+ }
121
+ lines.push('');
122
+ }
123
+ }
124
+ lines.push('## Handoffs');
125
+ lines.push('');
126
+ if (handoffFeatures.length === 0) {
127
+ lines.push('*No worker handoffs were recorded.*');
128
+ lines.push('');
129
+ }
130
+ else {
131
+ for (const feature of handoffFeatures) {
132
+ const handoff = feature.handoff ?? {};
133
+ lines.push(`### ${feature.description.split(/\r?\n/)[0] ?? feature.description}`);
134
+ lines.push('');
135
+ const summary = typeof handoff.salientSummary === 'string' ? handoff.salientSummary : null;
136
+ if (summary) {
137
+ lines.push(summary);
138
+ lines.push('');
139
+ }
140
+ if (handoff.verification && typeof handoff.verification === 'object') {
141
+ lines.push(`- **Verification:** \`${JSON.stringify(handoff.verification)}\``);
142
+ }
143
+ if (Array.isArray(handoff.discoveredIssues) && handoff.discoveredIssues.length > 0) {
144
+ lines.push('- **Discovered Issues:**');
145
+ for (const issue of handoff.discoveredIssues) {
146
+ lines.push(` - ${String(issue)}`);
147
+ }
148
+ }
149
+ lines.push('');
150
+ }
151
+ }
152
+ lines.push('## Timeline');
153
+ lines.push('');
154
+ const lifecycleEvents = events.filter((e) => isLifecycleEventType(e.eventType));
155
+ if (lifecycleEvents.length === 0) {
156
+ lines.push('*No lifecycle events recorded.*');
157
+ lines.push('');
158
+ }
159
+ else {
160
+ for (const event of lifecycleEvents) {
161
+ const time = formatTimestamp(event.eventTime);
162
+ lines.push(`- **${time}** \`${event.eventType}\` (${event.source})`);
163
+ const payloadSummary = summarizePayload(event.payload);
164
+ if (payloadSummary) {
165
+ lines.push(` - ${payloadSummary}`);
166
+ }
167
+ }
168
+ lines.push('');
169
+ }
170
+ lines.push('---');
171
+ lines.push('');
172
+ lines.push(`*Report generated by Bigbang Native Mission Runtime.*`);
173
+ lines.push('');
174
+ return lines.join('\n');
175
+ }
176
+ function isLifecycleEventType(eventType) {
177
+ return (eventType.startsWith('mission_') ||
178
+ eventType.startsWith('feature_') ||
179
+ eventType === 'validation_started' ||
180
+ eventType === 'validation_completed');
181
+ }
182
+ function summarizePayload(payload) {
183
+ const parts = [];
184
+ if (typeof payload.featureId === 'string') {
185
+ parts.push(`featureId=${payload.featureId}`);
186
+ }
187
+ if (typeof payload.milestone === 'string') {
188
+ parts.push(`milestone=${payload.milestone}`);
189
+ }
190
+ if (typeof payload.exitCode === 'number') {
191
+ parts.push(`exitCode=${payload.exitCode}`);
192
+ }
193
+ if (typeof payload.error === 'string') {
194
+ parts.push(`error=${payload.error}`);
195
+ }
196
+ if (typeof payload.reason === 'string') {
197
+ parts.push(`reason=${payload.reason}`);
198
+ }
199
+ if (parts.length === 0)
200
+ return null;
201
+ return parts.join(', ');
202
+ }
203
+ function formatTimestamp(timestamp) {
204
+ try {
205
+ return new Date(timestamp).toISOString();
206
+ }
207
+ catch {
208
+ return String(timestamp);
209
+ }
210
+ }
211
+ function countBy(items) {
212
+ const counts = {};
213
+ for (const item of items) {
214
+ counts[item] = (counts[item] ?? 0) + 1;
215
+ }
216
+ return counts;
217
+ }
218
+ function groupBy(items, keyFn) {
219
+ const groups = new Map();
220
+ for (const item of items) {
221
+ const key = keyFn(item);
222
+ const list = groups.get(key) ?? [];
223
+ list.push(item);
224
+ groups.set(key, list);
225
+ }
226
+ return groups;
227
+ }
@@ -0,0 +1,452 @@
1
+ const DEFAULT_TIMEOUT_MS = 60_000;
2
+ const DEFAULT_POLICY = 'continueOnWarning';
3
+ export class NativeMissionValidationRunner {
4
+ missionService;
5
+ defaultTimeoutMs;
6
+ defaultPolicy;
7
+ evaluator;
8
+ constructor(params) {
9
+ this.missionService = params.missionService;
10
+ this.defaultTimeoutMs = params.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS;
11
+ this.defaultPolicy = params.defaultPolicy ?? DEFAULT_POLICY;
12
+ this.evaluator = params.evaluator ?? defaultAssertionEvaluator;
13
+ }
14
+ async runValidation(input) {
15
+ const { missionId, milestone } = input;
16
+ const policy = input.policy ?? this.defaultPolicy;
17
+ const timeoutMs = input.timeoutMs ?? this.defaultTimeoutMs;
18
+ const forceInferredConfidence = input.forceInferredConfidence ?? false;
19
+ const features = this.missionService.listMissionFeatures({
20
+ missionId,
21
+ userId: '',
22
+ isAdmin: true,
23
+ });
24
+ const milestoneFeatureIds = new Set(features.filter((feature) => feature.milestone === milestone).map((feature) => feature.featureId));
25
+ const allAssertions = this.missionService.listMissionValidationAssertions({
26
+ missionId,
27
+ userId: '',
28
+ isAdmin: true,
29
+ });
30
+ const assertions = allAssertions.filter((assertion) => assertion.featureId && milestoneFeatureIds.has(assertion.featureId));
31
+ this.missionService.recordMissionRuntimeEvent({
32
+ missionId,
33
+ eventType: 'validation_started',
34
+ source: 'platform',
35
+ eventTime: Date.now(),
36
+ payload: {
37
+ milestone,
38
+ assertionCount: assertions.length,
39
+ assertionIds: assertions.map((assertion) => assertion.assertionId),
40
+ policy,
41
+ },
42
+ });
43
+ const featureById = new Map(features.map((feature) => [feature.featureId, feature]));
44
+ const runWithTimeout = async () => {
45
+ const result = {
46
+ milestone,
47
+ assertionCount: assertions.length,
48
+ passed: 0,
49
+ failed: 0,
50
+ blocked: 0,
51
+ failedAssertionIds: [],
52
+ shouldHalt: false,
53
+ timedOut: false,
54
+ };
55
+ for (const assertion of assertions) {
56
+ const feature = assertion.featureId ? featureById.get(assertion.featureId) ?? null : null;
57
+ const workerOutput = feature?.workerOutput ?? null;
58
+ const evaluation = await this.evaluator({ assertion, feature, workerOutput });
59
+ const confidence = forceInferredConfidence && evaluation.confidence !== 'unknown'
60
+ ? 'inferred'
61
+ : evaluation.confidence;
62
+ this.missionService.updateMissionValidationAssertion({
63
+ missionId,
64
+ assertionId: assertion.assertionId,
65
+ status: evaluation.status,
66
+ note: evaluation.note,
67
+ evidence: evaluation.evidence,
68
+ confidence,
69
+ });
70
+ if (evaluation.status === 'passed') {
71
+ result.passed += 1;
72
+ }
73
+ else if (evaluation.status === 'failed') {
74
+ result.failed += 1;
75
+ result.failedAssertionIds.push(assertion.assertionId);
76
+ }
77
+ else {
78
+ result.blocked += 1;
79
+ }
80
+ }
81
+ result.shouldHalt = policy === 'failOnFirst' && result.failed > 0;
82
+ return result;
83
+ };
84
+ let result;
85
+ let timedOut = false;
86
+ try {
87
+ result = await runWithTimeoutBound(runWithTimeout, timeoutMs, () => {
88
+ timedOut = true;
89
+ });
90
+ }
91
+ catch (error) {
92
+ const message = String(error?.message ?? error);
93
+ result = {
94
+ milestone,
95
+ assertionCount: assertions.length,
96
+ passed: 0,
97
+ failed: 0,
98
+ blocked: 0,
99
+ failedAssertionIds: [],
100
+ shouldHalt: policy === 'failOnFirst',
101
+ timedOut,
102
+ error: message,
103
+ };
104
+ }
105
+ result.timedOut = timedOut || result.timedOut;
106
+ this.missionService.recordMissionRuntimeEvent({
107
+ missionId,
108
+ eventType: 'validation_completed',
109
+ source: 'platform',
110
+ eventTime: Date.now(),
111
+ payload: {
112
+ milestone,
113
+ assertionCount: result.assertionCount,
114
+ assertionIds: assertions.map((assertion) => assertion.assertionId),
115
+ failedAssertionIds: result.failedAssertionIds,
116
+ passed: result.passed,
117
+ failed: result.failed,
118
+ blocked: result.blocked,
119
+ policy,
120
+ shouldHalt: result.shouldHalt,
121
+ timedOut: result.timedOut,
122
+ error: result.error ?? null,
123
+ },
124
+ });
125
+ return result;
126
+ }
127
+ async evaluateAssertion(input) {
128
+ const { missionId, assertionId, forceInferredConfidence } = input;
129
+ const assertion = this.missionService
130
+ .listMissionValidationAssertions({ missionId, userId: '', isAdmin: true })
131
+ .find((a) => a.assertionId === assertionId);
132
+ if (!assertion) {
133
+ throw new Error(`Assertion ${assertionId} not found`);
134
+ }
135
+ const features = this.missionService.listMissionFeatures({ missionId, userId: '', isAdmin: true });
136
+ const feature = assertion.featureId
137
+ ? features.find((f) => f.featureId === assertion.featureId) ?? null
138
+ : null;
139
+ const workerOutput = feature?.workerOutput ?? null;
140
+ const evaluation = await this.evaluator({ assertion, feature, workerOutput });
141
+ const confidence = forceInferredConfidence && evaluation.confidence !== 'unknown'
142
+ ? 'inferred'
143
+ : evaluation.confidence;
144
+ this.missionService.updateMissionValidationAssertion({
145
+ missionId,
146
+ assertionId,
147
+ status: evaluation.status,
148
+ note: evaluation.note,
149
+ evidence: evaluation.evidence,
150
+ confidence,
151
+ });
152
+ return { ...evaluation, confidence };
153
+ }
154
+ resolvePolicy(config) {
155
+ const value = config?.validationPolicy;
156
+ if (value === 'failOnFirst' || value === 'continueOnWarning')
157
+ return value;
158
+ return this.defaultPolicy;
159
+ }
160
+ resolveTimeoutMs(config) {
161
+ const value = config?.validationTimeoutMs;
162
+ if (typeof value === 'number' && Number.isFinite(value) && value > 0)
163
+ return Math.floor(value);
164
+ return this.defaultTimeoutMs;
165
+ }
166
+ }
167
+ function runWithTimeoutBound(fn, timeoutMs, onTimeout) {
168
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0)
169
+ return fn();
170
+ return new Promise((resolve, reject) => {
171
+ const timer = setTimeout(() => {
172
+ onTimeout();
173
+ reject(new Error(`Validation timed out after ${timeoutMs}ms`));
174
+ }, timeoutMs);
175
+ fn()
176
+ .then((value) => {
177
+ clearTimeout(timer);
178
+ resolve(value);
179
+ })
180
+ .catch((error) => {
181
+ clearTimeout(timer);
182
+ reject(error);
183
+ });
184
+ });
185
+ }
186
+ export function defaultAssertionEvaluator(input) {
187
+ const { assertion, feature, workerOutput } = input;
188
+ if (!feature) {
189
+ return {
190
+ status: 'blocked',
191
+ note: 'Source feature not found for assertion.',
192
+ evidence: `assertionId=${assertion.assertionId}`,
193
+ confidence: 'unknown',
194
+ };
195
+ }
196
+ const status = feature.status;
197
+ if (status === 'completed') {
198
+ return evaluateCompletedFeature(assertion, feature, workerOutput);
199
+ }
200
+ if (status === 'failed') {
201
+ return evaluateFailedFeature(assertion, feature, workerOutput);
202
+ }
203
+ if (status === 'skipped') {
204
+ return {
205
+ status: 'blocked',
206
+ note: 'Source feature was skipped.',
207
+ evidence: buildFeatureStatusEvidence(feature),
208
+ confidence: 'inferred',
209
+ };
210
+ }
211
+ if (status === 'cancelled') {
212
+ return {
213
+ status: 'blocked',
214
+ note: 'Source feature was cancelled.',
215
+ evidence: buildFeatureStatusEvidence(feature),
216
+ confidence: 'inferred',
217
+ };
218
+ }
219
+ return {
220
+ status: 'blocked',
221
+ note: `Source feature has unresolved status: ${status}.`,
222
+ evidence: buildFeatureStatusEvidence(feature),
223
+ confidence: 'unknown',
224
+ };
225
+ }
226
+ function evaluateCompletedFeature(assertion, feature, workerOutput) {
227
+ const commandsRun = extractCommandsRun(workerOutput);
228
+ if (commandsRun.length > 0) {
229
+ const failed = commandsRun.find((entry) => entry.exitCode !== 0);
230
+ if (failed) {
231
+ return {
232
+ status: 'failed',
233
+ note: `Verification command failed: ${failed.command}`,
234
+ evidence: `exitCode=${failed.exitCode}; observation=${truncateCitation(failed.observation)}`,
235
+ confidence: 'observed',
236
+ };
237
+ }
238
+ return {
239
+ status: 'passed',
240
+ note: 'Feature completed and verification commands ran successfully.',
241
+ evidence: commandsRun
242
+ .map((entry) => `${entry.command} (exitCode=${entry.exitCode})`)
243
+ .join('; '),
244
+ confidence: 'observed',
245
+ };
246
+ }
247
+ const evidence = buildEvidenceCorpus(workerOutput);
248
+ if (!evidence.hasAnyEvidence) {
249
+ return {
250
+ status: 'passed',
251
+ note: 'Feature completed; no worker output evidence was captured.',
252
+ evidence: buildFeatureStatusEvidence(feature),
253
+ confidence: 'inferred',
254
+ };
255
+ }
256
+ const match = findAssertionEvidenceMatch(assertion.assertionText, evidence);
257
+ if (match) {
258
+ return {
259
+ status: 'passed',
260
+ note: `Feature completed and worker output supports this assertion via ${match.source}.`,
261
+ evidence: match.citation,
262
+ confidence: 'observed',
263
+ };
264
+ }
265
+ return {
266
+ status: 'passed',
267
+ note: 'Feature completed with worker output; no direct assertion keyword match, so result is derived from feature success.',
268
+ evidence: buildEvidenceSummary(evidence),
269
+ confidence: 'derived',
270
+ };
271
+ }
272
+ function extractCommandsRun(workerOutput) {
273
+ const handoff = isPlainObject(workerOutput?.handoff) ? workerOutput.handoff : {};
274
+ const verification = isPlainObject(handoff.verification) ? handoff.verification : {};
275
+ const commandsRun = Array.isArray(verification.commandsRun) ? verification.commandsRun : [];
276
+ return commandsRun.filter((entry) => isPlainObject(entry) &&
277
+ typeof entry.command === 'string' &&
278
+ typeof entry.exitCode === 'number' &&
279
+ typeof entry.observation === 'string');
280
+ }
281
+ function evaluateFailedFeature(assertion, feature, workerOutput) {
282
+ const errorEvidence = buildErrorEvidence(workerOutput);
283
+ if (errorEvidence) {
284
+ return {
285
+ status: 'failed',
286
+ note: `Source feature ${feature.featureId} failed with worker-reported error.`,
287
+ evidence: errorEvidence,
288
+ confidence: 'observed',
289
+ };
290
+ }
291
+ return {
292
+ status: 'failed',
293
+ note: `Source feature ${feature.featureId} failed.`,
294
+ evidence: buildFeatureStatusEvidence(feature),
295
+ confidence: 'derived',
296
+ };
297
+ }
298
+ function buildEvidenceCorpus(workerOutput) {
299
+ const handoff = isPlainObject(workerOutput?.handoff) ? workerOutput.handoff : {};
300
+ const stdout = typeof workerOutput?.stdout === 'string' ? workerOutput.stdout : '';
301
+ const stderr = typeof workerOutput?.stderr === 'string' ? workerOutput.stderr : '';
302
+ const implementedFiles = Array.isArray(workerOutput?.implementedFiles)
303
+ ? workerOutput.implementedFiles.filter((item) => typeof item === 'string')
304
+ : [];
305
+ return {
306
+ hasAnyEvidence: Boolean(stdout
307
+ || stderr
308
+ || implementedFiles.length > 0
309
+ || handoff.salientSummary
310
+ || handoff.verification
311
+ || handoff.llmResponsePreview
312
+ || (Array.isArray(handoff.discoveredIssues) && handoff.discoveredIssues.length > 0)),
313
+ handoffSummary: typeof handoff.salientSummary === 'string' ? handoff.salientSummary : '',
314
+ handoffVerification: isPlainObject(handoff.verification)
315
+ ? JSON.stringify(handoff.verification)
316
+ : typeof handoff.verification === 'string'
317
+ ? handoff.verification
318
+ : '',
319
+ handoffLlmPreview: typeof handoff.llmResponsePreview === 'string' ? handoff.llmResponsePreview : '',
320
+ discoveredIssues: Array.isArray(handoff.discoveredIssues)
321
+ ? handoff.discoveredIssues
322
+ .map((item) => {
323
+ if (typeof item === 'string')
324
+ return item;
325
+ if (isPlainObject(item) && typeof item.description === 'string')
326
+ return item.description;
327
+ return JSON.stringify(item);
328
+ })
329
+ .join('\n')
330
+ : '',
331
+ stdout,
332
+ stderr,
333
+ implementedFiles,
334
+ };
335
+ }
336
+ function findAssertionEvidenceMatch(assertionText, evidence) {
337
+ const keywords = extractAssertionKeywords(assertionText);
338
+ if (keywords.length === 0)
339
+ return null;
340
+ // Prefer concrete file evidence first when a keyword names a changed file.
341
+ for (const filePath of evidence.implementedFiles) {
342
+ const normalizedPath = normalizeEvidenceText(filePath);
343
+ if (keywords.some((keyword) => normalizedPath.includes(keyword))) {
344
+ return {
345
+ source: 'implementedFiles',
346
+ citation: `implementedFiles: ${evidence.implementedFiles.join(', ')}`,
347
+ };
348
+ }
349
+ }
350
+ const searchableFields = [
351
+ { key: 'handoff.salientSummary', text: evidence.handoffSummary },
352
+ { key: 'handoff.verification', text: evidence.handoffVerification },
353
+ { key: 'handoff.llmResponsePreview', text: evidence.handoffLlmPreview },
354
+ { key: 'handoff.discoveredIssues', text: evidence.discoveredIssues },
355
+ { key: 'stdout', text: evidence.stdout },
356
+ ];
357
+ for (const field of searchableFields) {
358
+ const normalized = normalizeEvidenceText(field.text);
359
+ if (keywords.some((keyword) => normalized.includes(keyword))) {
360
+ return {
361
+ source: field.key,
362
+ citation: `${field.key}: ${truncateCitation(field.text)}`,
363
+ };
364
+ }
365
+ }
366
+ return null;
367
+ }
368
+ function buildErrorEvidence(workerOutput) {
369
+ const parts = [];
370
+ if (typeof workerOutput?.error === 'string' && workerOutput.error) {
371
+ parts.push(`error: ${truncateCitation(workerOutput.error)}`);
372
+ }
373
+ if (typeof workerOutput?.stderr === 'string' && workerOutput.stderr) {
374
+ parts.push(`stderr: ${truncateCitation(workerOutput.stderr)}`);
375
+ }
376
+ const handoff = isPlainObject(workerOutput?.handoff) ? workerOutput.handoff : {};
377
+ if (Array.isArray(handoff.discoveredIssues) && handoff.discoveredIssues.length > 0) {
378
+ const issues = handoff.discoveredIssues
379
+ .map((item) => {
380
+ if (typeof item === 'string')
381
+ return item;
382
+ if (isPlainObject(item) && typeof item.description === 'string')
383
+ return item.description;
384
+ return JSON.stringify(item);
385
+ })
386
+ .join('; ');
387
+ parts.push(`discoveredIssues: ${truncateCitation(issues)}`);
388
+ }
389
+ return parts.length > 0 ? parts.join('; ') : null;
390
+ }
391
+ function buildEvidenceSummary(evidence) {
392
+ const parts = [];
393
+ if (evidence.handoffSummary)
394
+ parts.push(`handoff.salientSummary: ${truncateCitation(evidence.handoffSummary)}`);
395
+ if (evidence.implementedFiles.length > 0) {
396
+ parts.push(`implementedFiles: ${evidence.implementedFiles.join(', ')}`);
397
+ }
398
+ if (evidence.stdout)
399
+ parts.push(`stdout: ${truncateCitation(evidence.stdout)}`);
400
+ if (evidence.stderr)
401
+ parts.push(`stderr: ${truncateCitation(evidence.stderr)}`);
402
+ return parts.length > 0 ? parts.join('; ') : 'Worker output present but no summary fields were extracted.';
403
+ }
404
+ function buildFeatureStatusEvidence(feature) {
405
+ const parts = [`featureId=${feature.featureId}`, `status=${feature.status}`];
406
+ if (feature.description) {
407
+ parts.push(`description=${feature.description.slice(0, 200)}`);
408
+ }
409
+ return parts.join('; ');
410
+ }
411
+ function extractAssertionKeywords(assertionText) {
412
+ const normalized = normalizeEvidenceText(assertionText);
413
+ const stopWords = new Set([
414
+ // Generic modals and auxiliaries
415
+ 'should', 'must', 'will', 'would', 'could', 'shall', 'need', 'needs', 'have', 'having',
416
+ 'with', 'without', 'from', 'into', 'through', 'during', 'before', 'after', 'above',
417
+ 'below', 'between', 'among', 'within', 'across', 'around', 'under', 'over', 'again',
418
+ 'further', 'then', 'once', 'here', 'there', 'when', 'where', 'what', 'which', 'while',
419
+ 'because', 'until', 'although', 'whether', 'unless', 'since', 'than', 'that', 'this',
420
+ 'these', 'those', 'they', 'them', 'their', 'such', 'only', 'also', 'just', 'about',
421
+ 'above', 'according', 'accordingly', 'across', 'actually', 'afterwards', 'able',
422
+ // Native mission domain generics that appear in boilerplate
423
+ 'worker', 'workers', 'feature', 'features', 'mission', 'missions', 'native', 'natives',
424
+ 'complete', 'completed', 'completes', 'completion', 'run', 'runs', 'running', 'execute',
425
+ 'executes', 'executed', 'perform', 'performs', 'performed', 'generate', 'generates',
426
+ 'generated', 'create', 'creates', 'created', 'write', 'writes', 'written', 'make',
427
+ 'makes', 'made', 'implement', 'implements', 'implemented', 'build', 'builds', 'built',
428
+ ]);
429
+ const tokens = normalized
430
+ .split(/\s+/)
431
+ .filter((token) => token.length >= 5 && !stopWords.has(token));
432
+ return [...new Set(tokens)];
433
+ }
434
+ function normalizeEvidenceText(value) {
435
+ return value
436
+ .toLowerCase()
437
+ .replace(/[^a-z0-9\/._-]+/g, ' ')
438
+ .replace(/\s+/g, ' ')
439
+ .trim();
440
+ }
441
+ function truncateCitation(value, limit = 300) {
442
+ const trimmed = value.trim();
443
+ if (trimmed.length <= limit)
444
+ return trimmed;
445
+ return `${trimmed.slice(0, limit)}...`;
446
+ }
447
+ function isPlainObject(value) {
448
+ if (!value || typeof value !== 'object' || Array.isArray(value))
449
+ return false;
450
+ const prototype = Object.getPrototypeOf(value);
451
+ return prototype === Object.prototype || prototype === null;
452
+ }