@doingdev/opencode-claude-manager-plugin 0.1.10 → 0.1.12
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 +26 -29
- package/dist/claude/claude-agent-sdk-adapter.d.ts +2 -1
- package/dist/claude/claude-agent-sdk-adapter.js +248 -61
- package/dist/index.d.ts +1 -1
- package/dist/manager/context-tracker.d.ts +33 -0
- package/dist/manager/context-tracker.js +108 -0
- package/dist/manager/git-operations.d.ts +12 -0
- package/dist/manager/git-operations.js +76 -0
- package/dist/manager/manager-orchestrator.d.ts +1 -4
- package/dist/manager/manager-orchestrator.js +37 -53
- package/dist/manager/persistent-manager.d.ts +71 -0
- package/dist/manager/persistent-manager.js +167 -0
- package/dist/manager/session-controller.d.ts +38 -0
- package/dist/manager/session-controller.js +135 -0
- package/dist/manager/task-planner.d.ts +2 -2
- package/dist/manager/task-planner.js +4 -31
- package/dist/plugin/claude-manager.plugin.d.ts +0 -2
- package/dist/plugin/claude-manager.plugin.js +202 -199
- package/dist/plugin/service-factory.d.ts +4 -3
- package/dist/plugin/service-factory.js +14 -4
- package/dist/prompts/registry.js +42 -8
- package/dist/state/file-run-state-store.d.ts +5 -5
- package/dist/state/file-run-state-store.js +1 -11
- package/dist/state/transcript-store.d.ts +15 -0
- package/dist/state/transcript-store.js +44 -0
- package/dist/types/contracts.d.ts +68 -45
- package/dist/util/fs-helpers.d.ts +2 -0
- package/dist/util/fs-helpers.js +12 -0
- package/dist/util/transcript-append.d.ts +7 -0
- package/dist/util/transcript-append.js +29 -0
- package/package.json +12 -10
|
@@ -0,0 +1,135 @@
|
|
|
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
|
+
export class SessionController {
|
|
5
|
+
sdkAdapter;
|
|
6
|
+
contextTracker;
|
|
7
|
+
sessionPrompt;
|
|
8
|
+
activeSessionId = null;
|
|
9
|
+
constructor(sdkAdapter, contextTracker, sessionPrompt) {
|
|
10
|
+
this.sdkAdapter = sdkAdapter;
|
|
11
|
+
this.contextTracker = contextTracker;
|
|
12
|
+
this.sessionPrompt = sessionPrompt;
|
|
13
|
+
}
|
|
14
|
+
get isActive() {
|
|
15
|
+
return this.activeSessionId !== null;
|
|
16
|
+
}
|
|
17
|
+
get sessionId() {
|
|
18
|
+
return this.activeSessionId;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Send a message to the persistent session. Creates one if none exists.
|
|
22
|
+
* Returns the session result including usage data.
|
|
23
|
+
*/
|
|
24
|
+
async sendMessage(cwd, message, options, onEvent) {
|
|
25
|
+
const input = {
|
|
26
|
+
cwd,
|
|
27
|
+
prompt: message,
|
|
28
|
+
persistSession: true,
|
|
29
|
+
permissionMode: 'acceptEdits',
|
|
30
|
+
includePartialMessages: true,
|
|
31
|
+
model: options?.model,
|
|
32
|
+
settingSources: options?.settingSources ?? ['user', 'project', 'local'],
|
|
33
|
+
};
|
|
34
|
+
if (this.activeSessionId) {
|
|
35
|
+
// Resume existing session
|
|
36
|
+
input.resumeSessionId = this.activeSessionId;
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
// New session — apply the expert operator system prompt
|
|
40
|
+
input.systemPrompt = this.sessionPrompt;
|
|
41
|
+
}
|
|
42
|
+
const result = await this.sdkAdapter.runSession(input, onEvent);
|
|
43
|
+
// Track the session ID
|
|
44
|
+
if (result.sessionId) {
|
|
45
|
+
this.activeSessionId = result.sessionId;
|
|
46
|
+
}
|
|
47
|
+
// Update context tracking
|
|
48
|
+
this.contextTracker.recordResult({
|
|
49
|
+
sessionId: result.sessionId,
|
|
50
|
+
turns: result.turns,
|
|
51
|
+
totalCostUsd: result.totalCostUsd,
|
|
52
|
+
inputTokens: result.inputTokens,
|
|
53
|
+
outputTokens: result.outputTokens,
|
|
54
|
+
contextWindowSize: result.contextWindowSize,
|
|
55
|
+
});
|
|
56
|
+
// Persist active session state
|
|
57
|
+
await this.persistActiveSession(cwd);
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Send /compact to the current session to compress context.
|
|
62
|
+
*/
|
|
63
|
+
async compactSession(cwd, onEvent) {
|
|
64
|
+
if (!this.activeSessionId) {
|
|
65
|
+
throw new Error('No active session to compact');
|
|
66
|
+
}
|
|
67
|
+
const result = await this.sendMessage(cwd, '/compact', undefined, onEvent);
|
|
68
|
+
this.contextTracker.recordCompaction();
|
|
69
|
+
return result;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Clear the current session. The next sendMessage will create a fresh one.
|
|
73
|
+
*/
|
|
74
|
+
async clearSession(cwd) {
|
|
75
|
+
const clearedId = this.activeSessionId;
|
|
76
|
+
this.activeSessionId = null;
|
|
77
|
+
this.contextTracker.reset();
|
|
78
|
+
await this.removeActiveSession(cwd);
|
|
79
|
+
return clearedId;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Get current context tracking snapshot.
|
|
83
|
+
*/
|
|
84
|
+
getContextSnapshot() {
|
|
85
|
+
return this.contextTracker.snapshot();
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Try to restore active session from persisted state on startup.
|
|
89
|
+
*/
|
|
90
|
+
async tryRestore(cwd) {
|
|
91
|
+
const filePath = join(cwd, ACTIVE_SESSION_FILE);
|
|
92
|
+
try {
|
|
93
|
+
const raw = await readFile(filePath, 'utf-8');
|
|
94
|
+
const state = JSON.parse(raw);
|
|
95
|
+
if (state.sessionId && state.cwd === cwd) {
|
|
96
|
+
this.activeSessionId = state.sessionId;
|
|
97
|
+
this.contextTracker.restore(state);
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
// File doesn't exist or is corrupt — start fresh
|
|
103
|
+
}
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
async persistActiveSession(cwd) {
|
|
107
|
+
if (!this.activeSessionId) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const snap = this.contextTracker.snapshot();
|
|
111
|
+
const state = {
|
|
112
|
+
sessionId: this.activeSessionId,
|
|
113
|
+
cwd,
|
|
114
|
+
startedAt: new Date().toISOString(),
|
|
115
|
+
totalTurns: snap.totalTurns,
|
|
116
|
+
totalCostUsd: snap.totalCostUsd,
|
|
117
|
+
estimatedContextPercent: snap.estimatedContextPercent,
|
|
118
|
+
contextWindowSize: snap.contextWindowSize,
|
|
119
|
+
latestInputTokens: snap.latestInputTokens,
|
|
120
|
+
};
|
|
121
|
+
const filePath = join(cwd, ACTIVE_SESSION_FILE);
|
|
122
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
123
|
+
await writeFile(filePath, JSON.stringify(state, null, 2));
|
|
124
|
+
}
|
|
125
|
+
async removeActiveSession(cwd) {
|
|
126
|
+
const filePath = join(cwd, ACTIVE_SESSION_FILE);
|
|
127
|
+
try {
|
|
128
|
+
const { unlink } = await import('node:fs/promises');
|
|
129
|
+
await unlink(filePath);
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
// File doesn't exist — that's fine
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { ManagedSubtaskPlan
|
|
1
|
+
import type { ManagedSubtaskPlan } from '../types/contracts.js';
|
|
2
2
|
export declare class TaskPlanner {
|
|
3
|
-
plan(
|
|
3
|
+
plan(tasks: string[], maxSubagents: number): ManagedSubtaskPlan[];
|
|
4
4
|
private createPlan;
|
|
5
5
|
}
|
|
@@ -1,20 +1,9 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
export class TaskPlanner {
|
|
3
|
-
plan(
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const splitCandidates = extractTaskCandidates(task).slice(0, maxSubagents);
|
|
8
|
-
if (mode === 'split' && splitCandidates.length === 0) {
|
|
9
|
-
return [this.createPlan('Primary task', task)];
|
|
10
|
-
}
|
|
11
|
-
if (mode === 'auto' && splitCandidates.length < 2) {
|
|
12
|
-
return [this.createPlan('Primary task', task)];
|
|
13
|
-
}
|
|
14
|
-
if (splitCandidates.length === 0) {
|
|
15
|
-
return [this.createPlan('Primary task', task)];
|
|
16
|
-
}
|
|
17
|
-
return splitCandidates.map((candidate, index) => this.createPlan(`Subtask ${index + 1}`, candidate));
|
|
3
|
+
plan(tasks, maxSubagents) {
|
|
4
|
+
return tasks
|
|
5
|
+
.slice(0, maxSubagents)
|
|
6
|
+
.map((task, index) => this.createPlan(tasks.length === 1 ? 'Primary task' : `Subtask ${index + 1}`, task));
|
|
18
7
|
}
|
|
19
8
|
createPlan(title, prompt) {
|
|
20
9
|
return {
|
|
@@ -24,19 +13,3 @@ export class TaskPlanner {
|
|
|
24
13
|
};
|
|
25
14
|
}
|
|
26
15
|
}
|
|
27
|
-
function extractTaskCandidates(task) {
|
|
28
|
-
const listMatches = task
|
|
29
|
-
.split(/\r?\n/)
|
|
30
|
-
.map((line) => line.trim())
|
|
31
|
-
.filter((line) => /^(-|\*|\d+\.)\s+/.test(line))
|
|
32
|
-
.map((line) => line.replace(/^(-|\*|\d+\.)\s+/, '').trim())
|
|
33
|
-
.filter(Boolean);
|
|
34
|
-
if (listMatches.length > 0) {
|
|
35
|
-
return listMatches;
|
|
36
|
-
}
|
|
37
|
-
const sentenceMatches = task
|
|
38
|
-
.split(/;|\n{2,}/)
|
|
39
|
-
.map((part) => part.trim())
|
|
40
|
-
.filter((part) => part.length > 20);
|
|
41
|
-
return sentenceMatches.length > 1 ? sentenceMatches : [];
|
|
42
|
-
}
|