@aion0/forge 0.4.15 → 0.5.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/CLAUDE.md +1 -1
- package/README.md +2 -2
- package/RELEASE_NOTES.md +170 -13
- package/app/api/agents/route.ts +17 -0
- package/app/api/delivery/[id]/route.ts +62 -0
- package/app/api/delivery/route.ts +40 -0
- package/app/api/mobile-chat/route.ts +13 -7
- package/app/api/monitor/route.ts +10 -6
- package/app/api/pipelines/[id]/route.ts +16 -3
- package/app/api/tasks/route.ts +2 -1
- package/app/api/workspace/[id]/agents/route.ts +35 -0
- package/app/api/workspace/[id]/memory/route.ts +23 -0
- package/app/api/workspace/[id]/smith/route.ts +22 -0
- package/app/api/workspace/[id]/stream/route.ts +28 -0
- package/app/api/workspace/route.ts +100 -0
- package/app/global-error.tsx +10 -4
- package/app/icon.ico +0 -0
- package/app/layout.tsx +2 -2
- package/app/login/LoginForm.tsx +96 -0
- package/app/login/page.tsx +7 -98
- package/app/page.tsx +2 -2
- package/bin/forge-server.mjs +23 -4
- package/check-forge-status.sh +9 -0
- package/cli/mw.ts +2 -2
- package/components/ConversationEditor.tsx +411 -0
- package/components/ConversationGraphView.tsx +347 -0
- package/components/ConversationTerminalView.tsx +303 -0
- package/components/Dashboard.tsx +36 -39
- package/components/DashboardWrapper.tsx +9 -0
- package/components/DeliveryFlowEditor.tsx +491 -0
- package/components/DeliveryList.tsx +230 -0
- package/components/DeliveryWorkspace.tsx +589 -0
- package/components/DocTerminal.tsx +12 -4
- package/components/DocsViewer.tsx +10 -2
- package/components/HelpTerminal.tsx +13 -8
- package/components/InlinePipelineView.tsx +111 -0
- package/components/MobileView.tsx +20 -0
- package/components/MonitorPanel.tsx +9 -4
- package/components/NewTaskModal.tsx +32 -0
- package/components/PipelineEditor.tsx +49 -6
- package/components/PipelineView.tsx +482 -64
- package/components/ProjectDetail.tsx +314 -56
- package/components/ProjectManager.tsx +49 -4
- package/components/SessionView.tsx +27 -13
- package/components/SettingsModal.tsx +790 -124
- package/components/SkillsPanel.tsx +34 -8
- package/components/TaskBoard.tsx +3 -0
- package/components/WebTerminal.tsx +259 -45
- package/components/WorkspaceTree.tsx +221 -0
- package/components/WorkspaceView.tsx +2224 -0
- package/docs/LOCAL-DEPLOY.md +15 -15
- package/install.sh +2 -2
- package/lib/agents/claude-adapter.ts +104 -0
- package/lib/agents/generic-adapter.ts +64 -0
- package/lib/agents/index.ts +242 -0
- package/lib/agents/types.ts +70 -0
- package/lib/artifacts.ts +106 -0
- package/lib/cloudflared.ts +1 -1
- package/lib/delivery.ts +787 -0
- package/lib/forge-skills/forge-inbox.md +37 -0
- package/lib/forge-skills/forge-send.md +40 -0
- package/lib/forge-skills/forge-status.md +32 -0
- package/lib/forge-skills/forge-workspace-sync.md +37 -0
- package/lib/help-docs/00-overview.md +8 -2
- package/lib/help-docs/01-settings.md +159 -2
- package/lib/help-docs/05-pipelines.md +95 -6
- package/lib/help-docs/07-projects.md +35 -1
- package/lib/help-docs/11-workspace.md +204 -0
- package/lib/help-docs/CLAUDE.md +5 -2
- package/lib/init.ts +62 -12
- package/lib/pipeline.ts +537 -1
- package/lib/settings.ts +115 -22
- package/lib/skills.ts +249 -372
- package/lib/task-manager.ts +113 -33
- package/lib/telegram-bot.ts +33 -1
- package/lib/telegram-standalone.ts +1 -1
- package/lib/terminal-server.ts +2 -2
- package/lib/terminal-standalone.ts +1 -1
- package/lib/workspace/__tests__/state-machine.test.ts +388 -0
- package/lib/workspace/__tests__/workspace.test.ts +311 -0
- package/lib/workspace/agent-bus.ts +416 -0
- package/lib/workspace/agent-worker.ts +667 -0
- package/lib/workspace/backends/api-backend.ts +262 -0
- package/lib/workspace/backends/cli-backend.ts +479 -0
- package/lib/workspace/index.ts +82 -0
- package/lib/workspace/manager.ts +136 -0
- package/lib/workspace/orchestrator.ts +1804 -0
- package/lib/workspace/persistence.ts +310 -0
- package/lib/workspace/presets.ts +170 -0
- package/lib/workspace/skill-installer.ts +188 -0
- package/lib/workspace/smith-memory.ts +498 -0
- package/lib/workspace/types.ts +231 -0
- package/lib/workspace/watch-manager.ts +288 -0
- package/lib/workspace-standalone.ts +790 -0
- package/middleware.ts +1 -0
- package/next-env.d.ts +1 -1
- package/package.json +5 -2
- package/src/config/index.ts +13 -2
- package/src/core/db/database.ts +1 -0
- package/start.sh +10 -0
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace system integration tests.
|
|
3
|
+
* Run: npx tsx lib/workspace/__tests__/workspace.test.ts
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { AgentBus } from '../agent-bus';
|
|
7
|
+
import { AgentWorker } from '../agent-worker';
|
|
8
|
+
import { WorkspaceOrchestrator } from '../orchestrator';
|
|
9
|
+
import { createDevPipeline } from '../presets';
|
|
10
|
+
import { formatMemoryForPrompt, createMemory, addObservation, loadMemory } from '../smith-memory';
|
|
11
|
+
import type { WorkspaceAgentConfig, BusMessage } from '../types';
|
|
12
|
+
import { mkdirSync, rmSync, existsSync } from 'node:fs';
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
import { homedir } from 'node:os';
|
|
15
|
+
|
|
16
|
+
const TEST_WORKSPACE_ID = '__test_workspace__';
|
|
17
|
+
const TEST_PROJECT_PATH = '/tmp/forge-test-workspace';
|
|
18
|
+
const TEST_DIR = join(homedir(), '.forge', 'workspaces', TEST_WORKSPACE_ID);
|
|
19
|
+
|
|
20
|
+
let passed = 0;
|
|
21
|
+
let failed = 0;
|
|
22
|
+
|
|
23
|
+
function assert(condition: boolean, msg: string) {
|
|
24
|
+
if (condition) {
|
|
25
|
+
console.log(` ✅ ${msg}`);
|
|
26
|
+
passed++;
|
|
27
|
+
} else {
|
|
28
|
+
console.log(` ❌ ${msg}`);
|
|
29
|
+
failed++;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function cleanup() {
|
|
34
|
+
if (existsSync(TEST_DIR)) rmSync(TEST_DIR, { recursive: true, force: true });
|
|
35
|
+
mkdirSync(TEST_PROJECT_PATH, { recursive: true });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ─── Test 1: CLI Marker Protocol ─────────────────────────
|
|
39
|
+
|
|
40
|
+
function testMarkerParsing() {
|
|
41
|
+
console.log('\n📋 Test 1: CLI Marker Protocol');
|
|
42
|
+
|
|
43
|
+
const orch = new WorkspaceOrchestrator(TEST_WORKSPACE_ID, TEST_PROJECT_PATH, 'test');
|
|
44
|
+
|
|
45
|
+
orch.addAgent({
|
|
46
|
+
id: 'qa-1', label: 'QA', icon: '🧪', role: '', backend: 'cli',
|
|
47
|
+
dependsOn: [], outputs: [], steps: [],
|
|
48
|
+
});
|
|
49
|
+
orch.addAgent({
|
|
50
|
+
id: 'eng-1', label: 'Engineer', icon: '🔨', role: '', backend: 'cli',
|
|
51
|
+
dependsOn: [], outputs: [], steps: [],
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Simulate QA output with markers
|
|
55
|
+
const messages: BusMessage[] = [];
|
|
56
|
+
orch.on('event', (e: any) => {
|
|
57
|
+
if (e.type === 'bus_message') messages.push(e.message);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Access private method via any
|
|
61
|
+
(orch as any).parseBusMarkers('qa-1', [
|
|
62
|
+
{ type: 'assistant', content: 'Found bug in auth module.\n[SEND:Engineer:fix_request] Authentication bypass in login flow' },
|
|
63
|
+
{ type: 'assistant', content: 'Another issue:\n[SEND:Engineer:fix_request] SQL injection in search' },
|
|
64
|
+
{ type: 'assistant', content: 'No marker here, just text.' },
|
|
65
|
+
]);
|
|
66
|
+
|
|
67
|
+
assert(messages.length === 2, `Parsed 2 markers (got ${messages.length})`);
|
|
68
|
+
assert(messages[0]?.to === 'eng-1', `First message targets Engineer`);
|
|
69
|
+
assert(messages[0]?.payload.action === 'fix_request', `Action is fix_request`);
|
|
70
|
+
assert(messages[0]?.payload.content?.includes('Authentication bypass') ?? false, `Content preserved`);
|
|
71
|
+
|
|
72
|
+
// Test dedup — same content shouldn't send twice
|
|
73
|
+
(orch as any).parseBusMarkers('qa-1', [
|
|
74
|
+
{ type: 'assistant', content: '[SEND:Engineer:fix_request] Authentication bypass in login flow' },
|
|
75
|
+
{ type: 'assistant', content: '[SEND:Engineer:fix_request] Authentication bypass in login flow' },
|
|
76
|
+
]);
|
|
77
|
+
|
|
78
|
+
// Should still be 2 from before (dedup within this call)
|
|
79
|
+
// Actually messages accumulate, but parseBusMarkers dedup is within one call
|
|
80
|
+
// The 2nd call's dedup means only 1 new unique message
|
|
81
|
+
const newMsgs = messages.length;
|
|
82
|
+
assert(newMsgs === 3, `Dedup works: 3 total (got ${newMsgs})`);
|
|
83
|
+
|
|
84
|
+
orch.shutdown();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ─── Test 2: Bus ACK and Retry ───────────────────────────
|
|
88
|
+
|
|
89
|
+
async function testBusAckRetry() {
|
|
90
|
+
console.log('\n📋 Test 2: Bus ACK and Retry');
|
|
91
|
+
|
|
92
|
+
const bus = new AgentBus();
|
|
93
|
+
bus.setAgentStatus('agent-a', 'alive');
|
|
94
|
+
bus.setAgentStatus('agent-b', 'alive');
|
|
95
|
+
|
|
96
|
+
// Send a message
|
|
97
|
+
const msg = bus.send('agent-a', 'agent-b', 'notify', { action: 'test', content: 'hello' });
|
|
98
|
+
assert(msg.status === 'pending', `Message starts as pending`);
|
|
99
|
+
assert(msg.id.length > 0, `Message has ID`);
|
|
100
|
+
|
|
101
|
+
// Mark as done directly (ACK timer removed — smith manages status via callbacks)
|
|
102
|
+
msg.status = 'done';
|
|
103
|
+
assert(msg.status === 'done', `Message done after processing`);
|
|
104
|
+
|
|
105
|
+
// Test outbox for down agents
|
|
106
|
+
bus.setAgentStatus('agent-c', 'down');
|
|
107
|
+
const msg2 = bus.send('agent-a', 'agent-c', 'notify', { action: 'queued', content: 'for later' });
|
|
108
|
+
assert(msg2.status === 'pending', `Message to down agent is pending`);
|
|
109
|
+
assert(bus.getOutbox('agent-c').length === 1, `Outbox has 1 message`);
|
|
110
|
+
|
|
111
|
+
// Agent comes back
|
|
112
|
+
const flushed: BusMessage[] = [];
|
|
113
|
+
bus.on('message', (m: BusMessage) => { if (m.payload.action === 'queued') flushed.push(m); });
|
|
114
|
+
bus.setAgentStatus('agent-c', 'alive');
|
|
115
|
+
assert(flushed.length === 1, `Outbox flushed on agent recovery`);
|
|
116
|
+
assert(bus.getOutbox('agent-c').length === 0, `Outbox empty after flush`);
|
|
117
|
+
|
|
118
|
+
// Test dedup
|
|
119
|
+
assert(bus.isDuplicate('unique-id-1') === false, `First check: not duplicate`);
|
|
120
|
+
assert(bus.isDuplicate('unique-id-1') === true, `Second check: is duplicate`);
|
|
121
|
+
|
|
122
|
+
bus.clear();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ─── Test 3: Memory Injection ────────────────────────────
|
|
126
|
+
|
|
127
|
+
async function testMemoryInjection() {
|
|
128
|
+
console.log('\n📋 Test 3: Memory Injection');
|
|
129
|
+
|
|
130
|
+
cleanup();
|
|
131
|
+
|
|
132
|
+
// Create memory with observations
|
|
133
|
+
const memory = createMemory('pm-1', 'PM', 'Product Manager');
|
|
134
|
+
assert(memory.observations.length === 0, `New memory is empty`);
|
|
135
|
+
|
|
136
|
+
// Add observations
|
|
137
|
+
await addObservation(TEST_WORKSPACE_ID, 'pm-1', 'PM', 'Product Manager', {
|
|
138
|
+
type: 'feature',
|
|
139
|
+
title: 'Wrote PRD v1.0 for dictionary app',
|
|
140
|
+
filesModified: ['docs/prd/v1.0-initial.md'],
|
|
141
|
+
stepLabel: 'Write PRD',
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
await addObservation(TEST_WORKSPACE_ID, 'pm-1', 'PM', 'Product Manager', {
|
|
145
|
+
type: 'decision',
|
|
146
|
+
title: 'Chose localStorage over IndexedDB for history',
|
|
147
|
+
stepLabel: 'Analyze',
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// Load and check
|
|
151
|
+
const loaded = loadMemory(TEST_WORKSPACE_ID, 'pm-1');
|
|
152
|
+
assert(loaded !== null, `Memory loaded from disk`);
|
|
153
|
+
assert(loaded!.observations.length === 2, `Has 2 observations`);
|
|
154
|
+
|
|
155
|
+
// Format for prompt
|
|
156
|
+
const prompt = formatMemoryForPrompt(loaded);
|
|
157
|
+
assert(prompt.includes('Smith Memory'), `Prompt has header`);
|
|
158
|
+
assert(prompt.includes('Wrote PRD v1.0'), `Prompt includes observation title`);
|
|
159
|
+
assert(prompt.includes('localStorage'), `Prompt includes decision`);
|
|
160
|
+
assert(prompt.includes('Do NOT redo completed work'), `Prompt has incremental instruction`);
|
|
161
|
+
assert(prompt.length < 5000, `Prompt is reasonably sized (${prompt.length} chars)`);
|
|
162
|
+
|
|
163
|
+
// Test empty memory
|
|
164
|
+
const emptyPrompt = formatMemoryForPrompt(null);
|
|
165
|
+
assert(emptyPrompt === '', `Empty memory returns empty string`);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ─── Test 4: Input Incremental ───────────────────────────
|
|
169
|
+
|
|
170
|
+
function testInputIncremental() {
|
|
171
|
+
console.log('\n📋 Test 4: Input Incremental (latest only to downstream)');
|
|
172
|
+
|
|
173
|
+
cleanup();
|
|
174
|
+
const orch = new WorkspaceOrchestrator(TEST_WORKSPACE_ID, TEST_PROJECT_PATH, 'test');
|
|
175
|
+
|
|
176
|
+
// Create Input + PM
|
|
177
|
+
orch.addAgent({
|
|
178
|
+
id: 'input-1', label: 'Requirements', icon: '📝', type: 'input',
|
|
179
|
+
content: '', entries: [], role: '', backend: 'cli',
|
|
180
|
+
dependsOn: [], outputs: [], steps: [],
|
|
181
|
+
});
|
|
182
|
+
orch.addAgent({
|
|
183
|
+
id: 'pm-1', label: 'PM', icon: '📋', role: 'Product Manager', backend: 'cli',
|
|
184
|
+
dependsOn: ['input-1'], outputs: ['docs/prd/'],
|
|
185
|
+
steps: [{ id: 's1', label: 'Analyze', prompt: 'Analyze requirements' }],
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// Submit 3 entries
|
|
189
|
+
orch.completeInput('input-1', 'Build a dictionary app');
|
|
190
|
+
orch.completeInput('input-1', 'Add search history feature');
|
|
191
|
+
orch.completeInput('input-1', 'Change history limit to 20');
|
|
192
|
+
|
|
193
|
+
// Check entries
|
|
194
|
+
const snapshot = orch.getSnapshot();
|
|
195
|
+
const input = snapshot.agents.find(a => a.id === 'input-1');
|
|
196
|
+
assert(input?.entries?.length === 3, `Input has 3 entries (got ${input?.entries?.length})`);
|
|
197
|
+
|
|
198
|
+
// Check upstream context — should only contain latest
|
|
199
|
+
const pmConfig = snapshot.agents.find(a => a.id === 'pm-1')!;
|
|
200
|
+
const context = (orch as any).buildUpstreamContext(pmConfig) as string;
|
|
201
|
+
assert(context.includes('Change history limit to 20'), `Context has latest entry`);
|
|
202
|
+
assert(!context.includes('Build a dictionary app'), `Context does NOT have first entry`);
|
|
203
|
+
assert(!context.includes('Add search history feature'), `Context does NOT have second entry`);
|
|
204
|
+
|
|
205
|
+
orch.shutdown();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// ─── Test 5: Dev Pipeline Creation ───────────────────────
|
|
209
|
+
|
|
210
|
+
function testDevPipeline() {
|
|
211
|
+
console.log('\n📋 Test 5: Dev Pipeline Creation');
|
|
212
|
+
|
|
213
|
+
const pipeline = createDevPipeline();
|
|
214
|
+
assert(pipeline.length === 5, `Pipeline has 5 agents (got ${pipeline.length})`);
|
|
215
|
+
|
|
216
|
+
const [input, pm, eng, qa, rev] = pipeline;
|
|
217
|
+
|
|
218
|
+
assert(input.type === 'input', `First is Input node`);
|
|
219
|
+
assert(pm.label === 'PM', `Second is PM`);
|
|
220
|
+
assert(eng.label === 'Engineer', `Third is Engineer`);
|
|
221
|
+
assert(qa.label === 'QA', `Fourth is QA`);
|
|
222
|
+
assert(rev.label === 'Reviewer', `Fifth is Reviewer`);
|
|
223
|
+
|
|
224
|
+
// Check dependencies
|
|
225
|
+
assert(pm.dependsOn.includes(input.id), `PM depends on Input`);
|
|
226
|
+
assert(eng.dependsOn.includes(pm.id), `Engineer depends on PM`);
|
|
227
|
+
assert(qa.dependsOn.includes(eng.id), `QA depends on Engineer`);
|
|
228
|
+
assert(rev.dependsOn.includes(eng.id), `Reviewer depends on Engineer`);
|
|
229
|
+
assert(rev.dependsOn.includes(qa.id), `Reviewer depends on QA`);
|
|
230
|
+
|
|
231
|
+
// Check versioned outputs
|
|
232
|
+
assert(pm.outputs.includes('docs/prd/'), `PM outputs to docs/prd/`);
|
|
233
|
+
assert(eng.outputs.includes('docs/architecture/'), `Engineer outputs to docs/architecture/`);
|
|
234
|
+
assert(qa.outputs.includes('docs/qa/'), `QA outputs to docs/qa/`);
|
|
235
|
+
assert(rev.outputs.includes('docs/review/'), `Reviewer outputs to docs/review/`);
|
|
236
|
+
|
|
237
|
+
// Check all have steps
|
|
238
|
+
assert(pm.steps.length >= 2, `PM has steps`);
|
|
239
|
+
assert(eng.steps.length >= 2, `Engineer has steps`);
|
|
240
|
+
assert(qa.steps.length >= 2, `QA has steps`);
|
|
241
|
+
assert(rev.steps.length >= 2, `Reviewer has steps`);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// ─── Test 6: Engineer→QA Revalidation ────────────────────
|
|
245
|
+
|
|
246
|
+
function testRevalidation() {
|
|
247
|
+
console.log('\n📋 Test 6: Engineer completes → QA gets waiting_approval');
|
|
248
|
+
|
|
249
|
+
cleanup();
|
|
250
|
+
const orch = new WorkspaceOrchestrator(TEST_WORKSPACE_ID, TEST_PROJECT_PATH, 'test');
|
|
251
|
+
|
|
252
|
+
orch.addAgent({
|
|
253
|
+
id: 'eng-1', label: 'Engineer', icon: '🔨', role: '', backend: 'cli',
|
|
254
|
+
dependsOn: [], outputs: ['src/'], steps: [{ id: 's1', label: 'Implement', prompt: 'code' }],
|
|
255
|
+
});
|
|
256
|
+
orch.addAgent({
|
|
257
|
+
id: 'qa-1', label: 'QA', icon: '🧪', role: '', backend: 'cli',
|
|
258
|
+
dependsOn: ['eng-1'], outputs: ['tests/'], steps: [{ id: 's1', label: 'Test', prompt: 'test' }],
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// Manually set states to simulate: Engineer done first time, QA done first time
|
|
262
|
+
const engEntry = (orch as any).agents.get('eng-1');
|
|
263
|
+
const qaEntry = (orch as any).agents.get('qa-1');
|
|
264
|
+
engEntry.state.status = 'done';
|
|
265
|
+
engEntry.state.artifacts = [{ type: 'file', path: 'src/app.ts' }];
|
|
266
|
+
qaEntry.state.status = 'done';
|
|
267
|
+
|
|
268
|
+
// Track events
|
|
269
|
+
const events: any[] = [];
|
|
270
|
+
orch.on('event', (e: any) => events.push(e));
|
|
271
|
+
|
|
272
|
+
// Now simulate Engineer completing again (re-run)
|
|
273
|
+
(orch as any).notifyDownstreamForRevalidation('eng-1', ['src/app.ts']);
|
|
274
|
+
|
|
275
|
+
// QA should be set to waiting_approval
|
|
276
|
+
assert(qaEntry.state.status === 'waiting_approval', `QA is waiting_approval (got ${qaEntry.state.status})`);
|
|
277
|
+
|
|
278
|
+
const approvalEvent = events.find(e => e.type === 'approval_required' && e.agentId === 'qa-1');
|
|
279
|
+
assert(!!approvalEvent, `approval_required event emitted for QA`);
|
|
280
|
+
assert(approvalEvent?.upstreamId === 'eng-1', `Upstream is Engineer`);
|
|
281
|
+
|
|
282
|
+
// Check bus message was sent
|
|
283
|
+
const busMsg = events.find(e => e.type === 'bus_message' && e.message?.payload?.action === 'update_notify');
|
|
284
|
+
assert(!!busMsg, `update_notify bus message sent`);
|
|
285
|
+
|
|
286
|
+
orch.shutdown();
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// ─── Run all tests ───────────────────────────────────────
|
|
290
|
+
|
|
291
|
+
async function main() {
|
|
292
|
+
console.log('🧪 Forge Smiths Workspace Tests\n');
|
|
293
|
+
|
|
294
|
+
try { testMarkerParsing(); } catch (e: any) { console.log(` 💥 Test 1 crashed: ${e.message}`); failed++; }
|
|
295
|
+
try { await testBusAckRetry(); } catch (e: any) { console.log(` 💥 Test 2 crashed: ${e.message}`); failed++; }
|
|
296
|
+
try { await testMemoryInjection(); } catch (e: any) { console.log(` 💥 Test 3 crashed: ${e.message}`); failed++; }
|
|
297
|
+
try { testInputIncremental(); } catch (e: any) { console.log(` 💥 Test 4 crashed: ${e.message}`); failed++; }
|
|
298
|
+
try { testDevPipeline(); } catch (e: any) { console.log(` 💥 Test 5 crashed: ${e.message}`); failed++; }
|
|
299
|
+
try { testRevalidation(); } catch (e: any) { console.log(` 💥 Test 6 crashed: ${e.message}`); failed++; }
|
|
300
|
+
|
|
301
|
+
// Cleanup
|
|
302
|
+
cleanup();
|
|
303
|
+
|
|
304
|
+
console.log(`\n${'═'.repeat(40)}`);
|
|
305
|
+
console.log(`Results: ${passed} passed, ${failed} failed`);
|
|
306
|
+
console.log(`${'═'.repeat(40)}`);
|
|
307
|
+
|
|
308
|
+
process.exit(failed > 0 ? 1 : 0);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
main();
|