hungry-ghost-hive 0.43.2 → 0.45.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/agents/base-agent.d.ts +1 -0
- package/dist/agents/base-agent.d.ts.map +1 -1
- package/dist/agents/base-agent.js +4 -0
- package/dist/agents/base-agent.js.map +1 -1
- package/dist/agents/intermediate.js +2 -2
- package/dist/agents/intermediate.js.map +1 -1
- package/dist/agents/junior.js +2 -2
- package/dist/agents/junior.js.map +1 -1
- package/dist/agents/qa.d.ts.map +1 -1
- package/dist/agents/qa.js +5 -5
- package/dist/agents/qa.js.map +1 -1
- package/dist/agents/senior.d.ts.map +1 -1
- package/dist/agents/senior.js +5 -5
- package/dist/agents/senior.js.map +1 -1
- package/dist/agents/tech-lead.d.ts.map +1 -1
- package/dist/agents/tech-lead.js +8 -3
- package/dist/agents/tech-lead.js.map +1 -1
- package/dist/cli/commands/assign.d.ts.map +1 -1
- package/dist/cli/commands/assign.js +4 -2
- package/dist/cli/commands/assign.js.map +1 -1
- package/dist/cli/commands/assign.test.js +5 -0
- package/dist/cli/commands/assign.test.js.map +1 -1
- package/dist/cli/commands/manager/handoff-recovery.d.ts.map +1 -1
- package/dist/cli/commands/manager/handoff-recovery.js +4 -2
- package/dist/cli/commands/manager/handoff-recovery.js.map +1 -1
- package/dist/cli/commands/manager/index.d.ts.map +1 -1
- package/dist/cli/commands/manager/index.js +16 -12
- package/dist/cli/commands/manager/index.js.map +1 -1
- package/dist/cli/commands/manager/tech-lead-lifecycle.d.ts.map +1 -1
- package/dist/cli/commands/manager/tech-lead-lifecycle.js +8 -3
- package/dist/cli/commands/manager/tech-lead-lifecycle.js.map +1 -1
- package/dist/cli/commands/msg.d.ts.map +1 -1
- package/dist/cli/commands/msg.js +8 -7
- package/dist/cli/commands/msg.js.map +1 -1
- package/dist/cli/commands/my-stories.js +3 -3
- package/dist/cli/commands/my-stories.js.map +1 -1
- package/dist/cli/commands/nuke.d.ts.map +1 -1
- package/dist/cli/commands/nuke.js +18 -7
- package/dist/cli/commands/nuke.js.map +1 -1
- package/dist/cli/commands/nuke.test.js +24 -0
- package/dist/cli/commands/nuke.test.js.map +1 -1
- package/dist/cli/commands/pr.js +5 -0
- package/dist/cli/commands/pr.js.map +1 -1
- package/dist/cli/commands/pr.test.js +43 -1
- package/dist/cli/commands/pr.test.js.map +1 -1
- package/dist/cli/commands/req.d.ts +1 -1
- package/dist/cli/commands/req.d.ts.map +1 -1
- package/dist/cli/commands/req.js +9 -6
- package/dist/cli/commands/req.js.map +1 -1
- package/dist/cli/commands/resume.d.ts.map +1 -1
- package/dist/cli/commands/resume.js +4 -1
- package/dist/cli/commands/resume.js.map +1 -1
- package/dist/cli/commands/stories.js +3 -3
- package/dist/cli/commands/stories.js.map +1 -1
- package/dist/cli/dashboard/panels/agents.d.ts.map +1 -1
- package/dist/cli/dashboard/panels/agents.js +7 -3
- package/dist/cli/dashboard/panels/agents.js.map +1 -1
- package/dist/cli-runtimes/chrome.d.ts +17 -0
- package/dist/cli-runtimes/chrome.d.ts.map +1 -0
- package/dist/cli-runtimes/chrome.js +36 -0
- package/dist/cli-runtimes/chrome.js.map +1 -0
- package/dist/cli-runtimes/claude.d.ts +3 -3
- package/dist/cli-runtimes/claude.d.ts.map +1 -1
- package/dist/cli-runtimes/claude.js +14 -8
- package/dist/cli-runtimes/claude.js.map +1 -1
- package/dist/cli-runtimes/codex.d.ts +3 -3
- package/dist/cli-runtimes/codex.d.ts.map +1 -1
- package/dist/cli-runtimes/codex.js +2 -2
- package/dist/cli-runtimes/codex.js.map +1 -1
- package/dist/cli-runtimes/gemini.d.ts +3 -3
- package/dist/cli-runtimes/gemini.d.ts.map +1 -1
- package/dist/cli-runtimes/gemini.js +2 -2
- package/dist/cli-runtimes/gemini.js.map +1 -1
- package/dist/cli-runtimes/index.d.ts +3 -2
- package/dist/cli-runtimes/index.d.ts.map +1 -1
- package/dist/cli-runtimes/index.js +1 -0
- package/dist/cli-runtimes/index.js.map +1 -1
- package/dist/cli-runtimes/index.test.js +133 -1
- package/dist/cli-runtimes/index.test.js.map +1 -1
- package/dist/cli-runtimes/types.d.ts +9 -2
- package/dist/cli-runtimes/types.d.ts.map +1 -1
- package/dist/config/schema.d.ts +8 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +6 -0
- package/dist/config/schema.js.map +1 -1
- package/dist/context-files/generator.d.ts +1 -1
- package/dist/context-files/generator.d.ts.map +1 -1
- package/dist/context-files/generator.js +3 -2
- package/dist/context-files/generator.js.map +1 -1
- package/dist/context-files/index.test.js +2 -0
- package/dist/context-files/index.test.js.map +1 -1
- package/dist/db/client.d.ts +1 -0
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +6 -0
- package/dist/db/client.js.map +1 -1
- package/dist/db/migrations/015-add-story-markdown-path.sql +5 -0
- package/dist/db/queries/stories.d.ts +3 -3
- package/dist/db/queries/stories.d.ts.map +1 -1
- package/dist/db/queries/stories.js +23 -5
- package/dist/db/queries/stories.js.map +1 -1
- package/dist/db/queries/test-helpers.d.ts.map +1 -1
- package/dist/db/queries/test-helpers.js +1 -0
- package/dist/db/queries/test-helpers.js.map +1 -1
- package/dist/git/worktree.d.ts.map +1 -1
- package/dist/git/worktree.js +7 -0
- package/dist/git/worktree.js.map +1 -1
- package/dist/git/worktree.test.js +30 -0
- package/dist/git/worktree.test.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 +16 -8
- package/dist/orchestrator/prompt-templates.js.map +1 -1
- package/dist/orchestrator/prompt-templates.test.js +4 -0
- package/dist/orchestrator/prompt-templates.test.js.map +1 -1
- package/dist/orchestrator/scheduler.d.ts.map +1 -1
- package/dist/orchestrator/scheduler.js +23 -12
- package/dist/orchestrator/scheduler.js.map +1 -1
- package/dist/orchestrator/scheduler.test.js +1 -0
- package/dist/orchestrator/scheduler.test.js.map +1 -1
- package/dist/tmux/manager.d.ts +7 -6
- package/dist/tmux/manager.d.ts.map +1 -1
- package/dist/tmux/manager.js +29 -13
- package/dist/tmux/manager.js.map +1 -1
- package/dist/utils/auto-merge.d.ts.map +1 -1
- package/dist/utils/auto-merge.js +66 -5
- package/dist/utils/auto-merge.js.map +1 -1
- package/dist/utils/auto-merge.test.js +62 -0
- package/dist/utils/auto-merge.test.js.map +1 -1
- package/dist/utils/instance.d.ts +32 -0
- package/dist/utils/instance.d.ts.map +1 -0
- package/dist/utils/instance.js +82 -0
- package/dist/utils/instance.js.map +1 -0
- package/dist/utils/instance.test.d.ts +2 -0
- package/dist/utils/instance.test.d.ts.map +1 -0
- package/dist/utils/instance.test.js +103 -0
- package/dist/utils/instance.test.js.map +1 -0
- package/dist/utils/paths.d.ts +2 -0
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +2 -0
- package/dist/utils/paths.js.map +1 -1
- package/dist/utils/paths.test.js +6 -0
- package/dist/utils/paths.test.js.map +1 -1
- package/dist/utils/story-markdown.d.ts +16 -0
- package/dist/utils/story-markdown.d.ts.map +1 -0
- package/dist/utils/story-markdown.js +82 -0
- package/dist/utils/story-markdown.js.map +1 -0
- package/dist/utils/story-markdown.test.d.ts +2 -0
- package/dist/utils/story-markdown.test.d.ts.map +1 -0
- package/dist/utils/story-markdown.test.js +143 -0
- package/dist/utils/story-markdown.test.js.map +1 -0
- package/package.json +1 -1
- package/src/agents/base-agent.ts +5 -0
- package/src/agents/intermediate.ts +2 -2
- package/src/agents/junior.ts +2 -2
- package/src/agents/qa.ts +13 -8
- package/src/agents/senior.ts +21 -11
- package/src/agents/tech-lead.ts +28 -13
- package/src/cli/commands/assign.test.ts +5 -0
- package/src/cli/commands/assign.ts +4 -2
- package/src/cli/commands/manager/handoff-recovery.ts +4 -2
- package/src/cli/commands/manager/index.ts +16 -11
- package/src/cli/commands/manager/tech-lead-lifecycle.ts +9 -3
- package/src/cli/commands/msg.ts +8 -7
- package/src/cli/commands/my-stories.ts +22 -13
- package/src/cli/commands/nuke.test.ts +31 -0
- package/src/cli/commands/nuke.ts +18 -7
- package/src/cli/commands/pr.test.ts +77 -1
- package/src/cli/commands/pr.ts +5 -0
- package/src/cli/commands/req.ts +13 -6
- package/src/cli/commands/resume.ts +4 -1
- package/src/cli/commands/stories.ts +22 -13
- package/src/cli/dashboard/panels/agents.ts +7 -3
- package/src/cli-runtimes/chrome.ts +43 -0
- package/src/cli-runtimes/claude.ts +26 -9
- package/src/cli-runtimes/codex.ts +12 -3
- package/src/cli-runtimes/gemini.ts +12 -3
- package/src/cli-runtimes/index.test.ts +158 -0
- package/src/cli-runtimes/index.ts +3 -2
- package/src/cli-runtimes/types.ts +19 -2
- package/src/config/schema.ts +6 -0
- package/src/context-files/generator.ts +3 -2
- package/src/context-files/index.test.ts +2 -0
- package/src/db/client.ts +7 -0
- package/src/db/migrations/015-add-story-markdown-path.sql +5 -0
- package/src/db/queries/stories.ts +29 -5
- package/src/db/queries/test-helpers.ts +1 -0
- package/src/git/worktree.test.ts +43 -0
- package/src/git/worktree.ts +10 -0
- package/src/orchestrator/prompt-templates.test.ts +4 -0
- package/src/orchestrator/prompt-templates.ts +20 -8
- package/src/orchestrator/scheduler.test.ts +1 -0
- package/src/orchestrator/scheduler.ts +33 -12
- package/src/tmux/manager.ts +42 -13
- package/src/utils/auto-merge.test.ts +81 -0
- package/src/utils/auto-merge.ts +78 -5
- package/src/utils/instance.test.ts +129 -0
- package/src/utils/instance.ts +95 -0
- package/src/utils/paths.test.ts +8 -0
- package/src/utils/paths.ts +3 -0
- package/src/utils/story-markdown.test.ts +176 -0
- package/src/utils/story-markdown.ts +94 -0
|
@@ -2,7 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
import type { Command } from 'commander';
|
|
4
4
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
getOpenPullRequestsByStory,
|
|
7
|
+
getPullRequestById,
|
|
8
|
+
updatePullRequest,
|
|
9
|
+
} from '../../db/queries/pull-requests.js';
|
|
6
10
|
import { autoMergeApprovedPRs } from '../../utils/auto-merge.js';
|
|
7
11
|
|
|
8
12
|
// Mock dependencies
|
|
@@ -205,6 +209,78 @@ describe('pr command', () => {
|
|
|
205
209
|
const fromOpt = submitCmd?.options.find(opt => opt.long === '--from');
|
|
206
210
|
expect(fromOpt).toBeDefined();
|
|
207
211
|
});
|
|
212
|
+
|
|
213
|
+
it('should auto-close existing PRs with different github_pr_number', async () => {
|
|
214
|
+
vi.mocked(getOpenPullRequestsByStory).mockReturnValue([
|
|
215
|
+
{
|
|
216
|
+
id: 'old-pr-1',
|
|
217
|
+
story_id: 'TEST-1',
|
|
218
|
+
team_id: 'team-1',
|
|
219
|
+
branch_name: 'feature/old-branch',
|
|
220
|
+
github_pr_number: 42,
|
|
221
|
+
github_pr_url: null,
|
|
222
|
+
submitted_by: null,
|
|
223
|
+
reviewed_by: null,
|
|
224
|
+
status: 'queued',
|
|
225
|
+
review_notes: null,
|
|
226
|
+
created_at: '2026-01-01T00:00:00.000Z',
|
|
227
|
+
updated_at: '2026-01-01T00:00:00.000Z',
|
|
228
|
+
reviewed_at: null,
|
|
229
|
+
},
|
|
230
|
+
]);
|
|
231
|
+
|
|
232
|
+
await run(
|
|
233
|
+
'submit',
|
|
234
|
+
'--branch',
|
|
235
|
+
'feature/new-branch',
|
|
236
|
+
'--story',
|
|
237
|
+
'TEST-1',
|
|
238
|
+
'--pr-number',
|
|
239
|
+
'99'
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
expect(updatePullRequest).toHaveBeenCalledWith(
|
|
243
|
+
expect.anything(),
|
|
244
|
+
'old-pr-1',
|
|
245
|
+
expect.objectContaining({ status: 'closed' })
|
|
246
|
+
);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it('should skip auto-close when resubmitting same github PR number', async () => {
|
|
250
|
+
vi.mocked(getOpenPullRequestsByStory).mockReturnValue([
|
|
251
|
+
{
|
|
252
|
+
id: 'existing-pr-1',
|
|
253
|
+
story_id: 'TEST-1',
|
|
254
|
+
team_id: 'team-1',
|
|
255
|
+
branch_name: 'feature/same-branch',
|
|
256
|
+
github_pr_number: 55,
|
|
257
|
+
github_pr_url: null,
|
|
258
|
+
submitted_by: null,
|
|
259
|
+
reviewed_by: null,
|
|
260
|
+
status: 'queued',
|
|
261
|
+
review_notes: null,
|
|
262
|
+
created_at: '2026-01-01T00:00:00.000Z',
|
|
263
|
+
updated_at: '2026-01-01T00:00:00.000Z',
|
|
264
|
+
reviewed_at: null,
|
|
265
|
+
},
|
|
266
|
+
]);
|
|
267
|
+
|
|
268
|
+
await run(
|
|
269
|
+
'submit',
|
|
270
|
+
'--branch',
|
|
271
|
+
'feature/same-branch',
|
|
272
|
+
'--story',
|
|
273
|
+
'TEST-1',
|
|
274
|
+
'--pr-number',
|
|
275
|
+
'55'
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
expect(updatePullRequest).not.toHaveBeenCalledWith(
|
|
279
|
+
expect.anything(),
|
|
280
|
+
'existing-pr-1',
|
|
281
|
+
expect.objectContaining({ status: 'closed' })
|
|
282
|
+
);
|
|
283
|
+
});
|
|
208
284
|
});
|
|
209
285
|
|
|
210
286
|
describe('queue subcommand', () => {
|
package/src/cli/commands/pr.ts
CHANGED
|
@@ -62,8 +62,13 @@ prCommand
|
|
|
62
62
|
teamId = story.team_id;
|
|
63
63
|
|
|
64
64
|
// Auto-close any existing open PRs for this story
|
|
65
|
+
const incomingPrNumber = options.prNumber ? parseInt(options.prNumber, 10) : null;
|
|
65
66
|
const existingPRs = getOpenPullRequestsByStory(db.db, storyId);
|
|
66
67
|
for (const existingPR of existingPRs) {
|
|
68
|
+
// Skip auto-close if this is a resubmit of the same GitHub PR
|
|
69
|
+
if (incomingPrNumber !== null && existingPR.github_pr_number === incomingPrNumber) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
67
72
|
updatePullRequest(db.db, existingPR.id, { status: 'closed' });
|
|
68
73
|
createLog(db.db, {
|
|
69
74
|
agentId: options.from || 'system',
|
package/src/cli/commands/req.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { createLog } from '../../db/queries/logs.js';
|
|
|
15
15
|
import { createRequirement, updateRequirement } from '../../db/queries/requirements.js';
|
|
16
16
|
import { getAllTeams } from '../../db/queries/teams.js';
|
|
17
17
|
import { isTmuxAvailable, spawnTmuxSession } from '../../tmux/manager.js';
|
|
18
|
+
import { getTechLeadSessionName } from '../../utils/instance.js';
|
|
18
19
|
import { withHiveContext } from '../../utils/with-hive-context.js';
|
|
19
20
|
import { startDashboard } from '../dashboard/index.js';
|
|
20
21
|
|
|
@@ -209,21 +210,25 @@ export const reqCommand = new Command('req')
|
|
|
209
210
|
});
|
|
210
211
|
|
|
211
212
|
// Spawn Tech Lead tmux session
|
|
212
|
-
const sessionName =
|
|
213
|
+
const sessionName = getTechLeadSessionName(paths.hiveDir);
|
|
213
214
|
const techLeadPrompt = generateTechLeadPrompt(
|
|
214
215
|
req.id,
|
|
215
216
|
title,
|
|
216
217
|
description,
|
|
217
218
|
teams,
|
|
218
219
|
options.godmode,
|
|
219
|
-
targetBranch
|
|
220
|
+
targetBranch,
|
|
221
|
+
sessionName
|
|
220
222
|
);
|
|
221
223
|
|
|
222
224
|
try {
|
|
223
225
|
// Build CLI command using the configured runtime for Tech Lead
|
|
226
|
+
const chromeEnabled =
|
|
227
|
+
config.agents?.chrome_enabled === true && techLeadCliTool === 'claude';
|
|
224
228
|
const commandArgs = getCliRuntimeBuilder(techLeadCliTool).buildSpawnCommand(
|
|
225
229
|
techLeadModel,
|
|
226
|
-
techLeadSafetyMode
|
|
230
|
+
techLeadSafetyMode,
|
|
231
|
+
{ chrome: chromeEnabled }
|
|
227
232
|
);
|
|
228
233
|
|
|
229
234
|
// Pass the prompt as initialPrompt so it's included as a CLI positional
|
|
@@ -318,8 +323,10 @@ export function generateTechLeadPrompt(
|
|
|
318
323
|
description: string,
|
|
319
324
|
teams: { id: string; name: string; repo_path: string; repo_url: string }[],
|
|
320
325
|
godmode?: boolean,
|
|
321
|
-
targetBranch?: string
|
|
326
|
+
targetBranch?: string,
|
|
327
|
+
techLeadSession?: string
|
|
322
328
|
): string {
|
|
329
|
+
const tlSession = techLeadSession || 'hive-tech-lead';
|
|
323
330
|
const teamList = teams.map(t => `- ${t.name}: ${t.repo_path} (${t.repo_url})`).join('\n');
|
|
324
331
|
const godmodeNotice = godmode
|
|
325
332
|
? `
|
|
@@ -384,7 +391,7 @@ The SQLite database is at .hive/hive.db
|
|
|
384
391
|
|
|
385
392
|
Check your inbox for messages from developers:
|
|
386
393
|
\`\`\`bash
|
|
387
|
-
hive msg inbox
|
|
394
|
+
hive msg inbox ${tlSession}
|
|
388
395
|
\`\`\`
|
|
389
396
|
|
|
390
397
|
Read a specific message:
|
|
@@ -397,7 +404,7 @@ Reply to a message:
|
|
|
397
404
|
hive msg reply <msg-id> "Your response here"
|
|
398
405
|
\`\`\`
|
|
399
406
|
|
|
400
|
-
**IMPORTANT:** Periodically run \`hive msg inbox
|
|
407
|
+
**IMPORTANT:** Periodically run \`hive msg inbox ${tlSession}\` to check if any developers need guidance. Answer their questions promptly to keep the team unblocked.
|
|
401
408
|
|
|
402
409
|
When done planning, update the requirement status to 'planned' and run \`hive assign\` to spawn Senior developers who will implement the stories.
|
|
403
410
|
`;
|
|
@@ -91,8 +91,11 @@ export const resumeCommand = new Command('resume')
|
|
|
91
91
|
const model = resolveRuntimeModelForCli(selectedModel, cliTool);
|
|
92
92
|
|
|
93
93
|
// Build resume command using CLI runtime builder
|
|
94
|
+
const chromeEnabled = config.agents?.chrome_enabled === true && cliTool === 'claude';
|
|
94
95
|
const runtimeBuilder = getCliRuntimeBuilder(cliTool);
|
|
95
|
-
const commandArgs = runtimeBuilder.buildResumeCommand(model, sessionName, safetyMode
|
|
96
|
+
const commandArgs = runtimeBuilder.buildResumeCommand(model, sessionName, safetyMode, {
|
|
97
|
+
chrome: chromeEnabled,
|
|
98
|
+
});
|
|
96
99
|
|
|
97
100
|
// Spawn new session
|
|
98
101
|
await spawnTmuxSession({
|
|
@@ -87,23 +87,32 @@ storiesCommand
|
|
|
87
87
|
criteria?: string[];
|
|
88
88
|
json?: boolean;
|
|
89
89
|
}) => {
|
|
90
|
-
await withHiveContext(async ({ root, db }) => {
|
|
90
|
+
await withHiveContext(async ({ root, paths, db }) => {
|
|
91
91
|
// Create local story
|
|
92
|
-
const story = createStory(
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
92
|
+
const story = createStory(
|
|
93
|
+
db.db,
|
|
94
|
+
{
|
|
95
|
+
requirementId: options.requirement || null,
|
|
96
|
+
teamId: options.team || null,
|
|
97
|
+
title: options.title,
|
|
98
|
+
description: options.description,
|
|
99
|
+
acceptanceCriteria: options.criteria || null,
|
|
100
|
+
},
|
|
101
|
+
paths.storiesDir
|
|
102
|
+
);
|
|
99
103
|
|
|
100
104
|
// Update with optional fields
|
|
101
105
|
if (options.points !== undefined || options.complexity !== undefined) {
|
|
102
|
-
updateStory(
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
106
|
+
updateStory(
|
|
107
|
+
db.db,
|
|
108
|
+
story.id,
|
|
109
|
+
{
|
|
110
|
+
storyPoints: options.points ?? null,
|
|
111
|
+
complexityScore: options.complexity ?? null,
|
|
112
|
+
status: 'estimated',
|
|
113
|
+
},
|
|
114
|
+
paths.storiesDir
|
|
115
|
+
);
|
|
107
116
|
}
|
|
108
117
|
|
|
109
118
|
// Sync to PM provider if configured
|
|
@@ -9,6 +9,7 @@ import type { ModelsConfig } from '../../../config/schema.js';
|
|
|
9
9
|
import { getActiveAgents, type AgentRow } from '../../../db/queries/agents.js';
|
|
10
10
|
import { getTeamById } from '../../../db/queries/teams.js';
|
|
11
11
|
import { getHiveSessions } from '../../../tmux/manager.js';
|
|
12
|
+
import { getManagerSessionName } from '../../../utils/instance.js';
|
|
12
13
|
import { findHiveRoot, getHivePaths } from '../../../utils/paths.js';
|
|
13
14
|
|
|
14
15
|
function debugLog(msg: string) {
|
|
@@ -125,8 +126,11 @@ export async function updateAgentsPanel(list: Widgets.ListElement, db: Database)
|
|
|
125
126
|
}
|
|
126
127
|
|
|
127
128
|
// Check for manager session (not in DB)
|
|
128
|
-
const
|
|
129
|
-
const
|
|
129
|
+
const hiveRoot = findHiveRoot();
|
|
130
|
+
const hDir = hiveRoot ? getHivePaths(hiveRoot).hiveDir : undefined;
|
|
131
|
+
const managerName = hDir ? getManagerSessionName(hDir) : 'hive-manager';
|
|
132
|
+
const hiveSessions = await getHiveSessions(hDir);
|
|
133
|
+
const managerSession = hiveSessions.find(s => s.name === managerName);
|
|
130
134
|
|
|
131
135
|
// Build combined list - manager first if running
|
|
132
136
|
const displayAgents: DisplayAgent[] = [];
|
|
@@ -137,7 +141,7 @@ export async function updateAgentsPanel(list: Widgets.ListElement, db: Database)
|
|
|
137
141
|
id: 'manager',
|
|
138
142
|
type: 'manager' as AgentRow['type'],
|
|
139
143
|
team_id: null,
|
|
140
|
-
tmux_session:
|
|
144
|
+
tmux_session: managerName,
|
|
141
145
|
model: '-',
|
|
142
146
|
status: 'working',
|
|
143
147
|
current_story_id: null,
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Licensed under the Hungry Ghost Hive License. See LICENSE.
|
|
2
|
+
|
|
3
|
+
import { execa } from 'execa';
|
|
4
|
+
import type { CliRuntimeType } from './types.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Detect whether the Claude CLI supports the --chrome flag.
|
|
8
|
+
* Runs `claude --help` and checks if the output mentions --chrome.
|
|
9
|
+
* @returns true if --chrome is recognized by the CLI
|
|
10
|
+
*/
|
|
11
|
+
export async function detectChromeAvailability(): Promise<boolean> {
|
|
12
|
+
try {
|
|
13
|
+
const result = await execa('claude', ['--help']);
|
|
14
|
+
const output = result.stdout + result.stderr;
|
|
15
|
+
return output.includes('--chrome');
|
|
16
|
+
} catch {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Resolve the effective chrome enabled state from config value.
|
|
23
|
+
* - true/false: use the explicit value
|
|
24
|
+
* - 'auto': detect availability, but only enable for claude CLI tool
|
|
25
|
+
* @param configValue - The chrome_enabled config value (true, false, or 'auto')
|
|
26
|
+
* @param cliTool - The CLI tool configured for the agent
|
|
27
|
+
* @returns Whether chrome should be enabled
|
|
28
|
+
*/
|
|
29
|
+
export async function resolveChromeEnabled(
|
|
30
|
+
configValue: boolean | 'auto',
|
|
31
|
+
cliTool: CliRuntimeType
|
|
32
|
+
): Promise<boolean> {
|
|
33
|
+
if (typeof configValue === 'boolean') {
|
|
34
|
+
return configValue;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Auto-detect: only enable for claude CLI tool
|
|
38
|
+
if (cliTool !== 'claude') {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return detectChromeAvailability();
|
|
43
|
+
}
|
|
@@ -1,20 +1,37 @@
|
|
|
1
1
|
// Licensed under the Hungry Ghost Hive License. See LICENSE.
|
|
2
2
|
|
|
3
|
-
import { CliRuntimeBuilder, RuntimeSafetyMode } from './types.js';
|
|
3
|
+
import { CliRuntimeBuilder, RuntimeOptions, RuntimeSafetyMode } from './types.js';
|
|
4
4
|
|
|
5
5
|
export class ClaudeRuntimeBuilder implements CliRuntimeBuilder {
|
|
6
|
-
buildSpawnCommand(
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
buildSpawnCommand(
|
|
7
|
+
model: string,
|
|
8
|
+
safetyMode: RuntimeSafetyMode,
|
|
9
|
+
options?: RuntimeOptions
|
|
10
|
+
): string[] {
|
|
11
|
+
const args =
|
|
12
|
+
safetyMode === 'safe'
|
|
13
|
+
? ['claude', '--model', model]
|
|
14
|
+
: ['claude', '--dangerously-skip-permissions', '--model', model];
|
|
15
|
+
if (options?.chrome) {
|
|
16
|
+
args.push('--chrome');
|
|
9
17
|
}
|
|
10
|
-
return
|
|
18
|
+
return args;
|
|
11
19
|
}
|
|
12
20
|
|
|
13
|
-
buildResumeCommand(
|
|
14
|
-
|
|
15
|
-
|
|
21
|
+
buildResumeCommand(
|
|
22
|
+
model: string,
|
|
23
|
+
sessionId: string,
|
|
24
|
+
safetyMode: RuntimeSafetyMode,
|
|
25
|
+
options?: RuntimeOptions
|
|
26
|
+
): string[] {
|
|
27
|
+
const args =
|
|
28
|
+
safetyMode === 'safe'
|
|
29
|
+
? ['claude', '--model', model, '--resume', sessionId]
|
|
30
|
+
: ['claude', '--dangerously-skip-permissions', '--model', model, '--resume', sessionId];
|
|
31
|
+
if (options?.chrome) {
|
|
32
|
+
args.push('--chrome');
|
|
16
33
|
}
|
|
17
|
-
return
|
|
34
|
+
return args;
|
|
18
35
|
}
|
|
19
36
|
|
|
20
37
|
getAutoApprovalFlag(safetyMode: RuntimeSafetyMode): string {
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
// Licensed under the Hungry Ghost Hive License. See LICENSE.
|
|
2
2
|
|
|
3
|
-
import { CliRuntimeBuilder, RuntimeSafetyMode } from './types.js';
|
|
3
|
+
import { CliRuntimeBuilder, RuntimeOptions, RuntimeSafetyMode } from './types.js';
|
|
4
4
|
|
|
5
5
|
export class CodexRuntimeBuilder implements CliRuntimeBuilder {
|
|
6
|
-
buildSpawnCommand(
|
|
6
|
+
buildSpawnCommand(
|
|
7
|
+
model: string,
|
|
8
|
+
safetyMode: RuntimeSafetyMode,
|
|
9
|
+
_options?: RuntimeOptions
|
|
10
|
+
): string[] {
|
|
7
11
|
const approvalPolicy = safetyMode === 'safe' ? 'on-request' : 'never';
|
|
8
12
|
const sandboxMode = safetyMode === 'safe' ? 'workspace-write' : 'danger-full-access';
|
|
9
13
|
return [
|
|
@@ -17,7 +21,12 @@ export class CodexRuntimeBuilder implements CliRuntimeBuilder {
|
|
|
17
21
|
];
|
|
18
22
|
}
|
|
19
23
|
|
|
20
|
-
buildResumeCommand(
|
|
24
|
+
buildResumeCommand(
|
|
25
|
+
model: string,
|
|
26
|
+
sessionId: string,
|
|
27
|
+
safetyMode: RuntimeSafetyMode,
|
|
28
|
+
_options?: RuntimeOptions
|
|
29
|
+
): string[] {
|
|
21
30
|
const approvalPolicy = safetyMode === 'safe' ? 'on-request' : 'never';
|
|
22
31
|
const sandboxMode = safetyMode === 'safe' ? 'workspace-write' : 'danger-full-access';
|
|
23
32
|
return [
|
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
// Licensed under the Hungry Ghost Hive License. See LICENSE.
|
|
2
2
|
|
|
3
|
-
import { CliRuntimeBuilder, RuntimeSafetyMode } from './types.js';
|
|
3
|
+
import { CliRuntimeBuilder, RuntimeOptions, RuntimeSafetyMode } from './types.js';
|
|
4
4
|
|
|
5
5
|
export class GeminiRuntimeBuilder implements CliRuntimeBuilder {
|
|
6
|
-
buildSpawnCommand(
|
|
6
|
+
buildSpawnCommand(
|
|
7
|
+
model: string,
|
|
8
|
+
safetyMode: RuntimeSafetyMode,
|
|
9
|
+
_options?: RuntimeOptions
|
|
10
|
+
): string[] {
|
|
7
11
|
const sandboxMode = safetyMode === 'safe' ? 'workspace-write' : 'none';
|
|
8
12
|
return ['gemini', '--model', model, '--sandbox', sandboxMode];
|
|
9
13
|
}
|
|
10
14
|
|
|
11
|
-
buildResumeCommand(
|
|
15
|
+
buildResumeCommand(
|
|
16
|
+
model: string,
|
|
17
|
+
sessionId: string,
|
|
18
|
+
safetyMode: RuntimeSafetyMode,
|
|
19
|
+
_options?: RuntimeOptions
|
|
20
|
+
): string[] {
|
|
12
21
|
const sandboxMode = safetyMode === 'safe' ? 'workspace-write' : 'none';
|
|
13
22
|
return ['gemini', '--model', model, '--sandbox', sandboxMode, '--resume', sessionId];
|
|
14
23
|
}
|
|
@@ -5,7 +5,9 @@ import {
|
|
|
5
5
|
ClaudeRuntimeBuilder,
|
|
6
6
|
CodexRuntimeBuilder,
|
|
7
7
|
GeminiRuntimeBuilder,
|
|
8
|
+
detectChromeAvailability,
|
|
8
9
|
getCliRuntimeBuilder,
|
|
10
|
+
resolveChromeEnabled,
|
|
9
11
|
resolveRuntimeModelForCli,
|
|
10
12
|
selectCompatibleModelForCli,
|
|
11
13
|
validateCliBinary,
|
|
@@ -506,4 +508,160 @@ describe('CLI Runtime Builders', () => {
|
|
|
506
508
|
expect(selected).toBe('claude-sonnet-4-5-20250929');
|
|
507
509
|
});
|
|
508
510
|
});
|
|
511
|
+
|
|
512
|
+
describe('detectChromeAvailability', () => {
|
|
513
|
+
beforeEach(() => {
|
|
514
|
+
vi.clearAllMocks();
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
afterEach(() => {
|
|
518
|
+
vi.restoreAllMocks();
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
it('should return true when claude --help output includes --chrome', async () => {
|
|
522
|
+
const { execa } = await import('execa');
|
|
523
|
+
vi.mocked(execa).mockResolvedValue({
|
|
524
|
+
stdout: 'Usage: claude [options]\n --chrome Enable Chrome integration\n',
|
|
525
|
+
stderr: '',
|
|
526
|
+
exitCode: 0,
|
|
527
|
+
command: 'claude --help',
|
|
528
|
+
escapedCommand: 'claude --help',
|
|
529
|
+
failed: false,
|
|
530
|
+
timedOut: false,
|
|
531
|
+
isCanceled: false,
|
|
532
|
+
killed: false,
|
|
533
|
+
} as any);
|
|
534
|
+
|
|
535
|
+
const result = await detectChromeAvailability();
|
|
536
|
+
expect(result).toBe(true);
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
it('should return false when claude --help output does not include --chrome', async () => {
|
|
540
|
+
const { execa } = await import('execa');
|
|
541
|
+
vi.mocked(execa).mockResolvedValue({
|
|
542
|
+
stdout: 'Usage: claude [options]\n --model Set the model\n',
|
|
543
|
+
stderr: '',
|
|
544
|
+
exitCode: 0,
|
|
545
|
+
command: 'claude --help',
|
|
546
|
+
escapedCommand: 'claude --help',
|
|
547
|
+
failed: false,
|
|
548
|
+
timedOut: false,
|
|
549
|
+
isCanceled: false,
|
|
550
|
+
killed: false,
|
|
551
|
+
} as any);
|
|
552
|
+
|
|
553
|
+
const result = await detectChromeAvailability();
|
|
554
|
+
expect(result).toBe(false);
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
it('should return true when --chrome appears in stderr', async () => {
|
|
558
|
+
const { execa } = await import('execa');
|
|
559
|
+
vi.mocked(execa).mockResolvedValue({
|
|
560
|
+
stdout: '',
|
|
561
|
+
stderr: 'Options:\n --chrome Enable Chrome integration\n',
|
|
562
|
+
exitCode: 0,
|
|
563
|
+
command: 'claude --help',
|
|
564
|
+
escapedCommand: 'claude --help',
|
|
565
|
+
failed: false,
|
|
566
|
+
timedOut: false,
|
|
567
|
+
isCanceled: false,
|
|
568
|
+
killed: false,
|
|
569
|
+
} as any);
|
|
570
|
+
|
|
571
|
+
const result = await detectChromeAvailability();
|
|
572
|
+
expect(result).toBe(true);
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
it('should return false when the command fails', async () => {
|
|
576
|
+
const { execa } = await import('execa');
|
|
577
|
+
vi.mocked(execa).mockRejectedValue(new Error('Command failed'));
|
|
578
|
+
|
|
579
|
+
const result = await detectChromeAvailability();
|
|
580
|
+
expect(result).toBe(false);
|
|
581
|
+
});
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
describe('resolveChromeEnabled', () => {
|
|
585
|
+
beforeEach(() => {
|
|
586
|
+
vi.clearAllMocks();
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
afterEach(() => {
|
|
590
|
+
vi.restoreAllMocks();
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
it('should return true when configValue is explicitly true', async () => {
|
|
594
|
+
const result = await resolveChromeEnabled(true, 'claude');
|
|
595
|
+
expect(result).toBe(true);
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
it('should return true when configValue is explicitly true for non-claude tool', async () => {
|
|
599
|
+
const result = await resolveChromeEnabled(true, 'codex');
|
|
600
|
+
expect(result).toBe(true);
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
it('should return false when configValue is explicitly false', async () => {
|
|
604
|
+
const result = await resolveChromeEnabled(false, 'claude');
|
|
605
|
+
expect(result).toBe(false);
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
it('should return false when configValue is explicitly false for non-claude tool', async () => {
|
|
609
|
+
const result = await resolveChromeEnabled(false, 'gemini');
|
|
610
|
+
expect(result).toBe(false);
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
it('should return false for auto mode with codex CLI tool', async () => {
|
|
614
|
+
const result = await resolveChromeEnabled('auto', 'codex');
|
|
615
|
+
expect(result).toBe(false);
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
it('should return false for auto mode with gemini CLI tool', async () => {
|
|
619
|
+
const result = await resolveChromeEnabled('auto', 'gemini');
|
|
620
|
+
expect(result).toBe(false);
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
it('should detect chrome availability for auto mode with claude CLI tool when --chrome is available', async () => {
|
|
624
|
+
const { execa } = await import('execa');
|
|
625
|
+
vi.mocked(execa).mockResolvedValue({
|
|
626
|
+
stdout: 'Usage: claude [options]\n --chrome Enable Chrome integration\n',
|
|
627
|
+
stderr: '',
|
|
628
|
+
exitCode: 0,
|
|
629
|
+
command: 'claude --help',
|
|
630
|
+
escapedCommand: 'claude --help',
|
|
631
|
+
failed: false,
|
|
632
|
+
timedOut: false,
|
|
633
|
+
isCanceled: false,
|
|
634
|
+
killed: false,
|
|
635
|
+
} as any);
|
|
636
|
+
|
|
637
|
+
const result = await resolveChromeEnabled('auto', 'claude');
|
|
638
|
+
expect(result).toBe(true);
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
it('should return false for auto mode with claude CLI tool when --chrome is not available', async () => {
|
|
642
|
+
const { execa } = await import('execa');
|
|
643
|
+
vi.mocked(execa).mockResolvedValue({
|
|
644
|
+
stdout: 'Usage: claude [options]\n --model Set the model\n',
|
|
645
|
+
stderr: '',
|
|
646
|
+
exitCode: 0,
|
|
647
|
+
command: 'claude --help',
|
|
648
|
+
escapedCommand: 'claude --help',
|
|
649
|
+
failed: false,
|
|
650
|
+
timedOut: false,
|
|
651
|
+
isCanceled: false,
|
|
652
|
+
killed: false,
|
|
653
|
+
} as any);
|
|
654
|
+
|
|
655
|
+
const result = await resolveChromeEnabled('auto', 'claude');
|
|
656
|
+
expect(result).toBe(false);
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
it('should return false for auto mode with claude CLI tool when detection fails', async () => {
|
|
660
|
+
const { execa } = await import('execa');
|
|
661
|
+
vi.mocked(execa).mockRejectedValue(new Error('Command not found'));
|
|
662
|
+
|
|
663
|
+
const result = await resolveChromeEnabled('auto', 'claude');
|
|
664
|
+
expect(result).toBe(false);
|
|
665
|
+
});
|
|
666
|
+
});
|
|
509
667
|
});
|
|
@@ -5,7 +5,7 @@ import { UnsupportedFeatureError, ValidationError } from '../errors/index.js';
|
|
|
5
5
|
import { ClaudeRuntimeBuilder } from './claude.js';
|
|
6
6
|
import { CodexRuntimeBuilder } from './codex.js';
|
|
7
7
|
import { GeminiRuntimeBuilder } from './gemini.js';
|
|
8
|
-
import { CliRuntimeBuilder, CliRuntimeType, RuntimeSafetyMode } from './types.js';
|
|
8
|
+
import { CliRuntimeBuilder, CliRuntimeType, RuntimeOptions, RuntimeSafetyMode } from './types.js';
|
|
9
9
|
|
|
10
10
|
const CODEX_CHATGPT_SAFE_MODEL = 'gpt-5.2-codex';
|
|
11
11
|
|
|
@@ -144,7 +144,8 @@ export function resolveRuntimeModelForCli(model: string, cliTool: CliRuntimeType
|
|
|
144
144
|
return model;
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
+
export { detectChromeAvailability, resolveChromeEnabled } from './chrome.js';
|
|
147
148
|
export { ClaudeRuntimeBuilder } from './claude.js';
|
|
148
149
|
export { CodexRuntimeBuilder } from './codex.js';
|
|
149
150
|
export { GeminiRuntimeBuilder } from './gemini.js';
|
|
150
|
-
export type { CliRuntimeBuilder, CliRuntimeType, RuntimeSafetyMode };
|
|
151
|
+
export type { CliRuntimeBuilder, CliRuntimeType, RuntimeOptions, RuntimeSafetyMode };
|
|
@@ -3,21 +3,38 @@
|
|
|
3
3
|
export type CliRuntimeType = 'claude' | 'codex' | 'gemini';
|
|
4
4
|
export type RuntimeSafetyMode = 'safe' | 'unsafe';
|
|
5
5
|
|
|
6
|
+
export interface RuntimeOptions {
|
|
7
|
+
chrome?: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
6
10
|
export interface CliRuntimeBuilder {
|
|
7
11
|
/**
|
|
8
12
|
* Build command array for spawning a new agent session
|
|
9
13
|
* @param model - The model identifier to use
|
|
14
|
+
* @param safetyMode - The safety mode for the agent
|
|
15
|
+
* @param options - Optional runtime options (e.g., chrome flag)
|
|
10
16
|
* @returns Array of command and arguments suitable for spawn
|
|
11
17
|
*/
|
|
12
|
-
buildSpawnCommand(
|
|
18
|
+
buildSpawnCommand(
|
|
19
|
+
model: string,
|
|
20
|
+
safetyMode: RuntimeSafetyMode,
|
|
21
|
+
options?: RuntimeOptions
|
|
22
|
+
): string[];
|
|
13
23
|
|
|
14
24
|
/**
|
|
15
25
|
* Build command array for resuming an existing agent session
|
|
16
26
|
* @param model - The model identifier to use
|
|
17
27
|
* @param sessionId - The session ID to resume
|
|
28
|
+
* @param safetyMode - The safety mode for the agent
|
|
29
|
+
* @param options - Optional runtime options (e.g., chrome flag)
|
|
18
30
|
* @returns Array of command and arguments suitable for spawn
|
|
19
31
|
*/
|
|
20
|
-
buildResumeCommand(
|
|
32
|
+
buildResumeCommand(
|
|
33
|
+
model: string,
|
|
34
|
+
sessionId: string,
|
|
35
|
+
safetyMode: RuntimeSafetyMode,
|
|
36
|
+
options?: RuntimeOptions
|
|
37
|
+
): string[];
|
|
21
38
|
|
|
22
39
|
/**
|
|
23
40
|
* Get the auto-approval flag for this CLI runtime
|
package/src/config/schema.ts
CHANGED
|
@@ -210,6 +210,9 @@ const AgentsConfigSchema = z.object({
|
|
|
210
210
|
llm_timeout_ms: z.number().int().positive().default(1800000),
|
|
211
211
|
// Max retries for LLM calls on timeout
|
|
212
212
|
llm_max_retries: z.number().int().nonnegative().default(2),
|
|
213
|
+
// Enable Chrome browser automation via Claude in Chrome extension
|
|
214
|
+
// true = always enable, false = always disable, 'auto' = detect availability
|
|
215
|
+
chrome_enabled: z.union([z.boolean(), z.literal('auto')]).default('auto'),
|
|
213
216
|
});
|
|
214
217
|
|
|
215
218
|
// Manager daemon configuration
|
|
@@ -521,6 +524,9 @@ agents:
|
|
|
521
524
|
llm_timeout_ms: 1800000
|
|
522
525
|
# Max retries for LLM calls on timeout
|
|
523
526
|
llm_max_retries: 2
|
|
527
|
+
# Enable Chrome browser automation (true, false, or auto)
|
|
528
|
+
# auto = detect if Claude CLI supports --chrome flag
|
|
529
|
+
chrome_enabled: auto
|
|
524
530
|
|
|
525
531
|
# Manager daemon (micromanager nudge behavior)
|
|
526
532
|
manager:
|