@doingdev/opencode-claude-manager-plugin 0.1.43 → 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 +23 -7
- package/dist/manager/persistent-manager.js +95 -8
- package/dist/manager/session-controller.d.ts +0 -6
- package/dist/manager/session-controller.js +0 -55
- package/dist/plugin/agent-hierarchy.d.ts +9 -1
- package/dist/plugin/agent-hierarchy.js +31 -1
- package/dist/plugin/claude-manager.plugin.js +52 -12
- package/dist/plugin/service-factory.js +0 -2
- package/dist/prompts/registry.js +31 -5
- package/dist/types/contracts.d.ts +25 -9
- 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.
|
|
@@ -61,9 +65,25 @@ export declare class PersistentManager {
|
|
|
61
65
|
*/
|
|
62
66
|
gitReset(): Promise<GitOperationResult>;
|
|
63
67
|
/**
|
|
64
|
-
* Get current session status and
|
|
68
|
+
* Get current session status, context health, and slot usage.
|
|
65
69
|
*/
|
|
66
|
-
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[];
|
|
67
87
|
/**
|
|
68
88
|
* Clear the active session. Next send creates a fresh one.
|
|
69
89
|
*/
|
|
@@ -83,10 +103,6 @@ export declare class PersistentManager {
|
|
|
83
103
|
executeTask(cwd: string, task: string, options?: {
|
|
84
104
|
model?: string;
|
|
85
105
|
}, onProgress?: PersistentManagerProgressHandler): Promise<PersistentRunResult>;
|
|
86
|
-
/**
|
|
87
|
-
* Try to restore session state from disk on startup.
|
|
88
|
-
*/
|
|
89
|
-
tryRestore(): Promise<boolean>;
|
|
90
106
|
listRuns(cwd: string): Promise<PersistentRunRecord[]>;
|
|
91
107
|
getRun(cwd: string, runId: string): Promise<PersistentRunRecord | null>;
|
|
92
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;
|
|
@@ -63,10 +76,90 @@ 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.
|
|
@@ -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() {
|
|
174
|
-
return this.sessionController.tryRestore();
|
|
175
|
-
}
|
|
176
263
|
listRuns(cwd) {
|
|
177
264
|
return this.stateStore.listRuns(cwd);
|
|
178
265
|
}
|
|
@@ -38,10 +38,4 @@ export declare class SessionController {
|
|
|
38
38
|
* Get current context tracking snapshot.
|
|
39
39
|
*/
|
|
40
40
|
getContextSnapshot(): SessionContextSnapshot;
|
|
41
|
-
/**
|
|
42
|
-
* Try to restore active session from persisted state on startup.
|
|
43
|
-
*/
|
|
44
|
-
tryRestore(): Promise<boolean>;
|
|
45
|
-
private persistActiveSession;
|
|
46
|
-
private removeActiveSession;
|
|
47
41
|
}
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
-
import { dirname } from 'node:path';
|
|
3
|
-
function activeSessionFile(_wrapperType) {
|
|
4
|
-
return `.claude-manager/active-session.json`;
|
|
5
|
-
}
|
|
6
1
|
export class SessionController {
|
|
7
2
|
sdkAdapter;
|
|
8
3
|
contextTracker;
|
|
@@ -71,8 +66,6 @@ export class SessionController {
|
|
|
71
66
|
outputTokens: result.outputTokens,
|
|
72
67
|
contextWindowSize: result.contextWindowSize,
|
|
73
68
|
});
|
|
74
|
-
// Persist active session state
|
|
75
|
-
await this.persistActiveSession();
|
|
76
69
|
return result;
|
|
77
70
|
}
|
|
78
71
|
/**
|
|
@@ -93,7 +86,6 @@ export class SessionController {
|
|
|
93
86
|
const clearedId = this.activeSessionId;
|
|
94
87
|
this.activeSessionId = null;
|
|
95
88
|
this.contextTracker.reset();
|
|
96
|
-
await this.removeActiveSession();
|
|
97
89
|
return clearedId;
|
|
98
90
|
}
|
|
99
91
|
/**
|
|
@@ -102,51 +94,4 @@ export class SessionController {
|
|
|
102
94
|
getContextSnapshot() {
|
|
103
95
|
return this.contextTracker.snapshot();
|
|
104
96
|
}
|
|
105
|
-
/**
|
|
106
|
-
* Try to restore active session from persisted state on startup.
|
|
107
|
-
*/
|
|
108
|
-
async tryRestore() {
|
|
109
|
-
const filePath = activeSessionFile(this.wrapperType);
|
|
110
|
-
try {
|
|
111
|
-
const raw = await readFile(filePath, 'utf-8');
|
|
112
|
-
const state = JSON.parse(raw);
|
|
113
|
-
if (state.sessionId) {
|
|
114
|
-
this.activeSessionId = state.sessionId;
|
|
115
|
-
this.contextTracker.restore(state);
|
|
116
|
-
return true;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
catch {
|
|
120
|
-
// File doesn't exist or is corrupt — start fresh
|
|
121
|
-
}
|
|
122
|
-
return false;
|
|
123
|
-
}
|
|
124
|
-
async persistActiveSession() {
|
|
125
|
-
if (!this.activeSessionId) {
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
const snap = this.contextTracker.snapshot();
|
|
129
|
-
const state = {
|
|
130
|
-
sessionId: this.activeSessionId,
|
|
131
|
-
startedAt: new Date().toISOString(),
|
|
132
|
-
totalTurns: snap.totalTurns,
|
|
133
|
-
totalCostUsd: snap.totalCostUsd,
|
|
134
|
-
estimatedContextPercent: snap.estimatedContextPercent,
|
|
135
|
-
contextWindowSize: snap.contextWindowSize,
|
|
136
|
-
latestInputTokens: snap.latestInputTokens,
|
|
137
|
-
};
|
|
138
|
-
const filePath = activeSessionFile(this.wrapperType);
|
|
139
|
-
await mkdir(dirname(filePath), { recursive: true });
|
|
140
|
-
await writeFile(filePath, JSON.stringify(state, null, 2));
|
|
141
|
-
}
|
|
142
|
-
async removeActiveSession() {
|
|
143
|
-
const filePath = activeSessionFile(this.wrapperType);
|
|
144
|
-
try {
|
|
145
|
-
const { unlink } = await import('node:fs/promises');
|
|
146
|
-
await unlink(filePath);
|
|
147
|
-
}
|
|
148
|
-
catch {
|
|
149
|
-
// File doesn't exist — that's fine
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
97
|
}
|
|
@@ -10,8 +10,9 @@ import type { ManagerPromptRegistry } from '../types/contracts.js';
|
|
|
10
10
|
export declare const AGENT_CTO = "cto";
|
|
11
11
|
export declare const AGENT_ENGINEER_EXPLORE = "engineer_explore";
|
|
12
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;
|
|
@@ -55,6 +56,13 @@ export declare function buildEngineerImplementAgentConfig(prompts: ManagerPrompt
|
|
|
55
56
|
permission: AgentPermission;
|
|
56
57
|
prompt: string;
|
|
57
58
|
};
|
|
59
|
+
export declare function buildEngineerVerifyAgentConfig(prompts: ManagerPromptRegistry): {
|
|
60
|
+
description: string;
|
|
61
|
+
mode: "subagent";
|
|
62
|
+
color: string;
|
|
63
|
+
permission: AgentPermission;
|
|
64
|
+
prompt: string;
|
|
65
|
+
};
|
|
58
66
|
/** Deny all restricted tools at the global level so only designated agents can use them. */
|
|
59
67
|
export declare function denyRestrictedToolsGlobally(permissions: Record<string, ToolPermission>): void;
|
|
60
68
|
export {};
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
export const AGENT_CTO = 'cto';
|
|
13
13
|
export const AGENT_ENGINEER_EXPLORE = 'engineer_explore';
|
|
14
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
|
+
const ENGINEER_TOOL_IDS = ['explore', 'implement', 'verify', ...ENGINEER_SHARED_TOOL_IDS];
|
|
28
29
|
/** Tools for the engineer_explore wrapper (explore-mode send + shared) */
|
|
29
30
|
const ENGINEER_EXPLORE_TOOL_IDS = ['explore', ...ENGINEER_SHARED_TOOL_IDS];
|
|
30
31
|
/** Tools for the engineer_implement wrapper (implement-mode send + shared) */
|
|
31
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 */
|
|
@@ -79,6 +82,7 @@ function buildCtoPermissions() {
|
|
|
79
82
|
'*': 'deny',
|
|
80
83
|
[AGENT_ENGINEER_EXPLORE]: 'allow',
|
|
81
84
|
[AGENT_ENGINEER_IMPLEMENT]: 'allow',
|
|
85
|
+
[AGENT_ENGINEER_VERIFY]: 'allow',
|
|
82
86
|
},
|
|
83
87
|
};
|
|
84
88
|
}
|
|
@@ -116,6 +120,23 @@ function buildEngineerImplementPermissions() {
|
|
|
116
120
|
...allowed,
|
|
117
121
|
};
|
|
118
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) {
|
|
131
|
+
allowed[toolId] = 'allow';
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
'*': 'deny',
|
|
135
|
+
...READONLY_TOOLS,
|
|
136
|
+
...denied,
|
|
137
|
+
...allowed,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
119
140
|
// ---------------------------------------------------------------------------
|
|
120
141
|
// Agent config builders
|
|
121
142
|
// ---------------------------------------------------------------------------
|
|
@@ -146,6 +167,15 @@ export function buildEngineerImplementAgentConfig(prompts) {
|
|
|
146
167
|
prompt: prompts.engineerImplementPrompt,
|
|
147
168
|
};
|
|
148
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,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
149
179
|
// ---------------------------------------------------------------------------
|
|
150
180
|
// Global permission helper
|
|
151
181
|
// ---------------------------------------------------------------------------
|
|
@@ -1,7 +1,7 @@
|
|
|
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, AGENT_ENGINEER_EXPLORE, AGENT_ENGINEER_IMPLEMENT, buildCtoAgentConfig, buildEngineerExploreAgentConfig, buildEngineerImplementAgentConfig, denyRestrictedToolsGlobally, } from './agent-hierarchy.js';
|
|
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);
|
|
@@ -10,7 +10,8 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
|
|
|
10
10
|
if (args.freshSession) {
|
|
11
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,7 +19,7 @@ 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
|
});
|
|
@@ -213,10 +214,12 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
|
|
|
213
214
|
...managerPromptRegistry,
|
|
214
215
|
engineerExplorePrompt: composeWrapperPrompt(managerPromptRegistry.engineerExplorePrompt, claudeFiles),
|
|
215
216
|
engineerImplementPrompt: composeWrapperPrompt(managerPromptRegistry.engineerImplementPrompt, claudeFiles),
|
|
217
|
+
engineerVerifyPrompt: composeWrapperPrompt(managerPromptRegistry.engineerVerifyPrompt, claudeFiles),
|
|
216
218
|
};
|
|
217
219
|
config.agent[AGENT_CTO] ??= buildCtoAgentConfig(managerPromptRegistry);
|
|
218
220
|
config.agent[AGENT_ENGINEER_EXPLORE] ??= buildEngineerExploreAgentConfig(derivedPrompts);
|
|
219
221
|
config.agent[AGENT_ENGINEER_IMPLEMENT] ??= buildEngineerImplementAgentConfig(derivedPrompts);
|
|
222
|
+
config.agent[AGENT_ENGINEER_VERIFY] ??= buildEngineerVerifyAgentConfig(derivedPrompts);
|
|
220
223
|
},
|
|
221
224
|
tool: {
|
|
222
225
|
explore: tool({
|
|
@@ -233,7 +236,37 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
|
|
|
233
236
|
sessionSystemPrompt: tool.schema.string().optional(),
|
|
234
237
|
},
|
|
235
238
|
async execute(args, context) {
|
|
236
|
-
|
|
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
|
+
}
|
|
237
270
|
},
|
|
238
271
|
}),
|
|
239
272
|
implement: tool({
|
|
@@ -249,7 +282,14 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
|
|
|
249
282
|
sessionSystemPrompt: tool.schema.string().optional(),
|
|
250
283
|
},
|
|
251
284
|
async execute(args, context) {
|
|
252
|
-
|
|
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
|
+
}
|
|
253
293
|
},
|
|
254
294
|
}),
|
|
255
295
|
compact_context: tool({
|
|
@@ -263,11 +303,11 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
|
|
|
263
303
|
annotateToolRun(context, 'Compacting session', {});
|
|
264
304
|
const result = await wrapperServices.manager.compactSession(context.worktree);
|
|
265
305
|
const snap = wrapperServices.manager.getStatus();
|
|
266
|
-
const contextWarning = formatContextWarning(snap);
|
|
306
|
+
const contextWarning = formatContextWarning(snap.context);
|
|
267
307
|
context.metadata({
|
|
268
308
|
title: contextWarning
|
|
269
|
-
? `⚠️ Claude Code: Compacted — context at ${snap.estimatedContextPercent}%`
|
|
270
|
-
: `✅ 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)})`,
|
|
271
311
|
metadata: {
|
|
272
312
|
status: contextWarning ? 'warning' : 'success',
|
|
273
313
|
sessionId: result.sessionId,
|
|
@@ -278,7 +318,7 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
|
|
|
278
318
|
finalText: result.finalText,
|
|
279
319
|
turns: result.turns,
|
|
280
320
|
totalCostUsd: result.totalCostUsd,
|
|
281
|
-
context: snap,
|
|
321
|
+
context: snap.context,
|
|
282
322
|
contextWarning,
|
|
283
323
|
}, null, 2);
|
|
284
324
|
},
|
|
@@ -379,10 +419,10 @@ export const ClaudeManagerPlugin = async ({ worktree }) => {
|
|
|
379
419
|
const status = wrapperServices.manager.getStatus();
|
|
380
420
|
return JSON.stringify({
|
|
381
421
|
...status,
|
|
382
|
-
transcriptFile: status.sessionId
|
|
383
|
-
? `.claude-manager/transcripts/${status.sessionId}.json`
|
|
422
|
+
transcriptFile: status.context.sessionId
|
|
423
|
+
? `.claude-manager/transcripts/${status.context.sessionId}.json`
|
|
384
424
|
: null,
|
|
385
|
-
contextWarning: formatContextWarning(status),
|
|
425
|
+
contextWarning: formatContextWarning(status.context),
|
|
386
426
|
}, null, 2);
|
|
387
427
|
},
|
|
388
428
|
}),
|
|
@@ -20,8 +20,6 @@ export function getOrCreatePluginServices(worktree) {
|
|
|
20
20
|
const stateStore = new FileRunStateStore();
|
|
21
21
|
const transcriptStore = new TranscriptStore();
|
|
22
22
|
const manager = new PersistentManager(sessionController, gitOps, stateStore, contextTracker, transcriptStore);
|
|
23
|
-
// Try to restore active session state (fire and forget)
|
|
24
|
-
manager.tryRestore().catch(() => { });
|
|
25
23
|
const liveTailer = new SessionLiveTailer();
|
|
26
24
|
const services = {
|
|
27
25
|
manager,
|
package/dist/prompts/registry.js
CHANGED
|
@@ -158,11 +158,25 @@ export const managerPromptRegistry = {
|
|
|
158
158
|
' On second failure: git_reset and rewrite the prompt from scratch.',
|
|
159
159
|
' Never send three corrections for the same problem.',
|
|
160
160
|
'',
|
|
161
|
-
'##
|
|
162
|
-
'- `engineer_explore
|
|
163
|
-
'
|
|
164
|
-
'- `engineer_implement
|
|
165
|
-
'
|
|
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)',
|
|
166
180
|
'',
|
|
167
181
|
'## Context efficiency',
|
|
168
182
|
'- Use `engineer_explore` for broad exploration so your own context stays clean.',
|
|
@@ -217,6 +231,18 @@ export const managerPromptRegistry = {
|
|
|
217
231
|
'- effort "max": complex refactors, subtle bugs, cross-cutting changes.',
|
|
218
232
|
],
|
|
219
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
|
+
}),
|
|
220
246
|
engineerSessionPrompt: [
|
|
221
247
|
'You are an expert engineer. Execute instructions precisely.',
|
|
222
248
|
'',
|
|
@@ -2,6 +2,7 @@ export interface ManagerPromptRegistry {
|
|
|
2
2
|
ctoSystemPrompt: string;
|
|
3
3
|
engineerExplorePrompt: string;
|
|
4
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,15 +136,6 @@ export interface GitOperationResult {
|
|
|
111
136
|
output: string;
|
|
112
137
|
error?: string;
|
|
113
138
|
}
|
|
114
|
-
export interface ActiveSessionState {
|
|
115
|
-
sessionId: string;
|
|
116
|
-
startedAt: string;
|
|
117
|
-
totalTurns: number;
|
|
118
|
-
totalCostUsd: number;
|
|
119
|
-
estimatedContextPercent: number | null;
|
|
120
|
-
contextWindowSize: number | null;
|
|
121
|
-
latestInputTokens: number | null;
|
|
122
|
-
}
|
|
123
139
|
export type PersistentRunStatus = 'running' | 'completed' | 'failed';
|
|
124
140
|
export interface PersistentRunMessageRecord {
|
|
125
141
|
timestamp: string;
|