hungry-ghost-hive 0.45.0 → 0.46.0
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/cli/commands/cluster.d.ts.map +1 -1
- package/dist/cli/commands/cluster.js +348 -1
- package/dist/cli/commands/cluster.js.map +1 -1
- package/dist/cli/commands/cluster.test.js +313 -9
- package/dist/cli/commands/cluster.test.js.map +1 -1
- package/dist/cli/commands/req-spawn.test.d.ts +2 -0
- package/dist/cli/commands/req-spawn.test.d.ts.map +1 -0
- package/dist/cli/commands/req-spawn.test.js +116 -0
- package/dist/cli/commands/req-spawn.test.js.map +1 -0
- package/dist/cli/commands/req.d.ts.map +1 -1
- package/dist/cli/commands/req.js +21 -13
- package/dist/cli/commands/req.js.map +1 -1
- package/dist/cluster/cluster-http-server.d.ts +32 -0
- package/dist/cluster/cluster-http-server.d.ts.map +1 -1
- package/dist/cluster/cluster-http-server.js +42 -0
- package/dist/cluster/cluster-http-server.js.map +1 -1
- package/dist/cluster/distributed-runtime-coverage.test.js +9 -0
- package/dist/cluster/distributed-runtime-coverage.test.js.map +1 -1
- package/dist/cluster/distributed-system.test.js +135 -0
- package/dist/cluster/distributed-system.test.js.map +1 -1
- package/dist/cluster/events.d.ts +23 -0
- package/dist/cluster/events.d.ts.map +1 -1
- package/dist/cluster/events.js +74 -0
- package/dist/cluster/events.js.map +1 -1
- package/dist/cluster/heartbeat-manager.d.ts +2 -0
- package/dist/cluster/heartbeat-manager.d.ts.map +1 -1
- package/dist/cluster/heartbeat-manager.js +42 -6
- package/dist/cluster/heartbeat-manager.js.map +1 -1
- package/dist/cluster/membership.test.d.ts +2 -0
- package/dist/cluster/membership.test.d.ts.map +1 -0
- package/dist/cluster/membership.test.js +416 -0
- package/dist/cluster/membership.test.js.map +1 -0
- package/dist/cluster/partition-safety.test.d.ts +2 -0
- package/dist/cluster/partition-safety.test.d.ts.map +1 -0
- package/dist/cluster/partition-safety.test.js +440 -0
- package/dist/cluster/partition-safety.test.js.map +1 -0
- package/dist/cluster/raft-state-machine.d.ts +33 -1
- package/dist/cluster/raft-state-machine.d.ts.map +1 -1
- package/dist/cluster/raft-state-machine.js +65 -3
- package/dist/cluster/raft-state-machine.js.map +1 -1
- package/dist/cluster/raft-store.d.ts +26 -1
- package/dist/cluster/raft-store.d.ts.map +1 -1
- package/dist/cluster/raft-store.js +137 -0
- package/dist/cluster/raft-store.js.map +1 -1
- package/dist/cluster/replication-lag.test.d.ts +2 -0
- package/dist/cluster/replication-lag.test.d.ts.map +1 -0
- package/dist/cluster/replication-lag.test.js +239 -0
- package/dist/cluster/replication-lag.test.js.map +1 -0
- package/dist/cluster/replication.d.ts +2 -2
- package/dist/cluster/replication.d.ts.map +1 -1
- package/dist/cluster/replication.js +1 -1
- package/dist/cluster/replication.js.map +1 -1
- package/dist/cluster/runtime.d.ts +78 -0
- package/dist/cluster/runtime.d.ts.map +1 -1
- package/dist/cluster/runtime.js +400 -13
- package/dist/cluster/runtime.js.map +1 -1
- package/dist/cluster/state-recovery.test.d.ts +2 -0
- package/dist/cluster/state-recovery.test.d.ts.map +1 -0
- package/dist/cluster/state-recovery.test.js +310 -0
- package/dist/cluster/state-recovery.test.js.map +1 -0
- package/dist/cluster/types.d.ts +30 -0
- package/dist/cluster/types.d.ts.map +1 -1
- package/dist/config/schema.d.ts +48 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +11 -0
- package/dist/config/schema.js.map +1 -1
- package/dist/context-files/generator.js +1 -1
- package/dist/context-files/generator.js.map +1 -1
- package/dist/context-files/generator.test.js +51 -0
- package/dist/context-files/generator.test.js.map +1 -1
- package/dist/orchestrator/orphan-recovery.d.ts +1 -1
- package/dist/orchestrator/orphan-recovery.d.ts.map +1 -1
- package/dist/orchestrator/orphan-recovery.js +4 -4
- package/dist/orchestrator/orphan-recovery.js.map +1 -1
- package/dist/orchestrator/prompt-templates.d.ts +3 -1
- package/dist/orchestrator/prompt-templates.d.ts.map +1 -1
- package/dist/orchestrator/prompt-templates.js +45 -8
- package/dist/orchestrator/prompt-templates.js.map +1 -1
- package/dist/orchestrator/prompt-templates.test.js +210 -0
- package/dist/orchestrator/prompt-templates.test.js.map +1 -1
- package/dist/orchestrator/scheduler.d.ts +1 -0
- package/dist/orchestrator/scheduler.d.ts.map +1 -1
- package/dist/orchestrator/scheduler.js +15 -10
- package/dist/orchestrator/scheduler.js.map +1 -1
- package/dist/orchestrator/scheduler.test.js +97 -6
- package/dist/orchestrator/scheduler.test.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/commands/cluster.test.ts +387 -9
- package/src/cli/commands/cluster.ts +486 -1
- package/src/cli/commands/req-spawn.test.ts +153 -0
- package/src/cli/commands/req.ts +31 -18
- package/src/cluster/cluster-http-server.ts +80 -0
- package/src/cluster/distributed-runtime-coverage.test.ts +9 -0
- package/src/cluster/distributed-system.test.ts +168 -0
- package/src/cluster/events.ts +90 -0
- package/src/cluster/heartbeat-manager.ts +48 -6
- package/src/cluster/membership.test.ts +498 -0
- package/src/cluster/partition-safety.test.ts +523 -0
- package/src/cluster/raft-state-machine.ts +76 -4
- package/src/cluster/raft-store.ts +167 -1
- package/src/cluster/replication-lag.test.ts +284 -0
- package/src/cluster/replication.ts +6 -0
- package/src/cluster/runtime.ts +551 -12
- package/src/cluster/state-recovery.test.ts +420 -0
- package/src/cluster/types.ts +32 -0
- package/src/config/schema.ts +11 -0
- package/src/context-files/generator.test.ts +55 -0
- package/src/context-files/generator.ts +5 -5
- package/src/orchestrator/orphan-recovery.ts +32 -13
- package/src/orchestrator/prompt-templates.test.ts +263 -0
- package/src/orchestrator/prompt-templates.ts +49 -8
- package/src/orchestrator/scheduler.test.ts +129 -6
- package/src/orchestrator/scheduler.ts +46 -20
|
@@ -164,6 +164,82 @@ describe('Prompt Templates', () => {
|
|
|
164
164
|
expect(prompt).toContain('complexity: ?');
|
|
165
165
|
});
|
|
166
166
|
|
|
167
|
+
it('should include markdown_path in prompt when set on a story', () => {
|
|
168
|
+
const stories: StoryRow[] = [
|
|
169
|
+
{
|
|
170
|
+
id: 'STORY-003',
|
|
171
|
+
title: 'Story With Markdown',
|
|
172
|
+
description: 'DB description',
|
|
173
|
+
complexity_score: 3,
|
|
174
|
+
status: 'planned',
|
|
175
|
+
team_id: 'team-1',
|
|
176
|
+
requirement_id: null,
|
|
177
|
+
acceptance_criteria: null,
|
|
178
|
+
story_points: null,
|
|
179
|
+
assigned_agent_id: null,
|
|
180
|
+
branch_name: null,
|
|
181
|
+
pr_url: null,
|
|
182
|
+
jira_issue_key: null,
|
|
183
|
+
jira_issue_id: null,
|
|
184
|
+
jira_project_key: null,
|
|
185
|
+
jira_subtask_key: null,
|
|
186
|
+
jira_subtask_id: null,
|
|
187
|
+
external_issue_key: null,
|
|
188
|
+
external_issue_id: null,
|
|
189
|
+
external_project_key: null,
|
|
190
|
+
external_subtask_key: null,
|
|
191
|
+
external_subtask_id: null,
|
|
192
|
+
external_provider: null,
|
|
193
|
+
in_sprint: 0,
|
|
194
|
+
markdown_path: '/stories/STORY-003.md',
|
|
195
|
+
created_at: '2024-01-01',
|
|
196
|
+
updated_at: '2024-01-01',
|
|
197
|
+
},
|
|
198
|
+
];
|
|
199
|
+
const prompt = generateSeniorPrompt(teamName, repoUrl, repoPath, stories);
|
|
200
|
+
|
|
201
|
+
expect(prompt).toContain('/stories/STORY-003.md');
|
|
202
|
+
expect(prompt).toContain('read the story markdown file');
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('should fall back to DB description when markdown_path is null', () => {
|
|
206
|
+
const stories: StoryRow[] = [
|
|
207
|
+
{
|
|
208
|
+
id: 'STORY-004',
|
|
209
|
+
title: 'Story Without Markdown',
|
|
210
|
+
description: 'DB only description',
|
|
211
|
+
complexity_score: 2,
|
|
212
|
+
status: 'planned',
|
|
213
|
+
team_id: 'team-1',
|
|
214
|
+
requirement_id: null,
|
|
215
|
+
acceptance_criteria: null,
|
|
216
|
+
story_points: null,
|
|
217
|
+
assigned_agent_id: null,
|
|
218
|
+
branch_name: null,
|
|
219
|
+
pr_url: null,
|
|
220
|
+
jira_issue_key: null,
|
|
221
|
+
jira_issue_id: null,
|
|
222
|
+
jira_project_key: null,
|
|
223
|
+
jira_subtask_key: null,
|
|
224
|
+
jira_subtask_id: null,
|
|
225
|
+
external_issue_key: null,
|
|
226
|
+
external_issue_id: null,
|
|
227
|
+
external_project_key: null,
|
|
228
|
+
external_subtask_key: null,
|
|
229
|
+
external_subtask_id: null,
|
|
230
|
+
external_provider: null,
|
|
231
|
+
in_sprint: 0,
|
|
232
|
+
markdown_path: null,
|
|
233
|
+
created_at: '2024-01-01',
|
|
234
|
+
updated_at: '2024-01-01',
|
|
235
|
+
},
|
|
236
|
+
];
|
|
237
|
+
const prompt = generateSeniorPrompt(teamName, repoUrl, repoPath, stories);
|
|
238
|
+
|
|
239
|
+
expect(prompt).toContain('DB only description');
|
|
240
|
+
expect(prompt).not.toContain('read the story markdown file');
|
|
241
|
+
});
|
|
242
|
+
|
|
167
243
|
it('should handle empty stories list', () => {
|
|
168
244
|
const stories: StoryRow[] = [];
|
|
169
245
|
const prompt = generateSeniorPrompt(teamName, repoUrl, repoPath, stories);
|
|
@@ -814,3 +890,190 @@ describe('Prompt Templates', () => {
|
|
|
814
890
|
});
|
|
815
891
|
});
|
|
816
892
|
});
|
|
893
|
+
|
|
894
|
+
describe('Chrome Tab Isolation', () => {
|
|
895
|
+
const teamName = 'TestTeam';
|
|
896
|
+
const repoUrl = 'https://github.com/test/repo.git';
|
|
897
|
+
const repoPath = 'repos/test-repo';
|
|
898
|
+
const sessionName = 'hive-test-session';
|
|
899
|
+
|
|
900
|
+
const CHROME_TAB_MARKER = 'Chrome Browser Tab Isolation';
|
|
901
|
+
const CHROME_TAB_CREATE = 'tabs_create_mcp';
|
|
902
|
+
|
|
903
|
+
describe('generateSeniorPrompt', () => {
|
|
904
|
+
it('should include chrome tab isolation section when chromeEnabled is true', () => {
|
|
905
|
+
const prompt = generateSeniorPrompt(teamName, repoUrl, repoPath, [], 'main', {
|
|
906
|
+
chromeEnabled: true,
|
|
907
|
+
});
|
|
908
|
+
expect(prompt).toContain(CHROME_TAB_MARKER);
|
|
909
|
+
expect(prompt).toContain(CHROME_TAB_CREATE);
|
|
910
|
+
});
|
|
911
|
+
|
|
912
|
+
it('should not include chrome tab isolation section when chromeEnabled is false', () => {
|
|
913
|
+
const prompt = generateSeniorPrompt(teamName, repoUrl, repoPath, [], 'main', {
|
|
914
|
+
chromeEnabled: false,
|
|
915
|
+
});
|
|
916
|
+
expect(prompt).not.toContain(CHROME_TAB_MARKER);
|
|
917
|
+
});
|
|
918
|
+
|
|
919
|
+
it('should not include chrome tab isolation section when chromeEnabled is not set', () => {
|
|
920
|
+
const prompt = generateSeniorPrompt(teamName, repoUrl, repoPath, []);
|
|
921
|
+
expect(prompt).not.toContain(CHROME_TAB_MARKER);
|
|
922
|
+
});
|
|
923
|
+
});
|
|
924
|
+
|
|
925
|
+
describe('generateIntermediatePrompt', () => {
|
|
926
|
+
it('should include chrome tab isolation section when chromeEnabled is true', () => {
|
|
927
|
+
const prompt = generateIntermediatePrompt(teamName, repoUrl, repoPath, sessionName, 'main', {
|
|
928
|
+
chromeEnabled: true,
|
|
929
|
+
});
|
|
930
|
+
expect(prompt).toContain(CHROME_TAB_MARKER);
|
|
931
|
+
expect(prompt).toContain(CHROME_TAB_CREATE);
|
|
932
|
+
});
|
|
933
|
+
|
|
934
|
+
it('should not include chrome tab isolation section when chromeEnabled is false', () => {
|
|
935
|
+
const prompt = generateIntermediatePrompt(teamName, repoUrl, repoPath, sessionName, 'main', {
|
|
936
|
+
chromeEnabled: false,
|
|
937
|
+
});
|
|
938
|
+
expect(prompt).not.toContain(CHROME_TAB_MARKER);
|
|
939
|
+
});
|
|
940
|
+
|
|
941
|
+
it('should not include chrome tab isolation section when options not provided', () => {
|
|
942
|
+
const prompt = generateIntermediatePrompt(teamName, repoUrl, repoPath, sessionName);
|
|
943
|
+
expect(prompt).not.toContain(CHROME_TAB_MARKER);
|
|
944
|
+
});
|
|
945
|
+
});
|
|
946
|
+
|
|
947
|
+
describe('generateJuniorPrompt', () => {
|
|
948
|
+
it('should include chrome tab isolation section when chromeEnabled is true', () => {
|
|
949
|
+
const prompt = generateJuniorPrompt(teamName, repoUrl, repoPath, sessionName, 'main', {
|
|
950
|
+
chromeEnabled: true,
|
|
951
|
+
});
|
|
952
|
+
expect(prompt).toContain(CHROME_TAB_MARKER);
|
|
953
|
+
expect(prompt).toContain(CHROME_TAB_CREATE);
|
|
954
|
+
});
|
|
955
|
+
|
|
956
|
+
it('should not include chrome tab isolation section when chromeEnabled is false', () => {
|
|
957
|
+
const prompt = generateJuniorPrompt(teamName, repoUrl, repoPath, sessionName, 'main', {
|
|
958
|
+
chromeEnabled: false,
|
|
959
|
+
});
|
|
960
|
+
expect(prompt).not.toContain(CHROME_TAB_MARKER);
|
|
961
|
+
});
|
|
962
|
+
|
|
963
|
+
it('should not include chrome tab isolation section when options not provided', () => {
|
|
964
|
+
const prompt = generateJuniorPrompt(teamName, repoUrl, repoPath, sessionName);
|
|
965
|
+
expect(prompt).not.toContain(CHROME_TAB_MARKER);
|
|
966
|
+
});
|
|
967
|
+
});
|
|
968
|
+
|
|
969
|
+
describe('generateQAPrompt', () => {
|
|
970
|
+
it('should include chrome tab isolation section when chromeEnabled is true', () => {
|
|
971
|
+
const prompt = generateQAPrompt(teamName, repoUrl, repoPath, sessionName, 'main', {
|
|
972
|
+
chromeEnabled: true,
|
|
973
|
+
});
|
|
974
|
+
expect(prompt).toContain(CHROME_TAB_MARKER);
|
|
975
|
+
expect(prompt).toContain(CHROME_TAB_CREATE);
|
|
976
|
+
});
|
|
977
|
+
|
|
978
|
+
it('should not include chrome tab isolation section when chromeEnabled is false', () => {
|
|
979
|
+
const prompt = generateQAPrompt(teamName, repoUrl, repoPath, sessionName, 'main', {
|
|
980
|
+
chromeEnabled: false,
|
|
981
|
+
});
|
|
982
|
+
expect(prompt).not.toContain(CHROME_TAB_MARKER);
|
|
983
|
+
});
|
|
984
|
+
|
|
985
|
+
it('should not include chrome tab isolation section when options not provided', () => {
|
|
986
|
+
const prompt = generateQAPrompt(teamName, repoUrl, repoPath, sessionName);
|
|
987
|
+
expect(prompt).not.toContain(CHROME_TAB_MARKER);
|
|
988
|
+
});
|
|
989
|
+
});
|
|
990
|
+
|
|
991
|
+
describe('generateFeatureTestPrompt', () => {
|
|
992
|
+
it('should include chrome tab isolation section when chromeEnabled is true', () => {
|
|
993
|
+
const prompt = generateFeatureTestPrompt(
|
|
994
|
+
teamName,
|
|
995
|
+
repoUrl,
|
|
996
|
+
repoPath,
|
|
997
|
+
sessionName,
|
|
998
|
+
'feature/test',
|
|
999
|
+
'REQ-001',
|
|
1000
|
+
'e2e/tests',
|
|
1001
|
+
{ chromeEnabled: true }
|
|
1002
|
+
);
|
|
1003
|
+
expect(prompt).toContain(CHROME_TAB_MARKER);
|
|
1004
|
+
expect(prompt).toContain(CHROME_TAB_CREATE);
|
|
1005
|
+
});
|
|
1006
|
+
|
|
1007
|
+
it('should not include chrome tab isolation section when chromeEnabled is false', () => {
|
|
1008
|
+
const prompt = generateFeatureTestPrompt(
|
|
1009
|
+
teamName,
|
|
1010
|
+
repoUrl,
|
|
1011
|
+
repoPath,
|
|
1012
|
+
sessionName,
|
|
1013
|
+
'feature/test',
|
|
1014
|
+
'REQ-001',
|
|
1015
|
+
'e2e/tests',
|
|
1016
|
+
{ chromeEnabled: false }
|
|
1017
|
+
);
|
|
1018
|
+
expect(prompt).not.toContain(CHROME_TAB_MARKER);
|
|
1019
|
+
});
|
|
1020
|
+
|
|
1021
|
+
it('should not include chrome tab isolation section when options not provided', () => {
|
|
1022
|
+
const prompt = generateFeatureTestPrompt(
|
|
1023
|
+
teamName,
|
|
1024
|
+
repoUrl,
|
|
1025
|
+
repoPath,
|
|
1026
|
+
sessionName,
|
|
1027
|
+
'feature/test',
|
|
1028
|
+
'REQ-001',
|
|
1029
|
+
'e2e/tests'
|
|
1030
|
+
);
|
|
1031
|
+
expect(prompt).not.toContain(CHROME_TAB_MARKER);
|
|
1032
|
+
});
|
|
1033
|
+
});
|
|
1034
|
+
|
|
1035
|
+
describe('generateAuditorPrompt', () => {
|
|
1036
|
+
it('should include chrome tab isolation section when chromeEnabled is true', () => {
|
|
1037
|
+
const prompt = generateAuditorPrompt(sessionName, repoPath, repoUrl, {
|
|
1038
|
+
chromeEnabled: true,
|
|
1039
|
+
});
|
|
1040
|
+
expect(prompt).toContain(CHROME_TAB_MARKER);
|
|
1041
|
+
expect(prompt).toContain(CHROME_TAB_CREATE);
|
|
1042
|
+
});
|
|
1043
|
+
|
|
1044
|
+
it('should not include chrome tab isolation section when chromeEnabled is false', () => {
|
|
1045
|
+
const prompt = generateAuditorPrompt(sessionName, repoPath, repoUrl, {
|
|
1046
|
+
chromeEnabled: false,
|
|
1047
|
+
});
|
|
1048
|
+
expect(prompt).not.toContain(CHROME_TAB_MARKER);
|
|
1049
|
+
});
|
|
1050
|
+
|
|
1051
|
+
it('should not include chrome tab isolation section when options not provided', () => {
|
|
1052
|
+
const prompt = generateAuditorPrompt(sessionName, repoPath, repoUrl);
|
|
1053
|
+
expect(prompt).not.toContain(CHROME_TAB_MARKER);
|
|
1054
|
+
});
|
|
1055
|
+
});
|
|
1056
|
+
|
|
1057
|
+
describe('tab isolation instructions content', () => {
|
|
1058
|
+
it('should instruct agents to store and reuse tab ID', () => {
|
|
1059
|
+
const prompt = generateIntermediatePrompt(teamName, repoUrl, repoPath, sessionName, 'main', {
|
|
1060
|
+
chromeEnabled: true,
|
|
1061
|
+
});
|
|
1062
|
+
expect(prompt).toContain('tab ID');
|
|
1063
|
+
});
|
|
1064
|
+
|
|
1065
|
+
it('should instruct agents to recreate tab when closed externally', () => {
|
|
1066
|
+
const prompt = generateIntermediatePrompt(teamName, repoUrl, repoPath, sessionName, 'main', {
|
|
1067
|
+
chromeEnabled: true,
|
|
1068
|
+
});
|
|
1069
|
+
expect(prompt).toContain('closed externally');
|
|
1070
|
+
});
|
|
1071
|
+
|
|
1072
|
+
it('should warn agents not to use other agents tabs', () => {
|
|
1073
|
+
const prompt = generateIntermediatePrompt(teamName, repoUrl, repoPath, sessionName, 'main', {
|
|
1074
|
+
chromeEnabled: true,
|
|
1075
|
+
});
|
|
1076
|
+
expect(prompt).toContain('Never interact with tabs you did not create');
|
|
1077
|
+
});
|
|
1078
|
+
});
|
|
1079
|
+
});
|
|
@@ -6,6 +6,8 @@ export interface AgentPromptOptions {
|
|
|
6
6
|
includeProgressUpdates?: boolean;
|
|
7
7
|
/** The tech lead tmux session name for messaging. Defaults to 'hive-tech-lead' for backwards compatibility. */
|
|
8
8
|
techLeadSession?: string;
|
|
9
|
+
/** Whether Chrome browser tools are enabled for this agent session. */
|
|
10
|
+
chromeEnabled?: boolean;
|
|
9
11
|
}
|
|
10
12
|
|
|
11
13
|
/**
|
|
@@ -66,6 +68,35 @@ function resolveTechLeadSession(options?: AgentPromptOptions): string {
|
|
|
66
68
|
return options?.techLeadSession || 'hive-tech-lead';
|
|
67
69
|
}
|
|
68
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Generate the Chrome tab isolation section for agent prompts.
|
|
73
|
+
* Instructs agents to create a dedicated tab at session start and use it
|
|
74
|
+
* exclusively for all browser operations, preventing tab interference between
|
|
75
|
+
* concurrent agents.
|
|
76
|
+
*/
|
|
77
|
+
function chromeTabIsolationSection(): string {
|
|
78
|
+
return `# Chrome Browser Tab Isolation
|
|
79
|
+
Chrome browser tools are enabled for this session. Each agent must use its own dedicated tab to prevent interference with other agents running concurrently.
|
|
80
|
+
|
|
81
|
+
## Tab Lifecycle
|
|
82
|
+
|
|
83
|
+
**At session start**, create your dedicated tab immediately:
|
|
84
|
+
\`\`\`
|
|
85
|
+
Use mcp__claude-in-chrome__tabs_create_mcp to create a new tab.
|
|
86
|
+
Store the returned tab ID — you will use it for all browser operations.
|
|
87
|
+
\`\`\`
|
|
88
|
+
|
|
89
|
+
**For all browser operations**, always pass your stored tab ID to every \`mcp__claude-in-chrome__*\` tool call. Never interact with tabs you did not create.
|
|
90
|
+
|
|
91
|
+
**If your tab is closed externally** (tool returns a tab-not-found error):
|
|
92
|
+
\`\`\`
|
|
93
|
+
Call mcp__claude-in-chrome__tabs_create_mcp again to get a new tab ID.
|
|
94
|
+
Update your stored tab ID and continue.
|
|
95
|
+
\`\`\`
|
|
96
|
+
|
|
97
|
+
**At session end**, close your tab using the browser tools to free resources.`;
|
|
98
|
+
}
|
|
99
|
+
|
|
69
100
|
function repositorySection(repoPath: string, repoUrl: string): string {
|
|
70
101
|
return `## Your Repository
|
|
71
102
|
- Local path: ${repoPath}
|
|
@@ -243,15 +274,19 @@ export function generateSeniorPrompt(
|
|
|
243
274
|
const externalInfo = s.external_subtask_key
|
|
244
275
|
? ` | External Subtask: ${s.external_subtask_key}`
|
|
245
276
|
: '';
|
|
246
|
-
|
|
277
|
+
const descriptionOrMarkdown = s.markdown_path
|
|
278
|
+
? `For full details, read the story markdown file: \`${s.markdown_path}\``
|
|
279
|
+
: s.description;
|
|
280
|
+
return `- [${s.id}] ${s.title} (complexity: ${s.complexity_score || '?'}${externalInfo})\n ${descriptionOrMarkdown}`;
|
|
247
281
|
})
|
|
248
282
|
.join('\n\n');
|
|
249
283
|
|
|
250
284
|
const sessionName = sessionNameOverride || formatSeniorSessionName(teamName);
|
|
285
|
+
const chromeSection = options?.chromeEnabled ? '\n\n' + chromeTabIsolationSection() : '';
|
|
251
286
|
|
|
252
287
|
return `You are a Senior Developer on Team ${teamName}.
|
|
253
288
|
Your tmux session: ${sessionName}
|
|
254
|
-
|
|
289
|
+
${chromeSection}
|
|
255
290
|
${repositorySection(repoPath, repoUrl)}
|
|
256
291
|
|
|
257
292
|
## Your Responsibilities
|
|
@@ -327,10 +362,11 @@ export function generateIntermediatePrompt(
|
|
|
327
362
|
const includeProgressUpdates = shouldIncludeProgressUpdates(options);
|
|
328
363
|
const techLeadSession = resolveTechLeadSession(options);
|
|
329
364
|
const seniorSession = formatSeniorSessionName(teamName);
|
|
365
|
+
const chromeSection = options?.chromeEnabled ? '\n\n' + chromeTabIsolationSection() : '';
|
|
330
366
|
|
|
331
367
|
return `You are an Intermediate Developer on Team ${teamName}.
|
|
332
368
|
Your tmux session: ${sessionName}
|
|
333
|
-
|
|
369
|
+
${chromeSection}
|
|
334
370
|
${repositorySection(repoPath, repoUrl)}
|
|
335
371
|
|
|
336
372
|
## Your Responsibilities
|
|
@@ -399,10 +435,11 @@ export function generateJuniorPrompt(
|
|
|
399
435
|
const includeProgressUpdates = shouldIncludeProgressUpdates(options);
|
|
400
436
|
const techLeadSession = resolveTechLeadSession(options);
|
|
401
437
|
const seniorSession = formatSeniorSessionName(teamName);
|
|
438
|
+
const chromeSection = options?.chromeEnabled ? '\n\n' + chromeTabIsolationSection() : '';
|
|
402
439
|
|
|
403
440
|
return `You are a Junior Developer on Team ${teamName}.
|
|
404
441
|
Your tmux session: ${sessionName}
|
|
405
|
-
|
|
442
|
+
${chromeSection}
|
|
406
443
|
${repositorySection(repoPath, repoUrl)}
|
|
407
444
|
|
|
408
445
|
## Your Responsibilities
|
|
@@ -465,11 +502,13 @@ export function generateQAPrompt(
|
|
|
465
502
|
repoUrl: string,
|
|
466
503
|
repoPath: string,
|
|
467
504
|
sessionName: string,
|
|
468
|
-
targetBranch: string = 'main'
|
|
505
|
+
targetBranch: string = 'main',
|
|
506
|
+
options?: AgentPromptOptions
|
|
469
507
|
): string {
|
|
508
|
+
const chromeSection = options?.chromeEnabled ? '\n\n' + chromeTabIsolationSection() : '';
|
|
470
509
|
return `You are a QA Engineer on Team ${teamName}.
|
|
471
510
|
Your tmux session: ${sessionName}
|
|
472
|
-
|
|
511
|
+
${chromeSection}
|
|
473
512
|
${repositorySection(repoPath, repoUrl)}
|
|
474
513
|
|
|
475
514
|
## Your Responsibilities
|
|
@@ -567,6 +606,7 @@ export function generateFeatureTestPrompt(
|
|
|
567
606
|
): string {
|
|
568
607
|
const includeProgressUpdates = shouldIncludeProgressUpdates(options);
|
|
569
608
|
const techLeadSession = resolveTechLeadSession(options);
|
|
609
|
+
const chromeSection = options?.chromeEnabled ? '\n\n' + chromeTabIsolationSection() : '';
|
|
570
610
|
const reportResultsSection = includeProgressUpdates
|
|
571
611
|
? `**If all tests pass:**
|
|
572
612
|
\`\`\`bash
|
|
@@ -592,7 +632,7 @@ hive msg send ${techLeadSession} "E2E tests FAILED for ${requirementId} on ${fea
|
|
|
592
632
|
|
|
593
633
|
return `You are a Feature Test Agent on Team ${teamName}.
|
|
594
634
|
Your tmux session: ${sessionName}
|
|
595
|
-
|
|
635
|
+
${chromeSection}
|
|
596
636
|
${repositorySection(repoPath, repoUrl)}
|
|
597
637
|
|
|
598
638
|
## Your Mission
|
|
@@ -670,9 +710,10 @@ export function generateAuditorPrompt(
|
|
|
670
710
|
options?: AgentPromptOptions
|
|
671
711
|
): string {
|
|
672
712
|
const techLeadSession = resolveTechLeadSession(options);
|
|
713
|
+
const chromeSection = options?.chromeEnabled ? '\n\n' + chromeTabIsolationSection() : '';
|
|
673
714
|
return `You are a Hive Auditor Agent.
|
|
674
715
|
Your tmux session: ${sessionName}
|
|
675
|
-
|
|
716
|
+
${chromeSection}
|
|
676
717
|
${repositorySection(repoPath, repoUrl)}
|
|
677
718
|
|
|
678
719
|
## Your Mission
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
// Licensed under the Hungry Ghost Hive License. See LICENSE.
|
|
2
2
|
|
|
3
|
+
import { existsSync, mkdirSync, rmSync } from 'fs';
|
|
4
|
+
import { tmpdir } from 'os';
|
|
5
|
+
import { join } from 'path';
|
|
3
6
|
import type { Database } from 'sql.js';
|
|
4
7
|
import initSqlJs from 'sql.js';
|
|
5
|
-
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
8
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
6
9
|
|
|
7
10
|
import { getLogsByEventType } from '../db/queries/logs.js';
|
|
8
11
|
import { createPullRequest } from '../db/queries/pull-requests.js';
|
|
@@ -17,6 +20,7 @@ import {
|
|
|
17
20
|
import { createTeam } from '../db/queries/teams.js';
|
|
18
21
|
import * as worktreeModule from '../git/worktree.js';
|
|
19
22
|
import * as tmuxModule from '../tmux/manager.js';
|
|
23
|
+
import { generateSessionName } from '../tmux/manager.js';
|
|
20
24
|
import { getAgentWorkload, selectAgentWithLeastWorkload } from './agent-selector.js';
|
|
21
25
|
import {
|
|
22
26
|
getCapacityPoints,
|
|
@@ -107,13 +111,15 @@ CREATE TABLE IF NOT EXISTS teams (
|
|
|
107
111
|
|
|
108
112
|
CREATE TABLE IF NOT EXISTS agents (
|
|
109
113
|
id TEXT PRIMARY KEY,
|
|
110
|
-
type TEXT NOT NULL CHECK (type IN ('tech_lead', 'senior', 'intermediate', 'junior', 'qa')),
|
|
114
|
+
type TEXT NOT NULL CHECK (type IN ('tech_lead', 'senior', 'intermediate', 'junior', 'qa', 'feature_test', 'auditor')),
|
|
111
115
|
team_id TEXT REFERENCES teams(id),
|
|
112
116
|
tmux_session TEXT,
|
|
113
117
|
model TEXT,
|
|
114
118
|
status TEXT DEFAULT 'idle' CHECK (status IN ('idle', 'working', 'blocked', 'terminated')),
|
|
115
119
|
current_story_id TEXT,
|
|
116
120
|
memory_state TEXT,
|
|
121
|
+
last_seen TIMESTAMP,
|
|
122
|
+
worktree_path TEXT,
|
|
117
123
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
118
124
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
119
125
|
);
|
|
@@ -141,6 +147,7 @@ CREATE TABLE IF NOT EXISTS stories (
|
|
|
141
147
|
assigned_agent_id TEXT REFERENCES agents(id),
|
|
142
148
|
branch_name TEXT,
|
|
143
149
|
pr_url TEXT,
|
|
150
|
+
markdown_path TEXT,
|
|
144
151
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
145
152
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
146
153
|
);
|
|
@@ -793,6 +800,50 @@ describe('Scheduler Orphaned Story Recovery', () => {
|
|
|
793
800
|
expect(recovered).toContain(story1.id);
|
|
794
801
|
expect(recovered).toContain(story2.id);
|
|
795
802
|
});
|
|
803
|
+
|
|
804
|
+
it('should write markdown files when storiesDir is provided during orphan recovery', () => {
|
|
805
|
+
const storiesDir = join(tmpdir(), `hive-test-stories-${Date.now()}`);
|
|
806
|
+
mkdirSync(storiesDir, { recursive: true });
|
|
807
|
+
|
|
808
|
+
try {
|
|
809
|
+
const team = createTeam(db, {
|
|
810
|
+
name: 'MD Test Team',
|
|
811
|
+
repoUrl: 'https://github.com/test/repo',
|
|
812
|
+
repoPath: 'test',
|
|
813
|
+
});
|
|
814
|
+
|
|
815
|
+
const terminatedAgentId = 'agent-md-terminated';
|
|
816
|
+
db.run(
|
|
817
|
+
`INSERT INTO agents (id, type, team_id, status, created_at, updated_at)
|
|
818
|
+
VALUES (?, ?, ?, ?, datetime('now'), datetime('now'))`,
|
|
819
|
+
[terminatedAgentId, 'intermediate', team.id, 'terminated']
|
|
820
|
+
);
|
|
821
|
+
|
|
822
|
+
const story = createStory(db, {
|
|
823
|
+
teamId: team.id,
|
|
824
|
+
title: 'Story with Markdown',
|
|
825
|
+
description: 'Should get a markdown file on recovery',
|
|
826
|
+
});
|
|
827
|
+
updateStory(db, story.id, {
|
|
828
|
+
assignedAgentId: terminatedAgentId,
|
|
829
|
+
status: 'in_progress',
|
|
830
|
+
});
|
|
831
|
+
|
|
832
|
+
const recovered = detectAndRecoverOrphanedStories(db, '/tmp', storiesDir);
|
|
833
|
+
|
|
834
|
+
expect(recovered).toContain(story.id);
|
|
835
|
+
|
|
836
|
+
// Verify markdown file was written
|
|
837
|
+
const mdPath = join(storiesDir, `${story.id}.md`);
|
|
838
|
+
expect(existsSync(mdPath)).toBe(true);
|
|
839
|
+
|
|
840
|
+
// Verify markdown_path was set in DB
|
|
841
|
+
const updatedStory = getStoryById(db, story.id);
|
|
842
|
+
expect(updatedStory?.markdown_path).toBe(mdPath);
|
|
843
|
+
} finally {
|
|
844
|
+
rmSync(storiesDir, { recursive: true, force: true });
|
|
845
|
+
}
|
|
846
|
+
});
|
|
796
847
|
});
|
|
797
848
|
|
|
798
849
|
describe('Scheduler Refactor Capacity Policy', () => {
|
|
@@ -1905,6 +1956,9 @@ describe('Scheduler checkScaling', () => {
|
|
|
1905
1956
|
repoPath: 'test',
|
|
1906
1957
|
});
|
|
1907
1958
|
|
|
1959
|
+
const gapHiveDir = join(mockConfig.rootDir, '.hive');
|
|
1960
|
+
const gapSessionPrefix = generateSessionName('senior', team.name, undefined, gapHiveDir);
|
|
1961
|
+
|
|
1908
1962
|
for (const index of [2, 3, 4, 5]) {
|
|
1909
1963
|
db.run(
|
|
1910
1964
|
`INSERT INTO agents (id, type, team_id, tmux_session, status, current_story_id, created_at, updated_at)
|
|
@@ -1913,7 +1967,7 @@ describe('Scheduler checkScaling', () => {
|
|
|
1913
1967
|
`senior-gap-${index}`,
|
|
1914
1968
|
'senior',
|
|
1915
1969
|
team.id,
|
|
1916
|
-
|
|
1970
|
+
`${gapSessionPrefix}-${index}`,
|
|
1917
1971
|
'working',
|
|
1918
1972
|
`STORY-EXISTING-${index}`,
|
|
1919
1973
|
]
|
|
@@ -1935,7 +1989,7 @@ describe('Scheduler checkScaling', () => {
|
|
|
1935
1989
|
db.run(
|
|
1936
1990
|
`INSERT INTO agents (id, type, team_id, tmux_session, status, current_story_id, created_at, updated_at)
|
|
1937
1991
|
VALUES (?, ?, ?, ?, ?, NULL, datetime('now'), datetime('now'))`,
|
|
1938
|
-
['senior-gap-6', 'senior', team.id,
|
|
1992
|
+
['senior-gap-6', 'senior', team.id, `${gapSessionPrefix}-6`, 'idle']
|
|
1939
1993
|
);
|
|
1940
1994
|
return {
|
|
1941
1995
|
id: 'senior-gap-6',
|
|
@@ -1943,7 +1997,7 @@ describe('Scheduler checkScaling', () => {
|
|
|
1943
1997
|
team_id: team.id,
|
|
1944
1998
|
status: 'idle',
|
|
1945
1999
|
current_story_id: null,
|
|
1946
|
-
tmux_session:
|
|
2000
|
+
tmux_session: `${gapSessionPrefix}-6`,
|
|
1947
2001
|
};
|
|
1948
2002
|
});
|
|
1949
2003
|
|
|
@@ -2056,10 +2110,13 @@ describe('Scheduler checkScaling', () => {
|
|
|
2056
2110
|
repoPath: 'test',
|
|
2057
2111
|
});
|
|
2058
2112
|
|
|
2113
|
+
const hiveDir = join(mockConfig.rootDir, '.hive');
|
|
2114
|
+
const expectedSession = generateSessionName('senior', team.name, undefined, hiveDir);
|
|
2115
|
+
|
|
2059
2116
|
db.run(
|
|
2060
2117
|
`INSERT INTO agents (id, type, team_id, tmux_session, status, current_story_id, created_at, updated_at)
|
|
2061
2118
|
VALUES (?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))`,
|
|
2062
|
-
['senior-guard-1', 'senior', team.id,
|
|
2119
|
+
['senior-guard-1', 'senior', team.id, expectedSession, 'working', 'STORY-ACTIVE']
|
|
2063
2120
|
);
|
|
2064
2121
|
|
|
2065
2122
|
const isRunningSpy = vi.spyOn(tmuxModule, 'isTmuxSessionRunning').mockResolvedValue(true);
|
|
@@ -2171,6 +2228,72 @@ describe('Scheduler checkScaling', () => {
|
|
|
2171
2228
|
});
|
|
2172
2229
|
});
|
|
2173
2230
|
|
|
2231
|
+
describe('Scheduler Markdown File Writing', () => {
|
|
2232
|
+
let storiesDir: string;
|
|
2233
|
+
|
|
2234
|
+
beforeEach(() => {
|
|
2235
|
+
storiesDir = join(
|
|
2236
|
+
tmpdir(),
|
|
2237
|
+
`hive-test-stories-${Date.now()}-${Math.random().toString(36).slice(2)}`
|
|
2238
|
+
);
|
|
2239
|
+
mkdirSync(storiesDir, { recursive: true });
|
|
2240
|
+
});
|
|
2241
|
+
|
|
2242
|
+
afterEach(() => {
|
|
2243
|
+
rmSync(storiesDir, { recursive: true, force: true });
|
|
2244
|
+
});
|
|
2245
|
+
|
|
2246
|
+
it('should write markdown files when assigning stories via scheduler', async () => {
|
|
2247
|
+
const team = createTeam(db, {
|
|
2248
|
+
name: 'MD Write Team',
|
|
2249
|
+
repoUrl: 'https://github.com/test/repo',
|
|
2250
|
+
repoPath: 'test',
|
|
2251
|
+
});
|
|
2252
|
+
|
|
2253
|
+
// Create an idle senior agent
|
|
2254
|
+
db.run(
|
|
2255
|
+
`INSERT INTO agents (id, type, team_id, status, created_at, updated_at)
|
|
2256
|
+
VALUES (?, ?, ?, ?, datetime('now'), datetime('now'))`,
|
|
2257
|
+
['senior-md-1', 'senior', team.id, 'idle']
|
|
2258
|
+
);
|
|
2259
|
+
|
|
2260
|
+
const story = createStory(db, {
|
|
2261
|
+
teamId: team.id,
|
|
2262
|
+
title: 'Markdown Test Story',
|
|
2263
|
+
description: 'Should get a markdown file on assignment',
|
|
2264
|
+
});
|
|
2265
|
+
updateStory(db, story.id, { status: 'planned', complexityScore: 10, storyPoints: 8 });
|
|
2266
|
+
|
|
2267
|
+
// Create scheduler with rootDir pointing to a temp dir that has .hive/stories/
|
|
2268
|
+
const hiveRoot = join(tmpdir(), `hive-md-root-${Date.now()}`);
|
|
2269
|
+
const hiveStoriesDir = join(hiveRoot, '.hive', 'stories');
|
|
2270
|
+
mkdirSync(hiveStoriesDir, { recursive: true });
|
|
2271
|
+
|
|
2272
|
+
const mdScheduler = new Scheduler(db, {
|
|
2273
|
+
...mockConfig,
|
|
2274
|
+
rootDir: hiveRoot,
|
|
2275
|
+
} as any);
|
|
2276
|
+
|
|
2277
|
+
// Mock spawnSenior to avoid actual tmux operations
|
|
2278
|
+
vi.spyOn(mdScheduler as any, 'sendAssignmentHandoff').mockResolvedValue(undefined);
|
|
2279
|
+
|
|
2280
|
+
const result = await mdScheduler.assignStories();
|
|
2281
|
+
|
|
2282
|
+
expect(result.assigned).toBe(1);
|
|
2283
|
+
|
|
2284
|
+
// Verify markdown file was written
|
|
2285
|
+
const mdPath = join(hiveStoriesDir, `${story.id}.md`);
|
|
2286
|
+
expect(existsSync(mdPath)).toBe(true);
|
|
2287
|
+
|
|
2288
|
+
// Verify markdown_path was set in DB
|
|
2289
|
+
const updatedStory = getStoryById(db, story.id);
|
|
2290
|
+
expect(updatedStory?.markdown_path).toBe(mdPath);
|
|
2291
|
+
expect(updatedStory?.status).toBe('in_progress');
|
|
2292
|
+
|
|
2293
|
+
rmSync(hiveRoot, { recursive: true, force: true });
|
|
2294
|
+
});
|
|
2295
|
+
});
|
|
2296
|
+
|
|
2174
2297
|
describe('Scheduler Target Branch Propagation', () => {
|
|
2175
2298
|
it('should retrieve target_branch from requirement when creating story', () => {
|
|
2176
2299
|
const team = createTeam(db, {
|