@mrpatronz/nexusflow 2.6.0 → 2.8.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 (198) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1 -3
  3. package/dist/agent/ClaudeSdkAdapter.d.ts +18 -0
  4. package/dist/agent/ClaudeSdkAdapter.d.ts.map +1 -0
  5. package/dist/agent/ClaudeSdkAdapter.js +185 -0
  6. package/dist/agent/ClaudeSdkAdapter.js.map +1 -0
  7. package/dist/agent/ClaudeSdkAdapter.test.d.ts +2 -0
  8. package/dist/agent/ClaudeSdkAdapter.test.d.ts.map +1 -0
  9. package/dist/agent/ClaudeSdkAdapter.test.js +155 -0
  10. package/dist/agent/ClaudeSdkAdapter.test.js.map +1 -0
  11. package/dist/agent/CliAdapterBase.d.ts.map +1 -1
  12. package/dist/agent/CliAdapterBase.js +3 -0
  13. package/dist/agent/CliAdapterBase.js.map +1 -1
  14. package/dist/agent/CliAdapterBase.test.js +49 -1
  15. package/dist/agent/CliAdapterBase.test.js.map +1 -1
  16. package/dist/agent/CodexCliAdapter.d.ts +1 -1
  17. package/dist/agent/CodexCliAdapter.d.ts.map +1 -1
  18. package/dist/agent/CodexCliAdapter.js +5 -4
  19. package/dist/agent/CodexCliAdapter.js.map +1 -1
  20. package/dist/agent/CodexCliAdapter.test.js +16 -1
  21. package/dist/agent/CodexCliAdapter.test.js.map +1 -1
  22. package/dist/agent/CodexSdkAdapter.d.ts +17 -0
  23. package/dist/agent/CodexSdkAdapter.d.ts.map +1 -0
  24. package/dist/agent/CodexSdkAdapter.js +132 -0
  25. package/dist/agent/CodexSdkAdapter.js.map +1 -0
  26. package/dist/agent/CodexSdkAdapter.test.d.ts +2 -0
  27. package/dist/agent/CodexSdkAdapter.test.d.ts.map +1 -0
  28. package/dist/agent/CodexSdkAdapter.test.js +76 -0
  29. package/dist/agent/CodexSdkAdapter.test.js.map +1 -0
  30. package/dist/agent/ProviderRegistry.d.ts +7 -2
  31. package/dist/agent/ProviderRegistry.d.ts.map +1 -1
  32. package/dist/agent/ProviderRegistry.js +15 -0
  33. package/dist/agent/ProviderRegistry.js.map +1 -1
  34. package/dist/agent/ProviderRegistry.test.js +19 -0
  35. package/dist/agent/ProviderRegistry.test.js.map +1 -1
  36. package/dist/agent/adapters.d.ts.map +1 -1
  37. package/dist/agent/adapters.js +81 -0
  38. package/dist/agent/adapters.js.map +1 -1
  39. package/dist/agent/mcp-config.d.ts +16 -0
  40. package/dist/agent/mcp-config.d.ts.map +1 -0
  41. package/dist/agent/mcp-config.js +28 -0
  42. package/dist/agent/mcp-config.js.map +1 -0
  43. package/dist/agent/mcp-config.test.d.ts +2 -0
  44. package/dist/agent/mcp-config.test.d.ts.map +1 -0
  45. package/dist/agent/mcp-config.test.js +22 -0
  46. package/dist/agent/mcp-config.test.js.map +1 -0
  47. package/dist/agent/models.d.ts +10 -0
  48. package/dist/agent/models.d.ts.map +1 -0
  49. package/dist/agent/models.js +35 -0
  50. package/dist/agent/models.js.map +1 -0
  51. package/dist/agent/models.test.d.ts +2 -0
  52. package/dist/agent/models.test.d.ts.map +1 -0
  53. package/dist/agent/models.test.js +40 -0
  54. package/dist/agent/models.test.js.map +1 -0
  55. package/dist/agent/orchestrator.d.ts +65 -0
  56. package/dist/agent/orchestrator.d.ts.map +1 -0
  57. package/dist/agent/orchestrator.js +252 -0
  58. package/dist/agent/orchestrator.js.map +1 -0
  59. package/dist/agent/orchestrator.test.d.ts +2 -0
  60. package/dist/agent/orchestrator.test.d.ts.map +1 -0
  61. package/dist/agent/orchestrator.test.js +284 -0
  62. package/dist/agent/orchestrator.test.js.map +1 -0
  63. package/dist/agent/session.d.ts +1 -0
  64. package/dist/agent/session.d.ts.map +1 -1
  65. package/dist/agent/session.js +3 -2
  66. package/dist/agent/session.js.map +1 -1
  67. package/dist/agent/session.test.js +9 -0
  68. package/dist/agent/session.test.js.map +1 -1
  69. package/dist/agent/smoke-chat.test.d.ts +2 -0
  70. package/dist/agent/smoke-chat.test.d.ts.map +1 -0
  71. package/dist/agent/smoke-chat.test.js +166 -0
  72. package/dist/agent/smoke-chat.test.js.map +1 -0
  73. package/dist/commands/mcp.d.ts +7 -1
  74. package/dist/commands/mcp.d.ts.map +1 -1
  75. package/dist/commands/mcp.js +7 -2
  76. package/dist/commands/mcp.js.map +1 -1
  77. package/dist/core/locks.d.ts +4 -2
  78. package/dist/core/locks.d.ts.map +1 -1
  79. package/dist/core/locks.js +55 -7
  80. package/dist/core/locks.js.map +1 -1
  81. package/dist/core/locks.test.js +48 -0
  82. package/dist/core/locks.test.js.map +1 -1
  83. package/dist/core/scheduler.d.ts +1 -0
  84. package/dist/core/scheduler.d.ts.map +1 -1
  85. package/dist/core/scheduler.js +10 -0
  86. package/dist/core/scheduler.js.map +1 -1
  87. package/dist/core/scheduler.test.js +6 -0
  88. package/dist/core/scheduler.test.js.map +1 -1
  89. package/dist/core/workspace.create.test.js +51 -11
  90. package/dist/core/workspace.create.test.js.map +1 -1
  91. package/dist/core/workspace.d.ts.map +1 -1
  92. package/dist/core/workspace.js +123 -84
  93. package/dist/core/workspace.js.map +1 -1
  94. package/dist/gui/assets/AgentsPage-Bfyu13gc.js +3 -0
  95. package/dist/gui/assets/{CSPContext-BG7no5S_.js → CSPContext-DBUu9tE0.js} +1 -1
  96. package/dist/gui/assets/{DashboardPage-ByGTwAQa.js → DashboardPage-O5wj99kn.js} +1 -1
  97. package/dist/gui/assets/{GettingStartedPage-Xjx3M3RH.js → GettingStartedPage-DDWBueC7.js} +1 -1
  98. package/dist/gui/assets/ProjectsPage-NHkXrDM2.js +1 -0
  99. package/dist/gui/assets/ScaffoldRepoInline-Cmqg_s94.js +1 -0
  100. package/dist/gui/assets/{SettingsPage-B9iN6hsv.js → SettingsPage-Cc77HBp7.js} +1 -1
  101. package/dist/gui/assets/SkillsPage-D4CVxwrI.js +6 -0
  102. package/dist/gui/assets/StartWorkPage-RPZTN3Ui.js +1 -0
  103. package/dist/gui/assets/{StrategiesPage-DDu3jwJx.js → StrategiesPage-Dn66xfZN.js} +1 -1
  104. package/dist/gui/assets/WorkspacesPage-BgdoM5Hs.js +6 -0
  105. package/dist/gui/assets/{alert-dialog-CdqVrvZl.js → alert-dialog-BYZ7cgWA.js} +1 -1
  106. package/dist/gui/assets/index-BmH7a-Qy.css +2 -0
  107. package/dist/gui/assets/index-CoNaATPd.js +52 -0
  108. package/dist/gui/assets/isElementDisabled-uy_EOitH.js +1 -0
  109. package/dist/gui/assets/textarea-ByfoCb54.js +1 -0
  110. package/dist/gui/assets/{useAriaLabelledBy-DfRdwH8a.js → useAriaLabelledBy-ClesgjMe.js} +1 -1
  111. package/dist/gui/assets/useValueChanged-DeYvGnKs.js +1 -0
  112. package/dist/gui/assets/{vsc-D6UPv2ii.js → vsc-BTST_4ex.js} +1 -1
  113. package/dist/gui/index.html +5 -7
  114. package/dist/harness/claude.d.ts +18 -0
  115. package/dist/harness/claude.d.ts.map +1 -0
  116. package/dist/harness/claude.js +334 -0
  117. package/dist/harness/claude.js.map +1 -0
  118. package/dist/harness/codex.d.ts +20 -0
  119. package/dist/harness/codex.d.ts.map +1 -0
  120. package/dist/harness/codex.js +315 -0
  121. package/dist/harness/codex.js.map +1 -0
  122. package/dist/harness/contract.test.d.ts +2 -0
  123. package/dist/harness/contract.test.d.ts.map +1 -0
  124. package/dist/harness/contract.test.js +426 -0
  125. package/dist/harness/contract.test.js.map +1 -0
  126. package/dist/harness/harness.test.d.ts +2 -0
  127. package/dist/harness/harness.test.d.ts.map +1 -0
  128. package/dist/harness/harness.test.js +215 -0
  129. package/dist/harness/harness.test.js.map +1 -0
  130. package/dist/harness/index.d.ts +10 -0
  131. package/dist/harness/index.d.ts.map +1 -0
  132. package/dist/harness/index.js +13 -0
  133. package/dist/harness/index.js.map +1 -0
  134. package/dist/harness/interface.d.ts +42 -0
  135. package/dist/harness/interface.d.ts.map +1 -0
  136. package/dist/harness/interface.js +17 -0
  137. package/dist/harness/interface.js.map +1 -0
  138. package/dist/harness/pushable.d.ts +11 -0
  139. package/dist/harness/pushable.d.ts.map +1 -0
  140. package/dist/harness/pushable.js +32 -0
  141. package/dist/harness/pushable.js.map +1 -0
  142. package/dist/harness/types.d.ts +132 -0
  143. package/dist/harness/types.d.ts.map +1 -0
  144. package/dist/harness/types.js +12 -0
  145. package/dist/harness/types.js.map +1 -0
  146. package/dist/index.js +4 -1
  147. package/dist/index.js.map +1 -1
  148. package/dist/mcp/server.d.ts +8 -1
  149. package/dist/mcp/server.d.ts.map +1 -1
  150. package/dist/mcp/server.js +11 -6
  151. package/dist/mcp/server.js.map +1 -1
  152. package/dist/mcp/tools.d.ts +6 -2
  153. package/dist/mcp/tools.d.ts.map +1 -1
  154. package/dist/mcp/tools.js +213 -6
  155. package/dist/mcp/tools.js.map +1 -1
  156. package/dist/mcp/tools.test.js +115 -6
  157. package/dist/mcp/tools.test.js.map +1 -1
  158. package/dist/orchestration/runner.d.ts +1 -1
  159. package/dist/orchestration/runner.d.ts.map +1 -1
  160. package/dist/orchestration/runner.js +29 -14
  161. package/dist/orchestration/runner.js.map +1 -1
  162. package/dist/orchestration/runner.test.js +33 -1
  163. package/dist/orchestration/runner.test.js.map +1 -1
  164. package/dist/server.d.ts.map +1 -1
  165. package/dist/server.js +28 -7
  166. package/dist/server.js.map +1 -1
  167. package/dist/server.test.js +34 -1
  168. package/dist/server.test.js.map +1 -1
  169. package/dist/utils/git.d.ts +0 -15
  170. package/dist/utils/git.d.ts.map +1 -1
  171. package/dist/utils/git.js +4 -0
  172. package/dist/utils/git.js.map +1 -1
  173. package/dist/utils/git.test.js +33 -1
  174. package/dist/utils/git.test.js.map +1 -1
  175. package/dist/utils/multi-git.d.ts +30 -6
  176. package/dist/utils/multi-git.d.ts.map +1 -1
  177. package/dist/utils/multi-git.js +132 -12
  178. package/dist/utils/multi-git.js.map +1 -1
  179. package/dist/utils/multi-git.test.js +153 -1
  180. package/dist/utils/multi-git.test.js.map +1 -1
  181. package/dist/utils/session-finder.d.ts +3 -0
  182. package/dist/utils/session-finder.d.ts.map +1 -1
  183. package/dist/utils/session-finder.js +10 -9
  184. package/dist/utils/session-finder.js.map +1 -1
  185. package/package.json +4 -1
  186. package/dist/gui/assets/AgentsPage-YyYRodOR.js +0 -3
  187. package/dist/gui/assets/ProjectsPage-BMdhqAGs.js +0 -1
  188. package/dist/gui/assets/ScaffoldRepoInline-gXyYSIz5.js +0 -1
  189. package/dist/gui/assets/SkillsPage-CxcsbIsQ.js +0 -6
  190. package/dist/gui/assets/StartWorkPage-DPVHcdy4.js +0 -1
  191. package/dist/gui/assets/WorkspacesPage-BWRmP0mY.js +0 -2
  192. package/dist/gui/assets/index-CZoZQkeC.css +0 -2
  193. package/dist/gui/assets/index-wy68EqfB.js +0 -50
  194. package/dist/gui/assets/isElementDisabled-D_fu2zfc.js +0 -1
  195. package/dist/gui/assets/store-D5DUHbxn.js +0 -3
  196. package/dist/gui/assets/styles-CVwNuFCx.js +0 -1
  197. package/dist/gui/assets/textarea-BNRuIXNs.js +0 -1
  198. package/dist/gui/assets/useValueChanged-CStSGSsD.js +0 -1
@@ -0,0 +1,426 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { ClaudeCodeAdapter } from './claude.js';
3
+ import { CodexAdapter } from './codex.js';
4
+ describe('Harness Contract Test Suite (Issue #174)', () => {
5
+ describe('ClaudeCodeAdapter Contract', () => {
6
+ it('resolves lazy sessionId and normalizes stream events and usage', async () => {
7
+ async function* fakeQuery() {
8
+ yield {
9
+ type: 'system',
10
+ subtype: 'init',
11
+ session_id: '123e4567-e89b-12d3-a456-426614174000',
12
+ };
13
+ yield {
14
+ type: 'stream_event',
15
+ event: {
16
+ type: 'content_block_delta',
17
+ delta: { type: 'text_delta', text: 'Hello from Claude' },
18
+ },
19
+ };
20
+ yield {
21
+ type: 'assistant',
22
+ message: {
23
+ content: [{ type: 'text', text: 'Hello from Claude' }],
24
+ },
25
+ };
26
+ yield {
27
+ type: 'result',
28
+ subtype: 'success',
29
+ result: 'Hello from Claude',
30
+ total_cost_usd: 0.002,
31
+ usage: {
32
+ input_tokens: 120,
33
+ output_tokens: 30,
34
+ cache_read_input_tokens: 40,
35
+ },
36
+ };
37
+ }
38
+ const mockQueryFn = vi.fn().mockReturnValue(fakeQuery());
39
+ const adapter = new ClaudeCodeAdapter(undefined, mockQueryFn);
40
+ const handle = await adapter.start({
41
+ prompt: 'Say hello',
42
+ workspace: { workspaceId: 'test-ws', rootPath: 'C:/test' },
43
+ env: { ANTHROPIC_API_KEY: 'sk-ant-test' },
44
+ });
45
+ const sessionId = await handle.sessionId();
46
+ expect(sessionId).toBe('123e4567-e89b-12d3-a456-426614174000');
47
+ const events = [];
48
+ for await (const ev of handle.events) {
49
+ events.push(ev);
50
+ if (ev.type === 'turn_completed')
51
+ break;
52
+ }
53
+ expect(events).toContainEqual({
54
+ type: 'session_started',
55
+ sessionId: '123e4567-e89b-12d3-a456-426614174000',
56
+ });
57
+ expect(events).toContainEqual({
58
+ type: 'text_delta',
59
+ text: 'Hello from Claude',
60
+ });
61
+ expect(events).toContainEqual({
62
+ type: 'assistant_message',
63
+ text: 'Hello from Claude',
64
+ });
65
+ expect(events).toContainEqual({
66
+ type: 'turn_completed',
67
+ usage: {
68
+ inputTokens: 120,
69
+ outputTokens: 30,
70
+ cachedInputTokens: 40,
71
+ costUsdEstimate: 0.002,
72
+ },
73
+ });
74
+ await handle.dispose();
75
+ });
76
+ it('cancels active turn cleanly on handle.interrupt() via AbortController', async () => {
77
+ let aborted = false;
78
+ async function* fakeLongQuery(args) {
79
+ const signal = args.options.abortController.signal;
80
+ signal.addEventListener('abort', () => {
81
+ aborted = true;
82
+ });
83
+ yield {
84
+ type: 'system',
85
+ subtype: 'init',
86
+ session_id: '123e4567-e89b-12d3-a456-426614174000',
87
+ };
88
+ yield {
89
+ type: 'stream_event',
90
+ event: {
91
+ type: 'content_block_delta',
92
+ delta: { type: 'text_delta', text: 'Starting long task...' },
93
+ },
94
+ };
95
+ // Simulate waiting for abort
96
+ while (!signal.aborted) {
97
+ await new Promise((r) => setTimeout(r, 10));
98
+ }
99
+ }
100
+ const mockQueryFn = vi.fn().mockImplementation((args) => fakeLongQuery(args));
101
+ const adapter = new ClaudeCodeAdapter(undefined, mockQueryFn);
102
+ const handle = await adapter.start({
103
+ prompt: 'Long running task',
104
+ workspace: { workspaceId: 'test-ws', rootPath: 'C:/test' },
105
+ env: { ANTHROPIC_API_KEY: 'sk-ant-test' },
106
+ });
107
+ await handle.interrupt();
108
+ expect(aborted).toBe(true);
109
+ await handle.dispose();
110
+ });
111
+ });
112
+ describe('CodexAdapter Contract & MCP Smoke Test', () => {
113
+ it('executes Codex thread lifecycle, maps MCP tool items, and normalizes usage', async () => {
114
+ async function* fakeStream() {
115
+ yield {
116
+ type: 'thread.started',
117
+ thread_id: 'codex-thread-12345',
118
+ };
119
+ yield {
120
+ type: 'item.completed',
121
+ item: {
122
+ id: 'mcp-call-1',
123
+ type: 'mcp_tool_call',
124
+ server: 'nexusflow',
125
+ tool: 'list_repos',
126
+ arguments: {},
127
+ status: 'completed',
128
+ result: {
129
+ content: [{ type: 'text', text: '["repo1"]' }],
130
+ structured_content: ['repo1'],
131
+ },
132
+ },
133
+ };
134
+ yield {
135
+ type: 'item.completed',
136
+ item: {
137
+ id: 'agent-msg-1',
138
+ type: 'agent_message',
139
+ text: 'Found 1 repo in workspace.',
140
+ },
141
+ };
142
+ yield {
143
+ type: 'turn.completed',
144
+ usage: {
145
+ input_tokens: 200,
146
+ output_tokens: 50,
147
+ cached_input_tokens: 100,
148
+ },
149
+ };
150
+ }
151
+ const mockThread = {
152
+ id: 'codex-thread-12345',
153
+ runStreamed: vi.fn().mockResolvedValue({ events: fakeStream() }),
154
+ };
155
+ const mockClient = {
156
+ startThread: vi.fn().mockReturnValue(mockThread),
157
+ };
158
+ const adapter = new CodexAdapter({}, () => mockClient);
159
+ const handle = await adapter.start({
160
+ prompt: 'List repos via MCP',
161
+ workspace: { workspaceId: 'test-ws', rootPath: 'C:/test' },
162
+ env: { OPENAI_API_KEY: 'sk-test' },
163
+ mcpServers: {
164
+ nexusflow: {
165
+ command: process.execPath,
166
+ args: ['mcp', 'run'],
167
+ },
168
+ },
169
+ });
170
+ const sessionId = await handle.sessionId();
171
+ expect(sessionId).toBe('codex-thread-12345');
172
+ const events = [];
173
+ for await (const ev of handle.events) {
174
+ events.push(ev);
175
+ if (ev.type === 'turn_completed')
176
+ break;
177
+ }
178
+ expect(events).toContainEqual({
179
+ type: 'session_started',
180
+ sessionId: 'codex-thread-12345',
181
+ });
182
+ expect(events).toContainEqual({
183
+ type: 'tool_completed',
184
+ callId: 'mcp-call-1',
185
+ ok: true,
186
+ outputSummary: undefined,
187
+ });
188
+ expect(events).toContainEqual({
189
+ type: 'assistant_message',
190
+ text: 'Found 1 repo in workspace.',
191
+ });
192
+ expect(events).toContainEqual({
193
+ type: 'turn_completed',
194
+ usage: {
195
+ inputTokens: 200,
196
+ outputTokens: 50,
197
+ cachedInputTokens: 100,
198
+ },
199
+ });
200
+ await handle.dispose();
201
+ });
202
+ it('keeps the handle stream open for a queued follow-up turn', async () => {
203
+ let finishFirstTurn;
204
+ let finishSecondTurn;
205
+ const firstTurnFinished = new Promise((resolve) => {
206
+ finishFirstTurn = resolve;
207
+ });
208
+ const secondTurnFinished = new Promise((resolve) => {
209
+ finishSecondTurn = resolve;
210
+ });
211
+ async function* firstTurn() {
212
+ try {
213
+ yield {
214
+ type: 'thread.started',
215
+ thread_id: 'codex-thread-multi-turn',
216
+ };
217
+ yield {
218
+ type: 'item.completed',
219
+ item: {
220
+ id: 'agent-msg-first',
221
+ type: 'agent_message',
222
+ text: 'First response',
223
+ },
224
+ };
225
+ yield {
226
+ type: 'turn.completed',
227
+ usage: { input_tokens: 10, output_tokens: 2 },
228
+ };
229
+ }
230
+ finally {
231
+ finishFirstTurn();
232
+ }
233
+ }
234
+ async function* secondTurn() {
235
+ try {
236
+ yield {
237
+ type: 'item.completed',
238
+ item: {
239
+ id: 'agent-msg-second',
240
+ type: 'agent_message',
241
+ text: 'Second response',
242
+ },
243
+ };
244
+ yield {
245
+ type: 'turn.completed',
246
+ usage: { input_tokens: 12, output_tokens: 3 },
247
+ };
248
+ }
249
+ finally {
250
+ finishSecondTurn();
251
+ }
252
+ }
253
+ const runStreamed = vi.fn()
254
+ .mockResolvedValueOnce({ events: firstTurn() })
255
+ .mockResolvedValueOnce({ events: secondTurn() });
256
+ const mockThread = {
257
+ id: 'codex-thread-multi-turn',
258
+ runStreamed,
259
+ };
260
+ const mockClient = {
261
+ startThread: vi.fn().mockReturnValue(mockThread),
262
+ };
263
+ const adapter = new CodexAdapter({}, () => mockClient);
264
+ const handle = await adapter.start({
265
+ prompt: 'First prompt',
266
+ workspace: { workspaceId: 'test-ws', rootPath: 'C:/test' },
267
+ env: { OPENAI_API_KEY: 'sk-test' },
268
+ });
269
+ const events = handle.events[Symbol.asyncIterator]();
270
+ try {
271
+ await expect(events.next()).resolves.toEqual({
272
+ done: false,
273
+ value: { type: 'session_started', sessionId: 'codex-thread-multi-turn' },
274
+ });
275
+ await expect(events.next()).resolves.toEqual({
276
+ done: false,
277
+ value: { type: 'assistant_message', text: 'First response' },
278
+ });
279
+ await expect(events.next()).resolves.toMatchObject({
280
+ done: false,
281
+ value: { type: 'turn_completed' },
282
+ });
283
+ await firstTurnFinished;
284
+ await new Promise((resolve) => setImmediate(resolve));
285
+ handle.send('Second prompt');
286
+ await expect(events.next()).resolves.toEqual({
287
+ done: false,
288
+ value: { type: 'assistant_message', text: 'Second response' },
289
+ });
290
+ await expect(events.next()).resolves.toMatchObject({
291
+ done: false,
292
+ value: { type: 'turn_completed' },
293
+ });
294
+ await secondTurnFinished;
295
+ await new Promise((resolve) => setImmediate(resolve));
296
+ expect(runStreamed).toHaveBeenNthCalledWith(1, 'First prompt', expect.any(Object));
297
+ expect(runStreamed).toHaveBeenNthCalledWith(2, 'Second prompt', expect.any(Object));
298
+ const pendingNext = events.next();
299
+ const stateBeforeDispose = await Promise.race([
300
+ pendingNext.then(() => 'settled'),
301
+ new Promise((resolve) => setTimeout(() => resolve('pending'), 25)),
302
+ ]);
303
+ expect(stateBeforeDispose).toBe('pending');
304
+ await handle.dispose();
305
+ await expect(pendingNext).resolves.toEqual({ done: true, value: undefined });
306
+ }
307
+ finally {
308
+ await handle.dispose();
309
+ }
310
+ });
311
+ it('applies model and native thread options when resuming a Codex thread', async () => {
312
+ const mockThread = {
313
+ id: 'codex-thread-resumed',
314
+ runStreamed: vi.fn(),
315
+ };
316
+ const mockClient = {
317
+ resumeThread: vi.fn().mockReturnValue(mockThread),
318
+ };
319
+ const adapter = new CodexAdapter({}, () => mockClient);
320
+ const handle = await adapter.resume({
321
+ sessionId: 'codex-thread-resumed',
322
+ mode: 'resume',
323
+ workspace: { workspaceId: 'test-ws', rootPath: 'C:/test' },
324
+ env: { OPENAI_API_KEY: 'sk-test' },
325
+ model: 'gpt-5.6-terra',
326
+ nativeOptions: { sandboxMode: 'read-only' },
327
+ });
328
+ expect(mockClient.resumeThread).toHaveBeenCalledWith('codex-thread-resumed', {
329
+ model: 'gpt-5.6-terra',
330
+ sandboxMode: 'read-only',
331
+ });
332
+ expect(mockThread.runStreamed).not.toHaveBeenCalled();
333
+ await handle.dispose();
334
+ });
335
+ it('cancels active Codex turn on handle.interrupt() via AbortSignal', async () => {
336
+ let aborted = false;
337
+ async function* fakeLongStream(signal) {
338
+ if (signal) {
339
+ signal.addEventListener('abort', () => {
340
+ aborted = true;
341
+ });
342
+ }
343
+ yield {
344
+ type: 'thread.started',
345
+ thread_id: 'codex-thread-abort',
346
+ };
347
+ while (!signal?.aborted) {
348
+ await new Promise((r) => setTimeout(r, 10));
349
+ }
350
+ }
351
+ const mockThread = {
352
+ id: 'codex-thread-abort',
353
+ runStreamed: vi.fn().mockImplementation((_prompt, opts) => {
354
+ return Promise.resolve({ events: fakeLongStream(opts?.signal) });
355
+ }),
356
+ };
357
+ const mockClient = {
358
+ startThread: vi.fn().mockReturnValue(mockThread),
359
+ };
360
+ const adapter = new CodexAdapter({}, () => mockClient);
361
+ const handle = await adapter.start({
362
+ prompt: 'Long running codex task',
363
+ workspace: { workspaceId: 'test-ws', rootPath: 'C:/test' },
364
+ env: { OPENAI_API_KEY: 'sk-test' },
365
+ });
366
+ await handle.interrupt();
367
+ expect(aborted).toBe(true);
368
+ await handle.dispose();
369
+ });
370
+ it('does not run queued or later prompts after an interrupted turn fails fatally', async () => {
371
+ let streamWaiting;
372
+ const streamIsWaiting = new Promise((resolve) => {
373
+ streamWaiting = resolve;
374
+ });
375
+ async function* interruptedStream(signal) {
376
+ yield {
377
+ type: 'thread.started',
378
+ thread_id: 'codex-thread-interrupted',
379
+ };
380
+ await new Promise((_resolve, reject) => {
381
+ const fail = () => reject(new Error('turn aborted'));
382
+ if (signal?.aborted) {
383
+ fail();
384
+ return;
385
+ }
386
+ signal?.addEventListener('abort', fail, { once: true });
387
+ streamWaiting();
388
+ });
389
+ }
390
+ const runStreamed = vi.fn().mockImplementation((_prompt, opts) => (Promise.resolve({ events: interruptedStream(opts?.signal) })));
391
+ const mockThread = {
392
+ id: 'codex-thread-interrupted',
393
+ runStreamed,
394
+ };
395
+ const mockClient = {
396
+ startThread: vi.fn().mockReturnValue(mockThread),
397
+ };
398
+ const adapter = new CodexAdapter({}, () => mockClient);
399
+ const handle = await adapter.start({
400
+ prompt: 'Active prompt',
401
+ workspace: { workspaceId: 'test-ws', rootPath: 'C:/test' },
402
+ env: { OPENAI_API_KEY: 'sk-test' },
403
+ });
404
+ const events = handle.events[Symbol.asyncIterator]();
405
+ await expect(events.next()).resolves.toEqual({
406
+ done: false,
407
+ value: { type: 'session_started', sessionId: 'codex-thread-interrupted' },
408
+ });
409
+ await streamIsWaiting;
410
+ handle.send('Queued follow-up');
411
+ await handle.interrupt();
412
+ await expect(events.next()).resolves.toMatchObject({
413
+ done: false,
414
+ value: { type: 'turn_failed', fatal: true },
415
+ });
416
+ await expect(events.next()).resolves.toEqual({ done: true, value: undefined });
417
+ await new Promise((resolve) => setImmediate(resolve));
418
+ expect(runStreamed).toHaveBeenCalledTimes(1);
419
+ handle.send('Prompt after fatal failure');
420
+ await new Promise((resolve) => setImmediate(resolve));
421
+ expect(runStreamed).toHaveBeenCalledTimes(1);
422
+ await handle.dispose();
423
+ });
424
+ });
425
+ });
426
+ //# sourceMappingURL=contract.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract.test.js","sourceRoot":"","sources":["../../src/harness/contract.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=harness.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"harness.test.d.ts","sourceRoot":"","sources":["../../src/harness/harness.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,215 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { getAdapter } from './index.js';
3
+ import { Pushable } from './pushable.js';
4
+ import { UnsupportedOperationError, AuthRequiredError } from './interface.js';
5
+ describe('Harness Abstraction Layer', () => {
6
+ describe('Factory getAdapter', () => {
7
+ it('returns ClaudeCodeAdapter for claude-code', () => {
8
+ const adapter = getAdapter('claude-code');
9
+ expect(adapter.vendor).toBe('claude-code');
10
+ });
11
+ it('returns CodexAdapter for codex', () => {
12
+ const adapter = getAdapter('codex');
13
+ expect(adapter.vendor).toBe('codex');
14
+ });
15
+ });
16
+ describe('Pushable Async Iterable Queue', () => {
17
+ it('pushes and yields items sequentially', async () => {
18
+ const queue = new Pushable();
19
+ queue.push('item-1');
20
+ queue.push('item-2');
21
+ queue.end();
22
+ const items = [];
23
+ for await (const item of queue) {
24
+ items.push(item);
25
+ }
26
+ expect(items).toEqual(['item-1', 'item-2']);
27
+ });
28
+ it('handles async consumption after end', async () => {
29
+ const queue = new Pushable();
30
+ queue.push(10);
31
+ queue.end();
32
+ const result = [];
33
+ for await (const n of queue) {
34
+ result.push(n);
35
+ }
36
+ expect(result).toEqual([10]);
37
+ });
38
+ it('drains buffered items to a late subscriber', async () => {
39
+ const queue = new Pushable();
40
+ queue.push('a');
41
+ queue.push('b');
42
+ queue.push('c');
43
+ queue.end();
44
+ const received = [];
45
+ for await (const item of queue) {
46
+ received.push(item);
47
+ }
48
+ expect(received).toEqual(['a', 'b', 'c']);
49
+ });
50
+ it('is idempotent on double-end', async () => {
51
+ const queue = new Pushable();
52
+ queue.push('x');
53
+ queue.end();
54
+ expect(() => queue.end()).not.toThrow();
55
+ const items = [];
56
+ for await (const item of queue) {
57
+ items.push(item);
58
+ }
59
+ expect(items).toEqual(['x']);
60
+ });
61
+ it('safely handles dispose() mid-turn without unhandled rejections', async () => {
62
+ const queue = new Pushable();
63
+ let closed = false;
64
+ const safePush = (val) => {
65
+ if (closed)
66
+ return;
67
+ try {
68
+ queue.push(val);
69
+ }
70
+ catch {
71
+ // Disposed concurrently
72
+ }
73
+ };
74
+ safePush('first-turn');
75
+ // Simulate concurrent dispose
76
+ closed = true;
77
+ queue.end();
78
+ // In-flight producer tries to push post-disposal
79
+ expect(() => safePush('in-flight-event')).not.toThrow();
80
+ const events = [];
81
+ for await (const ev of queue) {
82
+ events.push(ev);
83
+ }
84
+ expect(events).toEqual(['first-turn']);
85
+ });
86
+ });
87
+ describe('Normalized Contract Stream Verification (Fake Engine)', () => {
88
+ it('normalizes stream events into canonical HarnessEvent sequence', async () => {
89
+ const events = [];
90
+ const out = new Pushable();
91
+ // Push a canonical turn sequence
92
+ out.push({ type: 'session_started', sessionId: '123e4567-e89b-12d3-a456-426614174000' });
93
+ out.push({ type: 'text_delta', text: 'Hel' });
94
+ out.push({ type: 'text_delta', text: 'lo' });
95
+ out.push({ type: 'assistant_message', text: 'Hello' });
96
+ out.push({
97
+ type: 'turn_completed',
98
+ usage: {
99
+ inputTokens: 100,
100
+ outputTokens: 25,
101
+ cachedInputTokens: 50,
102
+ costUsdEstimate: 0.0015,
103
+ },
104
+ });
105
+ out.end();
106
+ for await (const ev of out) {
107
+ events.push(ev);
108
+ }
109
+ expect(events).toHaveLength(5);
110
+ expect(events[0]).toEqual({ type: 'session_started', sessionId: '123e4567-e89b-12d3-a456-426614174000' });
111
+ expect(events[1]).toEqual({ type: 'text_delta', text: 'Hel' });
112
+ expect(events[2]).toEqual({ type: 'text_delta', text: 'lo' });
113
+ expect(events[3]).toEqual({ type: 'assistant_message', text: 'Hello' });
114
+ expect(events[4]).toEqual({
115
+ type: 'turn_completed',
116
+ usage: {
117
+ inputTokens: 100,
118
+ outputTokens: 25,
119
+ cachedInputTokens: 50,
120
+ costUsdEstimate: 0.0015,
121
+ },
122
+ });
123
+ });
124
+ it('serializes errors safely for JSON boundary in turn_failed event', () => {
125
+ const rawError = new TypeError('Network connection reset');
126
+ const serialized = {
127
+ message: rawError.message,
128
+ name: rawError.name,
129
+ stack: rawError.stack,
130
+ };
131
+ const event = {
132
+ type: 'turn_failed',
133
+ error: serialized,
134
+ fatal: true,
135
+ };
136
+ const json = JSON.stringify(event);
137
+ const parsed = JSON.parse(json);
138
+ expect(parsed.error.message).toBe('Network connection reset');
139
+ expect(parsed.error.name).toBe('TypeError');
140
+ expect(parsed.fatal).toBe(true);
141
+ });
142
+ });
143
+ describe('Authentication Status Detection & Preflight', () => {
144
+ it('checks authStatus for ClaudeCodeAdapter', async () => {
145
+ const adapter = getAdapter('claude-code');
146
+ const status = await adapter.authStatus();
147
+ expect(status).toHaveProperty('configured');
148
+ expect(status).toHaveProperty('method');
149
+ });
150
+ it('checks authStatus for CodexAdapter', async () => {
151
+ const adapter = getAdapter('codex');
152
+ const status = await adapter.authStatus();
153
+ expect(status).toHaveProperty('configured');
154
+ expect(status).toHaveProperty('method');
155
+ });
156
+ it('detects credentials passed via env parameter (multi-tenant / per-spec)', async () => {
157
+ const adapter = getAdapter('claude-code');
158
+ const status = await adapter.authStatus(undefined, { ANTHROPIC_API_KEY: 'test-key' });
159
+ expect(status.configured).toBe(true);
160
+ expect(status.method).toBe('api-key');
161
+ const codexAdapter = getAdapter('codex');
162
+ const codexStatus = await codexAdapter.authStatus(undefined, {
163
+ OPENAI_API_KEY: 'sk-test',
164
+ CODEX_HOME: '/non-existent-codex-home-dir',
165
+ });
166
+ expect(codexStatus.configured).toBe(true);
167
+ expect(codexStatus.method).toBe('api-key');
168
+ expect(codexStatus.hasApiKeyFallback).toBe(false);
169
+ });
170
+ it('correctly sets hasApiKeyFallback for Codex when auth.json exists (Issue #172)', async () => {
171
+ const codexAdapter = getAdapter('codex');
172
+ // If no API key and no auth.json in temp dir
173
+ const unauthStatus = await codexAdapter.authStatus(undefined, {
174
+ OPENAI_API_KEY: '',
175
+ CODEX_API_KEY: '',
176
+ CODEX_HOME: '/non-existent-dir',
177
+ });
178
+ // Should cleanly compute method
179
+ expect(unauthStatus.hasApiKeyFallback).toBeFalsy();
180
+ });
181
+ it('throws AuthRequiredError on start() if unauthenticated', async () => {
182
+ const origKey = process.env.ANTHROPIC_API_KEY;
183
+ const origToken = process.env.CLAUDE_CODE_OAUTH_TOKEN;
184
+ delete process.env.ANTHROPIC_API_KEY;
185
+ delete process.env.CLAUDE_CODE_OAUTH_TOKEN;
186
+ try {
187
+ const adapter = getAdapter('claude-code');
188
+ await expect(adapter.start({
189
+ prompt: 'test',
190
+ workspace: { workspaceId: 'test-ws', rootPath: 'C:/test' },
191
+ })).rejects.toThrow(AuthRequiredError);
192
+ }
193
+ finally {
194
+ if (origKey)
195
+ process.env.ANTHROPIC_API_KEY = origKey;
196
+ if (origToken)
197
+ process.env.CLAUDE_CODE_OAUTH_TOKEN = origToken;
198
+ }
199
+ });
200
+ });
201
+ describe('Error Classes', () => {
202
+ it('formats UnsupportedOperationError message correctly', () => {
203
+ const err = new UnsupportedOperationError('codex', 'fork', 'no native fork');
204
+ expect(err.message).toContain('[codex] fork unsupported: no native fork');
205
+ expect(err.vendor).toBe('codex');
206
+ expect(err.operation).toBe('fork');
207
+ });
208
+ it('formats AuthRequiredError message correctly', () => {
209
+ const err = new AuthRequiredError('claude-code', 'token missing');
210
+ expect(err.message).toContain('[claude-code] Authentication required: token missing');
211
+ expect(err.vendor).toBe('claude-code');
212
+ });
213
+ });
214
+ });
215
+ //# sourceMappingURL=harness.test.js.map