hungry-ghost-hive 0.41.0 → 0.42.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/manager/auto-assignment.js +4 -4
- package/dist/cli/commands/manager/auto-assignment.js.map +1 -1
- package/dist/cli/commands/manager/index.d.ts.map +1 -1
- package/dist/cli/commands/manager/index.js +68 -3
- package/dist/cli/commands/manager/index.js.map +1 -1
- package/dist/cli/commands/manager/restart-cooldown.d.ts +10 -0
- package/dist/cli/commands/manager/restart-cooldown.d.ts.map +1 -0
- package/dist/cli/commands/manager/restart-cooldown.js +16 -0
- package/dist/cli/commands/manager/restart-cooldown.js.map +1 -0
- package/dist/cli/commands/manager/restart-cooldown.test.d.ts +2 -0
- package/dist/cli/commands/manager/restart-cooldown.test.d.ts.map +1 -0
- package/dist/cli/commands/manager/restart-cooldown.test.js +54 -0
- package/dist/cli/commands/manager/restart-cooldown.test.js.map +1 -0
- package/dist/cli/commands/req.d.ts +6 -0
- package/dist/cli/commands/req.d.ts.map +1 -1
- package/dist/cli/commands/req.js +1 -1
- package/dist/cli/commands/req.js.map +1 -1
- package/dist/db/queries/agents.d.ts +1 -0
- package/dist/db/queries/agents.d.ts.map +1 -1
- package/dist/db/queries/agents.js +4 -0
- package/dist/db/queries/agents.js.map +1 -1
- package/dist/db/queries/agents.test.js +10 -0
- package/dist/db/queries/agents.test.js.map +1 -1
- package/dist/tmux/manager.d.ts.map +1 -1
- package/dist/tmux/manager.js +15 -8
- package/dist/tmux/manager.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/commands/manager/auto-assignment.ts +4 -4
- package/src/cli/commands/manager/index.ts +97 -3
- package/src/cli/commands/manager/restart-cooldown.test.ts +61 -0
- package/src/cli/commands/manager/restart-cooldown.ts +20 -0
- package/src/cli/commands/req.ts +1 -1
- package/src/db/queries/agents.test.ts +13 -0
- package/src/db/queries/agents.ts +5 -0
- package/src/tmux/manager.ts +19 -8
|
@@ -6,15 +6,15 @@ function verboseLog(ctx, message) {
|
|
|
6
6
|
return;
|
|
7
7
|
console.log(chalk.gray(` [verbose] ${message}`));
|
|
8
8
|
}
|
|
9
|
-
async function
|
|
9
|
+
async function getAssignableUnassignedStoryCount(ctx) {
|
|
10
10
|
return ctx.withDb(async (db) => {
|
|
11
|
-
const rows = queryAll(db.db, "SELECT COUNT(*) as count FROM stories WHERE status
|
|
11
|
+
const rows = queryAll(db.db, "SELECT COUNT(*) as count FROM stories WHERE status IN ('planned', 'qa_failed') AND assigned_agent_id IS NULL");
|
|
12
12
|
return rows[0]?.count || 0;
|
|
13
13
|
});
|
|
14
14
|
}
|
|
15
15
|
export async function autoAssignPlannedStories(ctx) {
|
|
16
|
-
const plannedUnassigned = await
|
|
17
|
-
verboseLog(ctx, `autoAssignPlannedStories:
|
|
16
|
+
const plannedUnassigned = await getAssignableUnassignedStoryCount(ctx);
|
|
17
|
+
verboseLog(ctx, `autoAssignPlannedStories: assignableUnassigned=${plannedUnassigned}`);
|
|
18
18
|
if (plannedUnassigned === 0) {
|
|
19
19
|
return;
|
|
20
20
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auto-assignment.js","sourceRoot":"","sources":["../../../../src/cli/commands/manager/auto-assignment.ts"],"names":[],"mappings":"AAAA,6DAA6D;AAE7D,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAGjD,SAAS,UAAU,CAAC,GAAyC,EAAE,OAAe;IAC5E,IAAI,CAAC,GAAG,CAAC,OAAO;QAAE,OAAO;IACzB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,eAAe,OAAO,EAAE,CAAC,CAAC,CAAC;AACpD,CAAC;AAED,KAAK,UAAU,
|
|
1
|
+
{"version":3,"file":"auto-assignment.js","sourceRoot":"","sources":["../../../../src/cli/commands/manager/auto-assignment.ts"],"names":[],"mappings":"AAAA,6DAA6D;AAE7D,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAGjD,SAAS,UAAU,CAAC,GAAyC,EAAE,OAAe;IAC5E,IAAI,CAAC,GAAG,CAAC,OAAO;QAAE,OAAO;IACzB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,eAAe,OAAO,EAAE,CAAC,CAAC,CAAC;AACpD,CAAC;AAED,KAAK,UAAU,iCAAiC,CAAC,GAAwB;IACvE,OAAO,GAAG,CAAC,MAAM,CAAC,KAAK,EAAC,EAAE,EAAC,EAAE;QAC3B,MAAM,IAAI,GAAG,QAAQ,CACnB,EAAE,CAAC,EAAE,EACL,8GAA8G,CAC/G,CAAC;QACF,OAAO,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,CAAC;IAC7B,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAAC,GAAwB;IACrE,MAAM,iBAAiB,GAAG,MAAM,iCAAiC,CAAC,GAAG,CAAC,CAAC;IACvE,UAAU,CAAC,GAAG,EAAE,kDAAkD,iBAAiB,EAAE,CAAC,CAAC;IAEvF,IAAI,iBAAiB,KAAK,CAAC,EAAE,CAAC;QAC5B,OAAO;IACT,CAAC;IAED,MAAM,gBAAgB,GAAG,MAAM,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE;QAChE,MAAM,SAAS,CAAC,YAAY,EAAE,CAAC;QAC/B,MAAM,SAAS,CAAC,eAAe,EAAE,CAAC;QAClC,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,aAAa,EAAE,CAAC;QAC/C,EAAE,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC,CAAC;IAEH,UAAU,CACR,GAAG,EACH,sCAAsC,gBAAgB,CAAC,QAAQ,YAAY,gBAAgB,CAAC,MAAM,CAAC,MAAM,EAAE,CAC5G,CAAC;IACF,GAAG,CAAC,QAAQ,CAAC,mBAAmB,IAAI,gBAAgB,CAAC,QAAQ,CAAC;IAE9D,IAAI,gBAAgB,CAAC,QAAQ,GAAG,CAAC,EAAE,CAAC;QAClC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,mBAAmB,gBAAgB,CAAC,QAAQ,qBAAqB,CAAC,CAAC,CAAC;IAC9F,CAAC;IAED,IAAI,gBAAgB,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvC,OAAO,CAAC,GAAG,CACT,KAAK,CAAC,MAAM,CAAC,iCAAiC,gBAAgB,CAAC,MAAM,CAAC,MAAM,WAAW,CAAC,CACzF,CAAC;QACF,KAAK,MAAM,GAAG,IAAI,gBAAgB,CAAC,MAAM,EAAE,CAAC;YAC1C,UAAU,CAAC,GAAG,EAAE,mCAAmC,GAAG,EAAE,CAAC,CAAC;QAC5D,CAAC;IACH,CAAC;AACH,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/manager/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/manager/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA8CpC,OAAO,EAAE,UAAU,EAAE,MAAM,mCAAmC,CAAC;AAgD/D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAqCtD,UAAU,uBAAuB;IAC/B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,wBAAwB,EAAE,MAAM,CAAC;IACjC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,mBAAmB,EAAE,MAAM,CAAC;CAC7B;AAED,UAAU,WAAW;IACnB,KAAK,EAAE,OAAO,GAAG,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;IAC3C,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,UAAU,kCAAkC;IAC1C,KAAK,EAAE,UAAU,CAAC;IAClB,SAAS,EAAE,OAAO,CAAC;IACnB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,2BAA2B,EAAE,MAAM,CAAC;CACrC;AAED,UAAU,6BAA6B;IACrC,KAAK,EAAE,UAAU,CAAC;IAClB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,2BAA2B,EAAE,MAAM,CAAC;CACrC;AAED,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,uBAAuB,GAAG,WAAW,CA0BtF;AAED,wBAAgB,gCAAgC,CAC9C,QAAQ,EAAE,kCAAkC,GAC3C,OAAO,CAOT;AAED,wBAAgB,yCAAyC,CACvD,QAAQ,EAAE,6BAA6B,GACtC,OAAO,CAMT;AAsWD,eAAO,MAAM,cAAc,SAE1B,CAAC;AA4qDF;;;;;;;;;GASG;AACH,wBAAsB,4BAA4B,CAAC,GAAG,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CA+K1F"}
|
|
@@ -15,7 +15,9 @@ import { createEscalation, getActiveEscalationsForAgent, getPendingEscalations,
|
|
|
15
15
|
import { createLog } from '../../../db/queries/logs.js';
|
|
16
16
|
import { getAllPendingMessages, markMessagesRead, } from '../../../db/queries/messages.js';
|
|
17
17
|
import { backfillGithubPrNumbers, createPullRequest, getMergeQueue, getOpenPullRequestsByStory, getPullRequestsByStatus, updatePullRequest, } from '../../../db/queries/pull-requests.js';
|
|
18
|
+
import { getRequirementsByStatus } from '../../../db/queries/requirements.js';
|
|
18
19
|
import { getStoriesByStatus, getStoryById, updateStory } from '../../../db/queries/stories.js';
|
|
20
|
+
import { getAllTeams } from '../../../db/queries/teams.js';
|
|
19
21
|
import { getPullRequestComments, getPullRequestReviews } from '../../../git/github.js';
|
|
20
22
|
import { Scheduler } from '../../../orchestrator/scheduler.js';
|
|
21
23
|
import { AgentState } from '../../../state-detectors/types.js';
|
|
@@ -25,6 +27,7 @@ import { findHiveRoot as findHiveRootFromDir, getHivePaths } from '../../../util
|
|
|
25
27
|
import { fetchOpenGitHubPRs, getExistingPRIdentifiers, ghRepoSlug, } from '../../../utils/pr-sync.js';
|
|
26
28
|
import { extractStoryIdFromBranch } from '../../../utils/story-id.js';
|
|
27
29
|
import { withHiveContext, withHiveRoot } from '../../../utils/with-hive-context.js';
|
|
30
|
+
import { generateTechLeadPrompt } from '../req.js';
|
|
28
31
|
import { agentStates, createManagerNudgeEnvelope, detectAgentState, enforceBypassMode, forwardMessages, getAgentSafetyMode, handlePermissionPrompt, handlePlanApproval, nudgeAgent, submitManagerNudgeWithVerification, updateAgentStateTracking, } from './agent-monitoring.js';
|
|
29
32
|
import { autoAssignPlannedStories } from './auto-assignment.js';
|
|
30
33
|
import { assessCompletionFromOutput } from './done-intelligence.js';
|
|
@@ -34,6 +37,7 @@ import { checkFeatureTestResult } from './feature-test-result.js';
|
|
|
34
37
|
import { handleStalledPlanningHandoff } from './handoff-recovery.js';
|
|
35
38
|
import { cleanupAgentsReferencingMergedStory } from './merged-story-cleanup.js';
|
|
36
39
|
import { shouldAutoResolveOrphanedManagerEscalation } from './orphaned-escalations.js';
|
|
40
|
+
import { isTechLeadRestartOnCooldown } from './restart-cooldown.js';
|
|
37
41
|
import { findSessionForAgent } from './session-resolution.js';
|
|
38
42
|
import { spinDownIdleAgents, spinDownMergedAgents } from './spin-down.js';
|
|
39
43
|
import { findStaleSessionEscalations } from './stale-escalations.js';
|
|
@@ -84,6 +88,7 @@ export function shouldDeferStuckReminderUntilStaticWindow(snapshot) {
|
|
|
84
88
|
const screenStaticBySession = new Map();
|
|
85
89
|
const classifierTimeoutInterventionsBySession = new Map();
|
|
86
90
|
const aiDoneFalseInterventionsBySession = new Map();
|
|
91
|
+
const techLeadLastRestartByAgentId = new Map();
|
|
87
92
|
function verboseLog(verbose, message) {
|
|
88
93
|
if (!verbose)
|
|
89
94
|
return;
|
|
@@ -1163,8 +1168,26 @@ async function recoverStaleReviewingPRs(ctx) {
|
|
|
1163
1168
|
const parsed = JSON.parse(result.stdout);
|
|
1164
1169
|
const state = parsed.state?.toUpperCase();
|
|
1165
1170
|
const url = parsed.url || null;
|
|
1166
|
-
if (state === 'OPEN')
|
|
1171
|
+
if (state === 'OPEN') {
|
|
1172
|
+
// PR is still open on GitHub but stale in 'reviewing' — the QA agent
|
|
1173
|
+
// may have missed the original nudge. Re-nudge if QA agent is idle.
|
|
1174
|
+
if (candidate.reviewedBy) {
|
|
1175
|
+
const qaAgent = ctx.agentsBySessionName.get(candidate.reviewedBy);
|
|
1176
|
+
if (qaAgent && qaAgent.status === 'idle') {
|
|
1177
|
+
const githubLine = candidate.repoSlug
|
|
1178
|
+
? `\n# GitHub: https://github.com/${candidate.repoSlug}/pull/${candidate.githubPrNumber}`
|
|
1179
|
+
: '';
|
|
1180
|
+
await sendManagerNudge(ctx, candidate.reviewedBy, `# [REMINDER] You are assigned PR review ${candidate.id} (${candidate.storyId || 'no-story'}).${githubLine}
|
|
1181
|
+
# This PR has been waiting for review. Execute now:
|
|
1182
|
+
# hive pr show ${candidate.id}
|
|
1183
|
+
# hive pr approve ${candidate.id}
|
|
1184
|
+
# or reject:
|
|
1185
|
+
# hive pr reject ${candidate.id} -r "reason"`);
|
|
1186
|
+
verboseLogCtx(ctx, `recoverStaleReviewingPRs: re-nudged idle QA ${candidate.reviewedBy} for stale pr=${candidate.id}`);
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1167
1189
|
continue;
|
|
1190
|
+
}
|
|
1168
1191
|
if (state === 'MERGED') {
|
|
1169
1192
|
mergedResults.push({
|
|
1170
1193
|
candidate,
|
|
@@ -1415,9 +1438,16 @@ async function scanAgentSessions(ctx) {
|
|
|
1415
1438
|
for (const session of ctx.hiveSessions) {
|
|
1416
1439
|
if (session.name === 'hive-manager')
|
|
1417
1440
|
continue;
|
|
1418
|
-
activeSessionNames.add(session.name);
|
|
1419
1441
|
const agent = ctx.agentsBySessionName.get(session.name);
|
|
1420
|
-
|
|
1442
|
+
// Skip sessions not registered in our DB (cross-project sessions).
|
|
1443
|
+
// This prevents escalation noise from sessions belonging to other
|
|
1444
|
+
// teams/projects sharing the same tmux server.
|
|
1445
|
+
if (!agent) {
|
|
1446
|
+
verboseLogCtx(ctx, `Skipping ${session.name}: no agent registered in DB (cross-project)`);
|
|
1447
|
+
continue;
|
|
1448
|
+
}
|
|
1449
|
+
activeSessionNames.add(session.name);
|
|
1450
|
+
const agentCliTool = (agent.cli_tool || 'claude');
|
|
1421
1451
|
const safetyMode = getAgentSafetyMode(ctx.config, agent);
|
|
1422
1452
|
verboseLogCtx(ctx, `Agent check: ${session.name} (cli=${agentCliTool}, safety=${safetyMode}, story=${agent?.current_story_id || '-'})`);
|
|
1423
1453
|
// Forward unread messages (tmux I/O, no lock needed)
|
|
@@ -2285,6 +2315,11 @@ async function restartStaleTechLead(ctx) {
|
|
|
2285
2315
|
verboseLogCtx(ctx, `restartStaleTechLead: techLead=${techLead.id} skip=not_stale remainingMs=${maxAgeMs - ageMs}`);
|
|
2286
2316
|
continue;
|
|
2287
2317
|
}
|
|
2318
|
+
const cooldown = isTechLeadRestartOnCooldown(techLeadLastRestartByAgentId.get(techLead.id), now, maxAgeHours);
|
|
2319
|
+
if (cooldown.onCooldown) {
|
|
2320
|
+
verboseLogCtx(ctx, `restartStaleTechLead: techLead=${techLead.id} skip=cooldown cooldownHours=${cooldown.cooldownHours} remainingMs=${cooldown.remainingMs}`);
|
|
2321
|
+
continue;
|
|
2322
|
+
}
|
|
2288
2323
|
const output = await captureTmuxPane(techLead.tmuxSession, TMUX_CAPTURE_LINES_SHORT);
|
|
2289
2324
|
const stateResult = detectAgentState(output, techLead.cliTool);
|
|
2290
2325
|
verboseLogCtx(ctx, `restartStaleTechLead: techLead=${techLead.id} state=${stateResult.state} waiting=${stateResult.isWaiting} needsHuman=${stateResult.needsHuman}`);
|
|
@@ -2311,10 +2346,38 @@ async function restartStaleTechLead(ctx) {
|
|
|
2311
2346
|
const model = resolveRuntimeModelForCli(agentConfig.model, cliTool);
|
|
2312
2347
|
const runtimeBuilder = getCliRuntimeBuilder(cliTool);
|
|
2313
2348
|
const commandArgs = runtimeBuilder.buildSpawnCommand(model, safetyMode);
|
|
2349
|
+
// Look up active requirement and teams to provide context to the restarted tech lead
|
|
2350
|
+
const initialPrompt = await ctx.withDb(async (db) => {
|
|
2351
|
+
const planningReqs = getRequirementsByStatus(db.db, 'planning');
|
|
2352
|
+
const inProgressReqs = getRequirementsByStatus(db.db, 'in_progress');
|
|
2353
|
+
const activeReq = planningReqs[0] ?? inProgressReqs[0] ?? null;
|
|
2354
|
+
const teams = getAllTeams(db.db);
|
|
2355
|
+
if (activeReq) {
|
|
2356
|
+
return generateTechLeadPrompt(activeReq.id, activeReq.title, activeReq.description, teams, activeReq.godmode === 1, activeReq.target_branch || 'main');
|
|
2357
|
+
}
|
|
2358
|
+
return `You are the Tech Lead of Hive, an AI development team orchestrator.
|
|
2359
|
+
|
|
2360
|
+
You have been restarted to refresh your context. No active requirement is currently being planned.
|
|
2361
|
+
|
|
2362
|
+
## Next Steps
|
|
2363
|
+
|
|
2364
|
+
1. Check the current status of the Hive workspace:
|
|
2365
|
+
\`\`\`bash
|
|
2366
|
+
hive status
|
|
2367
|
+
\`\`\`
|
|
2368
|
+
|
|
2369
|
+
2. Check your inbox for messages from developers:
|
|
2370
|
+
\`\`\`bash
|
|
2371
|
+
hive msg inbox hive-tech-lead
|
|
2372
|
+
\`\`\`
|
|
2373
|
+
|
|
2374
|
+
3. If there are pending requirements, begin planning them. If all work is complete, monitor for new requirements.`;
|
|
2375
|
+
});
|
|
2314
2376
|
await spawnTmuxSession({
|
|
2315
2377
|
sessionName: techLead.tmuxSession,
|
|
2316
2378
|
workDir: ctx.root,
|
|
2317
2379
|
commandArgs,
|
|
2380
|
+
initialPrompt,
|
|
2318
2381
|
});
|
|
2319
2382
|
// DB writes under brief lock
|
|
2320
2383
|
await ctx.withDb(async (db) => {
|
|
@@ -2333,9 +2396,11 @@ async function restartStaleTechLead(ctx) {
|
|
|
2333
2396
|
});
|
|
2334
2397
|
updateAgent(db.db, techLead.id, {
|
|
2335
2398
|
status: 'working',
|
|
2399
|
+
createdAt: new Date().toISOString(),
|
|
2336
2400
|
});
|
|
2337
2401
|
db.save();
|
|
2338
2402
|
});
|
|
2403
|
+
techLeadLastRestartByAgentId.set(techLead.id, now);
|
|
2339
2404
|
console.log(chalk.green(` Tech lead ${techLead.id} restarted for context freshness (age: ${ageHours.toFixed(1)}h)`));
|
|
2340
2405
|
}
|
|
2341
2406
|
}
|