@doingdev/opencode-claude-manager-plugin 0.1.35 → 0.1.43
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/claude/claude-agent-sdk-adapter.js +1 -0
- package/dist/manager/git-operations.d.ts +10 -1
- package/dist/manager/git-operations.js +18 -3
- package/dist/manager/persistent-manager.d.ts +19 -3
- package/dist/manager/persistent-manager.js +21 -9
- package/dist/manager/session-controller.d.ts +8 -5
- package/dist/manager/session-controller.js +25 -20
- package/dist/metadata/claude-metadata.service.d.ts +12 -0
- package/dist/metadata/claude-metadata.service.js +38 -0
- package/dist/metadata/repo-claude-config-reader.d.ts +7 -0
- package/dist/metadata/repo-claude-config-reader.js +154 -0
- package/dist/plugin/agent-hierarchy.d.ts +9 -9
- package/dist/plugin/agent-hierarchy.js +25 -25
- package/dist/plugin/claude-manager.plugin.js +83 -46
- package/dist/plugin/orchestrator.plugin.d.ts +2 -0
- package/dist/plugin/orchestrator.plugin.js +116 -0
- package/dist/plugin/service-factory.js +3 -8
- package/dist/prompts/registry.js +100 -103
- package/dist/providers/claude-code-wrapper.d.ts +13 -0
- package/dist/providers/claude-code-wrapper.js +13 -0
- package/dist/safety/bash-safety.d.ts +21 -0
- package/dist/safety/bash-safety.js +62 -0
- package/dist/src/claude/claude-agent-sdk-adapter.d.ts +27 -0
- package/dist/src/claude/claude-agent-sdk-adapter.js +517 -0
- package/dist/src/claude/claude-session.service.d.ts +10 -0
- package/dist/src/claude/claude-session.service.js +18 -0
- package/dist/src/claude/session-live-tailer.d.ts +51 -0
- package/dist/src/claude/session-live-tailer.js +269 -0
- package/dist/src/claude/tool-approval-manager.d.ts +27 -0
- package/dist/src/claude/tool-approval-manager.js +232 -0
- package/dist/src/index.d.ts +6 -0
- package/dist/src/index.js +4 -0
- package/dist/src/manager/context-tracker.d.ts +33 -0
- package/dist/src/manager/context-tracker.js +106 -0
- package/dist/src/manager/git-operations.d.ts +12 -0
- package/dist/src/manager/git-operations.js +76 -0
- package/dist/src/manager/persistent-manager.d.ts +77 -0
- package/dist/src/manager/persistent-manager.js +170 -0
- package/dist/src/manager/session-controller.d.ts +44 -0
- package/dist/src/manager/session-controller.js +147 -0
- package/dist/src/plugin/agent-hierarchy.d.ts +60 -0
- package/dist/src/plugin/agent-hierarchy.js +157 -0
- package/dist/src/plugin/claude-manager.plugin.d.ts +2 -0
- package/dist/src/plugin/claude-manager.plugin.js +563 -0
- package/dist/src/plugin/service-factory.d.ts +12 -0
- package/dist/src/plugin/service-factory.js +38 -0
- package/dist/src/prompts/registry.d.ts +11 -0
- package/dist/src/prompts/registry.js +260 -0
- package/dist/src/state/file-run-state-store.d.ts +14 -0
- package/dist/src/state/file-run-state-store.js +85 -0
- package/dist/src/state/transcript-store.d.ts +15 -0
- package/dist/src/state/transcript-store.js +44 -0
- package/dist/src/types/contracts.d.ts +200 -0
- package/dist/src/types/contracts.js +1 -0
- package/dist/src/util/fs-helpers.d.ts +2 -0
- package/dist/src/util/fs-helpers.js +10 -0
- package/dist/src/util/project-context.d.ts +10 -0
- package/dist/src/util/project-context.js +105 -0
- package/dist/src/util/transcript-append.d.ts +7 -0
- package/dist/src/util/transcript-append.js +29 -0
- package/dist/test/claude-agent-sdk-adapter.test.d.ts +1 -0
- package/dist/test/claude-agent-sdk-adapter.test.js +459 -0
- package/dist/test/claude-manager.plugin.test.d.ts +1 -0
- package/dist/test/claude-manager.plugin.test.js +331 -0
- package/dist/test/context-tracker.test.d.ts +1 -0
- package/dist/test/context-tracker.test.js +138 -0
- package/dist/test/file-run-state-store.test.d.ts +1 -0
- package/dist/test/file-run-state-store.test.js +82 -0
- package/dist/test/git-operations.test.d.ts +1 -0
- package/dist/test/git-operations.test.js +90 -0
- package/dist/test/persistent-manager.test.d.ts +1 -0
- package/dist/test/persistent-manager.test.js +208 -0
- package/dist/test/project-context.test.d.ts +1 -0
- package/dist/test/project-context.test.js +92 -0
- package/dist/test/prompt-registry.test.d.ts +1 -0
- package/dist/test/prompt-registry.test.js +256 -0
- package/dist/test/session-controller.test.d.ts +1 -0
- package/dist/test/session-controller.test.js +149 -0
- package/dist/test/session-live-tailer.test.d.ts +1 -0
- package/dist/test/session-live-tailer.test.js +313 -0
- package/dist/test/tool-approval-manager.test.d.ts +1 -0
- package/dist/test/tool-approval-manager.test.js +264 -0
- package/dist/test/transcript-append.test.d.ts +1 -0
- package/dist/test/transcript-append.test.js +37 -0
- package/dist/test/transcript-store.test.d.ts +1 -0
- package/dist/test/transcript-store.test.js +50 -0
- package/dist/types/contracts.d.ts +3 -4
- package/dist/vitest.config.d.ts +2 -0
- package/dist/vitest.config.js +11 -0
- package/package.json +2 -2
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { ToolApprovalManager } from '../src/claude/tool-approval-manager.js';
|
|
3
|
+
describe('ToolApprovalManager', () => {
|
|
4
|
+
describe('evaluate()', () => {
|
|
5
|
+
it('allows Skill tool by default policy', () => {
|
|
6
|
+
const manager = new ToolApprovalManager();
|
|
7
|
+
const result = manager.evaluate('Skill', { command: 'my-skill' });
|
|
8
|
+
expect(result).toEqual({ behavior: 'allow' });
|
|
9
|
+
});
|
|
10
|
+
it('allows read-only tools by default policy', () => {
|
|
11
|
+
const manager = new ToolApprovalManager();
|
|
12
|
+
expect(manager.evaluate('Read', { file_path: '/tmp/foo.ts' })).toEqual({
|
|
13
|
+
behavior: 'allow',
|
|
14
|
+
});
|
|
15
|
+
expect(manager.evaluate('Grep', { pattern: 'foo' })).toEqual({
|
|
16
|
+
behavior: 'allow',
|
|
17
|
+
});
|
|
18
|
+
expect(manager.evaluate('Glob', { pattern: '*.ts' })).toEqual({
|
|
19
|
+
behavior: 'allow',
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
it('allows edit tools by default policy', () => {
|
|
23
|
+
const manager = new ToolApprovalManager();
|
|
24
|
+
expect(manager.evaluate('Edit', {
|
|
25
|
+
file_path: '/tmp/foo.ts',
|
|
26
|
+
old_string: 'a',
|
|
27
|
+
new_string: 'b',
|
|
28
|
+
})).toEqual({ behavior: 'allow' });
|
|
29
|
+
expect(manager.evaluate('Write', {
|
|
30
|
+
file_path: '/tmp/foo.ts',
|
|
31
|
+
content: 'hello',
|
|
32
|
+
})).toEqual({ behavior: 'allow' });
|
|
33
|
+
});
|
|
34
|
+
it('denies bash rm -rf on root', () => {
|
|
35
|
+
const manager = new ToolApprovalManager();
|
|
36
|
+
const result = manager.evaluate('Bash', { command: 'rm -rf /' });
|
|
37
|
+
expect(result.behavior).toBe('deny');
|
|
38
|
+
if (result.behavior === 'deny') {
|
|
39
|
+
expect(result.message).toContain('rm -rf');
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
it('denies git push --force', () => {
|
|
43
|
+
const manager = new ToolApprovalManager();
|
|
44
|
+
const result = manager.evaluate('Bash', {
|
|
45
|
+
command: 'git push --force origin main',
|
|
46
|
+
});
|
|
47
|
+
expect(result.behavior).toBe('deny');
|
|
48
|
+
if (result.behavior === 'deny') {
|
|
49
|
+
expect(result.message).toContain('Force push');
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
it('denies git reset --hard', () => {
|
|
53
|
+
const manager = new ToolApprovalManager();
|
|
54
|
+
const result = manager.evaluate('Bash', {
|
|
55
|
+
command: 'git reset --hard HEAD',
|
|
56
|
+
});
|
|
57
|
+
expect(result.behavior).toBe('deny');
|
|
58
|
+
if (result.behavior === 'deny') {
|
|
59
|
+
expect(result.message).toContain('git reset --hard');
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
it('allows normal bash commands', () => {
|
|
63
|
+
const manager = new ToolApprovalManager();
|
|
64
|
+
expect(manager.evaluate('Bash', { command: 'ls -la' })).toEqual({
|
|
65
|
+
behavior: 'allow',
|
|
66
|
+
});
|
|
67
|
+
expect(manager.evaluate('Bash', { command: 'pnpm test' })).toEqual({
|
|
68
|
+
behavior: 'allow',
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
it('returns allow when disabled', () => {
|
|
72
|
+
const manager = new ToolApprovalManager({ enabled: false });
|
|
73
|
+
const result = manager.evaluate('Bash', { command: 'rm -rf /' });
|
|
74
|
+
expect(result).toEqual({ behavior: 'allow' });
|
|
75
|
+
});
|
|
76
|
+
it('uses default action when no rule matches', () => {
|
|
77
|
+
const manager = new ToolApprovalManager({
|
|
78
|
+
rules: [],
|
|
79
|
+
defaultAction: 'deny',
|
|
80
|
+
});
|
|
81
|
+
const result = manager.evaluate('SomeUnknownTool', {});
|
|
82
|
+
expect(result.behavior).toBe('deny');
|
|
83
|
+
});
|
|
84
|
+
it('first matching rule wins', () => {
|
|
85
|
+
const manager = new ToolApprovalManager({
|
|
86
|
+
rules: [
|
|
87
|
+
{
|
|
88
|
+
id: 'deny-all-bash',
|
|
89
|
+
toolPattern: 'Bash',
|
|
90
|
+
action: 'deny',
|
|
91
|
+
denyMessage: 'No bash',
|
|
92
|
+
},
|
|
93
|
+
{ id: 'allow-bash', toolPattern: 'Bash', action: 'allow' },
|
|
94
|
+
],
|
|
95
|
+
defaultAction: 'allow',
|
|
96
|
+
enabled: true,
|
|
97
|
+
});
|
|
98
|
+
const result = manager.evaluate('Bash', { command: 'echo hi' });
|
|
99
|
+
expect(result.behavior).toBe('deny');
|
|
100
|
+
});
|
|
101
|
+
it('matches glob patterns in tool names', () => {
|
|
102
|
+
const manager = new ToolApprovalManager({
|
|
103
|
+
rules: [
|
|
104
|
+
{
|
|
105
|
+
id: 'deny-notebook',
|
|
106
|
+
toolPattern: 'Notebook*',
|
|
107
|
+
action: 'deny',
|
|
108
|
+
denyMessage: 'No notebooks',
|
|
109
|
+
},
|
|
110
|
+
],
|
|
111
|
+
defaultAction: 'allow',
|
|
112
|
+
enabled: true,
|
|
113
|
+
});
|
|
114
|
+
expect(manager.evaluate('NotebookEdit', {}).behavior).toBe('deny');
|
|
115
|
+
expect(manager.evaluate('NotebookRead', {}).behavior).toBe('deny');
|
|
116
|
+
expect(manager.evaluate('Read', {}).behavior).toBe('allow');
|
|
117
|
+
});
|
|
118
|
+
it('matches inputPattern as substring of serialized input', () => {
|
|
119
|
+
const manager = new ToolApprovalManager({
|
|
120
|
+
rules: [
|
|
121
|
+
{
|
|
122
|
+
id: 'deny-secret-files',
|
|
123
|
+
toolPattern: '*',
|
|
124
|
+
inputPattern: '.env',
|
|
125
|
+
action: 'deny',
|
|
126
|
+
denyMessage: 'No .env access',
|
|
127
|
+
},
|
|
128
|
+
],
|
|
129
|
+
defaultAction: 'allow',
|
|
130
|
+
enabled: true,
|
|
131
|
+
});
|
|
132
|
+
expect(manager.evaluate('Read', { file_path: '/project/.env' }).behavior).toBe('deny');
|
|
133
|
+
expect(manager.evaluate('Read', { file_path: '/project/src/index.ts' }).behavior).toBe('allow');
|
|
134
|
+
});
|
|
135
|
+
it('records decisions in the log', () => {
|
|
136
|
+
const manager = new ToolApprovalManager();
|
|
137
|
+
manager.evaluate('Read', { file_path: '/tmp/foo.ts' });
|
|
138
|
+
const decisions = manager.getDecisions();
|
|
139
|
+
expect(decisions).toHaveLength(1);
|
|
140
|
+
expect(decisions[0].toolName).toBe('Read');
|
|
141
|
+
expect(decisions[0].action).toBe('allow');
|
|
142
|
+
expect(decisions[0].matchedRuleId).toBe('allow-read');
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
describe('policy management', () => {
|
|
146
|
+
it('addRule inserts at specified position', () => {
|
|
147
|
+
const manager = new ToolApprovalManager({
|
|
148
|
+
rules: [],
|
|
149
|
+
enabled: true,
|
|
150
|
+
defaultAction: 'allow',
|
|
151
|
+
});
|
|
152
|
+
manager.addRule({ id: 'a', toolPattern: 'A', action: 'allow' });
|
|
153
|
+
manager.addRule({ id: 'b', toolPattern: 'B', action: 'allow' });
|
|
154
|
+
manager.addRule({ id: 'c', toolPattern: 'C', action: 'deny' }, 1);
|
|
155
|
+
const rules = manager.getPolicy().rules;
|
|
156
|
+
expect(rules.map((r) => r.id)).toEqual(['a', 'c', 'b']);
|
|
157
|
+
});
|
|
158
|
+
it('addRule appends to end by default', () => {
|
|
159
|
+
const manager = new ToolApprovalManager({
|
|
160
|
+
rules: [],
|
|
161
|
+
enabled: true,
|
|
162
|
+
defaultAction: 'allow',
|
|
163
|
+
});
|
|
164
|
+
manager.addRule({ id: 'a', toolPattern: 'A', action: 'allow' });
|
|
165
|
+
manager.addRule({ id: 'b', toolPattern: 'B', action: 'allow' });
|
|
166
|
+
expect(manager.getPolicy().rules.map((r) => r.id)).toEqual(['a', 'b']);
|
|
167
|
+
});
|
|
168
|
+
it('removeRule removes by ID and returns true', () => {
|
|
169
|
+
const manager = new ToolApprovalManager();
|
|
170
|
+
const removed = manager.removeRule('allow-read');
|
|
171
|
+
expect(removed).toBe(true);
|
|
172
|
+
expect(manager.getPolicy().rules.find((r) => r.id === 'allow-read')).toBeUndefined();
|
|
173
|
+
});
|
|
174
|
+
it('removeRule returns false for non-existent ID', () => {
|
|
175
|
+
const manager = new ToolApprovalManager();
|
|
176
|
+
expect(manager.removeRule('nonexistent')).toBe(false);
|
|
177
|
+
});
|
|
178
|
+
it('setPolicy replaces entire policy', () => {
|
|
179
|
+
const manager = new ToolApprovalManager();
|
|
180
|
+
manager.setPolicy({
|
|
181
|
+
rules: [{ id: 'only', toolPattern: '*', action: 'deny' }],
|
|
182
|
+
defaultAction: 'deny',
|
|
183
|
+
enabled: true,
|
|
184
|
+
});
|
|
185
|
+
expect(manager.getPolicy().rules).toHaveLength(1);
|
|
186
|
+
expect(manager.getPolicy().defaultAction).toBe('deny');
|
|
187
|
+
});
|
|
188
|
+
it('setDefaultAction changes the fallback', () => {
|
|
189
|
+
const manager = new ToolApprovalManager({
|
|
190
|
+
rules: [],
|
|
191
|
+
enabled: true,
|
|
192
|
+
defaultAction: 'allow',
|
|
193
|
+
});
|
|
194
|
+
manager.setDefaultAction('deny');
|
|
195
|
+
expect(manager.evaluate('Unknown', {}).behavior).toBe('deny');
|
|
196
|
+
});
|
|
197
|
+
it('setEnabled toggles the manager', () => {
|
|
198
|
+
const manager = new ToolApprovalManager({
|
|
199
|
+
rules: [
|
|
200
|
+
{
|
|
201
|
+
id: 'deny-all',
|
|
202
|
+
toolPattern: '*',
|
|
203
|
+
action: 'deny',
|
|
204
|
+
denyMessage: 'blocked',
|
|
205
|
+
},
|
|
206
|
+
],
|
|
207
|
+
enabled: true,
|
|
208
|
+
defaultAction: 'deny',
|
|
209
|
+
});
|
|
210
|
+
expect(manager.evaluate('Read', {}).behavior).toBe('deny');
|
|
211
|
+
manager.setEnabled(false);
|
|
212
|
+
expect(manager.evaluate('Read', {}).behavior).toBe('allow');
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
describe('decision log', () => {
|
|
216
|
+
it('getDecisions returns most recent first', () => {
|
|
217
|
+
const manager = new ToolApprovalManager();
|
|
218
|
+
manager.evaluate('Read', {});
|
|
219
|
+
manager.evaluate('Grep', {});
|
|
220
|
+
manager.evaluate('Glob', {});
|
|
221
|
+
const decisions = manager.getDecisions();
|
|
222
|
+
expect(decisions[0].toolName).toBe('Glob');
|
|
223
|
+
expect(decisions[2].toolName).toBe('Read');
|
|
224
|
+
});
|
|
225
|
+
it('getDeniedDecisions filters to denials only', () => {
|
|
226
|
+
const manager = new ToolApprovalManager();
|
|
227
|
+
manager.evaluate('Read', {});
|
|
228
|
+
manager.evaluate('Bash', { command: 'rm -rf /' });
|
|
229
|
+
manager.evaluate('Grep', {});
|
|
230
|
+
const denied = manager.getDeniedDecisions();
|
|
231
|
+
expect(denied).toHaveLength(1);
|
|
232
|
+
expect(denied[0].toolName).toBe('Bash');
|
|
233
|
+
});
|
|
234
|
+
it('clearDecisions empties the log', () => {
|
|
235
|
+
const manager = new ToolApprovalManager();
|
|
236
|
+
manager.evaluate('Read', {});
|
|
237
|
+
manager.evaluate('Grep', {});
|
|
238
|
+
manager.clearDecisions();
|
|
239
|
+
expect(manager.getDecisions()).toHaveLength(0);
|
|
240
|
+
});
|
|
241
|
+
it('respects maxDecisions ring buffer limit', () => {
|
|
242
|
+
const manager = new ToolApprovalManager({}, 3);
|
|
243
|
+
manager.evaluate('Read', {});
|
|
244
|
+
manager.evaluate('Grep', {});
|
|
245
|
+
manager.evaluate('Glob', {});
|
|
246
|
+
manager.evaluate('Edit', {});
|
|
247
|
+
const decisions = manager.getDecisions();
|
|
248
|
+
expect(decisions).toHaveLength(3);
|
|
249
|
+
// Oldest (Read) should be evicted
|
|
250
|
+
expect(decisions.map((d) => d.toolName)).toEqual(['Edit', 'Glob', 'Grep']);
|
|
251
|
+
});
|
|
252
|
+
it('includes agentId when provided', () => {
|
|
253
|
+
const manager = new ToolApprovalManager();
|
|
254
|
+
manager.evaluate('Read', {}, { agentID: 'agent_123' });
|
|
255
|
+
expect(manager.getDecisions()[0].agentId).toBe('agent_123');
|
|
256
|
+
});
|
|
257
|
+
it('truncates inputPreview for large inputs', () => {
|
|
258
|
+
const manager = new ToolApprovalManager();
|
|
259
|
+
manager.evaluate('Read', { data: 'x'.repeat(1000) });
|
|
260
|
+
const decision = manager.getDecisions()[0];
|
|
261
|
+
expect(decision.inputPreview.length).toBeLessThanOrEqual(300);
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { appendTranscriptEvents, stripTrailingPartials } from '../src/util/transcript-append.js';
|
|
3
|
+
function ev(type, text) {
|
|
4
|
+
return { type, text };
|
|
5
|
+
}
|
|
6
|
+
describe('transcript-append', () => {
|
|
7
|
+
it('replaces the last partial when another partial arrives', () => {
|
|
8
|
+
const next = appendTranscriptEvents([], [ev('partial', 'a')]);
|
|
9
|
+
expect(next).toEqual([ev('partial', 'a')]);
|
|
10
|
+
const next2 = appendTranscriptEvents(next, [ev('partial', 'ab')]);
|
|
11
|
+
expect(next2).toEqual([ev('partial', 'ab')]);
|
|
12
|
+
});
|
|
13
|
+
it('strips trailing partials before a non-partial event', () => {
|
|
14
|
+
const base = appendTranscriptEvents([], [ev('partial', 'streaming')]);
|
|
15
|
+
const next = appendTranscriptEvents(base, [ev('assistant', 'Final answer.')]);
|
|
16
|
+
expect(next.map((e) => e.type)).toEqual(['assistant']);
|
|
17
|
+
expect(next[0]?.text).toBe('Final answer.');
|
|
18
|
+
});
|
|
19
|
+
it('stripTrailingPartials removes only trailing partials', () => {
|
|
20
|
+
const events = [
|
|
21
|
+
ev('init', 'init'),
|
|
22
|
+
ev('partial', 'x'),
|
|
23
|
+
ev('assistant', 'done'),
|
|
24
|
+
ev('partial', 'orphan'),
|
|
25
|
+
];
|
|
26
|
+
expect(stripTrailingPartials(events).map((e) => e.type)).toEqual([
|
|
27
|
+
'init',
|
|
28
|
+
'partial',
|
|
29
|
+
'assistant',
|
|
30
|
+
]);
|
|
31
|
+
});
|
|
32
|
+
it('appends multiple incoming events in order with partial rules', () => {
|
|
33
|
+
const batch = appendTranscriptEvents([ev('init', 'init')], [ev('partial', 'p1'), ev('partial', 'p2'), ev('user', 'tool output')]);
|
|
34
|
+
expect(batch.map((e) => e.type)).toEqual(['init', 'user']);
|
|
35
|
+
expect(batch[1]?.text).toBe('tool output');
|
|
36
|
+
});
|
|
37
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { describe, expect, it, beforeEach, afterEach } from 'vitest';
|
|
4
|
+
import { TranscriptStore } from '../src/state/transcript-store.js';
|
|
5
|
+
function ev(type, text) {
|
|
6
|
+
return { type, text };
|
|
7
|
+
}
|
|
8
|
+
describe('TranscriptStore', () => {
|
|
9
|
+
const testDir = path.join(process.cwd(), '.test-transcript-store-' + process.pid);
|
|
10
|
+
let store;
|
|
11
|
+
beforeEach(async () => {
|
|
12
|
+
await fs.mkdir(testDir, { recursive: true });
|
|
13
|
+
store = new TranscriptStore('.claude-manager');
|
|
14
|
+
});
|
|
15
|
+
afterEach(async () => {
|
|
16
|
+
await fs.rm(testDir, { recursive: true, force: true });
|
|
17
|
+
});
|
|
18
|
+
it('returns empty array for nonexistent session', async () => {
|
|
19
|
+
const events = await store.readEvents(testDir, 'nonexistent');
|
|
20
|
+
expect(events).toEqual([]);
|
|
21
|
+
});
|
|
22
|
+
it('appends and reads events', async () => {
|
|
23
|
+
await store.appendEvents(testDir, 'ses_1', [ev('init', 'init'), ev('assistant', 'Hello.')]);
|
|
24
|
+
const events = await store.readEvents(testDir, 'ses_1');
|
|
25
|
+
expect(events).toEqual([ev('init', 'init'), ev('assistant', 'Hello.')]);
|
|
26
|
+
});
|
|
27
|
+
it('accumulates events across multiple appends', async () => {
|
|
28
|
+
await store.appendEvents(testDir, 'ses_2', [
|
|
29
|
+
ev('init', 'init'),
|
|
30
|
+
ev('assistant', 'First response.'),
|
|
31
|
+
]);
|
|
32
|
+
await store.appendEvents(testDir, 'ses_2', [
|
|
33
|
+
ev('user', 'Follow-up question'),
|
|
34
|
+
ev('assistant', 'Second response.'),
|
|
35
|
+
]);
|
|
36
|
+
const events = await store.readEvents(testDir, 'ses_2');
|
|
37
|
+
expect(events.map((e) => e.type)).toEqual(['init', 'assistant', 'user', 'assistant']);
|
|
38
|
+
expect(events[3].text).toBe('Second response.');
|
|
39
|
+
});
|
|
40
|
+
it('strips trailing partials before persisting', async () => {
|
|
41
|
+
await store.appendEvents(testDir, 'ses_3', [ev('init', 'init'), ev('partial', 'streaming...')]);
|
|
42
|
+
const events = await store.readEvents(testDir, 'ses_3');
|
|
43
|
+
expect(events).toEqual([ev('init', 'init')]);
|
|
44
|
+
});
|
|
45
|
+
it('skips write when newEvents is empty', async () => {
|
|
46
|
+
await store.appendEvents(testDir, 'ses_4', []);
|
|
47
|
+
const events = await store.readEvents(testDir, 'ses_4');
|
|
48
|
+
expect(events).toEqual([]);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export interface ManagerPromptRegistry {
|
|
2
2
|
ctoSystemPrompt: string;
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
engineerExplorePrompt: string;
|
|
4
|
+
engineerImplementPrompt: string;
|
|
5
5
|
engineerSessionPrompt: string;
|
|
6
6
|
modePrefixes: {
|
|
7
7
|
plan: string;
|
|
@@ -113,7 +113,6 @@ export interface GitOperationResult {
|
|
|
113
113
|
}
|
|
114
114
|
export interface ActiveSessionState {
|
|
115
115
|
sessionId: string;
|
|
116
|
-
cwd: string;
|
|
117
116
|
startedAt: string;
|
|
118
117
|
totalTurns: number;
|
|
119
118
|
totalCostUsd: number;
|
|
@@ -133,7 +132,7 @@ export interface PersistentRunMessageRecord {
|
|
|
133
132
|
}
|
|
134
133
|
export interface PersistentRunActionRecord {
|
|
135
134
|
timestamp: string;
|
|
136
|
-
type: 'git_diff' | 'git_commit' | 'git_reset' | 'compact' | 'clear';
|
|
135
|
+
type: 'git_diff' | 'git_commit' | 'git_reset' | 'git_status' | 'git_log' | 'compact' | 'clear';
|
|
137
136
|
result: string;
|
|
138
137
|
}
|
|
139
138
|
export interface PersistentRunRecord {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@doingdev/opencode-claude-manager-plugin",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.43",
|
|
4
4
|
"description": "OpenCode plugin that orchestrates Claude Code sessions.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"opencode",
|
|
@@ -52,6 +52,6 @@
|
|
|
52
52
|
"format": "prettier --write .",
|
|
53
53
|
"test": "vitest run",
|
|
54
54
|
"knip": "knip",
|
|
55
|
-
"release": "pnpm run build && pnpm version patch &&
|
|
55
|
+
"release": "pnpm run build && pnpm version patch && git push --follow-tags"
|
|
56
56
|
}
|
|
57
57
|
}
|