@cmgmyr/hive 1.11.4 → 1.11.6
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/agentKinds.js +6 -0
- package/dist/agentRecords.js +98 -0
- package/dist/backup.js +1 -1
- package/dist/build-info.json +1 -1
- package/dist/cli.js +46 -26
- package/dist/codexHome.js +0 -1
- package/dist/context.js +15 -26
- package/dist/crew.js +7 -24
- package/dist/dashboard.js +15 -21
- package/dist/db.js +37 -1
- package/dist/errors.js +3 -0
- package/dist/git.js +16 -0
- package/dist/globalConfig.js +1 -1
- package/dist/harnesses.js +0 -3
- package/dist/kickoff.js +6 -15
- package/dist/leadHandoffState.js +1 -1
- package/dist/leadLaunch.js +1 -1
- package/dist/leadWatch.js +1 -1
- package/dist/pads.js +70 -0
- package/dist/portfolio.js +4 -3
- package/dist/processes.js +12 -0
- package/dist/projectRemove.js +1 -1
- package/dist/projectYml.js +1 -1
- package/dist/queenAudit.js +2 -18
- package/dist/queenAuditStore.js +20 -0
- package/dist/questions.js +1 -1
- package/dist/result.js +2 -3
- package/dist/scheduler.js +4 -2
- package/dist/spawn.js +12 -35
- package/dist/todoScope.js +6 -0
- package/dist/todos.js +90 -0
- package/dist/tools/agents.js +13 -109
- package/dist/tools/pads.js +3 -82
- package/dist/tools/todos.js +8 -96
- package/dist/tools/wakes.js +7 -75
- package/dist/version.js +4 -17
- package/dist/wakeSql.js +1 -0
- package/dist/wakeTarget.js +76 -0
- package/package.json +1 -1
package/dist/kickoff.js
CHANGED
|
@@ -1,22 +1,13 @@
|
|
|
1
|
-
import { execFileSync } from "node:child_process";
|
|
2
1
|
import { realpathSync } from "node:fs";
|
|
3
2
|
import { fileURLToPath } from "node:url";
|
|
4
3
|
import { cutToUnitBudget } from "./slug.js";
|
|
5
4
|
import { FIRST_MESSAGE_SHA_ENV } from "./firstMessage.js";
|
|
5
|
+
import { gitOut } from "./git.js";
|
|
6
6
|
export const OUTPUT_BUDGET = 10_000;
|
|
7
7
|
const CONTEXT_BUDGET = 6_000;
|
|
8
8
|
const BOARD_BUDGET = 1_800;
|
|
9
9
|
function currentBranch(dir) {
|
|
10
|
-
|
|
11
|
-
return execFileSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
12
|
-
cwd: dir,
|
|
13
|
-
encoding: "utf8",
|
|
14
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
15
|
-
}).trim();
|
|
16
|
-
}
|
|
17
|
-
catch {
|
|
18
|
-
return null;
|
|
19
|
-
}
|
|
10
|
+
return gitOut(dir, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
20
11
|
}
|
|
21
12
|
export function truncate(text, limit) {
|
|
22
13
|
if (text.length <= limit)
|
|
@@ -26,9 +17,9 @@ export function truncate(text, limit) {
|
|
|
26
17
|
async function digest(projectPath, profile, warnings) {
|
|
27
18
|
const { migrate, db } = await import("./db.js");
|
|
28
19
|
const { findProjectForCwd } = await import("./context.js");
|
|
29
|
-
const {
|
|
30
|
-
const {
|
|
31
|
-
const {
|
|
20
|
+
const { ACTIVE_TODO_SQL, OPEN_BLOCKERS_SQL } = await import("./todoScope.js");
|
|
21
|
+
const { ACTIVE_TIMER_WHERE } = await import("./wakeSql.js");
|
|
22
|
+
const { getActivePadByName } = await import("./pads.js");
|
|
32
23
|
migrate();
|
|
33
24
|
const project = findProjectForCwd();
|
|
34
25
|
if (!project || project.path !== projectPath)
|
|
@@ -53,7 +44,7 @@ async function digest(projectPath, profile, warnings) {
|
|
|
53
44
|
.all(project.id);
|
|
54
45
|
const blocked = db
|
|
55
46
|
.prepare(`SELECT COUNT(*) AS n FROM todos t
|
|
56
|
-
WHERE t.project_id = ? AND
|
|
47
|
+
WHERE t.project_id = ? AND ${ACTIVE_TODO_SQL}
|
|
57
48
|
AND EXISTS (${OPEN_BLOCKERS_SQL})`)
|
|
58
49
|
.get(project.id).n;
|
|
59
50
|
if (inFlight.length > 0) {
|
package/dist/leadHandoffState.js
CHANGED
|
@@ -30,7 +30,7 @@ export function readActiveHandoff(leadAgentId) {
|
|
|
30
30
|
export function handoffHoldsAutomation(row) {
|
|
31
31
|
switch (row.state) {
|
|
32
32
|
case "grace":
|
|
33
|
-
return
|
|
33
|
+
return row.hold_released_at === null && !olderThan(row.grace_started_at, STOP_HOLD_CAP_SECONDS);
|
|
34
34
|
case "started":
|
|
35
35
|
return !olderThan(row.updated_at, HOLD_CEILING_SECONDS);
|
|
36
36
|
case "respawning":
|
package/dist/leadLaunch.js
CHANGED
|
@@ -4,7 +4,7 @@ import { carriesNameFlag } from "./harnesses.js";
|
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { checkoutRoot, renderProfileFile, resolveProfileFile } from "./profiles.js";
|
|
6
6
|
import { activeProfile, configHash, mergedProjectVars } from "./projectYml.js";
|
|
7
|
-
import { LEAD_NAME } from "./
|
|
7
|
+
import { LEAD_NAME } from "./agentKinds.js";
|
|
8
8
|
import { shellQuote } from "./tmux.js";
|
|
9
9
|
// What `hive lead` and the handoff bootstrap both need to launch a claude lead: trust, posture,
|
|
10
10
|
// hooks and identity env. Pane adoption and leftover cleanup stay in cmdLead.
|
package/dist/leadWatch.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { db } from "./db.js";
|
|
2
2
|
import { paneClassifierFor } from "./harnesses.js";
|
|
3
3
|
import { readLeadTurnState } from "./leadState.js";
|
|
4
|
-
import { LEAD_KIND } from "./
|
|
4
|
+
import { LEAD_KIND } from "./agentKinds.js";
|
|
5
5
|
import { foreignSocket, holdsHumanInput, observationFailed, rowOwnership } from "./tmux.js";
|
|
6
6
|
export const LEAD_TARGET_GONE = "LEAD_TARGET_GONE";
|
|
7
7
|
export const LEAD_TARGET_REISSUED = "LEAD_TARGET_REISSUED";
|
package/dist/pads.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { db } from "./db.js";
|
|
2
|
+
import { currentActor } from "./context.js";
|
|
3
|
+
export function splitsSurrogatePair(content, index) {
|
|
4
|
+
if (index <= 0 || index >= content.length)
|
|
5
|
+
return false;
|
|
6
|
+
const before = content.charCodeAt(index - 1);
|
|
7
|
+
const after = content.charCodeAt(index);
|
|
8
|
+
return before >= 0xd800 && before <= 0xdbff && after >= 0xdc00 && after <= 0xdfff;
|
|
9
|
+
}
|
|
10
|
+
function selectPad(projectId, padId, columns) {
|
|
11
|
+
const row = db.prepare(`SELECT ${columns} FROM pads WHERE project_id = ? AND id = ?`).get(projectId, padId);
|
|
12
|
+
if (!row)
|
|
13
|
+
throw new Error(`No pad with id ${padId} in project ${projectId}. Call pad_list.`);
|
|
14
|
+
return row;
|
|
15
|
+
}
|
|
16
|
+
export function getPad(projectId, padId) {
|
|
17
|
+
return selectPad(projectId, padId, "*");
|
|
18
|
+
}
|
|
19
|
+
export function getPadMeta(projectId, padId) {
|
|
20
|
+
return selectPad(projectId, padId, "id, name, revision, archived");
|
|
21
|
+
}
|
|
22
|
+
function revisionMismatchError(padId, expected, current) {
|
|
23
|
+
if (current == null) {
|
|
24
|
+
return new Error(`Pad ${padId} no longer exists; it may have just been deleted by another write. Call pad_list to confirm.`);
|
|
25
|
+
}
|
|
26
|
+
return new Error(`Revision mismatch for pad ${padId}: expected ${expected}, current ${current}. Re-read with pad_read and retry.`);
|
|
27
|
+
}
|
|
28
|
+
function currentRevisionOrDeleted(padId) {
|
|
29
|
+
const row = db.prepare("SELECT revision FROM pads WHERE id = ?").get(padId);
|
|
30
|
+
return row?.revision ?? null;
|
|
31
|
+
}
|
|
32
|
+
export function assertRowChanged(padId, predicateRevision, changed) {
|
|
33
|
+
if (!changed)
|
|
34
|
+
throw revisionMismatchError(padId, predicateRevision, currentRevisionOrDeleted(padId));
|
|
35
|
+
}
|
|
36
|
+
export function checkRevision(pad, expected, required) {
|
|
37
|
+
if (expected == null) {
|
|
38
|
+
if (required)
|
|
39
|
+
throw new Error(`expected_revision is required to overwrite pad ${pad.id}. Current revision: ${pad.revision}. Read it first with pad_read.`);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (expected !== pad.revision)
|
|
43
|
+
throw revisionMismatchError(pad.id, expected, pad.revision);
|
|
44
|
+
}
|
|
45
|
+
export function bumpPad(padId, predicateRevision, set, ...params) {
|
|
46
|
+
const where = predicateRevision != null ? "id = ? AND revision = ?" : "id = ?";
|
|
47
|
+
const whereParams = predicateRevision != null ? [padId, predicateRevision] : [padId];
|
|
48
|
+
const row = db.prepare(`UPDATE pads SET ${set}, revision = revision + 1,
|
|
49
|
+
updated_by = ?, updated_at = datetime('now') WHERE ${where} RETURNING revision`).get(...params, currentActor(), ...whereParams);
|
|
50
|
+
assertRowChanged(padId, predicateRevision, row != null);
|
|
51
|
+
return row.revision;
|
|
52
|
+
}
|
|
53
|
+
export const APPEND_WITH_SEPARATOR_SET = "content = content || (CASE WHEN content = '' OR substr(content, -1) = char(10) THEN '' ELSE char(10) END) || ?";
|
|
54
|
+
export function getActivePadByName(projectId, name) {
|
|
55
|
+
return db.prepare("SELECT * FROM pads WHERE project_id = ? AND name = ? AND archived = 0").get(projectId, name);
|
|
56
|
+
}
|
|
57
|
+
export function listActivePads(projectId) {
|
|
58
|
+
return db.prepare(`SELECT name, revision, length(content) AS content_length, updated_by, updated_at
|
|
59
|
+
FROM pads WHERE project_id = ? AND archived = 0 ORDER BY name`).all(projectId);
|
|
60
|
+
}
|
|
61
|
+
export function overwritePadContent(projectId, padId, content, expectedRevision) {
|
|
62
|
+
const pad = getPadMeta(projectId, padId);
|
|
63
|
+
checkRevision(pad, expectedRevision, true);
|
|
64
|
+
return bumpPad(padId, pad.revision, "content = ?", content);
|
|
65
|
+
}
|
|
66
|
+
export function createPad(projectId, name, content, tags) {
|
|
67
|
+
const row = db.prepare(`INSERT OR IGNORE INTO pads (project_id, name, content, tags, updated_by)
|
|
68
|
+
VALUES (?, ?, ?, ?, ?) RETURNING id`).get(projectId, name, content, JSON.stringify(tags), currentActor());
|
|
69
|
+
return row?.id ?? null;
|
|
70
|
+
}
|
package/dist/portfolio.js
CHANGED
|
@@ -2,8 +2,10 @@ import { db } from "./db.js";
|
|
|
2
2
|
import { listProjects } from "./context.js";
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
4
|
import { parseTags } from "./result.js";
|
|
5
|
-
import { OPEN_BLOCKERS_SQL } from "./
|
|
5
|
+
import { ACTIVE_TODO_SQL, OPEN_BLOCKERS_SQL } from "./todoScope.js";
|
|
6
6
|
import { pendingQuestionCount } from "./questions.js";
|
|
7
|
+
import { ACTIVE_TIMER_WHERE as ACTIVE_WAKE_WHERE } from "./wakeSql.js";
|
|
8
|
+
export { ACTIVE_WAKE_WHERE };
|
|
7
9
|
import { leadSessionEnded, readLeadTurnState } from "./leadState.js";
|
|
8
10
|
import { liveTargets, ownershipLiveness, rowOwnership } from "./tmux.js";
|
|
9
11
|
export const PORTFOLIO_REASONS = [
|
|
@@ -26,7 +28,6 @@ const NEEDS_HUMAN_ITEM_CAP = 5;
|
|
|
26
28
|
const WAKE_OVERDUE_GRACE = "-5 minutes";
|
|
27
29
|
const STALE_IN_PROGRESS = "-48 hours";
|
|
28
30
|
const WAKE_UPCOMING = "+24 hours";
|
|
29
|
-
export const ACTIVE_WAKE_WHERE = "cancelled_at IS NULL AND (fired_at IS NULL OR repeat_every_ms IS NOT NULL)";
|
|
30
31
|
export function leadText(p) {
|
|
31
32
|
return p.lead.state === "alive" && p.lead.turn !== "unknown"
|
|
32
33
|
? `alive, turn ${p.lead.turn === "working" ? "working" : "ended"}`
|
|
@@ -63,7 +64,7 @@ function projectRow(project, now, snapshot) {
|
|
|
63
64
|
.prepare(`SELECT t.id, t.title, t.slug, t.priority, t.status, t.tags, t.updated_at,
|
|
64
65
|
EXISTS (${OPEN_BLOCKERS_SQL}) AS blocked
|
|
65
66
|
FROM todos t
|
|
66
|
-
WHERE t.project_id = ? AND
|
|
67
|
+
WHERE t.project_id = ? AND ${ACTIVE_TODO_SQL}`)
|
|
67
68
|
.all(project.id);
|
|
68
69
|
const todos = { open: 0, in_progress: 0, blocked: 0, blocked_in_progress: 0, high: 0 };
|
|
69
70
|
const tagged = [];
|
package/dist/processes.js
CHANGED
|
@@ -38,6 +38,18 @@ export const STOP_REASONS = {
|
|
|
38
38
|
leadPaneExited: "lead pane exited",
|
|
39
39
|
previousLead: "left running by a previous lead",
|
|
40
40
|
};
|
|
41
|
+
export function renderStart(result) {
|
|
42
|
+
switch (result.status) {
|
|
43
|
+
case "started":
|
|
44
|
+
return result.message;
|
|
45
|
+
case "already-running":
|
|
46
|
+
return `already running${result.location ? ` (${result.location})` : ""}`;
|
|
47
|
+
case "refused":
|
|
48
|
+
return result.message ?? "";
|
|
49
|
+
case "failed":
|
|
50
|
+
return `failed: ${result.message}`;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
41
53
|
const STOPPABLE_COLUMNS = "id, project_id, name, tmux_target, tmux_socket, pane_pid, created_at";
|
|
42
54
|
export function runningCommandRows(projectId) {
|
|
43
55
|
return db
|
package/dist/projectRemove.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { backupNow } from "./backup.js";
|
|
2
2
|
import { dataDir, db } from "./db.js";
|
|
3
|
-
import { LEAD_KIND } from "./
|
|
3
|
+
import { LEAD_KIND } from "./agentKinds.js";
|
|
4
4
|
import { foreignSocket, liveTargets, observationFailed, rowOwnership } from "./tmux.js";
|
|
5
5
|
export const PROJECT_OWNER_TABLES = [
|
|
6
6
|
"pads",
|
package/dist/projectYml.js
CHANGED
|
@@ -4,7 +4,7 @@ import { resolve, sep } from "node:path";
|
|
|
4
4
|
import { parse } from "yaml";
|
|
5
5
|
import { harnessNames } from "./harnesses.js";
|
|
6
6
|
import { isValidProfileName } from "./profiles.js";
|
|
7
|
-
import { errorMessage } from "./
|
|
7
|
+
import { errorMessage } from "./errors.js";
|
|
8
8
|
import { isWindowLayout, WINDOW_LAYOUTS } from "./tmux.js";
|
|
9
9
|
import { globalConfigPath, isEmptyYamlDocument, readGlobalConfig, resolveAttachSetting, } from "./globalConfig.js";
|
|
10
10
|
export const BUILT_IN_PROJECT_YML = Object.freeze({
|
package/dist/queenAudit.js
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import { captureQueenWriteIdentity, effectiveProjectId, isQueenLead } from "./context.js";
|
|
2
|
+
import { insertQueenAuditRow, QUEEN_AUDIT_DEFAULT_LIMIT, QUEEN_AUDIT_MAX_LIMIT, QUEEN_AUDIT_SUMMARY_MAX, } from "./queenAuditStore.js";
|
|
2
3
|
import { db } from "./db.js";
|
|
3
4
|
import { cutToUnitBudget, flatten } from "./slug.js";
|
|
4
|
-
export
|
|
5
|
-
export const QUEEN_AUDIT_DEFAULT_LIMIT = 20;
|
|
6
|
-
export const QUEEN_AUDIT_MAX_LIMIT = 100;
|
|
7
|
-
export const QUEEN_AUDIT_RETENTION = "-30 days";
|
|
8
|
-
export const QUEEN_AUDIT_MAX_ROWS = 20_000;
|
|
5
|
+
export { QUEEN_AUDIT_DEFAULT_LIMIT, QUEEN_AUDIT_MAX_LIMIT, QUEEN_AUDIT_MAX_ROWS, QUEEN_AUDIT_RETENTION, QUEEN_AUDIT_SUMMARY_MAX, recordQueenRefusal, } from "./queenAuditStore.js";
|
|
9
6
|
function resource(type, value) {
|
|
10
7
|
return typeof value === "number" && Number.isInteger(value) && value > 0 ? { type, id: value } : null;
|
|
11
8
|
}
|
|
@@ -13,11 +10,6 @@ function removalSummary(r) {
|
|
|
13
10
|
const counts = Object.entries((r.counts ?? {})).map(([t, n]) => `${t} ${n}`);
|
|
14
11
|
return `removed project "${r.name}"${counts.length > 0 ? ` with ${counts.join(", ")}` : ""}`;
|
|
15
12
|
}
|
|
16
|
-
function insertQueenAuditRow(operation, targetProjectId, identity, resourceType, resourceId, summary, outcome = "confirmed", problem = "") {
|
|
17
|
-
db.prepare(`INSERT INTO queen_audit
|
|
18
|
-
(actor_id, home_project_id, target_project_id, operation, resource_type, resource_id, summary, outcome, problem)
|
|
19
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(identity.actor_id, identity.home_project_id, targetProjectId, operation, resourceType, resourceId, summary, outcome, problem);
|
|
20
|
-
}
|
|
21
13
|
export const QUEEN_AUDIT_OPERATIONS = {
|
|
22
14
|
todo_create: {
|
|
23
15
|
mode: "database",
|
|
@@ -85,14 +77,6 @@ function insertAudit(operation, targetProjectId, args, receipt, identity) {
|
|
|
85
77
|
const summary = text.length <= QUEEN_AUDIT_SUMMARY_MAX ? text : cutToUnitBudget(text, QUEEN_AUDIT_SUMMARY_MAX - 1) + "…";
|
|
86
78
|
insertQueenAuditRow(operation, targetProjectId, identity, touched.type, touched.id, summary);
|
|
87
79
|
}
|
|
88
|
-
export function recordQueenRefusal(operation, targetProjectId, problem, identity) {
|
|
89
|
-
const text = flatten(problem);
|
|
90
|
-
const summaryText = `refused: ${text}`;
|
|
91
|
-
const summary = summaryText.length <= QUEEN_AUDIT_SUMMARY_MAX
|
|
92
|
-
? summaryText
|
|
93
|
-
: cutToUnitBudget(summaryText, QUEEN_AUDIT_SUMMARY_MAX - 1) + "…";
|
|
94
|
-
insertQueenAuditRow(operation, targetProjectId, identity, "project", targetProjectId, summary, "refused", text);
|
|
95
|
-
}
|
|
96
80
|
export function commitQueenStateWrite(operation, targetProjectId, args, mutate) {
|
|
97
81
|
const identity = captureQueenWriteIdentity(targetProjectId);
|
|
98
82
|
if (!identity)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { db } from "./db.js";
|
|
2
|
+
import { cutToUnitBudget, flatten } from "./slug.js";
|
|
3
|
+
export const QUEEN_AUDIT_SUMMARY_MAX = 160;
|
|
4
|
+
export const QUEEN_AUDIT_DEFAULT_LIMIT = 20;
|
|
5
|
+
export const QUEEN_AUDIT_MAX_LIMIT = 100;
|
|
6
|
+
export const QUEEN_AUDIT_RETENTION = "-30 days";
|
|
7
|
+
export const QUEEN_AUDIT_MAX_ROWS = 20_000;
|
|
8
|
+
export function insertQueenAuditRow(operation, targetProjectId, identity, resourceType, resourceId, summary, outcome = "confirmed", problem = "") {
|
|
9
|
+
db.prepare(`INSERT INTO queen_audit
|
|
10
|
+
(actor_id, home_project_id, target_project_id, operation, resource_type, resource_id, summary, outcome, problem)
|
|
11
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(identity.actor_id, identity.home_project_id, targetProjectId, operation, resourceType, resourceId, summary, outcome, problem);
|
|
12
|
+
}
|
|
13
|
+
export function recordQueenRefusal(operation, targetProjectId, problem, identity) {
|
|
14
|
+
const text = flatten(problem);
|
|
15
|
+
const summaryText = `refused: ${text}`;
|
|
16
|
+
const summary = summaryText.length <= QUEEN_AUDIT_SUMMARY_MAX
|
|
17
|
+
? summaryText
|
|
18
|
+
: cutToUnitBudget(summaryText, QUEEN_AUDIT_SUMMARY_MAX - 1) + "…";
|
|
19
|
+
insertQueenAuditRow(operation, targetProjectId, identity, "project", targetProjectId, summary, "refused", text);
|
|
20
|
+
}
|
package/dist/questions.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { db } from "./db.js";
|
|
2
2
|
import { HUMAN_ACTOR_PREFIX } from "./context.js";
|
|
3
|
-
import { assertSafeWakeBody, resolveWakeDelivery } from "./
|
|
3
|
+
import { assertSafeWakeBody, resolveWakeDelivery } from "./wakeTarget.js";
|
|
4
4
|
const QUESTION_STATE_CONFLICT = "QUESTION_STATE_CONFLICT";
|
|
5
5
|
const HEADER_MAX_CHARS = 12;
|
|
6
6
|
const MIN_OPTIONS = 2;
|
package/dist/result.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { runningBuildChange, runningBuildNotice } from "./version.js";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
}
|
|
2
|
+
import { errorMessage } from "./errors.js";
|
|
3
|
+
export { errorMessage } from "./errors.js";
|
|
5
4
|
function ok(data) {
|
|
6
5
|
const text = typeof data === "string" ? data : JSON.stringify(data, null, 1);
|
|
7
6
|
return { content: [{ type: "text", text }] };
|
package/dist/scheduler.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { QUEEN_AUDIT_RETENTION, QUEEN_AUDIT_MAX_ROWS, recentQueenAudit } from "./queenAudit.js";
|
|
2
|
+
import { ACTIVE_TIMER_WHERE } from "./wakeSql.js";
|
|
3
|
+
export { ACTIVE_TIMER_WHERE } from "./wakeSql.js";
|
|
2
4
|
import { runningBuildChange, runningBuildNotice, versionInfo } from "./version.js";
|
|
3
5
|
import { existsSync, lstatSync, mkdirSync, realpathSync, renameSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
4
6
|
import { createHash } from "node:crypto";
|
|
@@ -13,7 +15,8 @@ import { getProjectByPath, listProjects, queenHomeDir } from "./context.js";
|
|
|
13
15
|
import { collectPortfolio } from "./portfolio.js";
|
|
14
16
|
import { QUEEN_GENERATED_MARKER, readQueenBrief, renderQueenDashboard } from "./queenDashboard.js";
|
|
15
17
|
import { COMMAND_KIND, stoppingMarkerLive } from "./processes.js";
|
|
16
|
-
import { closeAgentRow,
|
|
18
|
+
import { closeAgentRow, PaneOwnershipLost, reapCodexHomeForClosedAgent, requireStillOwned, } from "./spawn.js";
|
|
19
|
+
import { isLeadActorId, LEAD_ACTOR_PREFIX, LEAD_KIND, } from "./agentKinds.js";
|
|
17
20
|
import { awaitingFirstPrompt, awaitingFirstPromptSql } from "./firstPrompt.js";
|
|
18
21
|
import { evaluateLeadWatch, leadSubscription } from "./leadWatch.js";
|
|
19
22
|
import { describeLiveTasks, describeOneTask, liveBackgroundTasks, STALL_BOUND_SECONDS, } from "./backgroundTasks.js";
|
|
@@ -56,7 +59,6 @@ function deliveryOwnership(row, snapshot) {
|
|
|
56
59
|
return "closed";
|
|
57
60
|
return rowOwnership(deliveryIdentity(row), snapshot);
|
|
58
61
|
}
|
|
59
|
-
export const ACTIVE_TIMER_WHERE = "cancelled_at IS NULL AND (fired_at IS NULL OR repeat_every_ms IS NOT NULL)";
|
|
60
62
|
const SETTLE_WINDOW = "-15 seconds";
|
|
61
63
|
const prepared = new Map();
|
|
62
64
|
function stmt(sql) {
|
package/dist/spawn.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { execFileSync } from "node:child_process";
|
|
2
1
|
import { existsSync } from "node:fs";
|
|
3
2
|
import { preservedRolloutPath, reapCodexHome } from "./codexHome.js";
|
|
4
3
|
import { getProject } from "./context.js";
|
|
4
|
+
import { LEAD_KIND, LEAD_NAME } from "./agentKinds.js";
|
|
5
|
+
import { gitOut } from "./git.js";
|
|
5
6
|
import { dataDir, db } from "./db.js";
|
|
6
7
|
import { loadProjectYml } from "./projectYml.js";
|
|
7
8
|
import { applyLayout, claimInitialWindow, createWindow, DEFAULT_LAYOUT, ensureSession, crossServerRefusal, findProcessesWindow, findProjectWindow, isPaneTarget, paneProcessExited, paneWindow, PROCESSES_LAYOUT, processesPaneTitle, processesWindowName, retainOnExitArgs, rowLive, sessionName, setPaneTitle, setRemainOnExit, makeProcessesWindow, tmux, tmuxSocketPath, untrustedTmuxServer, waitForPaneEstablished, windowLayout, windowOwner, windowTitle, rowOwnership, } from "./tmux.js";
|
|
@@ -113,13 +114,8 @@ export function asNameClash(e, name) {
|
|
|
113
114
|
}
|
|
114
115
|
return e;
|
|
115
116
|
}
|
|
116
|
-
export
|
|
117
|
-
export const LEAD_ACTOR_PREFIX = `${LEAD_KIND}:`;
|
|
118
|
-
export const isLeadActorId = (actorId) => actorId.startsWith(LEAD_ACTOR_PREFIX);
|
|
119
|
-
export const mintLeadActorId = (agentId) => `${LEAD_ACTOR_PREFIX}${agentId}`;
|
|
117
|
+
export { LEAD_ACTOR_PREFIX, LEAD_KIND, LEAD_NAME, isLeadActorId, isReservedAgentName, mintLeadActorId } from "./agentKinds.js";
|
|
120
118
|
export const isRunningLeadActor = (actorId) => !!db.prepare("SELECT 1 FROM agents WHERE actor_id = ? AND kind = ? AND status = 'running'").get(actorId, LEAD_KIND);
|
|
121
|
-
export const LEAD_NAME = LEAD_KIND;
|
|
122
|
-
export const isReservedAgentName = (name) => name.toLowerCase() === LEAD_NAME;
|
|
123
119
|
export function upsertActor(actorId, name, kind) {
|
|
124
120
|
db.prepare(`INSERT INTO actors (id, name, kind) VALUES (?, ?, ?)
|
|
125
121
|
ON CONFLICT(id) DO UPDATE SET name = excluded.name, last_seen_at = datetime('now')`).run(actorId, name, kind);
|
|
@@ -347,34 +343,19 @@ export function renameAgent(agent, newName, projectName) {
|
|
|
347
343
|
}
|
|
348
344
|
}
|
|
349
345
|
export function branchAt(cwd) {
|
|
350
|
-
const
|
|
351
|
-
try {
|
|
352
|
-
return execFileSync("git", args, {
|
|
353
|
-
cwd,
|
|
354
|
-
encoding: "utf8",
|
|
355
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
356
|
-
timeout: 2000,
|
|
357
|
-
}).trim();
|
|
358
|
-
}
|
|
359
|
-
catch {
|
|
360
|
-
return null;
|
|
361
|
-
}
|
|
362
|
-
};
|
|
363
|
-
const branch = git(["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
346
|
+
const branch = gitOut(cwd, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
364
347
|
if (branch === null)
|
|
365
348
|
return "";
|
|
366
349
|
if (branch !== "HEAD")
|
|
367
350
|
return branch;
|
|
368
|
-
return
|
|
351
|
+
return gitOut(cwd, ["rev-parse", "--short", "HEAD"]) ?? "";
|
|
369
352
|
}
|
|
370
353
|
export function parkAgentRow(agentId, expected, branch) {
|
|
371
354
|
const park = "UPDATE agents SET status = 'closed', closed_at = datetime('now'), parked_at = datetime('now'), " +
|
|
372
355
|
"parked_branch = ? WHERE id = ? AND status = 'running' AND tmux_target = ?";
|
|
373
|
-
const row =
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
.prepare(`${park} AND tmux_socket = ? AND pane_pid = ? RETURNING parked_at`)
|
|
377
|
-
.get(branch, agentId, expected.tmux_target, expected.tmux_socket, expected.pane_pid);
|
|
356
|
+
const row = db
|
|
357
|
+
.prepare(`${park} AND tmux_socket = ? AND pane_pid = ? RETURNING parked_at`)
|
|
358
|
+
.get(branch, agentId, expected.tmux_target, expected.tmux_socket, expected.pane_pid);
|
|
378
359
|
return row?.parked_at;
|
|
379
360
|
}
|
|
380
361
|
export const paneIdentity = (row) => ({
|
|
@@ -455,13 +436,9 @@ export function closeAgentRow(agentId, expected) {
|
|
|
455
436
|
? db
|
|
456
437
|
.prepare("UPDATE agents SET status = 'closed', closed_at = datetime('now') WHERE id = ? AND status = 'running'")
|
|
457
438
|
.run(agentId)
|
|
458
|
-
:
|
|
459
|
-
?
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
: db
|
|
463
|
-
.prepare("UPDATE agents SET status = 'closed', closed_at = datetime('now') WHERE id = ? AND status = 'running' " +
|
|
464
|
-
"AND tmux_target = ? AND tmux_socket = ? AND pane_pid = ?")
|
|
465
|
-
.run(agentId, expected.tmux_target, expected.tmux_socket, expected.pane_pid);
|
|
439
|
+
: db
|
|
440
|
+
.prepare("UPDATE agents SET status = 'closed', closed_at = datetime('now') WHERE id = ? AND status = 'running' " +
|
|
441
|
+
"AND tmux_target = ? AND tmux_socket = ? AND pane_pid = ?")
|
|
442
|
+
.run(agentId, expected.tmux_target, expected.tmux_socket, expected.pane_pid);
|
|
466
443
|
return info.changes > 0;
|
|
467
444
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export const TODO_STATUSES = ["open", "in_progress", "backlog", "completed"];
|
|
2
|
+
export const ACTIVE_TODO_SQL = "t.status IN ('open', 'in_progress') AND t.archived_at IS NULL";
|
|
3
|
+
export const NOT_COMPLETED_TODO_SQL = "status != 'completed'";
|
|
4
|
+
export const OPEN_BLOCKERS_FROM_WHERE = `FROM todo_blockers b JOIN todos bt ON bt.id = b.blocker_id
|
|
5
|
+
WHERE b.todo_id = t.id AND bt.${NOT_COMPLETED_TODO_SQL}`;
|
|
6
|
+
export const OPEN_BLOCKERS_SQL = `SELECT 1 ${OPEN_BLOCKERS_FROM_WHERE}`;
|
package/dist/todos.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { db } from "./db.js";
|
|
2
|
+
import { matchesAnyTag, parseTags } from "./result.js";
|
|
3
|
+
import { fallbackSlug } from "./slug.js";
|
|
4
|
+
import { OPEN_BLOCKERS_FROM_WHERE } from "./todoScope.js";
|
|
5
|
+
export const COMMENT_COUNT_SQL = `(SELECT COUNT(*) FROM todo_comments c WHERE c.todo_id = t.id)`;
|
|
6
|
+
const SUMMARY_SQL = `
|
|
7
|
+
SELECT t.*,
|
|
8
|
+
(SELECT COUNT(*) ${OPEN_BLOCKERS_FROM_WHERE}) AS open_blockers,
|
|
9
|
+
${COMMENT_COUNT_SQL} AS comment_count
|
|
10
|
+
FROM todos t`;
|
|
11
|
+
export function getTodo(projectId, todoId) {
|
|
12
|
+
const row = db
|
|
13
|
+
.prepare(`${SUMMARY_SQL} WHERE t.project_id = ? AND t.id = ?`)
|
|
14
|
+
.get(projectId, todoId);
|
|
15
|
+
if (!row)
|
|
16
|
+
throw new Error(`No todo with id ${todoId} in project ${projectId}. Call todo_list.`);
|
|
17
|
+
return row;
|
|
18
|
+
}
|
|
19
|
+
function summarize(row) {
|
|
20
|
+
return {
|
|
21
|
+
todo_id: row.id,
|
|
22
|
+
title: row.title,
|
|
23
|
+
slug: row.slug || fallbackSlug(row.title) || `todo ${row.id}`,
|
|
24
|
+
status: row.status,
|
|
25
|
+
priority: row.priority,
|
|
26
|
+
tags: parseTags(row.tags),
|
|
27
|
+
archived: row.archived_at != null,
|
|
28
|
+
is_blocked: (row.open_blockers ?? 0) > 0,
|
|
29
|
+
open_blockers: row.open_blockers ?? 0,
|
|
30
|
+
comment_count: row.comment_count ?? 0,
|
|
31
|
+
updated_at: row.updated_at,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export function listTodoSummaries(projectId, filter = {}) {
|
|
35
|
+
const limit = Math.min(filter.limit ?? 50, 200);
|
|
36
|
+
const offset = filter.offset ?? 0;
|
|
37
|
+
let sql = `${SUMMARY_SQL} WHERE t.project_id = ?`;
|
|
38
|
+
const params = [projectId];
|
|
39
|
+
if (!filter.includeArchived) {
|
|
40
|
+
sql += " AND t.archived_at IS NULL";
|
|
41
|
+
}
|
|
42
|
+
if (filter.statuses && filter.statuses.length > 0) {
|
|
43
|
+
sql += ` AND t.status IN (${filter.statuses.map(() => "?").join(",")})`;
|
|
44
|
+
params.push(...filter.statuses);
|
|
45
|
+
}
|
|
46
|
+
if (filter.priority) {
|
|
47
|
+
sql += " AND t.priority = ?";
|
|
48
|
+
params.push(filter.priority);
|
|
49
|
+
}
|
|
50
|
+
if (filter.query) {
|
|
51
|
+
sql += " AND (t.title LIKE ? OR t.body LIKE ? OR t.slug LIKE ?)";
|
|
52
|
+
params.push(`%${filter.query}%`, `%${filter.query}%`, `%${filter.query}%`);
|
|
53
|
+
}
|
|
54
|
+
sql += " ORDER BY t.updated_at DESC";
|
|
55
|
+
let rows = db.prepare(sql).all(...params).filter((r) => matchesAnyTag(r.tags, filter.tags));
|
|
56
|
+
if (filter.isBlocked != null) {
|
|
57
|
+
rows = rows.filter((r) => ((r.open_blockers ?? 0) > 0) === filter.isBlocked);
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
total_count: rows.length,
|
|
61
|
+
offset,
|
|
62
|
+
limit,
|
|
63
|
+
todos: rows.slice(offset, offset + limit).map(summarize),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
export function getTodoDetail(projectId, todoId, includeComments) {
|
|
67
|
+
const todo = getTodo(projectId, todoId);
|
|
68
|
+
const blockers = db
|
|
69
|
+
.prepare(`SELECT t.id, t.title, t.status FROM todo_blockers b
|
|
70
|
+
JOIN todos t ON t.id = b.blocker_id WHERE b.todo_id = ?`)
|
|
71
|
+
.all(todo.id);
|
|
72
|
+
const blocking = db
|
|
73
|
+
.prepare(`SELECT t.id, t.title, t.status FROM todo_blockers b
|
|
74
|
+
JOIN todos t ON t.id = b.todo_id WHERE b.blocker_id = ?`)
|
|
75
|
+
.all(todo.id);
|
|
76
|
+
const result = {
|
|
77
|
+
...summarize(todo),
|
|
78
|
+
body: todo.body,
|
|
79
|
+
created_at: todo.created_at,
|
|
80
|
+
completed_at: todo.completed_at,
|
|
81
|
+
blockers,
|
|
82
|
+
blocking,
|
|
83
|
+
};
|
|
84
|
+
if (includeComments) {
|
|
85
|
+
result.comments = db
|
|
86
|
+
.prepare("SELECT id, author, body, created_at FROM todo_comments WHERE todo_id = ? ORDER BY created_at")
|
|
87
|
+
.all(todo.id);
|
|
88
|
+
}
|
|
89
|
+
return result;
|
|
90
|
+
}
|