@doingdev/opencode-claude-manager-plugin 0.1.42 → 0.1.44
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/persistent-manager.d.ts +25 -8
- package/dist/manager/persistent-manager.js +100 -13
- package/dist/manager/session-controller.d.ts +7 -10
- package/dist/manager/session-controller.js +12 -62
- package/dist/plugin/agent-hierarchy.d.ts +17 -9
- package/dist/plugin/agent-hierarchy.js +55 -25
- package/dist/plugin/claude-manager.plugin.js +87 -43
- package/dist/plugin/service-factory.js +2 -9
- package/dist/prompts/registry.js +123 -104
- package/dist/types/contracts.d.ts +27 -12
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Plugin } from '@opencode-ai/plugin';
|
|
2
2
|
import { ClaudeManagerPlugin } from './plugin/claude-manager.plugin.js';
|
|
3
|
-
export type { ClaudeCapabilitySnapshot, ClaudeSessionRunResult, ClaudeSessionSummary, ClaudeSessionTranscriptMessage, ManagerPromptRegistry, RunClaudeSessionInput, SessionContextSnapshot, GitDiffResult, GitOperationResult, PersistentRunRecord, PersistentRunResult,
|
|
3
|
+
export type { ClaudeCapabilitySnapshot, ClaudeSessionRunResult, ClaudeSessionSummary, ClaudeSessionTranscriptMessage, ManagerPromptRegistry, RunClaudeSessionInput, SessionContextSnapshot, GitDiffResult, GitOperationResult, PersistentRunRecord, PersistentRunResult, AgentSlotType, RunningTask, SlotStatus, ManagerStatus, ContextWarningLevel, SessionMode, LiveTailEvent, ToolOutputPreview, ToolApprovalRule, ToolApprovalPolicy, ToolApprovalDecision, } from './types/contracts.js';
|
|
4
4
|
export { SessionLiveTailer } from './claude/session-live-tailer.js';
|
|
5
5
|
export { ClaudeManagerPlugin };
|
|
6
6
|
export declare const plugin: Plugin;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ClaudeSessionEvent, ClaudeSessionRunResult, PersistentRunRecord, PersistentRunResult,
|
|
1
|
+
import type { AgentSlotType, ClaudeSessionEvent, ClaudeSessionRunResult, GitDiffResult, GitOperationResult, ManagerStatus, PersistentRunRecord, PersistentRunResult, RunningTask, SessionContextSnapshot, SlotStatus } from '../types/contracts.js';
|
|
2
2
|
import type { ClaudeSessionEventHandler } from '../claude/claude-agent-sdk-adapter.js';
|
|
3
3
|
import type { FileRunStateStore } from '../state/file-run-state-store.js';
|
|
4
4
|
import type { TranscriptStore } from '../state/transcript-store.js';
|
|
@@ -12,6 +12,10 @@ export declare class PersistentManager {
|
|
|
12
12
|
private readonly stateStore;
|
|
13
13
|
private readonly contextTracker;
|
|
14
14
|
private readonly transcriptStore;
|
|
15
|
+
private static readonly slotCounts;
|
|
16
|
+
private static readonly slotWaiters;
|
|
17
|
+
private static readonly runningTaskList;
|
|
18
|
+
private static readonly SLOT_MAXIMA;
|
|
15
19
|
constructor(sessionController: SessionController, gitOps: GitOperations, stateStore: FileRunStateStore, contextTracker: ContextTracker, transcriptStore: TranscriptStore);
|
|
16
20
|
/**
|
|
17
21
|
* Send a message to the persistent Claude Code session.
|
|
@@ -21,6 +25,7 @@ export declare class PersistentManager {
|
|
|
21
25
|
model?: string;
|
|
22
26
|
effort?: 'low' | 'medium' | 'high' | 'max';
|
|
23
27
|
mode?: 'plan' | 'free';
|
|
28
|
+
sessionSystemPrompt?: string;
|
|
24
29
|
abortSignal?: AbortSignal;
|
|
25
30
|
}, onEvent?: ClaudeSessionEventHandler): Promise<{
|
|
26
31
|
sessionId: string | undefined;
|
|
@@ -60,13 +65,29 @@ export declare class PersistentManager {
|
|
|
60
65
|
*/
|
|
61
66
|
gitReset(): Promise<GitOperationResult>;
|
|
62
67
|
/**
|
|
63
|
-
* Get current session status and
|
|
68
|
+
* Get current session status, context health, and slot usage.
|
|
64
69
|
*/
|
|
65
|
-
getStatus():
|
|
70
|
+
getStatus(): ManagerStatus;
|
|
71
|
+
/**
|
|
72
|
+
* Acquire a concurrency slot. Blocks if at capacity (implement=1, verify=5, explore=unlimited).
|
|
73
|
+
*/
|
|
74
|
+
acquireSlot(slotType: AgentSlotType, taskDescription?: string): Promise<void>;
|
|
75
|
+
/**
|
|
76
|
+
* Release a concurrency slot. Wakes the next waiter if any.
|
|
77
|
+
*/
|
|
78
|
+
releaseSlot(slotType: AgentSlotType, taskDescription?: string): void;
|
|
79
|
+
/**
|
|
80
|
+
* Get current slot usage across all agent types.
|
|
81
|
+
*/
|
|
82
|
+
getSlotStatus(): SlotStatus;
|
|
83
|
+
/**
|
|
84
|
+
* List all currently running tasks with their slot types.
|
|
85
|
+
*/
|
|
86
|
+
listRunningTasks(): RunningTask[];
|
|
66
87
|
/**
|
|
67
88
|
* Clear the active session. Next send creates a fresh one.
|
|
68
89
|
*/
|
|
69
|
-
clearSession(
|
|
90
|
+
clearSession(): Promise<string | null>;
|
|
70
91
|
/**
|
|
71
92
|
* Compact the current session to free context.
|
|
72
93
|
*/
|
|
@@ -82,10 +103,6 @@ export declare class PersistentManager {
|
|
|
82
103
|
executeTask(cwd: string, task: string, options?: {
|
|
83
104
|
model?: string;
|
|
84
105
|
}, onProgress?: PersistentManagerProgressHandler): Promise<PersistentRunResult>;
|
|
85
|
-
/**
|
|
86
|
-
* Try to restore session state from disk on startup.
|
|
87
|
-
*/
|
|
88
|
-
tryRestore(cwd: string): Promise<boolean>;
|
|
89
106
|
listRuns(cwd: string): Promise<PersistentRunRecord[]>;
|
|
90
107
|
getRun(cwd: string, runId: string): Promise<PersistentRunRecord | null>;
|
|
91
108
|
}
|
|
@@ -5,6 +5,19 @@ export class PersistentManager {
|
|
|
5
5
|
stateStore;
|
|
6
6
|
contextTracker;
|
|
7
7
|
transcriptStore;
|
|
8
|
+
// --- Slot tracking (static — shared across all instances) ---
|
|
9
|
+
static slotCounts = new Map([
|
|
10
|
+
['implement', 0],
|
|
11
|
+
['verify', 0],
|
|
12
|
+
['explore', 0],
|
|
13
|
+
]);
|
|
14
|
+
static slotWaiters = new Map();
|
|
15
|
+
static runningTaskList = [];
|
|
16
|
+
static SLOT_MAXIMA = {
|
|
17
|
+
implement: 1,
|
|
18
|
+
verify: 5,
|
|
19
|
+
explore: Infinity,
|
|
20
|
+
};
|
|
8
21
|
constructor(sessionController, gitOps, stateStore, contextTracker, transcriptStore) {
|
|
9
22
|
this.sessionController = sessionController;
|
|
10
23
|
this.gitOps = gitOps;
|
|
@@ -17,7 +30,7 @@ export class PersistentManager {
|
|
|
17
30
|
* Creates a new session if none exists.
|
|
18
31
|
*/
|
|
19
32
|
async sendMessage(cwd, message, options, onEvent) {
|
|
20
|
-
const result = await this.sessionController.sendMessage(
|
|
33
|
+
const result = await this.sessionController.sendMessage(message, options, onEvent);
|
|
21
34
|
if (result.sessionId && result.events.length > 0) {
|
|
22
35
|
await this.transcriptStore.appendEvents(cwd, result.sessionId, result.events);
|
|
23
36
|
}
|
|
@@ -63,22 +76,102 @@ export class PersistentManager {
|
|
|
63
76
|
return this.gitOps.resetHard();
|
|
64
77
|
}
|
|
65
78
|
/**
|
|
66
|
-
* Get current session status and
|
|
79
|
+
* Get current session status, context health, and slot usage.
|
|
67
80
|
*/
|
|
68
81
|
getStatus() {
|
|
69
|
-
return
|
|
82
|
+
return {
|
|
83
|
+
context: this.sessionController.getContextSnapshot(),
|
|
84
|
+
slotStatus: this.getSlotStatus(),
|
|
85
|
+
runningTasks: this.listRunningTasks(),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Acquire a concurrency slot. Blocks if at capacity (implement=1, verify=5, explore=unlimited).
|
|
90
|
+
*/
|
|
91
|
+
async acquireSlot(slotType, taskDescription) {
|
|
92
|
+
const max = PersistentManager.SLOT_MAXIMA[slotType];
|
|
93
|
+
const current = PersistentManager.slotCounts.get(slotType) ?? 0;
|
|
94
|
+
if (current < max) {
|
|
95
|
+
PersistentManager.slotCounts.set(slotType, current + 1);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
// At capacity — wait for a slot to be transferred by releaseSlot
|
|
99
|
+
await new Promise((resolve) => {
|
|
100
|
+
let waiters = PersistentManager.slotWaiters.get(slotType);
|
|
101
|
+
if (!waiters) {
|
|
102
|
+
waiters = [];
|
|
103
|
+
PersistentManager.slotWaiters.set(slotType, waiters);
|
|
104
|
+
}
|
|
105
|
+
waiters.push(resolve);
|
|
106
|
+
});
|
|
107
|
+
// Slot was transferred — count stays the same
|
|
108
|
+
}
|
|
109
|
+
if (taskDescription) {
|
|
110
|
+
PersistentManager.runningTaskList.push({
|
|
111
|
+
slotType,
|
|
112
|
+
task: taskDescription,
|
|
113
|
+
startedAt: new Date().toISOString(),
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Release a concurrency slot. Wakes the next waiter if any.
|
|
119
|
+
*/
|
|
120
|
+
releaseSlot(slotType, taskDescription) {
|
|
121
|
+
if (taskDescription) {
|
|
122
|
+
const idx = PersistentManager.runningTaskList.findIndex((t) => t.slotType === slotType && t.task === taskDescription);
|
|
123
|
+
if (idx !== -1) {
|
|
124
|
+
PersistentManager.runningTaskList.splice(idx, 1);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
const waiters = PersistentManager.slotWaiters.get(slotType);
|
|
128
|
+
if (waiters && waiters.length > 0) {
|
|
129
|
+
// Transfer the slot directly to the next waiter (don't decrement count)
|
|
130
|
+
const next = waiters.shift();
|
|
131
|
+
next();
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
const current = PersistentManager.slotCounts.get(slotType) ?? 0;
|
|
135
|
+
if (current > 0) {
|
|
136
|
+
PersistentManager.slotCounts.set(slotType, current - 1);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Get current slot usage across all agent types.
|
|
142
|
+
*/
|
|
143
|
+
getSlotStatus() {
|
|
144
|
+
return {
|
|
145
|
+
implement: {
|
|
146
|
+
used: PersistentManager.slotCounts.get('implement') ?? 0,
|
|
147
|
+
max: PersistentManager.SLOT_MAXIMA.implement,
|
|
148
|
+
},
|
|
149
|
+
verify: {
|
|
150
|
+
used: PersistentManager.slotCounts.get('verify') ?? 0,
|
|
151
|
+
max: PersistentManager.SLOT_MAXIMA.verify,
|
|
152
|
+
},
|
|
153
|
+
explore: {
|
|
154
|
+
used: PersistentManager.slotCounts.get('explore') ?? 0,
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* List all currently running tasks with their slot types.
|
|
160
|
+
*/
|
|
161
|
+
listRunningTasks() {
|
|
162
|
+
return [...PersistentManager.runningTaskList];
|
|
70
163
|
}
|
|
71
164
|
/**
|
|
72
165
|
* Clear the active session. Next send creates a fresh one.
|
|
73
166
|
*/
|
|
74
|
-
async clearSession(
|
|
75
|
-
return this.sessionController.clearSession(
|
|
167
|
+
async clearSession() {
|
|
168
|
+
return this.sessionController.clearSession();
|
|
76
169
|
}
|
|
77
170
|
/**
|
|
78
171
|
* Compact the current session to free context.
|
|
79
172
|
*/
|
|
80
173
|
async compactSession(cwd, onEvent) {
|
|
81
|
-
const result = await this.sessionController.compactSession(
|
|
174
|
+
const result = await this.sessionController.compactSession(onEvent);
|
|
82
175
|
if (result.sessionId && result.events.length > 0) {
|
|
83
176
|
await this.transcriptStore.appendEvents(cwd, result.sessionId, result.events);
|
|
84
177
|
}
|
|
@@ -120,7 +213,7 @@ export class PersistentManager {
|
|
|
120
213
|
await this.stateStore.saveRun(runRecord);
|
|
121
214
|
await onProgress?.(runRecord);
|
|
122
215
|
try {
|
|
123
|
-
const result = await this.sessionController.sendMessage(
|
|
216
|
+
const result = await this.sessionController.sendMessage(task, options, async (event) => {
|
|
124
217
|
// Update run record with progress events
|
|
125
218
|
const currentRun = await this.stateStore.getRun(cwd, runId);
|
|
126
219
|
if (currentRun) {
|
|
@@ -167,12 +260,6 @@ export class PersistentManager {
|
|
|
167
260
|
return { run: failedRun };
|
|
168
261
|
}
|
|
169
262
|
}
|
|
170
|
-
/**
|
|
171
|
-
* Try to restore session state from disk on startup.
|
|
172
|
-
*/
|
|
173
|
-
async tryRestore(cwd) {
|
|
174
|
-
return this.sessionController.tryRestore(cwd);
|
|
175
|
-
}
|
|
176
263
|
listRuns(cwd) {
|
|
177
264
|
return this.stateStore.listRuns(cwd);
|
|
178
265
|
}
|
|
@@ -5,9 +5,11 @@ export declare class SessionController {
|
|
|
5
5
|
private readonly sdkAdapter;
|
|
6
6
|
private readonly contextTracker;
|
|
7
7
|
private readonly sessionPrompt;
|
|
8
|
+
private readonly wrapperType;
|
|
9
|
+
private readonly worktree;
|
|
8
10
|
private readonly modePrefixes;
|
|
9
11
|
private activeSessionId;
|
|
10
|
-
constructor(sdkAdapter: ClaudeAgentSdkAdapter, contextTracker: ContextTracker, sessionPrompt: string, modePrefixes?: {
|
|
12
|
+
constructor(sdkAdapter: ClaudeAgentSdkAdapter, contextTracker: ContextTracker, sessionPrompt: string | undefined, wrapperType: string, worktree: string, modePrefixes?: {
|
|
11
13
|
plan: string;
|
|
12
14
|
free: string;
|
|
13
15
|
});
|
|
@@ -17,28 +19,23 @@ export declare class SessionController {
|
|
|
17
19
|
* Send a message to the persistent session. Creates one if none exists.
|
|
18
20
|
* Returns the session result including usage data.
|
|
19
21
|
*/
|
|
20
|
-
sendMessage(
|
|
22
|
+
sendMessage(message: string, options?: {
|
|
21
23
|
model?: string;
|
|
22
24
|
effort?: 'low' | 'medium' | 'high' | 'max';
|
|
23
25
|
mode?: SessionMode;
|
|
26
|
+
sessionSystemPrompt?: string;
|
|
24
27
|
abortSignal?: AbortSignal;
|
|
25
28
|
}, onEvent?: ClaudeSessionEventHandler): Promise<ClaudeSessionRunResult>;
|
|
26
29
|
/**
|
|
27
30
|
* Send /compact to the current session to compress context.
|
|
28
31
|
*/
|
|
29
|
-
compactSession(
|
|
32
|
+
compactSession(onEvent?: ClaudeSessionEventHandler): Promise<ClaudeSessionRunResult>;
|
|
30
33
|
/**
|
|
31
34
|
* Clear the current session. The next sendMessage will create a fresh one.
|
|
32
35
|
*/
|
|
33
|
-
clearSession(
|
|
36
|
+
clearSession(): Promise<string | null>;
|
|
34
37
|
/**
|
|
35
38
|
* Get current context tracking snapshot.
|
|
36
39
|
*/
|
|
37
40
|
getContextSnapshot(): SessionContextSnapshot;
|
|
38
|
-
/**
|
|
39
|
-
* Try to restore active session from persisted state on startup.
|
|
40
|
-
*/
|
|
41
|
-
tryRestore(cwd: string): Promise<boolean>;
|
|
42
|
-
private persistActiveSession;
|
|
43
|
-
private removeActiveSession;
|
|
44
41
|
}
|
|
@@ -1,19 +1,20 @@
|
|
|
1
|
-
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
-
import { dirname, join } from 'node:path';
|
|
3
|
-
const ACTIVE_SESSION_FILE = '.claude-manager/active-session.json';
|
|
4
1
|
export class SessionController {
|
|
5
2
|
sdkAdapter;
|
|
6
3
|
contextTracker;
|
|
7
4
|
sessionPrompt;
|
|
5
|
+
wrapperType;
|
|
6
|
+
worktree;
|
|
8
7
|
modePrefixes;
|
|
9
8
|
activeSessionId = null;
|
|
10
|
-
constructor(sdkAdapter, contextTracker, sessionPrompt, modePrefixes = {
|
|
9
|
+
constructor(sdkAdapter, contextTracker, sessionPrompt, wrapperType, worktree, modePrefixes = {
|
|
11
10
|
plan: '',
|
|
12
11
|
free: '',
|
|
13
12
|
}) {
|
|
14
13
|
this.sdkAdapter = sdkAdapter;
|
|
15
14
|
this.contextTracker = contextTracker;
|
|
16
15
|
this.sessionPrompt = sessionPrompt;
|
|
16
|
+
this.wrapperType = wrapperType;
|
|
17
|
+
this.worktree = worktree;
|
|
17
18
|
this.modePrefixes = modePrefixes;
|
|
18
19
|
}
|
|
19
20
|
get isActive() {
|
|
@@ -26,12 +27,12 @@ export class SessionController {
|
|
|
26
27
|
* Send a message to the persistent session. Creates one if none exists.
|
|
27
28
|
* Returns the session result including usage data.
|
|
28
29
|
*/
|
|
29
|
-
async sendMessage(
|
|
30
|
+
async sendMessage(message, options, onEvent) {
|
|
30
31
|
const mode = options?.mode ?? 'free';
|
|
31
32
|
const prefix = this.modePrefixes[mode];
|
|
32
33
|
const prompt = prefix ? `${prefix}\n\n${message}` : message;
|
|
33
34
|
const input = {
|
|
34
|
-
cwd,
|
|
35
|
+
cwd: this.worktree,
|
|
35
36
|
prompt,
|
|
36
37
|
persistSession: true,
|
|
37
38
|
permissionMode: mode === 'plan' ? 'plan' : 'acceptEdits',
|
|
@@ -46,8 +47,8 @@ export class SessionController {
|
|
|
46
47
|
input.resumeSessionId = this.activeSessionId;
|
|
47
48
|
}
|
|
48
49
|
else {
|
|
49
|
-
// New session —
|
|
50
|
-
input.systemPrompt = this.sessionPrompt;
|
|
50
|
+
// New session — prefer dynamically constructed prompt from wrapper, fall back to static default
|
|
51
|
+
input.systemPrompt = options?.sessionSystemPrompt ?? this.sessionPrompt;
|
|
51
52
|
input.model ??= 'claude-opus-4-6';
|
|
52
53
|
input.effort ??= 'high';
|
|
53
54
|
}
|
|
@@ -65,29 +66,26 @@ export class SessionController {
|
|
|
65
66
|
outputTokens: result.outputTokens,
|
|
66
67
|
contextWindowSize: result.contextWindowSize,
|
|
67
68
|
});
|
|
68
|
-
// Persist active session state
|
|
69
|
-
await this.persistActiveSession(cwd);
|
|
70
69
|
return result;
|
|
71
70
|
}
|
|
72
71
|
/**
|
|
73
72
|
* Send /compact to the current session to compress context.
|
|
74
73
|
*/
|
|
75
|
-
async compactSession(
|
|
74
|
+
async compactSession(onEvent) {
|
|
76
75
|
if (!this.activeSessionId) {
|
|
77
76
|
throw new Error('No active session to compact');
|
|
78
77
|
}
|
|
79
|
-
const result = await this.sendMessage(
|
|
78
|
+
const result = await this.sendMessage('/compact', undefined, onEvent);
|
|
80
79
|
this.contextTracker.recordCompaction();
|
|
81
80
|
return result;
|
|
82
81
|
}
|
|
83
82
|
/**
|
|
84
83
|
* Clear the current session. The next sendMessage will create a fresh one.
|
|
85
84
|
*/
|
|
86
|
-
async clearSession(
|
|
85
|
+
async clearSession() {
|
|
87
86
|
const clearedId = this.activeSessionId;
|
|
88
87
|
this.activeSessionId = null;
|
|
89
88
|
this.contextTracker.reset();
|
|
90
|
-
await this.removeActiveSession(cwd);
|
|
91
89
|
return clearedId;
|
|
92
90
|
}
|
|
93
91
|
/**
|
|
@@ -96,52 +94,4 @@ export class SessionController {
|
|
|
96
94
|
getContextSnapshot() {
|
|
97
95
|
return this.contextTracker.snapshot();
|
|
98
96
|
}
|
|
99
|
-
/**
|
|
100
|
-
* Try to restore active session from persisted state on startup.
|
|
101
|
-
*/
|
|
102
|
-
async tryRestore(cwd) {
|
|
103
|
-
const filePath = join(cwd, ACTIVE_SESSION_FILE);
|
|
104
|
-
try {
|
|
105
|
-
const raw = await readFile(filePath, 'utf-8');
|
|
106
|
-
const state = JSON.parse(raw);
|
|
107
|
-
if (state.sessionId && state.cwd === cwd) {
|
|
108
|
-
this.activeSessionId = state.sessionId;
|
|
109
|
-
this.contextTracker.restore(state);
|
|
110
|
-
return true;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
catch {
|
|
114
|
-
// File doesn't exist or is corrupt — start fresh
|
|
115
|
-
}
|
|
116
|
-
return false;
|
|
117
|
-
}
|
|
118
|
-
async persistActiveSession(cwd) {
|
|
119
|
-
if (!this.activeSessionId) {
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
const snap = this.contextTracker.snapshot();
|
|
123
|
-
const state = {
|
|
124
|
-
sessionId: this.activeSessionId,
|
|
125
|
-
cwd,
|
|
126
|
-
startedAt: new Date().toISOString(),
|
|
127
|
-
totalTurns: snap.totalTurns,
|
|
128
|
-
totalCostUsd: snap.totalCostUsd,
|
|
129
|
-
estimatedContextPercent: snap.estimatedContextPercent,
|
|
130
|
-
contextWindowSize: snap.contextWindowSize,
|
|
131
|
-
latestInputTokens: snap.latestInputTokens,
|
|
132
|
-
};
|
|
133
|
-
const filePath = join(cwd, ACTIVE_SESSION_FILE);
|
|
134
|
-
await mkdir(dirname(filePath), { recursive: true });
|
|
135
|
-
await writeFile(filePath, JSON.stringify(state, null, 2));
|
|
136
|
-
}
|
|
137
|
-
async removeActiveSession(cwd) {
|
|
138
|
-
const filePath = join(cwd, ACTIVE_SESSION_FILE);
|
|
139
|
-
try {
|
|
140
|
-
const { unlink } = await import('node:fs/promises');
|
|
141
|
-
await unlink(filePath);
|
|
142
|
-
}
|
|
143
|
-
catch {
|
|
144
|
-
// File doesn't exist — that's fine
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
97
|
}
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Agent hierarchy configuration for the CTO + Engineer Wrapper architecture.
|
|
3
3
|
*
|
|
4
|
-
* CTO (cto)
|
|
5
|
-
* Engineer
|
|
6
|
-
* Engineer
|
|
7
|
-
* Claude Code session
|
|
4
|
+
* CTO (cto) — pure orchestrator, spawns engineers, reviews diffs, commits
|
|
5
|
+
* Engineer Explore (engineer_explore) — manages a Claude Code session for read-only investigation
|
|
6
|
+
* Engineer Implement (engineer_implement) — manages a Claude Code session for implementation
|
|
7
|
+
* Claude Code session — the underlying AI session (prompt only, no OpenCode agent)
|
|
8
8
|
*/
|
|
9
9
|
import type { ManagerPromptRegistry } from '../types/contracts.js';
|
|
10
10
|
export declare const AGENT_CTO = "cto";
|
|
11
|
-
export declare const
|
|
12
|
-
export declare const
|
|
11
|
+
export declare const AGENT_ENGINEER_EXPLORE = "engineer_explore";
|
|
12
|
+
export declare const AGENT_ENGINEER_IMPLEMENT = "engineer_implement";
|
|
13
|
+
export declare const AGENT_ENGINEER_VERIFY = "engineer_verify";
|
|
13
14
|
/** All restricted tool IDs (union of all domain groups) */
|
|
14
|
-
export declare const ALL_RESTRICTED_TOOL_IDS: readonly ["explore", "implement", "compact_context", "clear_session", "session_health", "list_transcripts", "list_history", "git_diff", "git_commit", "git_reset", "git_status", "git_log", "approval_policy", "approval_decisions", "approval_update"];
|
|
15
|
+
export declare const ALL_RESTRICTED_TOOL_IDS: readonly ["explore", "implement", "verify", "compact_context", "clear_session", "session_health", "list_transcripts", "list_history", "git_diff", "git_commit", "git_reset", "git_status", "git_log", "approval_policy", "approval_decisions", "approval_update"];
|
|
15
16
|
type ToolPermission = 'allow' | 'ask' | 'deny';
|
|
16
17
|
type AgentPermission = {
|
|
17
18
|
'*'?: ToolPermission;
|
|
@@ -41,14 +42,21 @@ export declare function buildCtoAgentConfig(prompts: ManagerPromptRegistry): {
|
|
|
41
42
|
permission: AgentPermission;
|
|
42
43
|
prompt: string;
|
|
43
44
|
};
|
|
44
|
-
export declare function
|
|
45
|
+
export declare function buildEngineerExploreAgentConfig(prompts: ManagerPromptRegistry): {
|
|
45
46
|
description: string;
|
|
46
47
|
mode: "subagent";
|
|
47
48
|
color: string;
|
|
48
49
|
permission: AgentPermission;
|
|
49
50
|
prompt: string;
|
|
50
51
|
};
|
|
51
|
-
export declare function
|
|
52
|
+
export declare function buildEngineerImplementAgentConfig(prompts: ManagerPromptRegistry): {
|
|
53
|
+
description: string;
|
|
54
|
+
mode: "subagent";
|
|
55
|
+
color: string;
|
|
56
|
+
permission: AgentPermission;
|
|
57
|
+
prompt: string;
|
|
58
|
+
};
|
|
59
|
+
export declare function buildEngineerVerifyAgentConfig(prompts: ManagerPromptRegistry): {
|
|
52
60
|
description: string;
|
|
53
61
|
mode: "subagent";
|
|
54
62
|
color: string;
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Agent hierarchy configuration for the CTO + Engineer Wrapper architecture.
|
|
3
3
|
*
|
|
4
|
-
* CTO (cto)
|
|
5
|
-
* Engineer
|
|
6
|
-
* Engineer
|
|
7
|
-
* Claude Code session
|
|
4
|
+
* CTO (cto) — pure orchestrator, spawns engineers, reviews diffs, commits
|
|
5
|
+
* Engineer Explore (engineer_explore) — manages a Claude Code session for read-only investigation
|
|
6
|
+
* Engineer Implement (engineer_implement) — manages a Claude Code session for implementation
|
|
7
|
+
* Claude Code session — the underlying AI session (prompt only, no OpenCode agent)
|
|
8
8
|
*/
|
|
9
9
|
// ---------------------------------------------------------------------------
|
|
10
10
|
// Agent names
|
|
11
11
|
// ---------------------------------------------------------------------------
|
|
12
12
|
export const AGENT_CTO = 'cto';
|
|
13
|
-
export const
|
|
14
|
-
export const
|
|
13
|
+
export const AGENT_ENGINEER_EXPLORE = 'engineer_explore';
|
|
14
|
+
export const AGENT_ENGINEER_IMPLEMENT = 'engineer_implement';
|
|
15
|
+
export const AGENT_ENGINEER_VERIFY = 'engineer_verify';
|
|
15
16
|
// ---------------------------------------------------------------------------
|
|
16
17
|
// Tool IDs — grouped by domain
|
|
17
18
|
// ---------------------------------------------------------------------------
|
|
@@ -24,11 +25,13 @@ const ENGINEER_SHARED_TOOL_IDS = [
|
|
|
24
25
|
'list_history',
|
|
25
26
|
];
|
|
26
27
|
/** All engineer tools — mode-locked sends + shared session tools */
|
|
27
|
-
const ENGINEER_TOOL_IDS = ['explore', 'implement', ...ENGINEER_SHARED_TOOL_IDS];
|
|
28
|
-
/** Tools for the
|
|
29
|
-
const
|
|
30
|
-
/** Tools for the
|
|
31
|
-
const
|
|
28
|
+
const ENGINEER_TOOL_IDS = ['explore', 'implement', 'verify', ...ENGINEER_SHARED_TOOL_IDS];
|
|
29
|
+
/** Tools for the engineer_explore wrapper (explore-mode send + shared) */
|
|
30
|
+
const ENGINEER_EXPLORE_TOOL_IDS = ['explore', ...ENGINEER_SHARED_TOOL_IDS];
|
|
31
|
+
/** Tools for the engineer_implement wrapper (implement-mode send + shared) */
|
|
32
|
+
const ENGINEER_IMPLEMENT_TOOL_IDS = ['implement', ...ENGINEER_SHARED_TOOL_IDS];
|
|
33
|
+
/** Tools for the engineer_verify wrapper (verify-mode send + shared) */
|
|
34
|
+
const ENGINEER_VERIFY_TOOL_IDS = ['verify', ...ENGINEER_SHARED_TOOL_IDS];
|
|
32
35
|
/** Git tools — owned by CTO */
|
|
33
36
|
const GIT_TOOL_IDS = ['git_diff', 'git_commit', 'git_reset', 'git_status', 'git_log'];
|
|
34
37
|
/** Approval tools — owned by CTO */
|
|
@@ -77,19 +80,20 @@ function buildCtoPermissions() {
|
|
|
77
80
|
...allowed,
|
|
78
81
|
task: {
|
|
79
82
|
'*': 'deny',
|
|
80
|
-
[
|
|
81
|
-
[
|
|
83
|
+
[AGENT_ENGINEER_EXPLORE]: 'allow',
|
|
84
|
+
[AGENT_ENGINEER_IMPLEMENT]: 'allow',
|
|
85
|
+
[AGENT_ENGINEER_VERIFY]: 'allow',
|
|
82
86
|
},
|
|
83
87
|
};
|
|
84
88
|
}
|
|
85
|
-
/** Engineer
|
|
86
|
-
function
|
|
89
|
+
/** Engineer explore wrapper: read-only investigation + explore + shared session tools. */
|
|
90
|
+
function buildEngineerExplorePermissions() {
|
|
87
91
|
const denied = {};
|
|
88
92
|
for (const toolId of ALL_RESTRICTED_TOOL_IDS) {
|
|
89
93
|
denied[toolId] = 'deny';
|
|
90
94
|
}
|
|
91
95
|
const allowed = {};
|
|
92
|
-
for (const toolId of
|
|
96
|
+
for (const toolId of ENGINEER_EXPLORE_TOOL_IDS) {
|
|
93
97
|
allowed[toolId] = 'allow';
|
|
94
98
|
}
|
|
95
99
|
return {
|
|
@@ -99,14 +103,31 @@ function buildEngineerPlanPermissions() {
|
|
|
99
103
|
...allowed,
|
|
100
104
|
};
|
|
101
105
|
}
|
|
102
|
-
/** Engineer
|
|
103
|
-
function
|
|
106
|
+
/** Engineer implement wrapper: read-only investigation + implement + shared session tools. */
|
|
107
|
+
function buildEngineerImplementPermissions() {
|
|
104
108
|
const denied = {};
|
|
105
109
|
for (const toolId of ALL_RESTRICTED_TOOL_IDS) {
|
|
106
110
|
denied[toolId] = 'deny';
|
|
107
111
|
}
|
|
108
112
|
const allowed = {};
|
|
109
|
-
for (const toolId of
|
|
113
|
+
for (const toolId of ENGINEER_IMPLEMENT_TOOL_IDS) {
|
|
114
|
+
allowed[toolId] = 'allow';
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
'*': 'deny',
|
|
118
|
+
...READONLY_TOOLS,
|
|
119
|
+
...denied,
|
|
120
|
+
...allowed,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
/** Engineer verify wrapper: read-only + verify + restricted bash for test/lint/typecheck/build. */
|
|
124
|
+
function buildEngineerVerifyPermissions() {
|
|
125
|
+
const denied = {};
|
|
126
|
+
for (const toolId of ALL_RESTRICTED_TOOL_IDS) {
|
|
127
|
+
denied[toolId] = 'deny';
|
|
128
|
+
}
|
|
129
|
+
const allowed = {};
|
|
130
|
+
for (const toolId of ENGINEER_VERIFY_TOOL_IDS) {
|
|
110
131
|
allowed[toolId] = 'allow';
|
|
111
132
|
}
|
|
112
133
|
return {
|
|
@@ -128,22 +149,31 @@ export function buildCtoAgentConfig(prompts) {
|
|
|
128
149
|
prompt: prompts.ctoSystemPrompt,
|
|
129
150
|
};
|
|
130
151
|
}
|
|
131
|
-
export function
|
|
152
|
+
export function buildEngineerExploreAgentConfig(prompts) {
|
|
132
153
|
return {
|
|
133
154
|
description: 'Thin high-judgment wrapper that frames work quickly and dispatches to Claude Code in plan mode for read-only investigation.',
|
|
134
155
|
mode: 'subagent',
|
|
135
156
|
color: '#D97757',
|
|
136
|
-
permission:
|
|
137
|
-
prompt: prompts.
|
|
157
|
+
permission: buildEngineerExplorePermissions(),
|
|
158
|
+
prompt: prompts.engineerExplorePrompt,
|
|
138
159
|
};
|
|
139
160
|
}
|
|
140
|
-
export function
|
|
161
|
+
export function buildEngineerImplementAgentConfig(prompts) {
|
|
141
162
|
return {
|
|
142
163
|
description: 'Thin high-judgment wrapper that frames work quickly and dispatches to Claude Code in free mode for implementation.',
|
|
143
164
|
mode: 'subagent',
|
|
144
165
|
color: '#D97757',
|
|
145
|
-
permission:
|
|
146
|
-
prompt: prompts.
|
|
166
|
+
permission: buildEngineerImplementPermissions(),
|
|
167
|
+
prompt: prompts.engineerImplementPrompt,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
export function buildEngineerVerifyAgentConfig(prompts) {
|
|
171
|
+
return {
|
|
172
|
+
description: 'Thin high-judgment wrapper that runs verification commands (tests, lint, typecheck, build) and reports pass/fail.',
|
|
173
|
+
mode: 'subagent',
|
|
174
|
+
color: '#D97757',
|
|
175
|
+
permission: buildEngineerVerifyPermissions(),
|
|
176
|
+
prompt: prompts.engineerVerifyPrompt,
|
|
147
177
|
};
|
|
148
178
|
}
|
|
149
179
|
// ---------------------------------------------------------------------------
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import { tool } from '@opencode-ai/plugin';
|
|
2
2
|
import { composeWrapperPrompt, managerPromptRegistry } from '../prompts/registry.js';
|
|
3
3
|
import { discoverProjectClaudeFiles } from '../util/project-context.js';
|
|
4
|
-
import { AGENT_CTO,
|
|
4
|
+
import { AGENT_CTO, AGENT_ENGINEER_EXPLORE, AGENT_ENGINEER_IMPLEMENT, AGENT_ENGINEER_VERIFY, buildCtoAgentConfig, buildEngineerExploreAgentConfig, buildEngineerImplementAgentConfig, buildEngineerVerifyAgentConfig, denyRestrictedToolsGlobally, } from './agent-hierarchy.js';
|
|
5
5
|
import { getOrCreatePluginServices } from './service-factory.js';
|
|
6
6
|
export const ClaudeManagerPlugin = async ({ worktree }) => {
|
|
7
7
|
const services = getOrCreatePluginServices(worktree);
|
|
8
8
|
async function executeDelegate(args, context) {
|
|
9
|
-
const
|
|
9
|
+
const wrapperServices = getOrCreatePluginServices(context.worktree);
|
|
10
10
|
if (args.freshSession) {
|
|
11
|
-
await
|
|
11
|
+
await wrapperServices.manager.clearSession();
|
|
12
12
|
}
|
|
13
|
-
const
|
|
13
|
+
const managerStatus = wrapperServices.manager.getStatus();
|
|
14
|
+
const hasActiveSession = managerStatus.context.sessionId !== null;
|
|
14
15
|
const promptPreview = args.message.length > 100 ? args.message.slice(0, 100) + '...' : args.message;
|
|
15
16
|
context.metadata({
|
|
16
17
|
title: hasActiveSession
|
|
@@ -18,16 +19,17 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
|
|
|
18
19
|
: '⚡ Claude Code: Initializing...',
|
|
19
20
|
metadata: {
|
|
20
21
|
status: 'running',
|
|
21
|
-
sessionId:
|
|
22
|
+
sessionId: managerStatus.context.sessionId,
|
|
22
23
|
prompt: promptPreview,
|
|
23
24
|
},
|
|
24
25
|
});
|
|
25
26
|
let turnsSoFar;
|
|
26
27
|
let costSoFar;
|
|
27
|
-
const result = await
|
|
28
|
+
const result = await wrapperServices.manager.sendMessage(context.worktree, args.message, {
|
|
28
29
|
model: args.model,
|
|
29
30
|
effort: args.effort,
|
|
30
31
|
mode: args.mode,
|
|
32
|
+
sessionSystemPrompt: args.sessionSystemPrompt,
|
|
31
33
|
abortSignal: context.abort,
|
|
32
34
|
}, (event) => {
|
|
33
35
|
if (event.turns !== undefined) {
|
|
@@ -182,7 +184,7 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
|
|
|
182
184
|
let toolOutputs = [];
|
|
183
185
|
if (result.sessionId) {
|
|
184
186
|
try {
|
|
185
|
-
toolOutputs = await
|
|
187
|
+
toolOutputs = await wrapperServices.liveTailer.getToolOutputPreview(result.sessionId, context.worktree, 3);
|
|
186
188
|
}
|
|
187
189
|
catch {
|
|
188
190
|
// Non-critical — the JSONL file may not exist yet.
|
|
@@ -210,12 +212,14 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
|
|
|
210
212
|
const claudeFiles = await discoverProjectClaudeFiles(worktree);
|
|
211
213
|
const derivedPrompts = {
|
|
212
214
|
...managerPromptRegistry,
|
|
213
|
-
|
|
214
|
-
|
|
215
|
+
engineerExplorePrompt: composeWrapperPrompt(managerPromptRegistry.engineerExplorePrompt, claudeFiles),
|
|
216
|
+
engineerImplementPrompt: composeWrapperPrompt(managerPromptRegistry.engineerImplementPrompt, claudeFiles),
|
|
217
|
+
engineerVerifyPrompt: composeWrapperPrompt(managerPromptRegistry.engineerVerifyPrompt, claudeFiles),
|
|
215
218
|
};
|
|
216
219
|
config.agent[AGENT_CTO] ??= buildCtoAgentConfig(managerPromptRegistry);
|
|
217
|
-
config.agent[
|
|
218
|
-
config.agent[
|
|
220
|
+
config.agent[AGENT_ENGINEER_EXPLORE] ??= buildEngineerExploreAgentConfig(derivedPrompts);
|
|
221
|
+
config.agent[AGENT_ENGINEER_IMPLEMENT] ??= buildEngineerImplementAgentConfig(derivedPrompts);
|
|
222
|
+
config.agent[AGENT_ENGINEER_VERIFY] ??= buildEngineerVerifyAgentConfig(derivedPrompts);
|
|
219
223
|
},
|
|
220
224
|
tool: {
|
|
221
225
|
explore: tool({
|
|
@@ -225,14 +229,44 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
|
|
|
225
229
|
args: {
|
|
226
230
|
message: tool.schema.string().min(1),
|
|
227
231
|
model: tool.schema
|
|
228
|
-
.enum(['claude-opus-4-6', 'claude-sonnet-4-6'
|
|
232
|
+
.enum(['claude-opus-4-6', 'claude-sonnet-4-6'])
|
|
229
233
|
.optional(),
|
|
230
|
-
effort: tool.schema.enum(['
|
|
234
|
+
effort: tool.schema.enum(['medium', 'high', 'max']).default('high'),
|
|
231
235
|
freshSession: tool.schema.boolean().default(false),
|
|
232
|
-
|
|
236
|
+
sessionSystemPrompt: tool.schema.string().optional(),
|
|
233
237
|
},
|
|
234
238
|
async execute(args, context) {
|
|
235
|
-
|
|
239
|
+
const taskDesc = args.message.length > 100 ? args.message.slice(0, 100) + '...' : args.message;
|
|
240
|
+
await services.manager.acquireSlot('explore', taskDesc);
|
|
241
|
+
try {
|
|
242
|
+
return await executeDelegate({ ...args, mode: 'plan', wrapperType: 'explore' }, context);
|
|
243
|
+
}
|
|
244
|
+
finally {
|
|
245
|
+
services.manager.releaseSlot('explore', taskDesc);
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
}),
|
|
249
|
+
verify: tool({
|
|
250
|
+
description: 'Run verification commands (tests, lint, typecheck, build) and report pass/fail. ' +
|
|
251
|
+
'Use after implementation to verify correctness.',
|
|
252
|
+
args: {
|
|
253
|
+
message: tool.schema.string().min(1),
|
|
254
|
+
model: tool.schema
|
|
255
|
+
.enum(['claude-opus-4-6', 'claude-sonnet-4-6'])
|
|
256
|
+
.optional(),
|
|
257
|
+
effort: tool.schema.enum(['medium', 'high', 'max']).default('high'),
|
|
258
|
+
freshSession: tool.schema.boolean().default(false),
|
|
259
|
+
sessionSystemPrompt: tool.schema.string().optional(),
|
|
260
|
+
},
|
|
261
|
+
async execute(args, context) {
|
|
262
|
+
const taskDesc = args.message.length > 100 ? args.message.slice(0, 100) + '...' : args.message;
|
|
263
|
+
await services.manager.acquireSlot('verify', taskDesc);
|
|
264
|
+
try {
|
|
265
|
+
return await executeDelegate({ ...args, mode: 'free', wrapperType: 'engineer_verify' }, context);
|
|
266
|
+
}
|
|
267
|
+
finally {
|
|
268
|
+
services.manager.releaseSlot('verify', taskDesc);
|
|
269
|
+
}
|
|
236
270
|
},
|
|
237
271
|
}),
|
|
238
272
|
implement: tool({
|
|
@@ -241,32 +275,39 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
|
|
|
241
275
|
args: {
|
|
242
276
|
message: tool.schema.string().min(1),
|
|
243
277
|
model: tool.schema
|
|
244
|
-
.enum(['claude-opus-4-6', 'claude-sonnet-4-6'
|
|
278
|
+
.enum(['claude-opus-4-6', 'claude-sonnet-4-6'])
|
|
245
279
|
.optional(),
|
|
246
|
-
effort: tool.schema.enum(['
|
|
280
|
+
effort: tool.schema.enum(['medium', 'high', 'max']).default('high'),
|
|
247
281
|
freshSession: tool.schema.boolean().default(false),
|
|
248
|
-
|
|
282
|
+
sessionSystemPrompt: tool.schema.string().optional(),
|
|
249
283
|
},
|
|
250
284
|
async execute(args, context) {
|
|
251
|
-
|
|
285
|
+
const taskDesc = args.message.length > 100 ? args.message.slice(0, 100) + '...' : args.message;
|
|
286
|
+
await services.manager.acquireSlot('implement', taskDesc);
|
|
287
|
+
try {
|
|
288
|
+
return await executeDelegate({ ...args, mode: 'free', wrapperType: 'implement' }, context);
|
|
289
|
+
}
|
|
290
|
+
finally {
|
|
291
|
+
services.manager.releaseSlot('implement', taskDesc);
|
|
292
|
+
}
|
|
252
293
|
},
|
|
253
294
|
}),
|
|
254
295
|
compact_context: tool({
|
|
255
296
|
description: 'Compress session history to reclaim context window space. ' +
|
|
256
297
|
'Preserves state while reducing token usage.',
|
|
257
298
|
args: {
|
|
258
|
-
|
|
299
|
+
wrapperType: tool.schema.string().optional(),
|
|
259
300
|
},
|
|
260
301
|
async execute(args, context) {
|
|
261
|
-
const
|
|
302
|
+
const wrapperServices = getOrCreatePluginServices(context.worktree);
|
|
262
303
|
annotateToolRun(context, 'Compacting session', {});
|
|
263
|
-
const result = await
|
|
264
|
-
const snap =
|
|
265
|
-
const contextWarning = formatContextWarning(snap);
|
|
304
|
+
const result = await wrapperServices.manager.compactSession(context.worktree);
|
|
305
|
+
const snap = wrapperServices.manager.getStatus();
|
|
306
|
+
const contextWarning = formatContextWarning(snap.context);
|
|
266
307
|
context.metadata({
|
|
267
308
|
title: contextWarning
|
|
268
|
-
? `⚠️ Claude Code: Compacted — context at ${snap.estimatedContextPercent}%`
|
|
269
|
-
: `✅ Claude Code: Compacted (${snap.totalTurns} turns, $${(snap.totalCostUsd ?? 0).toFixed(4)})`,
|
|
309
|
+
? `⚠️ Claude Code: Compacted — context at ${snap.context.estimatedContextPercent}%`
|
|
310
|
+
: `✅ Claude Code: Compacted (${snap.context.totalTurns} turns, $${(snap.context.totalCostUsd ?? 0).toFixed(4)})`,
|
|
270
311
|
metadata: {
|
|
271
312
|
status: contextWarning ? 'warning' : 'success',
|
|
272
313
|
sessionId: result.sessionId,
|
|
@@ -277,7 +318,7 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
|
|
|
277
318
|
finalText: result.finalText,
|
|
278
319
|
turns: result.turns,
|
|
279
320
|
totalCostUsd: result.totalCostUsd,
|
|
280
|
-
context: snap,
|
|
321
|
+
context: snap.context,
|
|
281
322
|
contextWarning,
|
|
282
323
|
}, null, 2);
|
|
283
324
|
},
|
|
@@ -355,70 +396,73 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
|
|
|
355
396
|
description: 'Clear the active session to start fresh. ' +
|
|
356
397
|
'Use when context is full or starting a new task.',
|
|
357
398
|
args: {
|
|
358
|
-
|
|
399
|
+
wrapperType: tool.schema.string().optional(),
|
|
359
400
|
reason: tool.schema.string().optional(),
|
|
360
401
|
},
|
|
361
402
|
async execute(args, context) {
|
|
403
|
+
const wrapperServices = getOrCreatePluginServices(context.worktree);
|
|
362
404
|
annotateToolRun(context, 'Clearing session', {
|
|
363
405
|
reason: args.reason,
|
|
364
406
|
});
|
|
365
|
-
const clearedId = await
|
|
407
|
+
const clearedId = await wrapperServices.manager.clearSession();
|
|
366
408
|
return JSON.stringify({ clearedSessionId: clearedId });
|
|
367
409
|
},
|
|
368
410
|
}),
|
|
369
411
|
session_health: tool({
|
|
370
412
|
description: 'Check session health metrics: context usage %, turn count, cost, and session ID.',
|
|
371
413
|
args: {
|
|
372
|
-
|
|
414
|
+
wrapperType: tool.schema.string().optional(),
|
|
373
415
|
},
|
|
374
|
-
async execute(
|
|
416
|
+
async execute(args, context) {
|
|
417
|
+
const wrapperServices = getOrCreatePluginServices(context.worktree);
|
|
375
418
|
annotateToolRun(context, 'Checking session status', {});
|
|
376
|
-
const status =
|
|
419
|
+
const status = wrapperServices.manager.getStatus();
|
|
377
420
|
return JSON.stringify({
|
|
378
421
|
...status,
|
|
379
|
-
transcriptFile: status.sessionId
|
|
380
|
-
? `.claude-manager/transcripts/${status.sessionId}.json`
|
|
422
|
+
transcriptFile: status.context.sessionId
|
|
423
|
+
? `.claude-manager/transcripts/${status.context.sessionId}.json`
|
|
381
424
|
: null,
|
|
382
|
-
contextWarning: formatContextWarning(status),
|
|
425
|
+
contextWarning: formatContextWarning(status.context),
|
|
383
426
|
}, null, 2);
|
|
384
427
|
},
|
|
385
428
|
}),
|
|
386
429
|
list_transcripts: tool({
|
|
387
430
|
description: 'List available session transcripts or inspect a specific transcript by ID.',
|
|
388
431
|
args: {
|
|
389
|
-
|
|
432
|
+
wrapperType: tool.schema.string().optional(),
|
|
390
433
|
sessionId: tool.schema.string().optional(),
|
|
391
434
|
},
|
|
392
435
|
async execute(args, context) {
|
|
436
|
+
const wrapperServices = getOrCreatePluginServices(context.worktree);
|
|
393
437
|
annotateToolRun(context, 'Inspecting Claude session history', {});
|
|
394
|
-
const cwd = args.cwd ?? context.worktree;
|
|
395
438
|
if (args.sessionId) {
|
|
396
439
|
const [sdkTranscript, localEvents] = await Promise.all([
|
|
397
|
-
|
|
398
|
-
|
|
440
|
+
wrapperServices.sessions.getTranscript(args.sessionId, context.worktree),
|
|
441
|
+
wrapperServices.manager.getTranscriptEvents(context.worktree, args.sessionId),
|
|
399
442
|
]);
|
|
400
443
|
return JSON.stringify({
|
|
401
444
|
sdkTranscript,
|
|
402
445
|
localEvents: localEvents.length > 0 ? localEvents : undefined,
|
|
403
446
|
}, null, 2);
|
|
404
447
|
}
|
|
405
|
-
const sessions = await
|
|
448
|
+
const sessions = await wrapperServices.sessions.listSessions(context.worktree);
|
|
406
449
|
return JSON.stringify(sessions, null, 2);
|
|
407
450
|
},
|
|
408
451
|
}),
|
|
409
452
|
list_history: tool({
|
|
410
453
|
description: 'List persistent run records from the manager or inspect a specific run.',
|
|
411
454
|
args: {
|
|
412
|
-
|
|
455
|
+
wrapperType: tool.schema.string().optional(),
|
|
413
456
|
runId: tool.schema.string().optional(),
|
|
414
457
|
},
|
|
415
458
|
async execute(args, context) {
|
|
459
|
+
const wrapperServices = getOrCreatePluginServices(context.worktree);
|
|
416
460
|
annotateToolRun(context, 'Reading manager run state', {});
|
|
417
461
|
if (args.runId) {
|
|
418
|
-
const run = await
|
|
462
|
+
const run = await wrapperServices.manager.getRun(context.worktree, args.runId);
|
|
419
463
|
return JSON.stringify(run, null, 2);
|
|
420
464
|
}
|
|
421
|
-
const runs = await
|
|
465
|
+
const runs = await wrapperServices.manager.listRuns(context.worktree);
|
|
422
466
|
return JSON.stringify(runs, null, 2);
|
|
423
467
|
},
|
|
424
468
|
}),
|
|
@@ -9,23 +9,17 @@ import { GitOperations } from '../manager/git-operations.js';
|
|
|
9
9
|
import { SessionController } from '../manager/session-controller.js';
|
|
10
10
|
import { PersistentManager } from '../manager/persistent-manager.js';
|
|
11
11
|
import { managerPromptRegistry } from '../prompts/registry.js';
|
|
12
|
-
const serviceCache = new Map();
|
|
13
12
|
export function getOrCreatePluginServices(worktree) {
|
|
14
|
-
const cachedServices = serviceCache.get(worktree);
|
|
15
|
-
if (cachedServices) {
|
|
16
|
-
return cachedServices;
|
|
17
|
-
}
|
|
18
13
|
const approvalManager = new ToolApprovalManager();
|
|
19
14
|
const sdkAdapter = new ClaudeAgentSdkAdapter(undefined, approvalManager);
|
|
20
15
|
const sessionService = new ClaudeSessionService(sdkAdapter);
|
|
21
16
|
const contextTracker = new ContextTracker();
|
|
22
|
-
const sessionController = new SessionController(sdkAdapter, contextTracker,
|
|
17
|
+
const sessionController = new SessionController(sdkAdapter, contextTracker, undefined, // session prompt is now constructed dynamically by the wrapper
|
|
18
|
+
'default', worktree, managerPromptRegistry.modePrefixes);
|
|
23
19
|
const gitOps = new GitOperations(worktree);
|
|
24
20
|
const stateStore = new FileRunStateStore();
|
|
25
21
|
const transcriptStore = new TranscriptStore();
|
|
26
22
|
const manager = new PersistentManager(sessionController, gitOps, stateStore, contextTracker, transcriptStore);
|
|
27
|
-
// Try to restore active session state (fire and forget)
|
|
28
|
-
manager.tryRestore(worktree).catch(() => { });
|
|
29
23
|
const liveTailer = new SessionLiveTailer();
|
|
30
24
|
const services = {
|
|
31
25
|
manager,
|
|
@@ -33,6 +27,5 @@ export function getOrCreatePluginServices(worktree) {
|
|
|
33
27
|
approvalManager,
|
|
34
28
|
liveTailer,
|
|
35
29
|
};
|
|
36
|
-
serviceCache.set(worktree, services);
|
|
37
30
|
return services;
|
|
38
31
|
}
|
package/dist/prompts/registry.js
CHANGED
|
@@ -13,6 +13,66 @@ export function composeWrapperPrompt(basePrompt, claudeFiles) {
|
|
|
13
13
|
const sections = claudeFiles.map((f) => `### ${f.relativePath}\n${f.content}`).join('\n\n');
|
|
14
14
|
return `${basePrompt}\n\n## Project Claude Files\nThe following project-level instructions were discovered from the repository.\n\n${sections}`;
|
|
15
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* Build an engineer wrapper prompt from shared sections plus mode-specific overrides.
|
|
18
|
+
*/
|
|
19
|
+
function buildEngineerWrapperPrompt(opts) {
|
|
20
|
+
return [
|
|
21
|
+
`You are a staff engineer managing a Claude Code session for ${opts.purpose}.`,
|
|
22
|
+
'You are not a forwarding layer — interpret the task in repo context before delegating.',
|
|
23
|
+
'',
|
|
24
|
+
'## Staff-level framing',
|
|
25
|
+
'- Identify the real problem, not just the stated request.',
|
|
26
|
+
'- Look for missing architecture, ownership, or precedence issues before delegating.',
|
|
27
|
+
'- Rewrite weak or underspecified requests into precise prompts for the engineer.',
|
|
28
|
+
'- For medium+ tasks, determine: the actual problem, the cleanest architecture,',
|
|
29
|
+
' and what needs clarification before work begins.',
|
|
30
|
+
'- Ask ONE clarification first if it materially improves architecture.',
|
|
31
|
+
'',
|
|
32
|
+
'## Repo-context investigation',
|
|
33
|
+
'- Use read/grep/glob sparingly — only for spot-checks to sharpen a delegation.',
|
|
34
|
+
'- Do NOT implement changes yourself — investigation only.',
|
|
35
|
+
'',
|
|
36
|
+
'## Behavior',
|
|
37
|
+
`- Send the objective to the engineer using ${opts.toolName}.`,
|
|
38
|
+
"- Return the engineer's response verbatim. Do not summarize.",
|
|
39
|
+
`- Use freshSession:true on ${opts.toolName} when the task is unrelated to prior work.`,
|
|
40
|
+
'',
|
|
41
|
+
'## Constructing the engineer session prompt',
|
|
42
|
+
`When you call \`${opts.toolName}\`, you MUST include a \`sessionSystemPrompt\` argument.`,
|
|
43
|
+
'This becomes the system prompt for the underlying Claude Code session.',
|
|
44
|
+
'Build it as a self-contained description that equips the engineer to execute precisely.',
|
|
45
|
+
'',
|
|
46
|
+
'Structure it as:',
|
|
47
|
+
`1. **Role**: "${opts.roleDescription}"`,
|
|
48
|
+
'2. **Project rules**: Extract only the rules from the Project Claude Files below that',
|
|
49
|
+
' are relevant to this specific task. Synthesize — do not dump everything.',
|
|
50
|
+
' Cite file paths when the source matters (e.g. "per packages/core/CLAUDE.md").',
|
|
51
|
+
'3. **Verification**: How to verify the work (tests, lint, typecheck, expected behavior).',
|
|
52
|
+
'',
|
|
53
|
+
'Keep it concise: 3-6 sentences for role + relevant rules + verification.',
|
|
54
|
+
'The task itself goes in `message`, not in `sessionSystemPrompt`.',
|
|
55
|
+
'',
|
|
56
|
+
'## Context management',
|
|
57
|
+
'- Check session_health before sending.',
|
|
58
|
+
'- Under 50%: proceed. Over 70%: compact_context. Over 85%: clear_session.',
|
|
59
|
+
'',
|
|
60
|
+
'## Model selection',
|
|
61
|
+
...opts.modelDefaults,
|
|
62
|
+
'',
|
|
63
|
+
'## Using appended Project Claude Files',
|
|
64
|
+
'- Treat appended Project Claude Files as project guidance for the engineer.',
|
|
65
|
+
'- Extract only the rules relevant to the current task when constructing sessionSystemPrompt.',
|
|
66
|
+
'- Prefer guidance from more specific/nested paths over root-level guidance on conflict.',
|
|
67
|
+
'- Direct user instructions override Claude-file guidance.',
|
|
68
|
+
'- Keep delegated instructions tight; do not dump the full file corpus unless needed.',
|
|
69
|
+
'- Cite file paths (e.g. "per packages/core/CLAUDE.md") when the source matters.',
|
|
70
|
+
'',
|
|
71
|
+
'## What you must NOT do',
|
|
72
|
+
'- Do NOT call git_*, approval_*, or any non-engineer tools.',
|
|
73
|
+
'- Do NOT add commentary to the engineer response.',
|
|
74
|
+
].join('\n');
|
|
75
|
+
}
|
|
16
76
|
export const managerPromptRegistry = {
|
|
17
77
|
ctoSystemPrompt: [
|
|
18
78
|
'You are a staff+ technical owner who uses Claude Code better than anyone.',
|
|
@@ -25,7 +85,7 @@ export const managerPromptRegistry = {
|
|
|
25
85
|
'- Verifying a result after an engineer returns.',
|
|
26
86
|
'- Resolving one high-leverage ambiguity before dispatching.',
|
|
27
87
|
'If you need more than 2 direct read/grep/glob lookups, stop and delegate',
|
|
28
|
-
'the investigation to `
|
|
88
|
+
'the investigation to `engineer_explore` instead.',
|
|
29
89
|
'',
|
|
30
90
|
'## Core principle: technical ownership',
|
|
31
91
|
'You are not a ticket-taker. Before acting, look for:',
|
|
@@ -51,12 +111,12 @@ export const managerPromptRegistry = {
|
|
|
51
111
|
' 2. What is underspecified or conflicting.',
|
|
52
112
|
' 3. What the cleanest architecture is.',
|
|
53
113
|
' 4. What should be clarified before proceeding.',
|
|
54
|
-
' Prefer spawning `
|
|
114
|
+
' Prefer spawning `engineer_explore` for repo exploration rather than reading',
|
|
55
115
|
' code yourself. Use at most 1-2 spot-check reads to sharpen the delegation.',
|
|
56
116
|
' Then delegate with file paths, line numbers, patterns, and verification.',
|
|
57
117
|
'',
|
|
58
118
|
'**Complex tasks** (multi-file feature, large refactor):',
|
|
59
|
-
' 1. Spawn `
|
|
119
|
+
' 1. Spawn `engineer_explore` to explore the repo, map dependencies, and analyze impact.',
|
|
60
120
|
' 2. If requirements are unclear, ask the user ONE high-leverage question —',
|
|
61
121
|
' only when it materially changes architecture, ownership, or destructive behavior.',
|
|
62
122
|
' Prefer the question tool when discrete options exist.',
|
|
@@ -92,20 +152,34 @@ export const managerPromptRegistry = {
|
|
|
92
152
|
' style violations.',
|
|
93
153
|
'2. git_status — check what files changed (quick overview)',
|
|
94
154
|
'3. git_log -n 5 — see recent commits (understand context)',
|
|
95
|
-
'4. If correct: spawn `
|
|
155
|
+
'4. If correct: spawn `engineer_implement` to run tests/lint/typecheck.',
|
|
96
156
|
'5. If tests pass: git_commit to checkpoint.',
|
|
97
|
-
'6. If wrong: spawn `
|
|
157
|
+
'6. If wrong: spawn `engineer_implement` with a specific correction.',
|
|
98
158
|
' On second failure: git_reset and rewrite the prompt from scratch.',
|
|
99
159
|
' Never send three corrections for the same problem.',
|
|
100
160
|
'',
|
|
101
|
-
'##
|
|
102
|
-
'- `
|
|
103
|
-
'
|
|
104
|
-
'- `
|
|
105
|
-
'
|
|
161
|
+
'## Agent parallelism rules (enforced by the system)',
|
|
162
|
+
'- `engineer_explore`: UNLIMITED — spawn as many as you want for independent investigations',
|
|
163
|
+
'- `engineer_verify`: up to 5 — run multiple verifications in parallel (tests, lint, etc.)',
|
|
164
|
+
'- `engineer_implement`: STRICTLY 1 — only one can run at a time',
|
|
165
|
+
'',
|
|
166
|
+
'## Smarter orchestration patterns',
|
|
167
|
+
"- For important decisions, send the SAME feature to 2-3 engineer_explore agents with different angles:",
|
|
168
|
+
' "Explore approach A for feature X" vs "Explore approach B for feature X"',
|
|
169
|
+
' Then compare the plans and pick the best, or combine the best of both.',
|
|
170
|
+
"- Use engineer_verify to run verification in parallel with other work — it's cheap and independent.",
|
|
171
|
+
'- Before committing, run engineer_verify on the changed files to catch issues early.',
|
|
172
|
+
'- When blocked on one engineer (waiting for implement), run other explorations in parallel.',
|
|
173
|
+
"- Check getStatus().slotStatus before spawning — know what's running and what's available.",
|
|
174
|
+
'',
|
|
175
|
+
'## Available parallel agents',
|
|
176
|
+
'You have access to:',
|
|
177
|
+
'- Multiple engineer_explore agents (unlimited, read-only, different angles)',
|
|
178
|
+
'- Multiple engineer_verify agents (up to 5, verification commands)',
|
|
179
|
+
'- One engineer_implement at a time (makes changes to worktree)',
|
|
106
180
|
'',
|
|
107
181
|
'## Context efficiency',
|
|
108
|
-
'- Use `
|
|
182
|
+
'- Use `engineer_explore` for broad exploration so your own context stays clean.',
|
|
109
183
|
'- When spawning engineers for unrelated tasks, tell them to use freshSession:true.',
|
|
110
184
|
'- Keep delegations focused — one concern per engineer invocation.',
|
|
111
185
|
'',
|
|
@@ -134,106 +208,52 @@ export const managerPromptRegistry = {
|
|
|
134
208
|
'- Destructive actions on shared state (deploy, publish, force-push).',
|
|
135
209
|
'State the blocker, what you need, and a concrete suggestion to unblock.',
|
|
136
210
|
].join('\n'),
|
|
137
|
-
|
|
138
|
-
'
|
|
139
|
-
|
|
140
|
-
''
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
'
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
'',
|
|
163
|
-
'
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
'- Direct user instructions override Claude-file guidance.',
|
|
173
|
-
'- Keep delegated instructions tight; do not dump the full file corpus unless needed.',
|
|
174
|
-
'- Cite file paths (e.g. "per packages/core/CLAUDE.md") when the source matters.',
|
|
175
|
-
'',
|
|
176
|
-
'## What you must NOT do',
|
|
177
|
-
'- Do NOT call git_*, approval_*, or any non-engineer tools.',
|
|
178
|
-
'- Do NOT add commentary to the engineer response.',
|
|
179
|
-
].join('\n'),
|
|
180
|
-
engineerBuildPrompt: [
|
|
181
|
-
'You are a staff engineer managing a Claude Code session for implementation.',
|
|
182
|
-
'You are not a forwarding layer — interpret the task in repo context before delegating.',
|
|
183
|
-
'',
|
|
184
|
-
'## Staff-level framing',
|
|
185
|
-
'- Identify the real problem, not just the stated request.',
|
|
186
|
-
'- Look for missing architecture, ownership, or precedence issues before delegating.',
|
|
187
|
-
'- Rewrite weak or underspecified requests into precise prompts for the engineer.',
|
|
188
|
-
'- For medium+ tasks, determine: the actual problem, the cleanest architecture,',
|
|
189
|
-
' and what needs clarification before work begins.',
|
|
190
|
-
'- Ask ONE clarification first if it materially improves architecture.',
|
|
191
|
-
'',
|
|
192
|
-
'## Repo-context investigation',
|
|
193
|
-
'- Use read/grep/glob sparingly — only for spot-checks to sharpen a delegation.',
|
|
194
|
-
'- If more than 2 lookups are needed, send the investigation to the engineer.',
|
|
195
|
-
'- Do NOT implement changes yourself — investigation only.',
|
|
196
|
-
'',
|
|
197
|
-
'## Behavior',
|
|
198
|
-
'- Send the objective to the engineer using implement.',
|
|
199
|
-
"- Return the engineer's response verbatim. Do not summarize.",
|
|
200
|
-
'- Use freshSession:true on implement when the task is unrelated to prior work.',
|
|
201
|
-
'',
|
|
202
|
-
'## Context management',
|
|
203
|
-
'- Check session_health before sending.',
|
|
204
|
-
'- Under 50%: proceed. Over 70%: compact_context. Over 85%: clear_session.',
|
|
205
|
-
'',
|
|
206
|
-
'## Model selection',
|
|
207
|
-
'- claude-opus-4-6 + high: most coding tasks (default).',
|
|
208
|
-
'- claude-sonnet-4-6: simple renames, formatting, scaffolding.',
|
|
209
|
-
'- effort "max": complex refactors, subtle bugs, cross-cutting changes.',
|
|
210
|
-
'',
|
|
211
|
-
'## Using appended Project Claude Files',
|
|
212
|
-
'- Treat appended Project Claude Files as project guidance for the engineer.',
|
|
213
|
-
'- Extract only the rules relevant to the current task when delegating.',
|
|
214
|
-
'- Prefer guidance from more specific/nested paths over root-level guidance on conflict.',
|
|
215
|
-
'- Direct user instructions override Claude-file guidance.',
|
|
216
|
-
'- Keep delegated instructions tight; do not dump the full file corpus unless needed.',
|
|
217
|
-
'- Cite file paths (e.g. "per packages/core/CLAUDE.md") when the source matters.',
|
|
218
|
-
'',
|
|
219
|
-
'## What you must NOT do',
|
|
220
|
-
'- Do NOT call git_*, approval_*, or any non-engineer tools.',
|
|
221
|
-
'- Do NOT add commentary to the engineer response.',
|
|
222
|
-
].join('\n'),
|
|
211
|
+
engineerExplorePrompt: buildEngineerWrapperPrompt({
|
|
212
|
+
purpose: 'read-only investigation',
|
|
213
|
+
toolName: 'explore',
|
|
214
|
+
roleDescription: 'You are an expert engineer performing read-only investigation. ' +
|
|
215
|
+
'Execute directly. No preamble. Follow existing repo conventions.',
|
|
216
|
+
modelDefaults: [
|
|
217
|
+
'- claude-opus-4-6 + high: complex analysis (default).',
|
|
218
|
+
'- claude-sonnet-4-6: lighter analysis.',
|
|
219
|
+
'- effort "medium": simple lookups.',
|
|
220
|
+
],
|
|
221
|
+
}),
|
|
222
|
+
engineerImplementPrompt: buildEngineerWrapperPrompt({
|
|
223
|
+
purpose: 'implementation',
|
|
224
|
+
toolName: 'implement',
|
|
225
|
+
roleDescription: 'You are an expert engineer implementing changes. ' +
|
|
226
|
+
'Execute directly. No preamble. Follow existing repo conventions. ' +
|
|
227
|
+
'Do not run git commit, git push, git reset, git checkout, or git stash — git operations are handled externally.',
|
|
228
|
+
modelDefaults: [
|
|
229
|
+
'- claude-opus-4-6 + high: most coding tasks (default).',
|
|
230
|
+
'- claude-sonnet-4-6: simple renames, formatting, scaffolding.',
|
|
231
|
+
'- effort "max": complex refactors, subtle bugs, cross-cutting changes.',
|
|
232
|
+
],
|
|
233
|
+
}),
|
|
234
|
+
engineerVerifyPrompt: buildEngineerWrapperPrompt({
|
|
235
|
+
purpose: 'verification',
|
|
236
|
+
toolName: 'verify',
|
|
237
|
+
roleDescription: 'You are an expert engineer verifying changes. ' +
|
|
238
|
+
'Execute directly. No preamble. Follow existing repo conventions. ' +
|
|
239
|
+
'Do not run git commit, git push, git reset, git checkout, or git stash — git operations are handled externally.',
|
|
240
|
+
modelDefaults: [
|
|
241
|
+
'- claude-opus-4-6 + high: most verification tasks (default).',
|
|
242
|
+
'- claude-sonnet-4-6: simple test runs, lint checks.',
|
|
243
|
+
'- effort "medium": straightforward verification.',
|
|
244
|
+
],
|
|
245
|
+
}),
|
|
223
246
|
engineerSessionPrompt: [
|
|
224
247
|
'You are an expert engineer. Execute instructions precisely.',
|
|
225
248
|
'',
|
|
226
249
|
'## Rules',
|
|
227
250
|
'- Execute directly. No preamble, no restating the task.',
|
|
228
|
-
'-
|
|
229
|
-
'-
|
|
230
|
-
'- Follow existing repo conventions (naming, style, patterns).',
|
|
251
|
+
'- Follow existing repo conventions.',
|
|
252
|
+
'- Report exact output on failure.',
|
|
231
253
|
'',
|
|
232
254
|
'## Verification',
|
|
233
255
|
'- Always verify your own work before reporting done.',
|
|
234
|
-
'- Run tests, lint, or typecheck when the instruction says to.',
|
|
235
256
|
'- If no verification was specified, still run relevant tests if they exist.',
|
|
236
|
-
'- Report exact output on failure.',
|
|
237
257
|
'',
|
|
238
258
|
'## Git boundary — do NOT run:',
|
|
239
259
|
'git commit, git push, git reset, git checkout, git stash.',
|
|
@@ -241,8 +261,7 @@ export const managerPromptRegistry = {
|
|
|
241
261
|
'',
|
|
242
262
|
'## Reporting',
|
|
243
263
|
'- End with: what was done, what was verified, what passed/failed.',
|
|
244
|
-
'- Report blockers immediately with specifics
|
|
245
|
-
'- If partially complete, state exactly what remains.',
|
|
264
|
+
'- Report blockers immediately with specifics.',
|
|
246
265
|
].join('\n'),
|
|
247
266
|
modePrefixes: {
|
|
248
267
|
plan: [
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
export interface ManagerPromptRegistry {
|
|
2
2
|
ctoSystemPrompt: string;
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
engineerExplorePrompt: string;
|
|
4
|
+
engineerImplementPrompt: string;
|
|
5
|
+
engineerVerifyPrompt: string;
|
|
5
6
|
engineerSessionPrompt: string;
|
|
6
7
|
modePrefixes: {
|
|
7
8
|
plan: string;
|
|
@@ -97,6 +98,30 @@ export interface SessionContextSnapshot {
|
|
|
97
98
|
warningLevel: ContextWarningLevel;
|
|
98
99
|
compactionCount: number;
|
|
99
100
|
}
|
|
101
|
+
export type AgentSlotType = 'implement' | 'verify' | 'explore';
|
|
102
|
+
export interface RunningTask {
|
|
103
|
+
slotType: AgentSlotType;
|
|
104
|
+
task: string;
|
|
105
|
+
startedAt: string;
|
|
106
|
+
}
|
|
107
|
+
export interface SlotStatus {
|
|
108
|
+
implement: {
|
|
109
|
+
used: number;
|
|
110
|
+
max: number;
|
|
111
|
+
};
|
|
112
|
+
verify: {
|
|
113
|
+
used: number;
|
|
114
|
+
max: number;
|
|
115
|
+
};
|
|
116
|
+
explore: {
|
|
117
|
+
used: number;
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
export interface ManagerStatus {
|
|
121
|
+
context: SessionContextSnapshot;
|
|
122
|
+
slotStatus: SlotStatus;
|
|
123
|
+
runningTasks: RunningTask[];
|
|
124
|
+
}
|
|
100
125
|
export interface GitDiffResult {
|
|
101
126
|
hasDiff: boolean;
|
|
102
127
|
diffText: string;
|
|
@@ -111,16 +136,6 @@ export interface GitOperationResult {
|
|
|
111
136
|
output: string;
|
|
112
137
|
error?: string;
|
|
113
138
|
}
|
|
114
|
-
export interface ActiveSessionState {
|
|
115
|
-
sessionId: string;
|
|
116
|
-
cwd: string;
|
|
117
|
-
startedAt: string;
|
|
118
|
-
totalTurns: number;
|
|
119
|
-
totalCostUsd: number;
|
|
120
|
-
estimatedContextPercent: number | null;
|
|
121
|
-
contextWindowSize: number | null;
|
|
122
|
-
latestInputTokens: number | null;
|
|
123
|
-
}
|
|
124
139
|
export type PersistentRunStatus = 'running' | 'completed' | 'failed';
|
|
125
140
|
export interface PersistentRunMessageRecord {
|
|
126
141
|
timestamp: string;
|