@doingdev/opencode-claude-manager-plugin 0.1.44 → 0.1.47
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/README.md +29 -31
- package/dist/index.d.ts +1 -1
- package/dist/manager/persistent-manager.d.ts +3 -23
- package/dist/manager/persistent-manager.js +2 -95
- package/dist/manager/team-orchestrator.d.ts +50 -0
- package/dist/manager/team-orchestrator.js +360 -0
- package/dist/plugin/agent-hierarchy.d.ts +12 -34
- package/dist/plugin/agent-hierarchy.js +36 -129
- package/dist/plugin/claude-manager.plugin.js +190 -445
- package/dist/plugin/service-factory.d.ts +18 -3
- package/dist/plugin/service-factory.js +32 -1
- package/dist/prompts/registry.d.ts +1 -10
- package/dist/prompts/registry.js +42 -270
- package/dist/src/claude/claude-agent-sdk-adapter.js +2 -1
- package/dist/src/claude/session-live-tailer.js +2 -2
- package/dist/src/index.d.ts +1 -1
- package/dist/src/manager/git-operations.d.ts +10 -1
- package/dist/src/manager/git-operations.js +18 -3
- package/dist/src/manager/persistent-manager.d.ts +18 -6
- package/dist/src/manager/persistent-manager.js +19 -13
- package/dist/src/manager/session-controller.d.ts +7 -10
- package/dist/src/manager/session-controller.js +12 -62
- package/dist/src/manager/team-orchestrator.d.ts +50 -0
- package/dist/src/manager/team-orchestrator.js +360 -0
- package/dist/src/plugin/agent-hierarchy.d.ts +12 -26
- package/dist/src/plugin/agent-hierarchy.js +36 -99
- package/dist/src/plugin/claude-manager.plugin.js +214 -393
- package/dist/src/plugin/service-factory.d.ts +18 -3
- package/dist/src/plugin/service-factory.js +33 -9
- package/dist/src/prompts/registry.d.ts +1 -10
- package/dist/src/prompts/registry.js +41 -246
- package/dist/src/state/team-state-store.d.ts +14 -0
- package/dist/src/state/team-state-store.js +85 -0
- package/dist/src/team/roster.d.ts +5 -0
- package/dist/src/team/roster.js +38 -0
- package/dist/src/types/contracts.d.ts +55 -13
- package/dist/src/types/contracts.js +1 -1
- package/dist/state/team-state-store.d.ts +14 -0
- package/dist/state/team-state-store.js +85 -0
- package/dist/team/roster.d.ts +5 -0
- package/dist/team/roster.js +38 -0
- package/dist/test/claude-manager.plugin.test.js +55 -280
- package/dist/test/git-operations.test.js +65 -1
- package/dist/test/persistent-manager.test.js +3 -3
- package/dist/test/prompt-registry.test.js +32 -252
- package/dist/test/session-controller.test.js +27 -27
- package/dist/test/team-orchestrator.test.d.ts +1 -0
- package/dist/test/team-orchestrator.test.js +146 -0
- package/dist/test/team-state-store.test.d.ts +1 -0
- package/dist/test/team-state-store.test.js +54 -0
- package/dist/types/contracts.d.ts +50 -23
- package/dist/types/contracts.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,17 +4,20 @@ This package provides an OpenCode plugin that lets an OpenCode-side agent hierar
|
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
7
|
-
Use this when you want OpenCode to act
|
|
7
|
+
Use this when you want OpenCode to act like a real technical lead over Claude Code instead of being a thin relay. The plugin gives you a CTO agent that can ask better questions, explicitly assign named engineers, reuse each engineer's Claude session for continuity, preserve wrapper-level engineer memory, compare multiple plans, and keep git/review work at the manager layer.
|
|
8
8
|
|
|
9
9
|
## Features
|
|
10
10
|
|
|
11
11
|
- Runs Claude Code tasks from OpenCode through `@anthropic-ai/claude-agent-sdk`.
|
|
12
|
-
-
|
|
13
|
-
-
|
|
12
|
+
- Creates a persistent named team: `Tom`, `John`, `Maya`, `Sara`, and `Alex`.
|
|
13
|
+
- Reuses one Claude Code session per engineer within the active CTO team.
|
|
14
|
+
- Reloads prior engineer wrapper context so each named subagent can prompt Claude better over time.
|
|
15
|
+
- Uses named engineer subagents for live delegated work while keeping both wrapper memory and Claude session continuity underneath.
|
|
16
|
+
- Keeps session babysitting out of the normal user flow — no public reset/fresh-session controls.
|
|
14
17
|
- Discovers repo-local Claude metadata from `.claude/skills`, `.claude/commands`, `CLAUDE.md`, and settings hooks.
|
|
15
18
|
- Git integration: diff, commit, and reset from the manager layer.
|
|
16
19
|
- Tool approval policy for governing which Claude Code tools are allowed.
|
|
17
|
-
-
|
|
20
|
+
- Persists local team state and transcripts under `.claude-manager/` for continuity and inspection.
|
|
18
21
|
|
|
19
22
|
## Requirements
|
|
20
23
|
|
|
@@ -51,17 +54,17 @@ If you are testing locally, point OpenCode at the local package or plugin file u
|
|
|
51
54
|
|
|
52
55
|
## OpenCode tools
|
|
53
56
|
|
|
54
|
-
###
|
|
57
|
+
### CTO orchestration
|
|
55
58
|
|
|
56
|
-
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
- `
|
|
62
|
-
- `
|
|
63
|
-
- `
|
|
64
|
-
- `
|
|
59
|
+
- Use the built-in OpenCode `task` tool to delegate to named engineers: `tom`, `john`, `maya`, `sara`, `alex`.
|
|
60
|
+
- `team_status` — inspect the current CTO team's engineer bindings, Claude session IDs, busy flags, and context snapshots.
|
|
61
|
+
|
|
62
|
+
### Engineer bridge
|
|
63
|
+
|
|
64
|
+
- `claude` — available only inside named engineer subagents. Sends work through that engineer's persistent Claude Code session.
|
|
65
|
+
- `mode` (required) — `explore`, `implement`, or `verify`.
|
|
66
|
+
- `message` (required) — the work to do.
|
|
67
|
+
- `model` (optional) — `claude-opus-4-6` or `claude-sonnet-4-6`.
|
|
65
68
|
|
|
66
69
|
### Git operations
|
|
67
70
|
|
|
@@ -72,7 +75,7 @@ If you are testing locally, point OpenCode at the local package or plugin file u
|
|
|
72
75
|
### Inspection
|
|
73
76
|
|
|
74
77
|
- `list_transcripts` — list available session transcripts or inspect a specific transcript by ID.
|
|
75
|
-
- `list_history` — list
|
|
78
|
+
- `list_history` — list saved CTO teams for the worktree or inspect one team by ID.
|
|
76
79
|
|
|
77
80
|
### Tool approval
|
|
78
81
|
|
|
@@ -82,11 +85,11 @@ If you are testing locally, point OpenCode at the local package or plugin file u
|
|
|
82
85
|
|
|
83
86
|
## Agent hierarchy
|
|
84
87
|
|
|
85
|
-
The plugin registers a CTO
|
|
88
|
+
The plugin registers a CTO + named engineer team through the OpenCode plugin `config` hook:
|
|
86
89
|
|
|
87
|
-
- **`cto`** (primary agent) —
|
|
88
|
-
- **`
|
|
89
|
-
- **
|
|
90
|
+
- **`cto`** (primary agent) — owns the outcome, finds missing requirements, spawns named engineers with the Task tool, compares plans, reviews diffs, and manages git.
|
|
91
|
+
- **`tom`**, **`john`**, **`maya`**, **`sara`**, **`alex`** (subagents) — thin named engineer wrappers. Each uses the `claude` tool and keeps one persistent Claude Code session.
|
|
92
|
+
- **Claude Code sessions** — the underlying execution layer. One session per engineer inside the active team.
|
|
90
93
|
|
|
91
94
|
These are added to OpenCode config at runtime by the plugin, so they do not require separate manual `opencode.json` entries.
|
|
92
95
|
|
|
@@ -94,27 +97,21 @@ These are added to OpenCode config at runtime by the plugin, so they do not requ
|
|
|
94
97
|
|
|
95
98
|
Typical flow inside OpenCode:
|
|
96
99
|
|
|
97
|
-
1.
|
|
98
|
-
2.
|
|
100
|
+
1. Ask the `cto` agent for the work.
|
|
101
|
+
2. Let `cto` spawn one or more named engineers with the Task tool.
|
|
99
102
|
3. Review changes with `git_diff`, then commit or reset.
|
|
100
|
-
4. Inspect saved Claude history with `list_transcripts` or
|
|
103
|
+
4. Inspect saved Claude history with `list_transcripts` or saved team state with `list_history` / `team_status`.
|
|
101
104
|
|
|
102
105
|
Example tasks:
|
|
103
106
|
|
|
104
107
|
```text
|
|
105
|
-
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
Start a fresh session for an unrelated task:
|
|
109
|
-
|
|
110
|
-
```text
|
|
111
|
-
Use explore with freshSession:true to investigate the failing CI test in test/api.test.ts.
|
|
108
|
+
Ask CTO to send Tom to implement the new validation logic in src/auth.ts, then review with git_diff.
|
|
112
109
|
```
|
|
113
110
|
|
|
114
|
-
|
|
111
|
+
For a larger feature where you want two independent plans first:
|
|
115
112
|
|
|
116
113
|
```text
|
|
117
|
-
|
|
114
|
+
Ask CTO to spawn Maya and Alex in parallel to create two plans for the new billing feature, then synthesize the best combined plan.
|
|
118
115
|
```
|
|
119
116
|
|
|
120
117
|
## Local Development
|
|
@@ -176,6 +173,7 @@ After trusted publishing is working, you can tighten npm package security by dis
|
|
|
176
173
|
|
|
177
174
|
- Claude slash commands and skills come primarily from filesystem discovery; SDK probing is available but optional.
|
|
178
175
|
- Session state is local to the repo under `.claude-manager/` and is ignored by git.
|
|
176
|
+
- The strongest team continuity comes when engineers are spawned from the active `cto` session; the plugin maps named engineers back to that active team automatically.
|
|
179
177
|
- Context tracking is heuristic-based; actual SDK context usage may differ slightly.
|
|
180
178
|
|
|
181
179
|
## Scripts
|
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, ContextWarningLevel, SessionMode, EngineerName, EngineerWorkMode, WrapperHistoryEntry, TeamEngineerRecord, TeamRecord, EngineerTaskResult, PlanDraft, SynthesizedPlanResult, 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 {
|
|
1
|
+
import type { ClaudeSessionEvent, ClaudeSessionRunResult, PersistentRunRecord, PersistentRunResult, SessionContextSnapshot, GitDiffResult, GitOperationResult } 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,10 +12,6 @@ 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;
|
|
19
15
|
constructor(sessionController: SessionController, gitOps: GitOperations, stateStore: FileRunStateStore, contextTracker: ContextTracker, transcriptStore: TranscriptStore);
|
|
20
16
|
/**
|
|
21
17
|
* Send a message to the persistent Claude Code session.
|
|
@@ -65,25 +61,9 @@ export declare class PersistentManager {
|
|
|
65
61
|
*/
|
|
66
62
|
gitReset(): Promise<GitOperationResult>;
|
|
67
63
|
/**
|
|
68
|
-
* Get current session status
|
|
64
|
+
* Get current session status and context health.
|
|
69
65
|
*/
|
|
70
|
-
getStatus():
|
|
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
|
+
getStatus(): SessionContextSnapshot;
|
|
87
67
|
/**
|
|
88
68
|
* Clear the active session. Next send creates a fresh one.
|
|
89
69
|
*/
|
|
@@ -5,19 +5,6 @@ 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
|
-
};
|
|
21
8
|
constructor(sessionController, gitOps, stateStore, contextTracker, transcriptStore) {
|
|
22
9
|
this.sessionController = sessionController;
|
|
23
10
|
this.gitOps = gitOps;
|
|
@@ -76,90 +63,10 @@ export class PersistentManager {
|
|
|
76
63
|
return this.gitOps.resetHard();
|
|
77
64
|
}
|
|
78
65
|
/**
|
|
79
|
-
* Get current session status
|
|
66
|
+
* Get current session status and context health.
|
|
80
67
|
*/
|
|
81
68
|
getStatus() {
|
|
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];
|
|
69
|
+
return this.sessionController.getContextSnapshot();
|
|
163
70
|
}
|
|
164
71
|
/**
|
|
165
72
|
* Clear the active session. Next send creates a fresh one.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { ClaudeSessionEventHandler } from '../claude/claude-agent-sdk-adapter.js';
|
|
2
|
+
import type { ClaudeSessionService } from '../claude/claude-session.service.js';
|
|
3
|
+
import type { TeamStateStore } from '../state/team-state-store.js';
|
|
4
|
+
import type { TranscriptStore } from '../state/transcript-store.js';
|
|
5
|
+
import type { DiscoveredClaudeFile, EngineerName, EngineerTaskResult, EngineerWorkMode, SynthesizedPlanResult, TeamRecord } from '../types/contracts.js';
|
|
6
|
+
interface DispatchEngineerInput {
|
|
7
|
+
teamId: string;
|
|
8
|
+
cwd: string;
|
|
9
|
+
engineer: EngineerName;
|
|
10
|
+
mode: EngineerWorkMode;
|
|
11
|
+
message: string;
|
|
12
|
+
model?: string;
|
|
13
|
+
abortSignal?: AbortSignal;
|
|
14
|
+
onEvent?: ClaudeSessionEventHandler;
|
|
15
|
+
}
|
|
16
|
+
export declare class TeamOrchestrator {
|
|
17
|
+
private readonly sessions;
|
|
18
|
+
private readonly teamStore;
|
|
19
|
+
private readonly transcriptStore;
|
|
20
|
+
private readonly engineerSessionPrompt;
|
|
21
|
+
private readonly projectClaudeFiles;
|
|
22
|
+
constructor(sessions: ClaudeSessionService, teamStore: TeamStateStore, transcriptStore: TranscriptStore, engineerSessionPrompt: string, projectClaudeFiles: DiscoveredClaudeFile[]);
|
|
23
|
+
getOrCreateTeam(cwd: string, teamId: string): Promise<TeamRecord>;
|
|
24
|
+
listTeams(cwd: string): Promise<TeamRecord[]>;
|
|
25
|
+
recordWrapperSession(cwd: string, teamId: string, engineer: EngineerName, wrapperSessionId: string): Promise<void>;
|
|
26
|
+
recordWrapperExchange(cwd: string, teamId: string, engineer: EngineerName, wrapperSessionId: string, mode: EngineerWorkMode, assignment: string, result: string): Promise<void>;
|
|
27
|
+
getWrapperSystemContext(cwd: string, teamId: string, engineer: EngineerName): Promise<string | null>;
|
|
28
|
+
findTeamByWrapperSession(cwd: string, wrapperSessionId: string): Promise<{
|
|
29
|
+
teamId: string;
|
|
30
|
+
engineer: EngineerName;
|
|
31
|
+
} | null>;
|
|
32
|
+
dispatchEngineer(input: DispatchEngineerInput): Promise<EngineerTaskResult>;
|
|
33
|
+
planWithTeam(input: {
|
|
34
|
+
teamId: string;
|
|
35
|
+
cwd: string;
|
|
36
|
+
request: string;
|
|
37
|
+
leadEngineer: EngineerName;
|
|
38
|
+
challengerEngineer: EngineerName;
|
|
39
|
+
model?: string;
|
|
40
|
+
abortSignal?: AbortSignal;
|
|
41
|
+
}): Promise<SynthesizedPlanResult>;
|
|
42
|
+
private updateEngineer;
|
|
43
|
+
private reserveEngineer;
|
|
44
|
+
private getEngineerState;
|
|
45
|
+
private normalizeTeamRecord;
|
|
46
|
+
private buildSessionSystemPrompt;
|
|
47
|
+
private buildEngineerPrompt;
|
|
48
|
+
private mapWorkModeToSessionMode;
|
|
49
|
+
}
|
|
50
|
+
export {};
|