opencode-hive 1.3.3 → 1.3.5
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 +35 -0
- package/dist/hive-core/src/services/sessionService.d.ts +8 -1
- package/dist/hive-core/src/types.d.ts +8 -0
- package/dist/hive-core/src/utils/paths.d.ts +1 -0
- package/dist/index.js +523 -88
- package/dist/opencode-hive/src/hooks/variant-hook.d.ts +14 -2
- package/dist/opencode-hive/src/utils/compaction-anchor.d.ts +14 -0
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -108,6 +108,32 @@ When using Dynamic Context Pruning (DCP), use a Hive-safe config in `~/.config/o
|
|
|
108
108
|
|
|
109
109
|
For local plugin testing, keep OpenCode plugin entry as `"opencode-hive"` (not `"opencode-hive@latest"`).
|
|
110
110
|
|
|
111
|
+
#### Compaction recovery and session re-anchoring
|
|
112
|
+
|
|
113
|
+
OpenCode can compact long sessions. When that happens mid-orchestration or mid-task, Hive needs the session to recover its role and task boundaries without re-reading the whole repository.
|
|
114
|
+
|
|
115
|
+
The plugin now persists durable session metadata and uses it during `experimental.session.compacting` to rebuild a compact re-anchor prompt.
|
|
116
|
+
|
|
117
|
+
Where:
|
|
118
|
+
|
|
119
|
+
- Global session state is written to `.hive/sessions.json`.
|
|
120
|
+
- Feature-local mirrors are written to `.hive/features/<feature>/sessions.json`.
|
|
121
|
+
- Session classification distinguishes `primary`, `subagent`, `task-worker`, and `unknown`.
|
|
122
|
+
- Primary and subagent recovery can replay the stored user directive once after compaction.
|
|
123
|
+
- For task workers, the re-anchor context can include `.hive/features/<feature>/tasks/<task>/worker-prompt.md`.
|
|
124
|
+
|
|
125
|
+
Task-worker recovery is intentionally strict:
|
|
126
|
+
|
|
127
|
+
- keep the same role
|
|
128
|
+
- do not delegate
|
|
129
|
+
- do not re-read the full codebase
|
|
130
|
+
- re-read `worker-prompt.md`
|
|
131
|
+
- continue from the last known point
|
|
132
|
+
|
|
133
|
+
This split is deliberate: post-compaction replay is for primary/subagent intent, while task-worker recovery comes from durable worktree context plus `worker-prompt.md` so implementation sessions stay attached to the exact task contract.
|
|
134
|
+
|
|
135
|
+
This matters most for `forager-worker` and forager-derived custom agents, because they are the sessions most likely to be compacted mid-implementation.
|
|
136
|
+
|
|
111
137
|
## Prompt Budgeting & Observability
|
|
112
138
|
|
|
113
139
|
Hive automatically bounds worker prompt sizes to prevent context overflow and tool output truncation.
|
|
@@ -136,6 +162,8 @@ When limits are exceeded, content is truncated with `...[truncated]` markers and
|
|
|
136
162
|
|
|
137
163
|
Large prompts are written to `.hive/features/<feature>/tasks/<task>/worker-prompt.md` and passed by file reference (`workerPromptPath`) rather than inlined in tool output. This prevents truncation of large prompts.
|
|
138
164
|
|
|
165
|
+
That same `worker-prompt.md` path is also reused during compaction recovery so task workers can re-anchor to the exact task assignment after a compacted session resumes.
|
|
166
|
+
|
|
139
167
|
## Plan Format
|
|
140
168
|
|
|
141
169
|
```markdown
|
|
@@ -359,6 +387,13 @@ ID guardrails:
|
|
|
359
387
|
- plugin-reserved aliases are blocked (`hive`, `architect`, `swarm`, `scout`, `forager`, `hygienic`, `receiver`)
|
|
360
388
|
- operational IDs are blocked (`build`, `plan`, `code`)
|
|
361
389
|
|
|
390
|
+
Compaction classification follows the base agent:
|
|
391
|
+
|
|
392
|
+
- `forager-worker` derivatives are treated as `task-worker`
|
|
393
|
+
- `hygienic-reviewer` derivatives are treated as `subagent`
|
|
394
|
+
|
|
395
|
+
This ensures custom workers recover with the same execution constraints as their base role.
|
|
396
|
+
|
|
362
397
|
### Custom Models
|
|
363
398
|
|
|
364
399
|
Override models for specific agents:
|
|
@@ -1,10 +1,17 @@
|
|
|
1
|
-
import { SessionInfo } from '../types.js';
|
|
1
|
+
import type { SessionInfo } from '../types.js';
|
|
2
2
|
export declare class SessionService {
|
|
3
3
|
private projectRoot;
|
|
4
4
|
constructor(projectRoot: string);
|
|
5
|
+
private applySessionPatch;
|
|
5
6
|
private getSessionsPath;
|
|
6
7
|
private getSessions;
|
|
7
8
|
private saveSessions;
|
|
9
|
+
private getGlobalSessions;
|
|
10
|
+
private saveGlobalSessions;
|
|
11
|
+
private updateGlobalSessions;
|
|
12
|
+
trackGlobal(sessionId: string, patch?: Partial<SessionInfo>): SessionInfo;
|
|
13
|
+
bindFeature(sessionId: string, featureName: string, patch?: Partial<SessionInfo>): SessionInfo;
|
|
14
|
+
getGlobal(sessionId: string): SessionInfo | undefined;
|
|
8
15
|
track(featureName: string, sessionId: string, taskFolder?: string): SessionInfo;
|
|
9
16
|
setMaster(featureName: string, sessionId: string): void;
|
|
10
17
|
getMaster(featureName: string): string | undefined;
|
|
@@ -120,9 +120,17 @@ export interface ContextFile {
|
|
|
120
120
|
content: string;
|
|
121
121
|
updatedAt: string;
|
|
122
122
|
}
|
|
123
|
+
export type SessionKind = 'primary' | 'subagent' | 'task-worker' | 'unknown';
|
|
123
124
|
export interface SessionInfo {
|
|
124
125
|
sessionId: string;
|
|
126
|
+
featureName?: string;
|
|
125
127
|
taskFolder?: string;
|
|
128
|
+
agent?: string;
|
|
129
|
+
baseAgent?: string;
|
|
130
|
+
sessionKind?: SessionKind;
|
|
131
|
+
workerPromptPath?: string;
|
|
132
|
+
directivePrompt?: string;
|
|
133
|
+
replayDirectivePending?: boolean;
|
|
126
134
|
startedAt: string;
|
|
127
135
|
lastActiveAt: string;
|
|
128
136
|
messageCount?: number;
|
|
@@ -3,6 +3,7 @@ export declare function normalizePath(filePath: string): string;
|
|
|
3
3
|
export declare function getHivePath(projectRoot: string): string;
|
|
4
4
|
export declare function getFeaturesPath(projectRoot: string): string;
|
|
5
5
|
export declare function getActiveFeaturePath(projectRoot: string): string;
|
|
6
|
+
export declare function getGlobalSessionsPath(projectRoot: string): string;
|
|
6
7
|
export declare function listFeatureDirectories(projectRoot: string): FeatureDirectoryInfo[];
|
|
7
8
|
export declare function resolveFeatureDirectoryName(projectRoot: string, featureName: string): string;
|
|
8
9
|
export declare function getNextIndexedFeatureDirectoryName(projectRoot: string, featureName: string): string;
|
package/dist/index.js
CHANGED
|
@@ -871,8 +871,8 @@ var require_dist2 = __commonJS((exports) => {
|
|
|
871
871
|
});
|
|
872
872
|
|
|
873
873
|
// src/index.ts
|
|
874
|
-
import * as
|
|
875
|
-
import * as
|
|
874
|
+
import * as path11 from "path";
|
|
875
|
+
import * as fs13 from "fs";
|
|
876
876
|
import * as os from "os";
|
|
877
877
|
|
|
878
878
|
// ../../node_modules/zod/v4/classic/external.js
|
|
@@ -16738,6 +16738,9 @@ function getFeaturesPath(projectRoot) {
|
|
|
16738
16738
|
function getActiveFeaturePath(projectRoot) {
|
|
16739
16739
|
return path2.join(getHivePath(projectRoot), ACTIVE_FEATURE_FILE);
|
|
16740
16740
|
}
|
|
16741
|
+
function getGlobalSessionsPath(projectRoot) {
|
|
16742
|
+
return path2.join(getHivePath(projectRoot), "sessions.json");
|
|
16743
|
+
}
|
|
16741
16744
|
function parseIndexedFeatureDirectoryName(directoryName) {
|
|
16742
16745
|
const match = directoryName.match(/^(\d+)[_-](.+)$/);
|
|
16743
16746
|
if (!match) {
|
|
@@ -22431,17 +22434,217 @@ ${f.content}`);
|
|
|
22431
22434
|
return `${normalized}.md`;
|
|
22432
22435
|
}
|
|
22433
22436
|
}
|
|
22434
|
-
// ../hive-core/src/services/
|
|
22437
|
+
// ../hive-core/src/services/sessionService.ts
|
|
22435
22438
|
import * as fs9 from "fs";
|
|
22436
22439
|
import * as path7 from "path";
|
|
22440
|
+
class SessionService {
|
|
22441
|
+
projectRoot;
|
|
22442
|
+
constructor(projectRoot) {
|
|
22443
|
+
this.projectRoot = projectRoot;
|
|
22444
|
+
}
|
|
22445
|
+
applySessionPatch(target, patch) {
|
|
22446
|
+
if (!patch) {
|
|
22447
|
+
return;
|
|
22448
|
+
}
|
|
22449
|
+
const { sessionId: _sessionId, ...rest } = patch;
|
|
22450
|
+
Object.assign(target, rest);
|
|
22451
|
+
}
|
|
22452
|
+
getSessionsPath(featureName) {
|
|
22453
|
+
return path7.join(getFeaturePath(this.projectRoot, featureName), "sessions.json");
|
|
22454
|
+
}
|
|
22455
|
+
getSessions(featureName) {
|
|
22456
|
+
const sessionsPath = this.getSessionsPath(featureName);
|
|
22457
|
+
return readJson(sessionsPath) || { sessions: [] };
|
|
22458
|
+
}
|
|
22459
|
+
saveSessions(featureName, data) {
|
|
22460
|
+
const sessionsPath = this.getSessionsPath(featureName);
|
|
22461
|
+
ensureDir(path7.dirname(sessionsPath));
|
|
22462
|
+
writeJson(sessionsPath, data);
|
|
22463
|
+
}
|
|
22464
|
+
getGlobalSessions() {
|
|
22465
|
+
const globalPath = getGlobalSessionsPath(this.projectRoot);
|
|
22466
|
+
return readJson(globalPath) || { sessions: [] };
|
|
22467
|
+
}
|
|
22468
|
+
saveGlobalSessions(data) {
|
|
22469
|
+
const globalPath = getGlobalSessionsPath(this.projectRoot);
|
|
22470
|
+
ensureDir(path7.dirname(globalPath));
|
|
22471
|
+
writeJson(globalPath, data);
|
|
22472
|
+
}
|
|
22473
|
+
updateGlobalSessions(mutator) {
|
|
22474
|
+
const globalPath = getGlobalSessionsPath(this.projectRoot);
|
|
22475
|
+
ensureDir(path7.dirname(globalPath));
|
|
22476
|
+
const release = acquireLockSync(globalPath);
|
|
22477
|
+
try {
|
|
22478
|
+
const data = readJson(globalPath) || { sessions: [] };
|
|
22479
|
+
const session = mutator(data);
|
|
22480
|
+
writeJsonAtomic(globalPath, data);
|
|
22481
|
+
return session;
|
|
22482
|
+
} finally {
|
|
22483
|
+
release();
|
|
22484
|
+
}
|
|
22485
|
+
}
|
|
22486
|
+
trackGlobal(sessionId, patch) {
|
|
22487
|
+
return this.updateGlobalSessions((data) => {
|
|
22488
|
+
const now = new Date().toISOString();
|
|
22489
|
+
let session = data.sessions.find((s) => s.sessionId === sessionId);
|
|
22490
|
+
if (session) {
|
|
22491
|
+
session.lastActiveAt = now;
|
|
22492
|
+
this.applySessionPatch(session, patch);
|
|
22493
|
+
} else {
|
|
22494
|
+
session = {
|
|
22495
|
+
sessionId,
|
|
22496
|
+
startedAt: now,
|
|
22497
|
+
lastActiveAt: now
|
|
22498
|
+
};
|
|
22499
|
+
this.applySessionPatch(session, patch);
|
|
22500
|
+
data.sessions.push(session);
|
|
22501
|
+
}
|
|
22502
|
+
return session;
|
|
22503
|
+
});
|
|
22504
|
+
}
|
|
22505
|
+
bindFeature(sessionId, featureName, patch) {
|
|
22506
|
+
const session = this.updateGlobalSessions((data) => {
|
|
22507
|
+
let current = data.sessions.find((s) => s.sessionId === sessionId);
|
|
22508
|
+
const now = new Date().toISOString();
|
|
22509
|
+
if (!current) {
|
|
22510
|
+
current = {
|
|
22511
|
+
sessionId,
|
|
22512
|
+
startedAt: now,
|
|
22513
|
+
lastActiveAt: now
|
|
22514
|
+
};
|
|
22515
|
+
data.sessions.push(current);
|
|
22516
|
+
}
|
|
22517
|
+
current.featureName = featureName;
|
|
22518
|
+
current.lastActiveAt = now;
|
|
22519
|
+
this.applySessionPatch(current, patch);
|
|
22520
|
+
return current;
|
|
22521
|
+
});
|
|
22522
|
+
const featureData = this.getSessions(featureName);
|
|
22523
|
+
let featureSession = featureData.sessions.find((s) => s.sessionId === sessionId);
|
|
22524
|
+
if (featureSession) {
|
|
22525
|
+
Object.assign(featureSession, session);
|
|
22526
|
+
} else {
|
|
22527
|
+
featureData.sessions.push({ ...session });
|
|
22528
|
+
}
|
|
22529
|
+
this.saveSessions(featureName, featureData);
|
|
22530
|
+
return session;
|
|
22531
|
+
}
|
|
22532
|
+
getGlobal(sessionId) {
|
|
22533
|
+
const data = this.getGlobalSessions();
|
|
22534
|
+
return data.sessions.find((s) => s.sessionId === sessionId);
|
|
22535
|
+
}
|
|
22536
|
+
track(featureName, sessionId, taskFolder) {
|
|
22537
|
+
const data = this.getSessions(featureName);
|
|
22538
|
+
const now = new Date().toISOString();
|
|
22539
|
+
let session = data.sessions.find((s) => s.sessionId === sessionId);
|
|
22540
|
+
if (session) {
|
|
22541
|
+
session.lastActiveAt = now;
|
|
22542
|
+
if (taskFolder)
|
|
22543
|
+
session.taskFolder = taskFolder;
|
|
22544
|
+
} else {
|
|
22545
|
+
session = {
|
|
22546
|
+
sessionId,
|
|
22547
|
+
taskFolder,
|
|
22548
|
+
startedAt: now,
|
|
22549
|
+
lastActiveAt: now
|
|
22550
|
+
};
|
|
22551
|
+
data.sessions.push(session);
|
|
22552
|
+
}
|
|
22553
|
+
if (!data.master) {
|
|
22554
|
+
data.master = sessionId;
|
|
22555
|
+
}
|
|
22556
|
+
this.saveSessions(featureName, data);
|
|
22557
|
+
return session;
|
|
22558
|
+
}
|
|
22559
|
+
setMaster(featureName, sessionId) {
|
|
22560
|
+
const data = this.getSessions(featureName);
|
|
22561
|
+
data.master = sessionId;
|
|
22562
|
+
this.saveSessions(featureName, data);
|
|
22563
|
+
}
|
|
22564
|
+
getMaster(featureName) {
|
|
22565
|
+
return this.getSessions(featureName).master;
|
|
22566
|
+
}
|
|
22567
|
+
list(featureName) {
|
|
22568
|
+
return this.getSessions(featureName).sessions;
|
|
22569
|
+
}
|
|
22570
|
+
get(featureName, sessionId) {
|
|
22571
|
+
return this.getSessions(featureName).sessions.find((s) => s.sessionId === sessionId);
|
|
22572
|
+
}
|
|
22573
|
+
getByTask(featureName, taskFolder) {
|
|
22574
|
+
return this.getSessions(featureName).sessions.find((s) => s.taskFolder === taskFolder);
|
|
22575
|
+
}
|
|
22576
|
+
remove(featureName, sessionId) {
|
|
22577
|
+
const data = this.getSessions(featureName);
|
|
22578
|
+
const index = data.sessions.findIndex((s) => s.sessionId === sessionId);
|
|
22579
|
+
if (index === -1)
|
|
22580
|
+
return false;
|
|
22581
|
+
data.sessions.splice(index, 1);
|
|
22582
|
+
if (data.master === sessionId) {
|
|
22583
|
+
data.master = data.sessions[0]?.sessionId;
|
|
22584
|
+
}
|
|
22585
|
+
this.saveSessions(featureName, data);
|
|
22586
|
+
return true;
|
|
22587
|
+
}
|
|
22588
|
+
findFeatureBySession(sessionId) {
|
|
22589
|
+
const globalSession = this.getGlobal(sessionId);
|
|
22590
|
+
if (globalSession?.featureName) {
|
|
22591
|
+
return globalSession.featureName;
|
|
22592
|
+
}
|
|
22593
|
+
const featuresPath = path7.join(this.projectRoot, ".hive", "features");
|
|
22594
|
+
if (!fs9.existsSync(featuresPath))
|
|
22595
|
+
return null;
|
|
22596
|
+
const features = fs9.readdirSync(featuresPath, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
22597
|
+
for (const feature of features) {
|
|
22598
|
+
const sessions = this.getSessions(feature);
|
|
22599
|
+
if (sessions.sessions.some((s) => s.sessionId === sessionId)) {
|
|
22600
|
+
return feature;
|
|
22601
|
+
}
|
|
22602
|
+
if (sessions.master === sessionId) {
|
|
22603
|
+
return feature;
|
|
22604
|
+
}
|
|
22605
|
+
}
|
|
22606
|
+
return null;
|
|
22607
|
+
}
|
|
22608
|
+
fork(featureName, fromSessionId) {
|
|
22609
|
+
const data = this.getSessions(featureName);
|
|
22610
|
+
const now = new Date().toISOString();
|
|
22611
|
+
const sourceSession = fromSessionId ? data.sessions.find((s) => s.sessionId === fromSessionId) : data.sessions.find((s) => s.sessionId === data.master);
|
|
22612
|
+
const newSessionId = `ses_fork_${Date.now()}`;
|
|
22613
|
+
const newSession = {
|
|
22614
|
+
sessionId: newSessionId,
|
|
22615
|
+
taskFolder: sourceSession?.taskFolder,
|
|
22616
|
+
startedAt: now,
|
|
22617
|
+
lastActiveAt: now
|
|
22618
|
+
};
|
|
22619
|
+
data.sessions.push(newSession);
|
|
22620
|
+
this.saveSessions(featureName, data);
|
|
22621
|
+
return newSession;
|
|
22622
|
+
}
|
|
22623
|
+
fresh(featureName, title) {
|
|
22624
|
+
const data = this.getSessions(featureName);
|
|
22625
|
+
const now = new Date().toISOString();
|
|
22626
|
+
const newSessionId = `ses_${title ? title.replace(/\s+/g, "_").toLowerCase() : Date.now()}`;
|
|
22627
|
+
const newSession = {
|
|
22628
|
+
sessionId: newSessionId,
|
|
22629
|
+
startedAt: now,
|
|
22630
|
+
lastActiveAt: now
|
|
22631
|
+
};
|
|
22632
|
+
data.sessions.push(newSession);
|
|
22633
|
+
this.saveSessions(featureName, data);
|
|
22634
|
+
return newSession;
|
|
22635
|
+
}
|
|
22636
|
+
}
|
|
22637
|
+
// ../hive-core/src/services/configService.ts
|
|
22638
|
+
import * as fs10 from "fs";
|
|
22639
|
+
import * as path8 from "path";
|
|
22437
22640
|
class ConfigService {
|
|
22438
22641
|
configPath;
|
|
22439
22642
|
cachedConfig = null;
|
|
22440
22643
|
cachedCustomAgentConfigs = null;
|
|
22441
22644
|
constructor() {
|
|
22442
22645
|
const homeDir = process.env.HOME || process.env.USERPROFILE || "";
|
|
22443
|
-
const configDir =
|
|
22444
|
-
this.configPath =
|
|
22646
|
+
const configDir = path8.join(homeDir, ".config", "opencode");
|
|
22647
|
+
this.configPath = path8.join(configDir, "agent_hive.json");
|
|
22445
22648
|
}
|
|
22446
22649
|
getPath() {
|
|
22447
22650
|
return this.configPath;
|
|
@@ -22451,12 +22654,12 @@ class ConfigService {
|
|
|
22451
22654
|
return this.cachedConfig;
|
|
22452
22655
|
}
|
|
22453
22656
|
try {
|
|
22454
|
-
if (!
|
|
22657
|
+
if (!fs10.existsSync(this.configPath)) {
|
|
22455
22658
|
this.cachedConfig = { ...DEFAULT_HIVE_CONFIG };
|
|
22456
22659
|
this.cachedCustomAgentConfigs = null;
|
|
22457
22660
|
return this.cachedConfig;
|
|
22458
22661
|
}
|
|
22459
|
-
const raw =
|
|
22662
|
+
const raw = fs10.readFileSync(this.configPath, "utf-8");
|
|
22460
22663
|
const stored = JSON.parse(raw);
|
|
22461
22664
|
const storedCustomAgents = this.isObjectRecord(stored.customAgents) ? stored.customAgents : {};
|
|
22462
22665
|
const mergedBuiltInAgents = BUILT_IN_AGENT_NAMES.reduce((acc, agentName) => {
|
|
@@ -22504,17 +22707,17 @@ class ConfigService {
|
|
|
22504
22707
|
...updates.customAgents
|
|
22505
22708
|
} : current.customAgents
|
|
22506
22709
|
};
|
|
22507
|
-
const configDir =
|
|
22508
|
-
if (!
|
|
22509
|
-
|
|
22710
|
+
const configDir = path8.dirname(this.configPath);
|
|
22711
|
+
if (!fs10.existsSync(configDir)) {
|
|
22712
|
+
fs10.mkdirSync(configDir, { recursive: true });
|
|
22510
22713
|
}
|
|
22511
|
-
|
|
22714
|
+
fs10.writeFileSync(this.configPath, JSON.stringify(merged, null, 2));
|
|
22512
22715
|
this.cachedConfig = merged;
|
|
22513
22716
|
this.cachedCustomAgentConfigs = null;
|
|
22514
22717
|
return merged;
|
|
22515
22718
|
}
|
|
22516
22719
|
exists() {
|
|
22517
|
-
return
|
|
22720
|
+
return fs10.existsSync(this.configPath);
|
|
22518
22721
|
}
|
|
22519
22722
|
init() {
|
|
22520
22723
|
if (!this.exists()) {
|
|
@@ -22650,8 +22853,8 @@ class ConfigService {
|
|
|
22650
22853
|
}
|
|
22651
22854
|
}
|
|
22652
22855
|
// ../hive-core/src/services/agentsMdService.ts
|
|
22653
|
-
import * as
|
|
22654
|
-
import * as
|
|
22856
|
+
import * as fs11 from "fs";
|
|
22857
|
+
import * as path9 from "path";
|
|
22655
22858
|
class AgentsMdService {
|
|
22656
22859
|
rootDir;
|
|
22657
22860
|
contextService;
|
|
@@ -22660,7 +22863,7 @@ class AgentsMdService {
|
|
|
22660
22863
|
this.contextService = contextService;
|
|
22661
22864
|
}
|
|
22662
22865
|
async init() {
|
|
22663
|
-
const agentsMdPath =
|
|
22866
|
+
const agentsMdPath = path9.join(this.rootDir, "AGENTS.md");
|
|
22664
22867
|
const existed = fileExists(agentsMdPath);
|
|
22665
22868
|
if (existed) {
|
|
22666
22869
|
const existing = readText(agentsMdPath);
|
|
@@ -22671,14 +22874,14 @@ class AgentsMdService {
|
|
|
22671
22874
|
}
|
|
22672
22875
|
async sync(featureName) {
|
|
22673
22876
|
const contexts = this.contextService.list(featureName);
|
|
22674
|
-
const agentsMdPath =
|
|
22675
|
-
const current = await
|
|
22877
|
+
const agentsMdPath = path9.join(this.rootDir, "AGENTS.md");
|
|
22878
|
+
const current = await fs11.promises.readFile(agentsMdPath, "utf-8").catch(() => "");
|
|
22676
22879
|
const findings = this.extractFindings(contexts);
|
|
22677
22880
|
const proposals = this.generateProposals(findings, current);
|
|
22678
22881
|
return { proposals, diff: this.formatDiff(current, proposals) };
|
|
22679
22882
|
}
|
|
22680
22883
|
apply(content) {
|
|
22681
|
-
const agentsMdPath =
|
|
22884
|
+
const agentsMdPath = path9.join(this.rootDir, "AGENTS.md");
|
|
22682
22885
|
const isNew = !fileExists(agentsMdPath);
|
|
22683
22886
|
writeText(agentsMdPath, content);
|
|
22684
22887
|
return { path: agentsMdPath, chars: content.length, isNew };
|
|
@@ -22738,7 +22941,7 @@ class AgentsMdService {
|
|
|
22738
22941
|
return this.generateTemplate(detections);
|
|
22739
22942
|
}
|
|
22740
22943
|
async detectProjectInfo() {
|
|
22741
|
-
const packageJsonPath =
|
|
22944
|
+
const packageJsonPath = path9.join(this.rootDir, "package.json");
|
|
22742
22945
|
let packageJson = null;
|
|
22743
22946
|
if (fileExists(packageJsonPath)) {
|
|
22744
22947
|
try {
|
|
@@ -22758,26 +22961,26 @@ class AgentsMdService {
|
|
|
22758
22961
|
return info;
|
|
22759
22962
|
}
|
|
22760
22963
|
detectPackageManager() {
|
|
22761
|
-
if (fileExists(
|
|
22964
|
+
if (fileExists(path9.join(this.rootDir, "bun.lockb")))
|
|
22762
22965
|
return "bun";
|
|
22763
|
-
if (fileExists(
|
|
22966
|
+
if (fileExists(path9.join(this.rootDir, "pnpm-lock.yaml")))
|
|
22764
22967
|
return "pnpm";
|
|
22765
|
-
if (fileExists(
|
|
22968
|
+
if (fileExists(path9.join(this.rootDir, "yarn.lock")))
|
|
22766
22969
|
return "yarn";
|
|
22767
|
-
if (fileExists(
|
|
22970
|
+
if (fileExists(path9.join(this.rootDir, "package-lock.json")))
|
|
22768
22971
|
return "npm";
|
|
22769
22972
|
return "npm";
|
|
22770
22973
|
}
|
|
22771
22974
|
detectLanguage() {
|
|
22772
|
-
if (fileExists(
|
|
22975
|
+
if (fileExists(path9.join(this.rootDir, "tsconfig.json")))
|
|
22773
22976
|
return "TypeScript";
|
|
22774
|
-
if (fileExists(
|
|
22977
|
+
if (fileExists(path9.join(this.rootDir, "package.json")))
|
|
22775
22978
|
return "JavaScript";
|
|
22776
|
-
if (fileExists(
|
|
22979
|
+
if (fileExists(path9.join(this.rootDir, "requirements.txt")))
|
|
22777
22980
|
return "Python";
|
|
22778
|
-
if (fileExists(
|
|
22981
|
+
if (fileExists(path9.join(this.rootDir, "go.mod")))
|
|
22779
22982
|
return "Go";
|
|
22780
|
-
if (fileExists(
|
|
22983
|
+
if (fileExists(path9.join(this.rootDir, "Cargo.toml")))
|
|
22781
22984
|
return "Rust";
|
|
22782
22985
|
return "Unknown";
|
|
22783
22986
|
}
|
|
@@ -22856,25 +23059,25 @@ class AgentsMdService {
|
|
|
22856
23059
|
}
|
|
22857
23060
|
}
|
|
22858
23061
|
// ../hive-core/src/services/dockerSandboxService.ts
|
|
22859
|
-
import { existsSync as
|
|
22860
|
-
import { join as
|
|
23062
|
+
import { existsSync as existsSync5 } from "fs";
|
|
23063
|
+
import { join as join10, sep } from "path";
|
|
22861
23064
|
import { execSync } from "child_process";
|
|
22862
23065
|
|
|
22863
23066
|
class DockerSandboxService {
|
|
22864
23067
|
static detectImage(worktreePath) {
|
|
22865
|
-
if (
|
|
23068
|
+
if (existsSync5(join10(worktreePath, "Dockerfile"))) {
|
|
22866
23069
|
return null;
|
|
22867
23070
|
}
|
|
22868
|
-
if (
|
|
23071
|
+
if (existsSync5(join10(worktreePath, "package.json"))) {
|
|
22869
23072
|
return "node:22-slim";
|
|
22870
23073
|
}
|
|
22871
|
-
if (
|
|
23074
|
+
if (existsSync5(join10(worktreePath, "requirements.txt")) || existsSync5(join10(worktreePath, "pyproject.toml"))) {
|
|
22872
23075
|
return "python:3.12-slim";
|
|
22873
23076
|
}
|
|
22874
|
-
if (
|
|
23077
|
+
if (existsSync5(join10(worktreePath, "go.mod"))) {
|
|
22875
23078
|
return "golang:1.22-slim";
|
|
22876
23079
|
}
|
|
22877
|
-
if (
|
|
23080
|
+
if (existsSync5(join10(worktreePath, "Cargo.toml"))) {
|
|
22878
23081
|
return "rust:1.77-slim";
|
|
22879
23082
|
}
|
|
22880
23083
|
return "ubuntu:24.04";
|
|
@@ -23444,17 +23647,17 @@ function applyContextBudget(files, config2 = {}) {
|
|
|
23444
23647
|
}
|
|
23445
23648
|
|
|
23446
23649
|
// src/utils/prompt-file.ts
|
|
23447
|
-
import * as
|
|
23448
|
-
import * as
|
|
23650
|
+
import * as fs12 from "fs";
|
|
23651
|
+
import * as path10 from "path";
|
|
23449
23652
|
function writeWorkerPromptFile(feature, task, prompt, hiveDir) {
|
|
23450
|
-
const projectRoot =
|
|
23653
|
+
const projectRoot = path10.dirname(hiveDir);
|
|
23451
23654
|
const featureDir = resolveFeatureDirectoryName(projectRoot, feature);
|
|
23452
|
-
const promptDir =
|
|
23453
|
-
const promptPath =
|
|
23454
|
-
if (!
|
|
23455
|
-
|
|
23655
|
+
const promptDir = path10.join(hiveDir, "features", featureDir, "tasks", task);
|
|
23656
|
+
const promptPath = path10.join(promptDir, "worker-prompt.md");
|
|
23657
|
+
if (!fs12.existsSync(promptDir)) {
|
|
23658
|
+
fs12.mkdirSync(promptDir, { recursive: true });
|
|
23456
23659
|
}
|
|
23457
|
-
|
|
23660
|
+
fs12.writeFileSync(promptPath, prompt, "utf-8");
|
|
23458
23661
|
return promptPath;
|
|
23459
23662
|
}
|
|
23460
23663
|
|
|
@@ -23465,9 +23668,48 @@ function normalizeVariant(variant) {
|
|
|
23465
23668
|
const trimmed2 = variant.trim();
|
|
23466
23669
|
return trimmed2.length > 0 ? trimmed2 : undefined;
|
|
23467
23670
|
}
|
|
23468
|
-
|
|
23671
|
+
var BUILT_IN_AGENTS = {
|
|
23672
|
+
"hive-master": { sessionKind: "primary", baseAgent: "hive-master" },
|
|
23673
|
+
"architect-planner": { sessionKind: "primary", baseAgent: "architect-planner" },
|
|
23674
|
+
"swarm-orchestrator": { sessionKind: "primary", baseAgent: "swarm-orchestrator" },
|
|
23675
|
+
"forager-worker": { sessionKind: "task-worker", baseAgent: "forager-worker" },
|
|
23676
|
+
"scout-researcher": { sessionKind: "subagent", baseAgent: "scout-researcher" },
|
|
23677
|
+
"hygienic-reviewer": { sessionKind: "subagent", baseAgent: "hygienic-reviewer" }
|
|
23678
|
+
};
|
|
23679
|
+
var BASE_AGENT_KIND = {
|
|
23680
|
+
"forager-worker": "task-worker",
|
|
23681
|
+
"hygienic-reviewer": "subagent"
|
|
23682
|
+
};
|
|
23683
|
+
function classifySession(agent, customAgents = {}) {
|
|
23684
|
+
const builtIn = BUILT_IN_AGENTS[agent];
|
|
23685
|
+
if (builtIn) {
|
|
23686
|
+
return { sessionKind: builtIn.sessionKind, baseAgent: builtIn.baseAgent };
|
|
23687
|
+
}
|
|
23688
|
+
const custom2 = customAgents[agent];
|
|
23689
|
+
if (custom2) {
|
|
23690
|
+
const kind = BASE_AGENT_KIND[custom2.baseAgent];
|
|
23691
|
+
if (kind) {
|
|
23692
|
+
return { sessionKind: kind, baseAgent: custom2.baseAgent };
|
|
23693
|
+
}
|
|
23694
|
+
}
|
|
23695
|
+
return { sessionKind: "unknown", baseAgent: undefined };
|
|
23696
|
+
}
|
|
23697
|
+
function createVariantHook(configService, sessionService, customAgents, taskWorkerRecovery) {
|
|
23469
23698
|
return async (input, output) => {
|
|
23470
23699
|
const { agent } = input;
|
|
23700
|
+
if (agent && sessionService) {
|
|
23701
|
+
const { sessionKind, baseAgent } = classifySession(agent, customAgents);
|
|
23702
|
+
const patch = { agent, sessionKind };
|
|
23703
|
+
if (baseAgent) {
|
|
23704
|
+
patch.baseAgent = baseAgent;
|
|
23705
|
+
}
|
|
23706
|
+
if (sessionKind === "task-worker" && taskWorkerRecovery) {
|
|
23707
|
+
patch.featureName = taskWorkerRecovery.featureName;
|
|
23708
|
+
patch.taskFolder = taskWorkerRecovery.taskFolder;
|
|
23709
|
+
patch.workerPromptPath = taskWorkerRecovery.workerPromptPath;
|
|
23710
|
+
}
|
|
23711
|
+
sessionService.trackGlobal(input.sessionID, patch);
|
|
23712
|
+
}
|
|
23471
23713
|
if (!agent)
|
|
23472
23714
|
return;
|
|
23473
23715
|
if (!configService.hasConfiguredAgent(agent))
|
|
@@ -23501,10 +23743,69 @@ var HIVE_SYSTEM_PROMPT = `
|
|
|
23501
23743
|
Use hive_merge to integrate changes into the current branch.
|
|
23502
23744
|
`;
|
|
23503
23745
|
|
|
23504
|
-
// src/utils/compaction-
|
|
23505
|
-
var
|
|
23506
|
-
|
|
23507
|
-
|
|
23746
|
+
// src/utils/compaction-anchor.ts
|
|
23747
|
+
var AGENT_ROLE_MAP = {
|
|
23748
|
+
"hive-master": "Hive",
|
|
23749
|
+
"architect-planner": "Architect",
|
|
23750
|
+
"swarm-orchestrator": "Swarm",
|
|
23751
|
+
"forager-worker": "Forager",
|
|
23752
|
+
"scout-researcher": "Scout",
|
|
23753
|
+
"hygienic-reviewer": "Hygienic"
|
|
23754
|
+
};
|
|
23755
|
+
var BASE_AGENT_ROLE_MAP = {
|
|
23756
|
+
"forager-worker": "Forager",
|
|
23757
|
+
"hygienic-reviewer": "Hygienic",
|
|
23758
|
+
"scout-researcher": "Scout"
|
|
23759
|
+
};
|
|
23760
|
+
function resolveRole(ctx) {
|
|
23761
|
+
if (ctx.agent && AGENT_ROLE_MAP[ctx.agent]) {
|
|
23762
|
+
return AGENT_ROLE_MAP[ctx.agent];
|
|
23763
|
+
}
|
|
23764
|
+
if (ctx.baseAgent && BASE_AGENT_ROLE_MAP[ctx.baseAgent]) {
|
|
23765
|
+
return BASE_AGENT_ROLE_MAP[ctx.baseAgent];
|
|
23766
|
+
}
|
|
23767
|
+
return;
|
|
23768
|
+
}
|
|
23769
|
+
function resolveWorkerPromptPath(ctx) {
|
|
23770
|
+
if (ctx.workerPromptPath) {
|
|
23771
|
+
return ctx.workerPromptPath;
|
|
23772
|
+
}
|
|
23773
|
+
if (ctx.featureName && ctx.taskFolder) {
|
|
23774
|
+
return `.hive/features/${ctx.featureName}/tasks/${ctx.taskFolder}/worker-prompt.md`;
|
|
23775
|
+
}
|
|
23776
|
+
return;
|
|
23777
|
+
}
|
|
23778
|
+
function buildCompactionReanchor(ctx) {
|
|
23779
|
+
const role = resolveRole(ctx);
|
|
23780
|
+
const kind = ctx.sessionKind ?? "unknown";
|
|
23781
|
+
const workerPromptPath = resolveWorkerPromptPath(ctx);
|
|
23782
|
+
const lines = [];
|
|
23783
|
+
const context = [];
|
|
23784
|
+
lines.push("Compaction recovery — you were compacted mid-session.");
|
|
23785
|
+
if (role) {
|
|
23786
|
+
lines.push(`Role: ${role}`);
|
|
23787
|
+
}
|
|
23788
|
+
lines.push("Do not switch roles.");
|
|
23789
|
+
lines.push("Do not call status tools to rediscover state.");
|
|
23790
|
+
lines.push("Do not re-read the full codebase.");
|
|
23791
|
+
if (kind === "task-worker") {
|
|
23792
|
+
lines.push("Do not delegate.");
|
|
23793
|
+
if (workerPromptPath) {
|
|
23794
|
+
lines.push("Re-read worker-prompt.md now to recall your assignment.");
|
|
23795
|
+
context.push(workerPromptPath);
|
|
23796
|
+
} else {
|
|
23797
|
+
lines.push("Re-read worker-prompt.md from the Hive task metadata to recall your assignment.");
|
|
23798
|
+
}
|
|
23799
|
+
}
|
|
23800
|
+
if ((kind === "primary" || kind === "subagent") && ctx.directivePrompt) {
|
|
23801
|
+
lines.push("Original directive survives via post-compaction replay.");
|
|
23802
|
+
}
|
|
23803
|
+
lines.push("Next action: resume from where you left off.");
|
|
23804
|
+
return {
|
|
23805
|
+
prompt: lines.join(`
|
|
23806
|
+
`),
|
|
23807
|
+
context
|
|
23808
|
+
};
|
|
23508
23809
|
}
|
|
23509
23810
|
|
|
23510
23811
|
// src/index.ts
|
|
@@ -23619,13 +23920,14 @@ No Hive skills available.` : base + formatSkillsXml(filteredSkills);
|
|
|
23619
23920
|
});
|
|
23620
23921
|
}
|
|
23621
23922
|
var plugin = async (ctx) => {
|
|
23622
|
-
const { directory, client } = ctx;
|
|
23923
|
+
const { directory, client, worktree } = ctx;
|
|
23623
23924
|
const featureService = new FeatureService(directory);
|
|
23624
23925
|
const planService = new PlanService(directory);
|
|
23625
23926
|
const taskService = new TaskService(directory);
|
|
23626
23927
|
const contextService = new ContextService(directory);
|
|
23627
23928
|
const agentsMdService = new AgentsMdService(directory, contextService);
|
|
23628
23929
|
const configService = new ConfigService;
|
|
23930
|
+
const sessionService = new SessionService(directory);
|
|
23629
23931
|
const disabledMcps = configService.getDisabledMcps();
|
|
23630
23932
|
const disabledSkills = configService.getDisabledSkills();
|
|
23631
23933
|
const builtinMcps = createBuiltinMcps(disabledMcps);
|
|
@@ -23633,8 +23935,15 @@ var plugin = async (ctx) => {
|
|
|
23633
23935
|
const effectiveAutoLoadSkills = configService.getAgentConfig("hive-master").autoLoadSkills ?? [];
|
|
23634
23936
|
const worktreeService = new WorktreeService({
|
|
23635
23937
|
baseDir: directory,
|
|
23636
|
-
hiveDir:
|
|
23938
|
+
hiveDir: path11.join(directory, ".hive")
|
|
23637
23939
|
});
|
|
23940
|
+
const customAgentConfigsForClassification = getCustomAgentConfigsCompat(configService);
|
|
23941
|
+
const runtimeContext = detectContext(worktree || directory);
|
|
23942
|
+
const taskWorkerRecovery = runtimeContext.isWorktree && runtimeContext.feature && runtimeContext.task ? {
|
|
23943
|
+
featureName: runtimeContext.feature,
|
|
23944
|
+
taskFolder: runtimeContext.task,
|
|
23945
|
+
workerPromptPath: path11.posix.join(".hive", "features", resolveFeatureDirectoryName(directory, runtimeContext.feature), "tasks", runtimeContext.task, "worker-prompt.md")
|
|
23946
|
+
} : undefined;
|
|
23638
23947
|
const isOmoSlimEnabled = () => {
|
|
23639
23948
|
return configService.isOmoSlimEnabled();
|
|
23640
23949
|
};
|
|
@@ -23655,12 +23964,50 @@ var plugin = async (ctx) => {
|
|
|
23655
23964
|
}
|
|
23656
23965
|
}
|
|
23657
23966
|
};
|
|
23967
|
+
const bindFeatureSession = (feature, toolContext, patch) => {
|
|
23968
|
+
const ctx2 = toolContext;
|
|
23969
|
+
if (!ctx2?.sessionID)
|
|
23970
|
+
return;
|
|
23971
|
+
sessionService.bindFeature(ctx2.sessionID, feature, patch);
|
|
23972
|
+
};
|
|
23973
|
+
const extractTextParts = (parts) => {
|
|
23974
|
+
if (!Array.isArray(parts))
|
|
23975
|
+
return [];
|
|
23976
|
+
return parts.filter((part) => {
|
|
23977
|
+
return !!part && typeof part === "object" && part.type === "text" && typeof part.text === "string";
|
|
23978
|
+
}).map((part) => part.text.trim()).filter(Boolean);
|
|
23979
|
+
};
|
|
23980
|
+
const shouldCaptureDirective = (info, parts) => {
|
|
23981
|
+
if (info.role !== "user")
|
|
23982
|
+
return false;
|
|
23983
|
+
const textParts = parts.filter((part) => {
|
|
23984
|
+
return !!part && typeof part === "object" && part.type === "text";
|
|
23985
|
+
});
|
|
23986
|
+
if (textParts.length === 0)
|
|
23987
|
+
return false;
|
|
23988
|
+
return !textParts.every((part) => part.synthetic === true);
|
|
23989
|
+
};
|
|
23990
|
+
const buildDirectiveReplayText = (session) => {
|
|
23991
|
+
if (!session.directivePrompt)
|
|
23992
|
+
return null;
|
|
23993
|
+
const role = session.agent === "scout-researcher" || session.baseAgent === "scout-researcher" ? "Scout" : session.agent === "hygienic-reviewer" || session.baseAgent === "hygienic-reviewer" ? "Hygienic" : session.agent === "architect-planner" || session.baseAgent === "architect-planner" ? "Architect" : session.agent === "swarm-orchestrator" || session.baseAgent === "swarm-orchestrator" ? "Swarm" : session.agent === "hive-master" || session.baseAgent === "hive-master" ? "Hive" : "current role";
|
|
23994
|
+
return [
|
|
23995
|
+
`Post-compaction recovery: You are still ${role}.`,
|
|
23996
|
+
"Resume the original assignment below. Do not replace it with a new goal.",
|
|
23997
|
+
"",
|
|
23998
|
+
session.directivePrompt
|
|
23999
|
+
].join(`
|
|
24000
|
+
`);
|
|
24001
|
+
};
|
|
24002
|
+
const shouldUseDirectiveReplay = (session) => {
|
|
24003
|
+
return session?.sessionKind === "primary" || session?.sessionKind === "subagent";
|
|
24004
|
+
};
|
|
23658
24005
|
const checkBlocked = (feature) => {
|
|
23659
|
-
const
|
|
24006
|
+
const fs14 = __require("fs");
|
|
23660
24007
|
const featureDir = resolveFeatureDirectoryName(directory, feature);
|
|
23661
|
-
const blockedPath =
|
|
23662
|
-
if (
|
|
23663
|
-
const reason =
|
|
24008
|
+
const blockedPath = path11.join(directory, ".hive", "features", featureDir, "BLOCKED");
|
|
24009
|
+
if (fs14.existsSync(blockedPath)) {
|
|
24010
|
+
const reason = fs14.readFileSync(blockedPath, "utf-8").trim();
|
|
23664
24011
|
return `⛔ BLOCKED by Beekeeper
|
|
23665
24012
|
|
|
23666
24013
|
${reason || "(No reason provided)"}
|
|
@@ -23713,13 +24060,13 @@ To unblock: Remove .hive/features/${featureDir}/BLOCKED`;
|
|
|
23713
24060
|
feature,
|
|
23714
24061
|
task,
|
|
23715
24062
|
taskInfo,
|
|
23716
|
-
worktree,
|
|
24063
|
+
worktree: worktree2,
|
|
23717
24064
|
continueFrom,
|
|
23718
24065
|
decision
|
|
23719
24066
|
}) => {
|
|
23720
24067
|
taskService.update(feature, task, {
|
|
23721
24068
|
status: "in_progress",
|
|
23722
|
-
baseCommit:
|
|
24069
|
+
baseCommit: worktree2.commit
|
|
23723
24070
|
});
|
|
23724
24071
|
const planResult = planService.read(feature);
|
|
23725
24072
|
const allTasks = taskService.list(feature);
|
|
@@ -23770,8 +24117,8 @@ To unblock: Remove .hive/features/${featureDir}/BLOCKED`;
|
|
|
23770
24117
|
feature,
|
|
23771
24118
|
task,
|
|
23772
24119
|
taskOrder,
|
|
23773
|
-
worktreePath:
|
|
23774
|
-
branch:
|
|
24120
|
+
worktreePath: worktree2.path,
|
|
24121
|
+
branch: worktree2.branch,
|
|
23775
24122
|
plan: planResult?.content || "No plan available",
|
|
23776
24123
|
contextFiles,
|
|
23777
24124
|
spec: specContent,
|
|
@@ -23813,9 +24160,9 @@ To unblock: Remove .hive/features/${featureDir}/BLOCKED`;
|
|
|
23813
24160
|
spec: specContent,
|
|
23814
24161
|
workerPrompt
|
|
23815
24162
|
});
|
|
23816
|
-
const hiveDir =
|
|
24163
|
+
const hiveDir = path11.join(directory, ".hive");
|
|
23817
24164
|
const workerPromptPath = writeWorkerPromptFile(feature, task, workerPrompt, hiveDir);
|
|
23818
|
-
const relativePromptPath = normalizePath(
|
|
24165
|
+
const relativePromptPath = normalizePath(path11.relative(directory, workerPromptPath));
|
|
23819
24166
|
const PREVIEW_MAX_LENGTH = 200;
|
|
23820
24167
|
const workerPromptPreview = workerPrompt.length > PREVIEW_MAX_LENGTH ? workerPrompt.slice(0, PREVIEW_MAX_LENGTH) + "..." : workerPrompt;
|
|
23821
24168
|
const taskToolPrompt = `Follow instructions in @${relativePromptPath}`;
|
|
@@ -23844,8 +24191,8 @@ Use the \`@path\` attachment syntax in the prompt to reference the file. Do not
|
|
|
23844
24191
|
const responseBase = {
|
|
23845
24192
|
success: true,
|
|
23846
24193
|
terminal: false,
|
|
23847
|
-
worktreePath:
|
|
23848
|
-
branch:
|
|
24194
|
+
worktreePath: worktree2.path,
|
|
24195
|
+
branch: worktree2.branch,
|
|
23849
24196
|
mode: "delegate",
|
|
23850
24197
|
agent,
|
|
23851
24198
|
defaultAgent,
|
|
@@ -23981,8 +24328,8 @@ Use the \`@path\` attachment syntax in the prompt to reference the file. Do not
|
|
|
23981
24328
|
]
|
|
23982
24329
|
});
|
|
23983
24330
|
}
|
|
23984
|
-
const
|
|
23985
|
-
return buildWorktreeLaunchResponse({ feature, task, taskInfo, worktree });
|
|
24331
|
+
const worktree2 = await worktreeService.create(feature, task);
|
|
24332
|
+
return buildWorktreeLaunchResponse({ feature, task, taskInfo, worktree: worktree2 });
|
|
23986
24333
|
};
|
|
23987
24334
|
const executeBlockedResume = async ({
|
|
23988
24335
|
task,
|
|
@@ -24073,8 +24420,8 @@ Use the \`@path\` attachment syntax in the prompt to reference the file. Do not
|
|
|
24073
24420
|
]
|
|
24074
24421
|
});
|
|
24075
24422
|
}
|
|
24076
|
-
const
|
|
24077
|
-
if (!
|
|
24423
|
+
const worktree2 = await worktreeService.get(feature, task);
|
|
24424
|
+
if (!worktree2) {
|
|
24078
24425
|
return respond({
|
|
24079
24426
|
success: false,
|
|
24080
24427
|
terminal: true,
|
|
@@ -24090,12 +24437,23 @@ Use the \`@path\` attachment syntax in the prompt to reference the file. Do not
|
|
|
24090
24437
|
feature,
|
|
24091
24438
|
task,
|
|
24092
24439
|
taskInfo,
|
|
24093
|
-
worktree,
|
|
24440
|
+
worktree: worktree2,
|
|
24094
24441
|
continueFrom,
|
|
24095
24442
|
decision
|
|
24096
24443
|
});
|
|
24097
24444
|
};
|
|
24098
24445
|
return {
|
|
24446
|
+
event: async (input) => {
|
|
24447
|
+
if (input.event.type !== "session.compacted") {
|
|
24448
|
+
return;
|
|
24449
|
+
}
|
|
24450
|
+
const sessionID = input.event.properties.sessionID;
|
|
24451
|
+
const existing = sessionService.getGlobal(sessionID);
|
|
24452
|
+
if (!existing?.directivePrompt || !shouldUseDirectiveReplay(existing)) {
|
|
24453
|
+
return;
|
|
24454
|
+
}
|
|
24455
|
+
sessionService.trackGlobal(sessionID, { replayDirectivePending: true });
|
|
24456
|
+
},
|
|
24099
24457
|
"experimental.chat.system.transform": async (input, output) => {
|
|
24100
24458
|
if (!shouldExecuteHook("experimental.chat.system.transform", configService, turnCounters)) {
|
|
24101
24459
|
return;
|
|
@@ -24129,9 +24487,81 @@ Use the \`@path\` attachment syntax in the prompt to reference the file. Do not
|
|
|
24129
24487
|
}
|
|
24130
24488
|
},
|
|
24131
24489
|
"experimental.session.compacting": async (_input, output) => {
|
|
24132
|
-
|
|
24490
|
+
const session = sessionService.getGlobal(_input.sessionID);
|
|
24491
|
+
if (session) {
|
|
24492
|
+
const ctx2 = {
|
|
24493
|
+
agent: session.agent,
|
|
24494
|
+
baseAgent: session.baseAgent,
|
|
24495
|
+
sessionKind: session.sessionKind,
|
|
24496
|
+
featureName: session.featureName,
|
|
24497
|
+
taskFolder: session.taskFolder,
|
|
24498
|
+
workerPromptPath: session.workerPromptPath,
|
|
24499
|
+
directivePrompt: session.directivePrompt
|
|
24500
|
+
};
|
|
24501
|
+
const reanchor = buildCompactionReanchor(ctx2);
|
|
24502
|
+
output.prompt = reanchor.prompt;
|
|
24503
|
+
output.context.push(...reanchor.context);
|
|
24504
|
+
} else {
|
|
24505
|
+
const reanchor = buildCompactionReanchor({});
|
|
24506
|
+
output.prompt = reanchor.prompt;
|
|
24507
|
+
output.context.push(...reanchor.context);
|
|
24508
|
+
}
|
|
24509
|
+
},
|
|
24510
|
+
"chat.message": createVariantHook(configService, sessionService, customAgentConfigsForClassification, taskWorkerRecovery),
|
|
24511
|
+
"experimental.chat.messages.transform": async (_input, output) => {
|
|
24512
|
+
if (!Array.isArray(output.messages) || output.messages.length === 0) {
|
|
24513
|
+
return;
|
|
24514
|
+
}
|
|
24515
|
+
const firstMessage = output.messages[0];
|
|
24516
|
+
const sessionID = firstMessage?.info?.sessionID;
|
|
24517
|
+
if (!sessionID) {
|
|
24518
|
+
return;
|
|
24519
|
+
}
|
|
24520
|
+
const session = sessionService.getGlobal(sessionID);
|
|
24521
|
+
const captureCandidates = output.messages.filter(({ info, parts }) => info.sessionID === sessionID && shouldCaptureDirective(info, parts));
|
|
24522
|
+
const latestDirective = captureCandidates.at(-1);
|
|
24523
|
+
if (latestDirective) {
|
|
24524
|
+
const directiveText = extractTextParts(latestDirective.parts).join(`
|
|
24525
|
+
|
|
24526
|
+
`);
|
|
24527
|
+
const existingDirective = session?.directivePrompt;
|
|
24528
|
+
if (directiveText && directiveText !== existingDirective && shouldUseDirectiveReplay(session ?? { sessionKind: "subagent" })) {
|
|
24529
|
+
sessionService.trackGlobal(sessionID, { directivePrompt: directiveText });
|
|
24530
|
+
}
|
|
24531
|
+
}
|
|
24532
|
+
const refreshed = sessionService.getGlobal(sessionID);
|
|
24533
|
+
if (!refreshed?.replayDirectivePending || !shouldUseDirectiveReplay(refreshed)) {
|
|
24534
|
+
if (refreshed?.replayDirectivePending && !shouldUseDirectiveReplay(refreshed)) {
|
|
24535
|
+
sessionService.trackGlobal(sessionID, { replayDirectivePending: false });
|
|
24536
|
+
}
|
|
24537
|
+
return;
|
|
24538
|
+
}
|
|
24539
|
+
const replayText = buildDirectiveReplayText(refreshed);
|
|
24540
|
+
if (!replayText) {
|
|
24541
|
+
sessionService.trackGlobal(sessionID, { replayDirectivePending: false });
|
|
24542
|
+
return;
|
|
24543
|
+
}
|
|
24544
|
+
const now = Date.now();
|
|
24545
|
+
output.messages.push({
|
|
24546
|
+
info: {
|
|
24547
|
+
id: `msg_replay_${sessionID}`,
|
|
24548
|
+
sessionID,
|
|
24549
|
+
role: "user",
|
|
24550
|
+
time: { created: now }
|
|
24551
|
+
},
|
|
24552
|
+
parts: [
|
|
24553
|
+
{
|
|
24554
|
+
id: `prt_replay_${sessionID}`,
|
|
24555
|
+
sessionID,
|
|
24556
|
+
messageID: `msg_replay_${sessionID}`,
|
|
24557
|
+
type: "text",
|
|
24558
|
+
text: replayText,
|
|
24559
|
+
synthetic: true
|
|
24560
|
+
}
|
|
24561
|
+
]
|
|
24562
|
+
});
|
|
24563
|
+
sessionService.trackGlobal(sessionID, { replayDirectivePending: false });
|
|
24133
24564
|
},
|
|
24134
|
-
"chat.message": createVariantHook(configService),
|
|
24135
24565
|
"tool.execute.before": async (input, output) => {
|
|
24136
24566
|
if (!shouldExecuteHook("tool.execute.before", configService, turnCounters, { safetyCritical: true })) {
|
|
24137
24567
|
return;
|
|
@@ -24153,7 +24583,7 @@ Use the \`@path\` attachment syntax in the prompt to reference the file. Do not
|
|
|
24153
24583
|
const workdir = output.args?.workdir;
|
|
24154
24584
|
if (!workdir)
|
|
24155
24585
|
return;
|
|
24156
|
-
const hiveWorktreeBase =
|
|
24586
|
+
const hiveWorktreeBase = path11.join(directory, ".hive", ".worktrees");
|
|
24157
24587
|
if (!workdir.startsWith(hiveWorktreeBase))
|
|
24158
24588
|
return;
|
|
24159
24589
|
const wrapped = DockerSandboxService.wrapCommand(workdir, command, sandboxConfig);
|
|
@@ -24267,6 +24697,7 @@ Expand your Discovery section and try again.`;
|
|
|
24267
24697
|
if (!feature)
|
|
24268
24698
|
return "Error: No feature specified. Create a feature or provide feature param.";
|
|
24269
24699
|
captureSession(feature, toolContext);
|
|
24700
|
+
bindFeatureSession(feature, toolContext);
|
|
24270
24701
|
const result = planService.read(feature);
|
|
24271
24702
|
if (!result)
|
|
24272
24703
|
return "Error: No plan.md found";
|
|
@@ -24390,7 +24821,7 @@ Reminder: start work with hive_worktree_start to use its worktree, and ensure an
|
|
|
24390
24821
|
}).optional().describe("Blocker info when status is blocked"),
|
|
24391
24822
|
feature: tool.schema.string().optional().describe("Feature name (defaults to detection or single feature)")
|
|
24392
24823
|
},
|
|
24393
|
-
async execute({ task, summary, message, status = "completed", blocker, feature: explicitFeature }) {
|
|
24824
|
+
async execute({ task, summary, message, status = "completed", blocker, feature: explicitFeature }, toolContext) {
|
|
24394
24825
|
const respond2 = (payload) => JSON.stringify(payload, null, 2);
|
|
24395
24826
|
const feature = resolveFeature(explicitFeature);
|
|
24396
24827
|
if (!feature) {
|
|
@@ -24432,6 +24863,9 @@ Reminder: start work with hive_worktree_start to use its worktree, and ensure an
|
|
|
24432
24863
|
nextAction: "Only in_progress or blocked tasks can be committed. Start/resume the task first."
|
|
24433
24864
|
});
|
|
24434
24865
|
}
|
|
24866
|
+
const featureDir = resolveFeatureDirectoryName(directory, feature);
|
|
24867
|
+
const workerPromptPath = path11.posix.join(".hive", "features", featureDir, "tasks", task, "worker-prompt.md");
|
|
24868
|
+
bindFeatureSession(feature, toolContext, { taskFolder: task, workerPromptPath });
|
|
24435
24869
|
let verificationNote;
|
|
24436
24870
|
if (status === "completed") {
|
|
24437
24871
|
const verificationKeywords = ["test", "build", "lint", "vitest", "jest", "npm run", "pnpm", "cargo", "pytest", "verified", "passes", "succeeds", "ast-grep", "scan"];
|
|
@@ -24447,7 +24881,7 @@ Reminder: start work with hive_worktree_start to use its worktree, and ensure an
|
|
|
24447
24881
|
summary,
|
|
24448
24882
|
blocker
|
|
24449
24883
|
});
|
|
24450
|
-
const
|
|
24884
|
+
const worktree3 = await worktreeService.get(feature, task);
|
|
24451
24885
|
return respond2({
|
|
24452
24886
|
ok: true,
|
|
24453
24887
|
terminal: true,
|
|
@@ -24458,8 +24892,8 @@ Reminder: start work with hive_worktree_start to use its worktree, and ensure an
|
|
|
24458
24892
|
taskState: "blocked",
|
|
24459
24893
|
summary,
|
|
24460
24894
|
blocker,
|
|
24461
|
-
worktreePath:
|
|
24462
|
-
branch:
|
|
24895
|
+
worktreePath: worktree3?.path,
|
|
24896
|
+
branch: worktree3?.branch,
|
|
24463
24897
|
message: 'Task blocked. Hive Master will ask user and resume with hive_worktree_create(continueFrom: "blocked", decision: answer)',
|
|
24464
24898
|
nextAction: 'Wait for orchestrator to collect user decision and resume with continueFrom: "blocked".'
|
|
24465
24899
|
});
|
|
@@ -24518,7 +24952,7 @@ Reminder: start work with hive_worktree_start to use its worktree, and ensure an
|
|
|
24518
24952
|
`));
|
|
24519
24953
|
const finalStatus = status === "completed" ? "done" : status;
|
|
24520
24954
|
taskService.update(feature, task, { status: finalStatus, summary });
|
|
24521
|
-
const
|
|
24955
|
+
const worktree2 = await worktreeService.get(feature, task);
|
|
24522
24956
|
return respond2({
|
|
24523
24957
|
ok: true,
|
|
24524
24958
|
terminal: true,
|
|
@@ -24533,8 +24967,8 @@ Reminder: start work with hive_worktree_start to use its worktree, and ensure an
|
|
|
24533
24967
|
sha: commitResult.sha,
|
|
24534
24968
|
message: commitResult.message
|
|
24535
24969
|
},
|
|
24536
|
-
worktreePath:
|
|
24537
|
-
branch:
|
|
24970
|
+
worktreePath: worktree2?.path,
|
|
24971
|
+
branch: worktree2?.branch,
|
|
24538
24972
|
reportPath,
|
|
24539
24973
|
message: `Task "${task}" ${status}.`,
|
|
24540
24974
|
nextAction: "Use hive_merge to integrate changes. Worktree is preserved for review."
|
|
@@ -24596,10 +25030,11 @@ Files changed: ${result.filesChanged?.length || 0}`;
|
|
|
24596
25030
|
content: tool.schema.string().describe("Markdown content to write"),
|
|
24597
25031
|
feature: tool.schema.string().optional().describe("Feature name (defaults to active)")
|
|
24598
25032
|
},
|
|
24599
|
-
async execute({ name, content, feature: explicitFeature }) {
|
|
25033
|
+
async execute({ name, content, feature: explicitFeature }, toolContext) {
|
|
24600
25034
|
const feature = resolveFeature(explicitFeature);
|
|
24601
25035
|
if (!feature)
|
|
24602
25036
|
return "Error: No feature specified. Create a feature or provide feature param.";
|
|
25037
|
+
bindFeatureSession(feature, toolContext);
|
|
24603
25038
|
const filePath = contextService.write(feature, name, content);
|
|
24604
25039
|
return `Context file written: ${filePath}`;
|
|
24605
25040
|
}
|
|
@@ -24649,36 +25084,36 @@ Files changed: ${result.filesChanged?.length || 0}`;
|
|
|
24649
25084
|
const contextFiles = contextService.list(feature);
|
|
24650
25085
|
const overview = contextFiles.find((file2) => file2.name === "overview") ?? null;
|
|
24651
25086
|
const readThreads = (filePath) => {
|
|
24652
|
-
if (!
|
|
25087
|
+
if (!fs13.existsSync(filePath)) {
|
|
24653
25088
|
return null;
|
|
24654
25089
|
}
|
|
24655
25090
|
try {
|
|
24656
|
-
const data = JSON.parse(
|
|
25091
|
+
const data = JSON.parse(fs13.readFileSync(filePath, "utf-8"));
|
|
24657
25092
|
return data.threads ?? [];
|
|
24658
25093
|
} catch {
|
|
24659
25094
|
return [];
|
|
24660
25095
|
}
|
|
24661
25096
|
};
|
|
24662
|
-
const featurePath =
|
|
24663
|
-
const reviewDir =
|
|
24664
|
-
const planThreads = readThreads(
|
|
24665
|
-
const overviewThreads = readThreads(
|
|
25097
|
+
const featurePath = path11.join(directory, ".hive", "features", resolveFeatureDirectoryName(directory, feature));
|
|
25098
|
+
const reviewDir = path11.join(featurePath, "comments");
|
|
25099
|
+
const planThreads = readThreads(path11.join(reviewDir, "plan.json")) ?? readThreads(path11.join(featurePath, "comments.json"));
|
|
25100
|
+
const overviewThreads = readThreads(path11.join(reviewDir, "overview.json"));
|
|
24666
25101
|
const reviewCounts = {
|
|
24667
25102
|
plan: planThreads?.length ?? 0,
|
|
24668
25103
|
overview: overviewThreads?.length ?? 0
|
|
24669
25104
|
};
|
|
24670
25105
|
const tasksSummary = await Promise.all(tasks.map(async (t) => {
|
|
24671
25106
|
const rawStatus = taskService.getRawStatus(feature, t.folder);
|
|
24672
|
-
const
|
|
24673
|
-
const hasChanges =
|
|
25107
|
+
const worktree2 = await worktreeService.get(feature, t.folder);
|
|
25108
|
+
const hasChanges = worktree2 ? await worktreeService.hasUncommittedChanges(worktree2.feature, worktree2.step) : null;
|
|
24674
25109
|
return {
|
|
24675
25110
|
folder: t.folder,
|
|
24676
25111
|
name: t.name,
|
|
24677
25112
|
status: t.status,
|
|
24678
25113
|
origin: t.origin || "plan",
|
|
24679
25114
|
dependsOn: rawStatus?.dependsOn ?? null,
|
|
24680
|
-
worktree:
|
|
24681
|
-
branch:
|
|
25115
|
+
worktree: worktree2 ? {
|
|
25116
|
+
branch: worktree2.branch,
|
|
24682
25117
|
hasChanges
|
|
24683
25118
|
} : null
|
|
24684
25119
|
};
|
|
@@ -1,6 +1,18 @@
|
|
|
1
|
-
import type { ConfigService } from 'hive-core';
|
|
1
|
+
import type { ConfigService, SessionService, SessionKind } from 'hive-core';
|
|
2
2
|
export declare function normalizeVariant(variant: string | undefined): string | undefined;
|
|
3
|
-
export declare function
|
|
3
|
+
export declare function classifySession(agent: string, customAgents?: Record<string, {
|
|
4
|
+
baseAgent: string;
|
|
5
|
+
}>): {
|
|
6
|
+
sessionKind: SessionKind;
|
|
7
|
+
baseAgent?: string;
|
|
8
|
+
};
|
|
9
|
+
export declare function createVariantHook(configService: ConfigService, sessionService?: SessionService, customAgents?: Record<string, {
|
|
10
|
+
baseAgent: string;
|
|
11
|
+
}>, taskWorkerRecovery?: {
|
|
12
|
+
featureName: string;
|
|
13
|
+
taskFolder: string;
|
|
14
|
+
workerPromptPath: string;
|
|
15
|
+
}): (input: {
|
|
4
16
|
sessionID: string;
|
|
5
17
|
agent?: string;
|
|
6
18
|
model?: {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface CompactionSessionContext {
|
|
2
|
+
agent?: string;
|
|
3
|
+
baseAgent?: string;
|
|
4
|
+
sessionKind?: 'primary' | 'subagent' | 'task-worker' | 'unknown';
|
|
5
|
+
featureName?: string;
|
|
6
|
+
taskFolder?: string;
|
|
7
|
+
workerPromptPath?: string;
|
|
8
|
+
directivePrompt?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface CompactionReanchor {
|
|
11
|
+
prompt: string;
|
|
12
|
+
context: string[];
|
|
13
|
+
}
|
|
14
|
+
export declare function buildCompactionReanchor(ctx: CompactionSessionContext): CompactionReanchor;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-hive",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "OpenCode plugin for Agent Hive - from vibe coding to hive coding",
|
|
6
6
|
"license": "MIT WITH Commons-Clause",
|
|
@@ -39,17 +39,17 @@
|
|
|
39
39
|
"simple-git": "^3.27.0"
|
|
40
40
|
},
|
|
41
41
|
"optionalDependencies": {
|
|
42
|
-
"grep-mcp": "^1.1.0",
|
|
43
42
|
"@notprolands/ast-grep-mcp": "^1.1.1",
|
|
44
43
|
"@upstash/context7-mcp": "^2.1.0",
|
|
45
|
-
"exa-mcp-server": "^3.1.5"
|
|
44
|
+
"exa-mcp-server": "^3.1.5",
|
|
45
|
+
"grep-mcp": "^1.1.0"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
|
-
"hive-core": "workspace:*",
|
|
49
48
|
"@opencode-ai/plugin": "^1.0.143",
|
|
50
49
|
"@opencode-ai/sdk": "^0.13.0",
|
|
51
50
|
"@types/bun": "^1.2.0",
|
|
52
51
|
"@types/node": "^20.0.0",
|
|
52
|
+
"hive-core": "workspace:*",
|
|
53
53
|
"typescript": "^5.0.0"
|
|
54
54
|
},
|
|
55
55
|
"files": [
|