@doingdev/opencode-claude-manager-plugin 0.1.65 → 0.1.66
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/index.d.ts +1 -1
- package/dist/manager/team-orchestrator.js +1 -1
- package/dist/plugin/agents/common.d.ts +2 -2
- package/dist/plugin/agents/common.js +5 -0
- package/dist/plugin/claude-manager.plugin.js +104 -0
- package/dist/plugin/inbox-ops.d.ts +50 -0
- package/dist/plugin/inbox-ops.js +166 -0
- package/dist/types/contracts.d.ts +18 -0
- package/package.json +1 -1
- package/dist/claude/session-live-tailer.d.ts +0 -51
- package/dist/claude/session-live-tailer.js +0 -269
- package/dist/manager/session-controller.d.ts +0 -41
- package/dist/manager/session-controller.js +0 -97
- package/dist/metadata/claude-metadata.service.d.ts +0 -12
- package/dist/metadata/claude-metadata.service.js +0 -38
- package/dist/metadata/repo-claude-config-reader.d.ts +0 -7
- package/dist/metadata/repo-claude-config-reader.js +0 -154
- package/dist/plugin/orchestrator.plugin.d.ts +0 -2
- package/dist/plugin/orchestrator.plugin.js +0 -116
- package/dist/providers/claude-code-wrapper.d.ts +0 -13
- package/dist/providers/claude-code-wrapper.js +0 -13
- package/dist/safety/bash-safety.d.ts +0 -21
- package/dist/safety/bash-safety.js +0 -62
- package/dist/src/claude/claude-agent-sdk-adapter.d.ts +0 -28
- package/dist/src/claude/claude-agent-sdk-adapter.js +0 -559
- package/dist/src/claude/claude-session.service.d.ts +0 -9
- package/dist/src/claude/claude-session.service.js +0 -15
- package/dist/src/claude/session-live-tailer.d.ts +0 -51
- package/dist/src/claude/session-live-tailer.js +0 -269
- package/dist/src/claude/tool-approval-manager.d.ts +0 -30
- package/dist/src/claude/tool-approval-manager.js +0 -279
- package/dist/src/index.d.ts +0 -5
- package/dist/src/index.js +0 -3
- package/dist/src/manager/context-tracker.d.ts +0 -32
- package/dist/src/manager/context-tracker.js +0 -103
- package/dist/src/manager/git-operations.d.ts +0 -18
- package/dist/src/manager/git-operations.js +0 -86
- package/dist/src/manager/persistent-manager.d.ts +0 -39
- package/dist/src/manager/persistent-manager.js +0 -44
- package/dist/src/manager/session-controller.d.ts +0 -41
- package/dist/src/manager/session-controller.js +0 -97
- package/dist/src/manager/team-orchestrator.d.ts +0 -81
- package/dist/src/manager/team-orchestrator.js +0 -612
- package/dist/src/plugin/agent-hierarchy.d.ts +0 -1
- package/dist/src/plugin/agent-hierarchy.js +0 -2
- package/dist/src/plugin/agents/browser-qa.d.ts +0 -14
- package/dist/src/plugin/agents/browser-qa.js +0 -31
- package/dist/src/plugin/agents/common.d.ts +0 -36
- package/dist/src/plugin/agents/common.js +0 -59
- package/dist/src/plugin/agents/cto.d.ts +0 -9
- package/dist/src/plugin/agents/cto.js +0 -39
- package/dist/src/plugin/agents/engineers.d.ts +0 -9
- package/dist/src/plugin/agents/engineers.js +0 -11
- package/dist/src/plugin/agents/index.d.ts +0 -5
- package/dist/src/plugin/agents/index.js +0 -5
- package/dist/src/plugin/agents/team-planner.d.ts +0 -10
- package/dist/src/plugin/agents/team-planner.js +0 -23
- package/dist/src/plugin/claude-manager.plugin.d.ts +0 -10
- package/dist/src/plugin/claude-manager.plugin.js +0 -950
- package/dist/src/plugin/service-factory.d.ts +0 -38
- package/dist/src/plugin/service-factory.js +0 -101
- package/dist/src/prompts/registry.d.ts +0 -2
- package/dist/src/prompts/registry.js +0 -210
- package/dist/src/state/file-run-state-store.d.ts +0 -14
- package/dist/src/state/file-run-state-store.js +0 -85
- package/dist/src/state/team-state-store.d.ts +0 -14
- package/dist/src/state/team-state-store.js +0 -88
- package/dist/src/state/transcript-store.d.ts +0 -15
- package/dist/src/state/transcript-store.js +0 -44
- package/dist/src/team/roster.d.ts +0 -5
- package/dist/src/team/roster.js +0 -40
- package/dist/src/types/contracts.d.ts +0 -261
- package/dist/src/types/contracts.js +0 -2
- package/dist/src/util/fs-helpers.d.ts +0 -8
- package/dist/src/util/fs-helpers.js +0 -21
- package/dist/src/util/project-context.d.ts +0 -10
- package/dist/src/util/project-context.js +0 -105
- package/dist/src/util/transcript-append.d.ts +0 -7
- package/dist/src/util/transcript-append.js +0 -29
- package/dist/state/file-run-state-store.d.ts +0 -14
- package/dist/state/file-run-state-store.js +0 -85
- package/dist/test/claude-agent-sdk-adapter.test.d.ts +0 -1
- package/dist/test/claude-agent-sdk-adapter.test.js +0 -707
- package/dist/test/claude-manager.plugin.test.d.ts +0 -1
- package/dist/test/claude-manager.plugin.test.js +0 -316
- package/dist/test/context-tracker.test.d.ts +0 -1
- package/dist/test/context-tracker.test.js +0 -130
- package/dist/test/cto-active-team.test.d.ts +0 -1
- package/dist/test/cto-active-team.test.js +0 -199
- package/dist/test/file-run-state-store.test.d.ts +0 -1
- package/dist/test/file-run-state-store.test.js +0 -82
- package/dist/test/fs-helpers.test.d.ts +0 -1
- package/dist/test/fs-helpers.test.js +0 -56
- package/dist/test/git-operations.test.d.ts +0 -1
- package/dist/test/git-operations.test.js +0 -133
- package/dist/test/persistent-manager.test.d.ts +0 -1
- package/dist/test/persistent-manager.test.js +0 -48
- package/dist/test/project-context.test.d.ts +0 -1
- package/dist/test/project-context.test.js +0 -92
- package/dist/test/prompt-registry.test.d.ts +0 -1
- package/dist/test/prompt-registry.test.js +0 -117
- package/dist/test/report-claude-event.test.d.ts +0 -1
- package/dist/test/report-claude-event.test.js +0 -304
- package/dist/test/session-controller.test.d.ts +0 -1
- package/dist/test/session-controller.test.js +0 -149
- package/dist/test/session-live-tailer.test.d.ts +0 -1
- package/dist/test/session-live-tailer.test.js +0 -313
- package/dist/test/team-orchestrator.test.d.ts +0 -1
- package/dist/test/team-orchestrator.test.js +0 -583
- package/dist/test/team-state-store.test.d.ts +0 -1
- package/dist/test/team-state-store.test.js +0 -54
- package/dist/test/tool-approval-manager.test.d.ts +0 -1
- package/dist/test/tool-approval-manager.test.js +0 -260
- package/dist/test/transcript-append.test.d.ts +0 -1
- package/dist/test/transcript-append.test.js +0 -37
- package/dist/test/transcript-store.test.d.ts +0 -1
- package/dist/test/transcript-store.test.js +0 -50
- package/dist/test/undo-propagation.test.d.ts +0 -1
- package/dist/test/undo-propagation.test.js +0 -837
- package/dist/util/project-context.d.ts +0 -10
- package/dist/util/project-context.js +0 -105
- package/dist/vitest.config.d.ts +0 -2
- package/dist/vitest.config.js +0 -11
|
@@ -1,707 +0,0 @@
|
|
|
1
|
-
import { afterEach, describe, expect, it } from 'vitest';
|
|
2
|
-
import { mkdtemp, readFile, rm } from 'node:fs/promises';
|
|
3
|
-
import { join } from 'node:path';
|
|
4
|
-
import { tmpdir } from 'node:os';
|
|
5
|
-
import { ClaudeAgentSdkAdapter } from '../src/claude/claude-agent-sdk-adapter.js';
|
|
6
|
-
import { ToolApprovalManager } from '../src/claude/tool-approval-manager.js';
|
|
7
|
-
function createFakeQuery(messages) {
|
|
8
|
-
return {
|
|
9
|
-
async *[Symbol.asyncIterator]() {
|
|
10
|
-
for (const message of messages) {
|
|
11
|
-
yield message;
|
|
12
|
-
}
|
|
13
|
-
},
|
|
14
|
-
close() {
|
|
15
|
-
return undefined;
|
|
16
|
-
},
|
|
17
|
-
supportedCommands: async () => [
|
|
18
|
-
{
|
|
19
|
-
name: 'review',
|
|
20
|
-
description: 'Review changes',
|
|
21
|
-
argumentHint: '<path>',
|
|
22
|
-
},
|
|
23
|
-
],
|
|
24
|
-
supportedAgents: async () => [
|
|
25
|
-
{
|
|
26
|
-
name: 'researcher',
|
|
27
|
-
description: 'Looks around the repo',
|
|
28
|
-
model: 'claude-sonnet-4-5',
|
|
29
|
-
},
|
|
30
|
-
],
|
|
31
|
-
supportedModels: async () => [{ value: 'claude-sonnet-4-5' }],
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
describe('ClaudeAgentSdkAdapter', () => {
|
|
35
|
-
it('normalizes streamed Claude session events', async () => {
|
|
36
|
-
const adapter = new ClaudeAgentSdkAdapter({
|
|
37
|
-
query: () => createFakeQuery([
|
|
38
|
-
{ type: 'system', subtype: 'init', session_id: 'ses_123' },
|
|
39
|
-
{
|
|
40
|
-
type: 'assistant',
|
|
41
|
-
session_id: 'ses_123',
|
|
42
|
-
message: {
|
|
43
|
-
content: [{ type: 'text', text: 'Working through the task.' }],
|
|
44
|
-
},
|
|
45
|
-
},
|
|
46
|
-
{
|
|
47
|
-
type: 'result',
|
|
48
|
-
subtype: 'success',
|
|
49
|
-
session_id: 'ses_123',
|
|
50
|
-
is_error: false,
|
|
51
|
-
result: 'Done.',
|
|
52
|
-
num_turns: 2,
|
|
53
|
-
total_cost_usd: 0.42,
|
|
54
|
-
},
|
|
55
|
-
]),
|
|
56
|
-
listSessions: async () => [],
|
|
57
|
-
getSessionMessages: async () => [],
|
|
58
|
-
});
|
|
59
|
-
const result = await adapter.runSession({
|
|
60
|
-
cwd: '/tmp/project',
|
|
61
|
-
prompt: 'Do the thing',
|
|
62
|
-
includePartialMessages: true,
|
|
63
|
-
});
|
|
64
|
-
expect(result.sessionId).toBe('ses_123');
|
|
65
|
-
expect(result.finalText).toBe('Done.');
|
|
66
|
-
expect(result.events.map((event) => event.type)).toEqual(['init', 'assistant', 'result']);
|
|
67
|
-
});
|
|
68
|
-
it('splits assistant turns into text and tool_call events', async () => {
|
|
69
|
-
const adapter = new ClaudeAgentSdkAdapter({
|
|
70
|
-
query: () => createFakeQuery([
|
|
71
|
-
{ type: 'system', subtype: 'init', session_id: 'ses_tool' },
|
|
72
|
-
{
|
|
73
|
-
type: 'assistant',
|
|
74
|
-
session_id: 'ses_tool',
|
|
75
|
-
message: {
|
|
76
|
-
content: [
|
|
77
|
-
{ type: 'text', text: 'Searching.' },
|
|
78
|
-
{
|
|
79
|
-
type: 'tool_use',
|
|
80
|
-
name: 'Grep',
|
|
81
|
-
id: 'toolu_1',
|
|
82
|
-
input: { pattern: 'foo', path: '.' },
|
|
83
|
-
},
|
|
84
|
-
],
|
|
85
|
-
},
|
|
86
|
-
},
|
|
87
|
-
{
|
|
88
|
-
type: 'result',
|
|
89
|
-
subtype: 'success',
|
|
90
|
-
session_id: 'ses_tool',
|
|
91
|
-
is_error: false,
|
|
92
|
-
result: 'Found.',
|
|
93
|
-
num_turns: 1,
|
|
94
|
-
total_cost_usd: 0.02,
|
|
95
|
-
},
|
|
96
|
-
]),
|
|
97
|
-
listSessions: async () => [],
|
|
98
|
-
getSessionMessages: async () => [],
|
|
99
|
-
});
|
|
100
|
-
const result = await adapter.runSession({
|
|
101
|
-
cwd: '/tmp/project',
|
|
102
|
-
prompt: 'Search',
|
|
103
|
-
includePartialMessages: true,
|
|
104
|
-
});
|
|
105
|
-
expect(result.events.map((e) => e.type)).toEqual(['init', 'assistant', 'tool_call', 'result']);
|
|
106
|
-
expect(result.events[2]).toMatchObject({ type: 'tool_call' });
|
|
107
|
-
expect(result.events[2]?.text).toContain('Grep');
|
|
108
|
-
expect(result.events[2]?.text).toContain('toolu_1');
|
|
109
|
-
});
|
|
110
|
-
it('emits user messages with tool_result content', async () => {
|
|
111
|
-
const adapter = new ClaudeAgentSdkAdapter({
|
|
112
|
-
query: () => createFakeQuery([
|
|
113
|
-
{ type: 'system', subtype: 'init', session_id: 'ses_u' },
|
|
114
|
-
{
|
|
115
|
-
type: 'user',
|
|
116
|
-
session_id: 'ses_u',
|
|
117
|
-
parent_tool_use_id: null,
|
|
118
|
-
message: {
|
|
119
|
-
role: 'user',
|
|
120
|
-
content: [
|
|
121
|
-
{
|
|
122
|
-
type: 'tool_result',
|
|
123
|
-
tool_use_id: 'toolu_1',
|
|
124
|
-
content: 'file.ts:42: match',
|
|
125
|
-
},
|
|
126
|
-
],
|
|
127
|
-
},
|
|
128
|
-
},
|
|
129
|
-
{
|
|
130
|
-
type: 'result',
|
|
131
|
-
subtype: 'success',
|
|
132
|
-
session_id: 'ses_u',
|
|
133
|
-
is_error: false,
|
|
134
|
-
result: 'OK',
|
|
135
|
-
num_turns: 1,
|
|
136
|
-
total_cost_usd: 0,
|
|
137
|
-
},
|
|
138
|
-
]),
|
|
139
|
-
listSessions: async () => [],
|
|
140
|
-
getSessionMessages: async () => [],
|
|
141
|
-
});
|
|
142
|
-
const result = await adapter.runSession({
|
|
143
|
-
cwd: '/tmp/project',
|
|
144
|
-
prompt: 'Run',
|
|
145
|
-
includePartialMessages: true,
|
|
146
|
-
});
|
|
147
|
-
expect(result.events.map((e) => e.type)).toEqual(['init', 'user', 'result']);
|
|
148
|
-
expect(result.events[1]?.text).toContain('tool_result:toolu_1');
|
|
149
|
-
expect(result.events[1]?.text).toContain('file.ts');
|
|
150
|
-
});
|
|
151
|
-
it('drops stream_event payloads when includePartialMessages is false', async () => {
|
|
152
|
-
const adapter = new ClaudeAgentSdkAdapter({
|
|
153
|
-
query: () => createFakeQuery([
|
|
154
|
-
{ type: 'system', subtype: 'init', session_id: 'ses_123' },
|
|
155
|
-
{
|
|
156
|
-
type: 'stream_event',
|
|
157
|
-
session_id: 'ses_123',
|
|
158
|
-
event: {
|
|
159
|
-
type: 'content_block_delta',
|
|
160
|
-
delta: { text: 'Partial output' },
|
|
161
|
-
},
|
|
162
|
-
},
|
|
163
|
-
{
|
|
164
|
-
type: 'result',
|
|
165
|
-
subtype: 'success',
|
|
166
|
-
session_id: 'ses_123',
|
|
167
|
-
is_error: false,
|
|
168
|
-
result: 'Done.',
|
|
169
|
-
num_turns: 1,
|
|
170
|
-
total_cost_usd: 0.01,
|
|
171
|
-
},
|
|
172
|
-
]),
|
|
173
|
-
listSessions: async () => [],
|
|
174
|
-
getSessionMessages: async () => [],
|
|
175
|
-
});
|
|
176
|
-
const result = await adapter.runSession({
|
|
177
|
-
cwd: '/tmp/project',
|
|
178
|
-
prompt: 'Do the thing',
|
|
179
|
-
includePartialMessages: false,
|
|
180
|
-
});
|
|
181
|
-
expect(result.events.map((event) => event.type)).toEqual(['init', 'result']);
|
|
182
|
-
});
|
|
183
|
-
it('collapses partials and drops them before the final result', async () => {
|
|
184
|
-
const adapter = new ClaudeAgentSdkAdapter({
|
|
185
|
-
query: () => createFakeQuery([
|
|
186
|
-
{ type: 'system', subtype: 'init', session_id: 'ses_123' },
|
|
187
|
-
{
|
|
188
|
-
type: 'stream_event',
|
|
189
|
-
session_id: 'ses_123',
|
|
190
|
-
event: {
|
|
191
|
-
type: 'content_block_delta',
|
|
192
|
-
delta: { text: 'Partial ' },
|
|
193
|
-
},
|
|
194
|
-
},
|
|
195
|
-
{
|
|
196
|
-
type: 'stream_event',
|
|
197
|
-
session_id: 'ses_123',
|
|
198
|
-
event: {
|
|
199
|
-
type: 'content_block_delta',
|
|
200
|
-
delta: { text: 'output' },
|
|
201
|
-
},
|
|
202
|
-
},
|
|
203
|
-
{
|
|
204
|
-
type: 'result',
|
|
205
|
-
subtype: 'success',
|
|
206
|
-
session_id: 'ses_123',
|
|
207
|
-
is_error: false,
|
|
208
|
-
result: 'Done.',
|
|
209
|
-
num_turns: 1,
|
|
210
|
-
total_cost_usd: 0.01,
|
|
211
|
-
},
|
|
212
|
-
]),
|
|
213
|
-
listSessions: async () => [],
|
|
214
|
-
getSessionMessages: async () => [],
|
|
215
|
-
});
|
|
216
|
-
const result = await adapter.runSession({
|
|
217
|
-
cwd: '/tmp/project',
|
|
218
|
-
prompt: 'Do the thing',
|
|
219
|
-
includePartialMessages: true,
|
|
220
|
-
});
|
|
221
|
-
expect(result.events.map((event) => event.type)).toEqual(['init', 'result']);
|
|
222
|
-
});
|
|
223
|
-
it('ignores structural stream events without text payloads', async () => {
|
|
224
|
-
const adapter = new ClaudeAgentSdkAdapter({
|
|
225
|
-
query: () => createFakeQuery([
|
|
226
|
-
{ type: 'system', subtype: 'init', session_id: 'ses_123' },
|
|
227
|
-
{
|
|
228
|
-
type: 'stream_event',
|
|
229
|
-
session_id: 'ses_123',
|
|
230
|
-
event: { type: 'message_start' },
|
|
231
|
-
},
|
|
232
|
-
{
|
|
233
|
-
type: 'stream_event',
|
|
234
|
-
session_id: 'ses_123',
|
|
235
|
-
event: {
|
|
236
|
-
type: 'content_block_delta',
|
|
237
|
-
delta: { text: 'Partial output' },
|
|
238
|
-
},
|
|
239
|
-
},
|
|
240
|
-
{
|
|
241
|
-
type: 'result',
|
|
242
|
-
subtype: 'success',
|
|
243
|
-
session_id: 'ses_123',
|
|
244
|
-
is_error: false,
|
|
245
|
-
result: 'Done.',
|
|
246
|
-
num_turns: 1,
|
|
247
|
-
total_cost_usd: 0.01,
|
|
248
|
-
},
|
|
249
|
-
]),
|
|
250
|
-
listSessions: async () => [],
|
|
251
|
-
getSessionMessages: async () => [],
|
|
252
|
-
});
|
|
253
|
-
const result = await adapter.runSession({
|
|
254
|
-
cwd: '/tmp/project',
|
|
255
|
-
prompt: 'Do the thing',
|
|
256
|
-
includePartialMessages: true,
|
|
257
|
-
});
|
|
258
|
-
expect(result.events.map((event) => event.type)).toEqual(['init', 'result']);
|
|
259
|
-
});
|
|
260
|
-
it('defaults permissionMode to acceptEdits when omitted', async () => {
|
|
261
|
-
let capturedPermissionMode;
|
|
262
|
-
const adapter = new ClaudeAgentSdkAdapter({
|
|
263
|
-
query: (params) => {
|
|
264
|
-
capturedPermissionMode = params.options?.permissionMode;
|
|
265
|
-
return createFakeQuery([
|
|
266
|
-
{
|
|
267
|
-
type: 'result',
|
|
268
|
-
subtype: 'success',
|
|
269
|
-
session_id: 'ses_pm',
|
|
270
|
-
is_error: false,
|
|
271
|
-
result: 'ok',
|
|
272
|
-
num_turns: 1,
|
|
273
|
-
total_cost_usd: 0,
|
|
274
|
-
},
|
|
275
|
-
]);
|
|
276
|
-
},
|
|
277
|
-
listSessions: async () => [],
|
|
278
|
-
getSessionMessages: async () => [],
|
|
279
|
-
});
|
|
280
|
-
await adapter.runSession({
|
|
281
|
-
cwd: '/tmp/project',
|
|
282
|
-
prompt: 'Do the thing',
|
|
283
|
-
});
|
|
284
|
-
expect(capturedPermissionMode).toBe('acceptEdits');
|
|
285
|
-
});
|
|
286
|
-
it('merges Skill into allowedTools for SDK Agent Skills', async () => {
|
|
287
|
-
let capturedAllowed;
|
|
288
|
-
const adapter = new ClaudeAgentSdkAdapter({
|
|
289
|
-
query: (params) => {
|
|
290
|
-
capturedAllowed = params.options?.allowedTools;
|
|
291
|
-
return createFakeQuery([
|
|
292
|
-
{
|
|
293
|
-
type: 'result',
|
|
294
|
-
subtype: 'success',
|
|
295
|
-
session_id: 'ses_skill',
|
|
296
|
-
is_error: false,
|
|
297
|
-
result: 'ok',
|
|
298
|
-
num_turns: 1,
|
|
299
|
-
total_cost_usd: 0,
|
|
300
|
-
},
|
|
301
|
-
]);
|
|
302
|
-
},
|
|
303
|
-
listSessions: async () => [],
|
|
304
|
-
getSessionMessages: async () => [],
|
|
305
|
-
});
|
|
306
|
-
await adapter.runSession({
|
|
307
|
-
cwd: '/tmp/project',
|
|
308
|
-
prompt: 'Use a skill',
|
|
309
|
-
});
|
|
310
|
-
expect(capturedAllowed).toEqual(['Skill']);
|
|
311
|
-
});
|
|
312
|
-
it('appends Skill once when allowedTools is provided', async () => {
|
|
313
|
-
let capturedAllowed;
|
|
314
|
-
const adapter = new ClaudeAgentSdkAdapter({
|
|
315
|
-
query: (params) => {
|
|
316
|
-
capturedAllowed = params.options?.allowedTools;
|
|
317
|
-
return createFakeQuery([
|
|
318
|
-
{
|
|
319
|
-
type: 'result',
|
|
320
|
-
subtype: 'success',
|
|
321
|
-
session_id: 'ses_skill',
|
|
322
|
-
is_error: false,
|
|
323
|
-
result: 'ok',
|
|
324
|
-
num_turns: 1,
|
|
325
|
-
total_cost_usd: 0,
|
|
326
|
-
},
|
|
327
|
-
]);
|
|
328
|
-
},
|
|
329
|
-
listSessions: async () => [],
|
|
330
|
-
getSessionMessages: async () => [],
|
|
331
|
-
});
|
|
332
|
-
await adapter.runSession({
|
|
333
|
-
cwd: '/tmp/project',
|
|
334
|
-
prompt: 'x',
|
|
335
|
-
allowedTools: ['Read', 'Grep'],
|
|
336
|
-
});
|
|
337
|
-
expect(capturedAllowed).toEqual(['Read', 'Grep', 'Skill']);
|
|
338
|
-
});
|
|
339
|
-
it('does not add Skill when Skill is disallowed', async () => {
|
|
340
|
-
let capturedAllowed;
|
|
341
|
-
const adapter = new ClaudeAgentSdkAdapter({
|
|
342
|
-
query: (params) => {
|
|
343
|
-
capturedAllowed = params.options?.allowedTools;
|
|
344
|
-
return createFakeQuery([
|
|
345
|
-
{
|
|
346
|
-
type: 'result',
|
|
347
|
-
subtype: 'success',
|
|
348
|
-
session_id: 'ses_skill',
|
|
349
|
-
is_error: false,
|
|
350
|
-
result: 'ok',
|
|
351
|
-
num_turns: 1,
|
|
352
|
-
total_cost_usd: 0,
|
|
353
|
-
},
|
|
354
|
-
]);
|
|
355
|
-
},
|
|
356
|
-
listSessions: async () => [],
|
|
357
|
-
getSessionMessages: async () => [],
|
|
358
|
-
});
|
|
359
|
-
await adapter.runSession({
|
|
360
|
-
cwd: '/tmp/project',
|
|
361
|
-
prompt: 'x',
|
|
362
|
-
disallowedTools: ['Skill'],
|
|
363
|
-
});
|
|
364
|
-
expect(capturedAllowed).toBeUndefined();
|
|
365
|
-
});
|
|
366
|
-
it('extracts full usage from result with camelCase modelUsage', async () => {
|
|
367
|
-
const adapter = new ClaudeAgentSdkAdapter({
|
|
368
|
-
query: () => createFakeQuery([
|
|
369
|
-
{ type: 'system', subtype: 'init', session_id: 'ses_usage' },
|
|
370
|
-
{
|
|
371
|
-
type: 'result',
|
|
372
|
-
subtype: 'success',
|
|
373
|
-
session_id: 'ses_usage',
|
|
374
|
-
is_error: false,
|
|
375
|
-
result: 'Done.',
|
|
376
|
-
num_turns: 5,
|
|
377
|
-
total_cost_usd: 1.23,
|
|
378
|
-
usage: { input_tokens: 50_000, output_tokens: 8_000 },
|
|
379
|
-
modelUsage: {
|
|
380
|
-
'claude-opus-4-6': { contextWindow: 200_000 },
|
|
381
|
-
},
|
|
382
|
-
},
|
|
383
|
-
]),
|
|
384
|
-
listSessions: async () => [],
|
|
385
|
-
getSessionMessages: async () => [],
|
|
386
|
-
});
|
|
387
|
-
const result = await adapter.runSession({
|
|
388
|
-
cwd: '/tmp/project',
|
|
389
|
-
prompt: 'Do work',
|
|
390
|
-
});
|
|
391
|
-
expect(result.turns).toBe(5);
|
|
392
|
-
expect(result.totalCostUsd).toBe(1.23);
|
|
393
|
-
expect(result.inputTokens).toBe(50_000);
|
|
394
|
-
expect(result.outputTokens).toBe(8_000);
|
|
395
|
-
expect(result.contextWindowSize).toBe(200_000);
|
|
396
|
-
});
|
|
397
|
-
it('extracts usage from snake_case model_usage fallback', async () => {
|
|
398
|
-
const adapter = new ClaudeAgentSdkAdapter({
|
|
399
|
-
query: () => createFakeQuery([
|
|
400
|
-
{
|
|
401
|
-
type: 'result',
|
|
402
|
-
subtype: 'success',
|
|
403
|
-
session_id: 'ses_snake',
|
|
404
|
-
is_error: false,
|
|
405
|
-
result: 'Ok.',
|
|
406
|
-
num_turns: 1,
|
|
407
|
-
total_cost_usd: 0.01,
|
|
408
|
-
usage: { input_tokens: 1000, output_tokens: 200 },
|
|
409
|
-
model_usage: {
|
|
410
|
-
'claude-sonnet-4-5': { context_window: 180_000 },
|
|
411
|
-
},
|
|
412
|
-
},
|
|
413
|
-
]),
|
|
414
|
-
listSessions: async () => [],
|
|
415
|
-
getSessionMessages: async () => [],
|
|
416
|
-
});
|
|
417
|
-
const result = await adapter.runSession({
|
|
418
|
-
cwd: '/tmp/project',
|
|
419
|
-
prompt: 'x',
|
|
420
|
-
});
|
|
421
|
-
expect(result.inputTokens).toBe(1000);
|
|
422
|
-
expect(result.outputTokens).toBe(200);
|
|
423
|
-
expect(result.contextWindowSize).toBe(180_000);
|
|
424
|
-
});
|
|
425
|
-
it('denies write tools when restrictWriteTools is true', async () => {
|
|
426
|
-
let capturedCanUseTool;
|
|
427
|
-
const adapter = new ClaudeAgentSdkAdapter({
|
|
428
|
-
query: (params) => {
|
|
429
|
-
capturedCanUseTool = params.options?.canUseTool;
|
|
430
|
-
return createFakeQuery([
|
|
431
|
-
{
|
|
432
|
-
type: 'result',
|
|
433
|
-
subtype: 'success',
|
|
434
|
-
session_id: 'ses_rw',
|
|
435
|
-
is_error: false,
|
|
436
|
-
result: 'ok',
|
|
437
|
-
num_turns: 1,
|
|
438
|
-
total_cost_usd: 0,
|
|
439
|
-
},
|
|
440
|
-
]);
|
|
441
|
-
},
|
|
442
|
-
listSessions: async () => [],
|
|
443
|
-
getSessionMessages: async () => [],
|
|
444
|
-
});
|
|
445
|
-
await adapter.runSession({
|
|
446
|
-
cwd: '/tmp/project',
|
|
447
|
-
prompt: 'Investigate',
|
|
448
|
-
restrictWriteTools: true,
|
|
449
|
-
});
|
|
450
|
-
expect(capturedCanUseTool).toBeDefined();
|
|
451
|
-
const editResult = await capturedCanUseTool('Edit', { file_path: 'x.ts' }, {});
|
|
452
|
-
expect(editResult.behavior).toBe('deny');
|
|
453
|
-
const writeResult = await capturedCanUseTool('Write', { file_path: 'y.ts' }, {});
|
|
454
|
-
expect(writeResult.behavior).toBe('deny');
|
|
455
|
-
const multiEditResult = await capturedCanUseTool('MultiEdit', { file_path: 'z.ts' }, {});
|
|
456
|
-
expect(multiEditResult.behavior).toBe('deny');
|
|
457
|
-
const readResult = await capturedCanUseTool('Read', { file_path: 'a.ts' }, {});
|
|
458
|
-
expect(readResult.behavior).toBe('allow');
|
|
459
|
-
const grepResult = await capturedCanUseTool('Grep', { pattern: 'foo', path: '.' }, {});
|
|
460
|
-
expect(grepResult.behavior).toBe('allow');
|
|
461
|
-
});
|
|
462
|
-
it('denies destructive bash commands when restrictWriteTools is true', async () => {
|
|
463
|
-
let capturedCanUseTool;
|
|
464
|
-
const adapter = new ClaudeAgentSdkAdapter({
|
|
465
|
-
query: (params) => {
|
|
466
|
-
capturedCanUseTool = params.options?.canUseTool;
|
|
467
|
-
return createFakeQuery([
|
|
468
|
-
{
|
|
469
|
-
type: 'result',
|
|
470
|
-
subtype: 'success',
|
|
471
|
-
session_id: 'ses_bash',
|
|
472
|
-
is_error: false,
|
|
473
|
-
result: 'ok',
|
|
474
|
-
num_turns: 1,
|
|
475
|
-
total_cost_usd: 0,
|
|
476
|
-
},
|
|
477
|
-
]);
|
|
478
|
-
},
|
|
479
|
-
listSessions: async () => [],
|
|
480
|
-
getSessionMessages: async () => [],
|
|
481
|
-
});
|
|
482
|
-
await adapter.runSession({
|
|
483
|
-
cwd: '/tmp/project',
|
|
484
|
-
prompt: 'Check',
|
|
485
|
-
restrictWriteTools: true,
|
|
486
|
-
});
|
|
487
|
-
expect(capturedCanUseTool).toBeDefined();
|
|
488
|
-
const sedResult = await capturedCanUseTool('Bash', { command: "sed -i 's/old/new/' file.ts" }, {});
|
|
489
|
-
expect(sedResult.behavior).toBe('deny');
|
|
490
|
-
const echoResult = await capturedCanUseTool('Bash', { command: 'echo "data" > out.txt' }, {});
|
|
491
|
-
expect(echoResult.behavior).toBe('deny');
|
|
492
|
-
const gitCommitResult = await capturedCanUseTool('Bash', { command: 'git commit -m "fix"' }, {});
|
|
493
|
-
expect(gitCommitResult.behavior).toBe('deny');
|
|
494
|
-
const lsResult = await capturedCanUseTool('Bash', { command: 'ls -la src/' }, {});
|
|
495
|
-
expect(lsResult.behavior).toBe('allow');
|
|
496
|
-
const catResult = await capturedCanUseTool('Bash', { command: 'cat src/index.ts' }, {});
|
|
497
|
-
expect(catResult.behavior).toBe('allow');
|
|
498
|
-
const gitLogResult = await capturedCanUseTool('Bash', { command: 'git log --oneline -10' }, {});
|
|
499
|
-
expect(gitLogResult.behavior).toBe('allow');
|
|
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
|
-
});
|
|
540
|
-
it('allows write tools when restrictWriteTools is false', async () => {
|
|
541
|
-
let capturedCanUseTool;
|
|
542
|
-
const adapter = new ClaudeAgentSdkAdapter({
|
|
543
|
-
query: (params) => {
|
|
544
|
-
capturedCanUseTool = params.options?.canUseTool;
|
|
545
|
-
return createFakeQuery([
|
|
546
|
-
{
|
|
547
|
-
type: 'result',
|
|
548
|
-
subtype: 'success',
|
|
549
|
-
session_id: 'ses_free',
|
|
550
|
-
is_error: false,
|
|
551
|
-
result: 'ok',
|
|
552
|
-
num_turns: 1,
|
|
553
|
-
total_cost_usd: 0,
|
|
554
|
-
},
|
|
555
|
-
]);
|
|
556
|
-
},
|
|
557
|
-
listSessions: async () => [],
|
|
558
|
-
getSessionMessages: async () => [],
|
|
559
|
-
});
|
|
560
|
-
await adapter.runSession({
|
|
561
|
-
cwd: '/tmp/project',
|
|
562
|
-
prompt: 'Implement',
|
|
563
|
-
restrictWriteTools: false,
|
|
564
|
-
});
|
|
565
|
-
expect(capturedCanUseTool).toBeUndefined();
|
|
566
|
-
});
|
|
567
|
-
it('passes through explicit permissionMode', async () => {
|
|
568
|
-
let capturedPermissionMode;
|
|
569
|
-
const adapter = new ClaudeAgentSdkAdapter({
|
|
570
|
-
query: (params) => {
|
|
571
|
-
capturedPermissionMode = params.options?.permissionMode;
|
|
572
|
-
return createFakeQuery([
|
|
573
|
-
{
|
|
574
|
-
type: 'result',
|
|
575
|
-
subtype: 'success',
|
|
576
|
-
session_id: 'ses_pm',
|
|
577
|
-
is_error: false,
|
|
578
|
-
result: 'ok',
|
|
579
|
-
num_turns: 1,
|
|
580
|
-
total_cost_usd: 0,
|
|
581
|
-
},
|
|
582
|
-
]);
|
|
583
|
-
},
|
|
584
|
-
listSessions: async () => [],
|
|
585
|
-
getSessionMessages: async () => [],
|
|
586
|
-
});
|
|
587
|
-
await adapter.runSession({
|
|
588
|
-
cwd: '/tmp/project',
|
|
589
|
-
prompt: 'Do the thing',
|
|
590
|
-
permissionMode: 'plan',
|
|
591
|
-
});
|
|
592
|
-
expect(capturedPermissionMode).toBe('plan');
|
|
593
|
-
});
|
|
594
|
-
describe('debug log', () => {
|
|
595
|
-
let tmpDir;
|
|
596
|
-
afterEach(async () => {
|
|
597
|
-
if (tmpDir) {
|
|
598
|
-
await rm(tmpDir, { recursive: true, force: true });
|
|
599
|
-
}
|
|
600
|
-
});
|
|
601
|
-
it('appends a tool_denied entry when restrictWriteTools denies a write tool', async () => {
|
|
602
|
-
tmpDir = await mkdtemp(join(tmpdir(), 'adapter-log-'));
|
|
603
|
-
const logPath = join(tmpDir, '.claude-manager', 'debug.log');
|
|
604
|
-
let capturedCanUseTool;
|
|
605
|
-
const adapter = new ClaudeAgentSdkAdapter({
|
|
606
|
-
query: (params) => {
|
|
607
|
-
capturedCanUseTool = params.options?.canUseTool;
|
|
608
|
-
return createFakeQuery([
|
|
609
|
-
{
|
|
610
|
-
type: 'result',
|
|
611
|
-
subtype: 'success',
|
|
612
|
-
session_id: 'ses_log',
|
|
613
|
-
is_error: false,
|
|
614
|
-
result: 'ok',
|
|
615
|
-
num_turns: 1,
|
|
616
|
-
total_cost_usd: 0,
|
|
617
|
-
},
|
|
618
|
-
]);
|
|
619
|
-
},
|
|
620
|
-
listSessions: async () => [],
|
|
621
|
-
getSessionMessages: async () => [],
|
|
622
|
-
}, undefined, logPath);
|
|
623
|
-
await adapter.runSession({ cwd: '/tmp/project', prompt: 'Test', restrictWriteTools: true });
|
|
624
|
-
expect(capturedCanUseTool).toBeDefined();
|
|
625
|
-
const result = await capturedCanUseTool('Edit', { file_path: 'x.ts' }, {});
|
|
626
|
-
expect(result.behavior).toBe('deny');
|
|
627
|
-
const content = await readFile(logPath, 'utf8');
|
|
628
|
-
const entry = JSON.parse(content.trim().split('\n')[0]);
|
|
629
|
-
expect(entry.type).toBe('tool_denied');
|
|
630
|
-
expect(entry.toolName).toBe('Edit');
|
|
631
|
-
expect(entry.reason).toBe('restrictWriteTools');
|
|
632
|
-
expect(typeof entry.ts).toBe('string');
|
|
633
|
-
});
|
|
634
|
-
it('appends a tool_denied entry when the approval manager denies a tool', async () => {
|
|
635
|
-
tmpDir = await mkdtemp(join(tmpdir(), 'adapter-log-policy-'));
|
|
636
|
-
const logPath = join(tmpDir, '.claude-manager', 'debug.log');
|
|
637
|
-
const approvalManager = new ToolApprovalManager({
|
|
638
|
-
rules: [
|
|
639
|
-
{
|
|
640
|
-
id: 'deny-bash',
|
|
641
|
-
toolPattern: 'Bash',
|
|
642
|
-
inputPattern: 'rm -rf /',
|
|
643
|
-
action: 'deny',
|
|
644
|
-
denyMessage: 'rm -rf / is not allowed',
|
|
645
|
-
},
|
|
646
|
-
],
|
|
647
|
-
enabled: true,
|
|
648
|
-
});
|
|
649
|
-
let capturedCanUseTool;
|
|
650
|
-
const adapter = new ClaudeAgentSdkAdapter({
|
|
651
|
-
query: (params) => {
|
|
652
|
-
capturedCanUseTool = params.options?.canUseTool;
|
|
653
|
-
return createFakeQuery([
|
|
654
|
-
{
|
|
655
|
-
type: 'result',
|
|
656
|
-
subtype: 'success',
|
|
657
|
-
session_id: 'ses_policy',
|
|
658
|
-
is_error: false,
|
|
659
|
-
result: 'ok',
|
|
660
|
-
num_turns: 1,
|
|
661
|
-
total_cost_usd: 0,
|
|
662
|
-
},
|
|
663
|
-
]);
|
|
664
|
-
},
|
|
665
|
-
listSessions: async () => [],
|
|
666
|
-
getSessionMessages: async () => [],
|
|
667
|
-
}, approvalManager, logPath);
|
|
668
|
-
await adapter.runSession({ cwd: '/tmp/project', prompt: 'Test' });
|
|
669
|
-
expect(capturedCanUseTool).toBeDefined();
|
|
670
|
-
const result = await capturedCanUseTool('Bash', { command: 'rm -rf /' }, {});
|
|
671
|
-
expect(result.behavior).toBe('deny');
|
|
672
|
-
const content = await readFile(logPath, 'utf8');
|
|
673
|
-
const entry = JSON.parse(content.trim().split('\n')[0]);
|
|
674
|
-
expect(entry.type).toBe('tool_denied');
|
|
675
|
-
expect(entry.toolName).toBe('Bash');
|
|
676
|
-
expect(entry.reason).toBe('approvalPolicy');
|
|
677
|
-
expect(typeof entry.ts).toBe('string');
|
|
678
|
-
});
|
|
679
|
-
it('does not create a log file when no debugLogPath is provided', async () => {
|
|
680
|
-
tmpDir = await mkdtemp(join(tmpdir(), 'adapter-no-log-'));
|
|
681
|
-
let capturedCanUseTool;
|
|
682
|
-
const adapter = new ClaudeAgentSdkAdapter({
|
|
683
|
-
query: (params) => {
|
|
684
|
-
capturedCanUseTool = params.options?.canUseTool;
|
|
685
|
-
return createFakeQuery([
|
|
686
|
-
{
|
|
687
|
-
type: 'result',
|
|
688
|
-
subtype: 'success',
|
|
689
|
-
session_id: 'ses_nolog',
|
|
690
|
-
is_error: false,
|
|
691
|
-
result: 'ok',
|
|
692
|
-
num_turns: 1,
|
|
693
|
-
total_cost_usd: 0,
|
|
694
|
-
},
|
|
695
|
-
]);
|
|
696
|
-
},
|
|
697
|
-
listSessions: async () => [],
|
|
698
|
-
getSessionMessages: async () => [],
|
|
699
|
-
});
|
|
700
|
-
await adapter.runSession({ cwd: '/tmp/project', prompt: 'Test', restrictWriteTools: true });
|
|
701
|
-
const result = await capturedCanUseTool('Edit', { file_path: 'x.ts' }, {});
|
|
702
|
-
expect(result.behavior).toBe('deny');
|
|
703
|
-
// No log file should exist
|
|
704
|
-
await expect(readFile(join(tmpDir, 'debug.log'), 'utf8')).rejects.toThrow();
|
|
705
|
-
});
|
|
706
|
-
});
|
|
707
|
-
});
|