@doingdev/opencode-claude-manager-plugin 0.1.56 → 0.1.57

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.
@@ -203,4 +203,115 @@ describe('TeamOrchestrator', () => {
203
203
  engineer: 'Tom',
204
204
  });
205
205
  });
206
+ it('planWithTeam auto-selects two distinct engineers when names are omitted', async () => {
207
+ tempRoot = await mkdtemp(join(tmpdir(), 'team-orchestrator-'));
208
+ const runTask = vi.fn(async (_input, _onEvent) => {
209
+ // Return different results for lead vs challenger
210
+ const calls = runTask.mock.calls.length;
211
+ if (calls === 1) {
212
+ return {
213
+ sessionId: 'ses_lead',
214
+ events: [],
215
+ finalText: 'Lead plan',
216
+ turns: 1,
217
+ totalCostUsd: 0.01,
218
+ inputTokens: 100,
219
+ outputTokens: 50,
220
+ contextWindowSize: 200_000,
221
+ };
222
+ }
223
+ else if (calls === 2) {
224
+ return {
225
+ sessionId: 'ses_challenger',
226
+ events: [],
227
+ finalText: 'Challenger plan',
228
+ turns: 1,
229
+ totalCostUsd: 0.01,
230
+ inputTokens: 100,
231
+ outputTokens: 50,
232
+ contextWindowSize: 200_000,
233
+ };
234
+ }
235
+ else {
236
+ return {
237
+ sessionId: undefined,
238
+ events: [],
239
+ finalText: '## Synthesis\nBest plan\n## Recommended Question\nNONE\n## Recommended Answer\nNONE',
240
+ };
241
+ }
242
+ });
243
+ const orchestrator = new TeamOrchestrator({ runTask }, new TeamStateStore('.state'), { appendEvents: vi.fn(async () => { }) }, 'Base engineer prompt', 'Synthesis prompt');
244
+ const result = await orchestrator.planWithTeam({
245
+ teamId: 'team-1',
246
+ cwd: tempRoot,
247
+ request: 'Plan the refactor',
248
+ // NOTE: both leadEngineer and challengerEngineer are omitted
249
+ });
250
+ expect(result.leadEngineer).toBeDefined();
251
+ expect(result.challengerEngineer).toBeDefined();
252
+ expect(result.leadEngineer).not.toEqual(result.challengerEngineer);
253
+ });
254
+ it('throws error when fewer than 2 viable engineers exist for planning', async () => {
255
+ tempRoot = await mkdtemp(join(tmpdir(), 'team-orchestrator-'));
256
+ const orchestrator = new TeamOrchestrator({ runTask: vi.fn() }, new TeamStateStore('.state'), { appendEvents: vi.fn(async () => { }) }, 'Base engineer prompt', 'Synthesis prompt');
257
+ // Mark all engineers as busy
258
+ const team = await orchestrator.getOrCreateTeam(tempRoot, 'team-1');
259
+ for (const engineer of team.engineers) {
260
+ await orchestrator['updateEngineer'](tempRoot, 'team-1', engineer.name, (e) => ({
261
+ ...e,
262
+ busy: true,
263
+ busySince: new Date().toISOString(),
264
+ }));
265
+ }
266
+ await expect(orchestrator.planWithTeam({
267
+ teamId: 'team-1',
268
+ cwd: tempRoot,
269
+ request: 'Plan something',
270
+ })).rejects.toThrow('Not enough available engineers for dual planning');
271
+ });
272
+ it('context exhaustion retries exactly once with same assignment message and fresh session', async () => {
273
+ tempRoot = await mkdtemp(join(tmpdir(), 'team-orchestrator-'));
274
+ let callCount = 0;
275
+ let lastInputMessage = '';
276
+ const runTask = vi.fn(async (input) => {
277
+ callCount++;
278
+ lastInputMessage = input.prompt ?? '';
279
+ // First call throws context exhaustion, second succeeds
280
+ if (callCount === 1) {
281
+ const error = new Error('Token limit exceeded: context exhausted');
282
+ throw error;
283
+ }
284
+ return {
285
+ sessionId: 'ses_retry',
286
+ events: [],
287
+ finalText: 'Success after retry',
288
+ turns: 1,
289
+ totalCostUsd: 0.02,
290
+ inputTokens: 100,
291
+ outputTokens: 50,
292
+ contextWindowSize: 200_000,
293
+ };
294
+ });
295
+ const orchestrator = new TeamOrchestrator({ runTask }, new TeamStateStore('.state'), { appendEvents: vi.fn(async () => { }) }, 'Base engineer prompt', 'Synthesis prompt');
296
+ const allEvents = [];
297
+ const result = await orchestrator.dispatchEngineer({
298
+ teamId: 'team-1',
299
+ cwd: tempRoot,
300
+ engineer: 'Tom',
301
+ mode: 'implement',
302
+ message: 'Fix the bug',
303
+ onEvent: (event) => {
304
+ allEvents.push({ type: event.type, text: event.text });
305
+ },
306
+ });
307
+ // Verify retry happened exactly once (2 runTask calls total)
308
+ expect(callCount).toBe(2);
309
+ // Verify status event was emitted for context exhaustion
310
+ const statusEvent = allEvents.find((e) => e.type === 'status');
311
+ expect(statusEvent?.text).toContain('Context exhausted');
312
+ // Verify the retry message is the same (contains the original task)
313
+ expect(lastInputMessage).toContain('Fix the bug');
314
+ // Verify success result
315
+ expect(result.finalText).toBe('Success after retry');
316
+ });
206
317
  });
@@ -2,7 +2,7 @@ export interface ManagerPromptRegistry {
2
2
  ctoSystemPrompt: string;
3
3
  engineerAgentPrompt: string;
4
4
  engineerSessionPrompt: string;
5
- /** Prompt injected as the system prompt of the non-persistent synthesis runTask call inside plan_with_team. */
5
+ /** Prompt prepended to the user prompt of the synthesis runTask call inside plan_with_team. */
6
6
  planSynthesisPrompt: string;
7
7
  /** Visible subagent prompt for teamPlanner — thin bridge that calls plan_with_team. */
8
8
  teamPlannerPrompt: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@doingdev/opencode-claude-manager-plugin",
3
- "version": "0.1.56",
3
+ "version": "0.1.57",
4
4
  "description": "OpenCode plugin that orchestrates Claude Code sessions.",
5
5
  "keywords": [
6
6
  "opencode",