@doingdev/opencode-claude-manager-plugin 0.1.61 → 0.1.62
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/manager/team-orchestrator.js +1 -0
- package/dist/plugin/agents/browser-qa.js +4 -0
- package/dist/src/manager/team-orchestrator.js +1 -0
- package/dist/src/plugin/agents/browser-qa.js +4 -0
- package/dist/src/types/contracts.d.ts +7 -0
- package/dist/test/claude-agent-sdk-adapter.test.js +39 -0
- package/dist/test/team-orchestrator.test.js +53 -0
- package/dist/types/contracts.d.ts +7 -0
- package/package.json +1 -1
|
@@ -122,6 +122,7 @@ export class TeamOrchestrator {
|
|
|
122
122
|
persistSession: true,
|
|
123
123
|
includePartialMessages: true,
|
|
124
124
|
permissionMode: 'acceptEdits',
|
|
125
|
+
allowedTools: workerCaps?.sessionAllowedTools,
|
|
125
126
|
restrictWriteTools: input.mode === 'explore' || (workerCaps?.restrictWriteTools ?? false),
|
|
126
127
|
model: input.model,
|
|
127
128
|
effort: (workerCaps?.restrictWriteTools ?? false)
|
|
@@ -12,6 +12,10 @@ export function buildWorkerCapabilities(prompts) {
|
|
|
12
12
|
plannerEligible: false,
|
|
13
13
|
isRuntimeUnavailableResponse: (text) => text.trimStart().startsWith('PLAYWRIGHT_UNAVAILABLE:'),
|
|
14
14
|
runtimeUnavailableTitle: '❌ Playwright unavailable',
|
|
15
|
+
// Pre-approve the Playwriter toolchain at the SDK level so headless sessions
|
|
16
|
+
// never stall waiting for interactive confirmation. Write tools remain blocked
|
|
17
|
+
// by restrictWriteTools and the canUseTool write-filter.
|
|
18
|
+
sessionAllowedTools: ['Skill', 'Bash', 'Read', 'Grep', 'Glob', 'LS', 'ListDirectory'],
|
|
15
19
|
},
|
|
16
20
|
};
|
|
17
21
|
}
|
|
@@ -122,6 +122,7 @@ export class TeamOrchestrator {
|
|
|
122
122
|
persistSession: true,
|
|
123
123
|
includePartialMessages: true,
|
|
124
124
|
permissionMode: 'acceptEdits',
|
|
125
|
+
allowedTools: workerCaps?.sessionAllowedTools,
|
|
125
126
|
restrictWriteTools: input.mode === 'explore' || (workerCaps?.restrictWriteTools ?? false),
|
|
126
127
|
model: input.model,
|
|
127
128
|
effort: (workerCaps?.restrictWriteTools ?? false)
|
|
@@ -12,6 +12,10 @@ export function buildWorkerCapabilities(prompts) {
|
|
|
12
12
|
plannerEligible: false,
|
|
13
13
|
isRuntimeUnavailableResponse: (text) => text.trimStart().startsWith('PLAYWRIGHT_UNAVAILABLE:'),
|
|
14
14
|
runtimeUnavailableTitle: '❌ Playwright unavailable',
|
|
15
|
+
// Pre-approve the Playwriter toolchain at the SDK level so headless sessions
|
|
16
|
+
// never stall waiting for interactive confirmation. Write tools remain blocked
|
|
17
|
+
// by restrictWriteTools and the canUseTool write-filter.
|
|
18
|
+
sessionAllowedTools: ['Skill', 'Bash', 'Read', 'Grep', 'Glob', 'LS', 'ListDirectory'],
|
|
15
19
|
},
|
|
16
20
|
};
|
|
17
21
|
}
|
|
@@ -206,6 +206,13 @@ export interface WorkerCapabilities {
|
|
|
206
206
|
isRuntimeUnavailableResponse?: (finalText: string) => boolean;
|
|
207
207
|
/** Metadata title for the runtime-unavailable event. */
|
|
208
208
|
runtimeUnavailableTitle?: string;
|
|
209
|
+
/**
|
|
210
|
+
* Explicit SDK-level pre-approval list for this worker's inner Claude Code session.
|
|
211
|
+
* Tools in this list bypass interactive confirmation prompts (they are still subject to
|
|
212
|
+
* the `canUseTool` write-restriction and approval-policy filters).
|
|
213
|
+
* Absent = falls back to `['Skill']` via the default adapter behaviour.
|
|
214
|
+
*/
|
|
215
|
+
sessionAllowedTools?: string[];
|
|
209
216
|
}
|
|
210
217
|
export interface GitDiffResult {
|
|
211
218
|
hasDiff: boolean;
|
|
@@ -498,6 +498,45 @@ describe('ClaudeAgentSdkAdapter', () => {
|
|
|
498
498
|
const gitLogResult = await capturedCanUseTool('Bash', { command: 'git log --oneline -10' }, {});
|
|
499
499
|
expect(gitLogResult.behavior).toBe('allow');
|
|
500
500
|
});
|
|
501
|
+
it('allows Playwriter-style bash commands when restrictWriteTools is true', async () => {
|
|
502
|
+
let capturedCanUseTool;
|
|
503
|
+
const adapter = new ClaudeAgentSdkAdapter({
|
|
504
|
+
query: (params) => {
|
|
505
|
+
capturedCanUseTool = params.options?.canUseTool;
|
|
506
|
+
return createFakeQuery([
|
|
507
|
+
{
|
|
508
|
+
type: 'result',
|
|
509
|
+
subtype: 'success',
|
|
510
|
+
session_id: 'ses_playwriter',
|
|
511
|
+
is_error: false,
|
|
512
|
+
result: 'ok',
|
|
513
|
+
num_turns: 1,
|
|
514
|
+
total_cost_usd: 0,
|
|
515
|
+
},
|
|
516
|
+
]);
|
|
517
|
+
},
|
|
518
|
+
listSessions: async () => [],
|
|
519
|
+
getSessionMessages: async () => [],
|
|
520
|
+
});
|
|
521
|
+
await adapter.runSession({
|
|
522
|
+
cwd: '/tmp/project',
|
|
523
|
+
prompt: 'Run Playwriter tests',
|
|
524
|
+
restrictWriteTools: true,
|
|
525
|
+
});
|
|
526
|
+
expect(capturedCanUseTool).toBeDefined();
|
|
527
|
+
// Non-destructive Playwriter commands must be allowed
|
|
528
|
+
const playwriterResult = await capturedCanUseTool('Bash', { command: 'playwriter session new' }, {});
|
|
529
|
+
expect(playwriterResult.behavior).toBe('allow');
|
|
530
|
+
const npxResult = await capturedCanUseTool('Bash', { command: 'npx playwriter session new' }, {});
|
|
531
|
+
expect(npxResult.behavior).toBe('allow');
|
|
532
|
+
const listResult = await capturedCanUseTool('Bash', { command: 'playwriter session list' }, {});
|
|
533
|
+
expect(listResult.behavior).toBe('allow');
|
|
534
|
+
// Destructive commands must still be denied
|
|
535
|
+
const rmResult = await capturedCanUseTool('Bash', { command: 'rm -rf /tmp/screenshots' }, {});
|
|
536
|
+
expect(rmResult.behavior).toBe('deny');
|
|
537
|
+
const redirectResult = await capturedCanUseTool('Bash', { command: 'echo "data" > out.txt' }, {});
|
|
538
|
+
expect(redirectResult.behavior).toBe('deny');
|
|
539
|
+
});
|
|
501
540
|
it('allows write tools when restrictWriteTools is false', async () => {
|
|
502
541
|
let capturedCanUseTool;
|
|
503
542
|
const adapter = new ClaudeAgentSdkAdapter({
|
|
@@ -345,6 +345,59 @@ describe('TeamOrchestrator', () => {
|
|
|
345
345
|
expect(error.message).toContain('BrowserQA is a browser QA specialist');
|
|
346
346
|
expect(error.message).toContain('does not support implement mode');
|
|
347
347
|
});
|
|
348
|
+
it('forwards sessionAllowedTools to runTask when worker has them configured', async () => {
|
|
349
|
+
tempRoot = await mkdtemp(join(tmpdir(), 'browserqa-allowed-'));
|
|
350
|
+
const runTask = vi.fn().mockResolvedValueOnce({
|
|
351
|
+
sessionId: 'ses_qa',
|
|
352
|
+
events: [],
|
|
353
|
+
finalText: 'Done.',
|
|
354
|
+
turns: 1,
|
|
355
|
+
totalCostUsd: 0.01,
|
|
356
|
+
inputTokens: 500,
|
|
357
|
+
outputTokens: 100,
|
|
358
|
+
contextWindowSize: 200_000,
|
|
359
|
+
});
|
|
360
|
+
const orchestrator = new TeamOrchestrator({ runTask }, new TeamStateStore('.state'), { appendEvents: vi.fn(async () => { }) }, 'Engineer prompt', 'Synthesis prompt', {
|
|
361
|
+
BrowserQA: {
|
|
362
|
+
...BROWSER_QA_TEST_CAPS,
|
|
363
|
+
sessionAllowedTools: ['Skill', 'Bash', 'Read', 'Grep', 'Glob', 'LS', 'ListDirectory'],
|
|
364
|
+
},
|
|
365
|
+
});
|
|
366
|
+
await orchestrator.dispatchEngineer({
|
|
367
|
+
teamId: 'team-1',
|
|
368
|
+
cwd: tempRoot,
|
|
369
|
+
engineer: 'BrowserQA',
|
|
370
|
+
mode: 'explore',
|
|
371
|
+
message: 'Run Playwriter tests',
|
|
372
|
+
});
|
|
373
|
+
expect(runTask).toHaveBeenCalledOnce();
|
|
374
|
+
const taskInput = runTask.mock.calls[0]?.[0];
|
|
375
|
+
expect(taskInput.allowedTools).toEqual(expect.arrayContaining(['Skill', 'Bash', 'Read', 'Grep', 'Glob', 'LS', 'ListDirectory']));
|
|
376
|
+
});
|
|
377
|
+
it('passes undefined allowedTools for standard engineers without sessionAllowedTools', async () => {
|
|
378
|
+
tempRoot = await mkdtemp(join(tmpdir(), 'engineer-no-allowed-'));
|
|
379
|
+
const runTask = vi.fn().mockResolvedValueOnce({
|
|
380
|
+
sessionId: 'ses_tom',
|
|
381
|
+
events: [],
|
|
382
|
+
finalText: 'Done.',
|
|
383
|
+
turns: 1,
|
|
384
|
+
totalCostUsd: 0.01,
|
|
385
|
+
inputTokens: 500,
|
|
386
|
+
outputTokens: 100,
|
|
387
|
+
contextWindowSize: 200_000,
|
|
388
|
+
});
|
|
389
|
+
const orchestrator = new TeamOrchestrator({ runTask }, new TeamStateStore('.state'), { appendEvents: vi.fn(async () => { }) }, 'Engineer prompt', 'Synthesis prompt', { BrowserQA: BROWSER_QA_TEST_CAPS });
|
|
390
|
+
await orchestrator.dispatchEngineer({
|
|
391
|
+
teamId: 'team-1',
|
|
392
|
+
cwd: tempRoot,
|
|
393
|
+
engineer: 'Tom',
|
|
394
|
+
mode: 'explore',
|
|
395
|
+
message: 'Investigate something',
|
|
396
|
+
});
|
|
397
|
+
expect(runTask).toHaveBeenCalledOnce();
|
|
398
|
+
const taskInput = runTask.mock.calls[0]?.[0];
|
|
399
|
+
expect(taskInput.allowedTools).toBeUndefined();
|
|
400
|
+
});
|
|
348
401
|
it('classifyError returns modeNotSupported for implement-mode rejection', () => {
|
|
349
402
|
const result = TeamOrchestrator.classifyError(new Error('BrowserQA is a browser QA specialist and does not support implement mode. ' +
|
|
350
403
|
'It can only verify and explore.'));
|
|
@@ -206,6 +206,13 @@ export interface WorkerCapabilities {
|
|
|
206
206
|
isRuntimeUnavailableResponse?: (finalText: string) => boolean;
|
|
207
207
|
/** Metadata title for the runtime-unavailable event. */
|
|
208
208
|
runtimeUnavailableTitle?: string;
|
|
209
|
+
/**
|
|
210
|
+
* Explicit SDK-level pre-approval list for this worker's inner Claude Code session.
|
|
211
|
+
* Tools in this list bypass interactive confirmation prompts (they are still subject to
|
|
212
|
+
* the `canUseTool` write-restriction and approval-policy filters).
|
|
213
|
+
* Absent = falls back to `['Skill']` via the default adapter behaviour.
|
|
214
|
+
*/
|
|
215
|
+
sessionAllowedTools?: string[];
|
|
209
216
|
}
|
|
210
217
|
export interface GitDiffResult {
|
|
211
218
|
hasDiff: boolean;
|