xtrm-tools 2.1.25 → 2.1.26
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/cli/package.json
CHANGED
|
@@ -10,7 +10,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
10
10
|
const getCwd = (ctx: any) => ctx.cwd || process.cwd();
|
|
11
11
|
const isBeadsProject = (cwd: string) => fs.existsSync(path.join(cwd, ".beads"));
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
// Pin session to PID — stable for the life of the Pi process, consistent across all extension handlers
|
|
14
|
+
const sessionId = process.pid.toString();
|
|
15
|
+
|
|
16
|
+
const getSessionClaim = async (cwd: string): Promise<string | null> => {
|
|
14
17
|
const result = await SubprocessRunner.run("bd", ["kv", "get", `claimed:${sessionId}`], { cwd });
|
|
15
18
|
if (result.code === 0) return result.stdout.trim();
|
|
16
19
|
return null;
|
|
@@ -33,10 +36,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
33
36
|
const cwd = getCwd(ctx);
|
|
34
37
|
if (!isBeadsProject(cwd)) return undefined;
|
|
35
38
|
|
|
36
|
-
const sessionId = ctx.sessionManager.sessionId;
|
|
37
|
-
|
|
38
39
|
if (EventAdapter.isMutatingFileTool(event)) {
|
|
39
|
-
const claim = await getSessionClaim(
|
|
40
|
+
const claim = await getSessionClaim(cwd);
|
|
40
41
|
if (!claim) {
|
|
41
42
|
const hasWork = await hasTrackableWork(cwd);
|
|
42
43
|
if (hasWork) {
|
|
@@ -45,7 +46,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
45
46
|
}
|
|
46
47
|
return {
|
|
47
48
|
block: true,
|
|
48
|
-
reason: `No active issue claim for this session (
|
|
49
|
+
reason: `No active issue claim for this session (pid:${sessionId}).\n bd update <id> --claim\n bd kv set "claimed:${sessionId}" "<id>"`,
|
|
49
50
|
};
|
|
50
51
|
}
|
|
51
52
|
}
|
|
@@ -54,7 +55,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
54
55
|
if (isToolCallEventType("bash", event)) {
|
|
55
56
|
const command = event.input.command;
|
|
56
57
|
if (command && /\bgit\s+commit\b/.test(command)) {
|
|
57
|
-
const claim = await getSessionClaim(
|
|
58
|
+
const claim = await getSessionClaim(cwd);
|
|
58
59
|
if (claim) {
|
|
59
60
|
return {
|
|
60
61
|
block: true,
|
|
@@ -78,9 +79,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
78
79
|
if (issueMatch) {
|
|
79
80
|
const issueId = issueMatch[1];
|
|
80
81
|
const cwd = getCwd(ctx);
|
|
81
|
-
const sessionId = ctx.sessionManager.sessionId;
|
|
82
82
|
await SubprocessRunner.run("bd", ["kv", "set", `claimed:${sessionId}`, issueId], { cwd });
|
|
83
|
-
const claimNotice = `\n\n✅ **Beads**: Session
|
|
83
|
+
const claimNotice = `\n\n✅ **Beads**: Session \`pid:${sessionId}\` claimed issue \`${issueId}\`. File edits are now unblocked.`;
|
|
84
84
|
return { content: [...event.content, { type: "text", text: claimNotice }] };
|
|
85
85
|
}
|
|
86
86
|
}
|
|
@@ -12,89 +12,97 @@ import * as fs from "node:fs";
|
|
|
12
12
|
import { SubprocessRunner } from "./core/lib";
|
|
13
13
|
|
|
14
14
|
export default function (pi: ExtensionAPI) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
await SubprocessRunner.run("bd", ["kv", "clear", `claimed:${sessionId}`], { cwd });
|
|
63
|
-
beadState = { claimId: null, shortId: null, status: null, openCount: beadState.openCount, lastFetch: Date.now() };
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
15
|
+
// Pin session to PID — stable for the life of the Pi process, matches beads.ts
|
|
16
|
+
const SESSION_KEY = process.pid.toString();
|
|
17
|
+
|
|
18
|
+
interface BeadState {
|
|
19
|
+
claimId: string | null;
|
|
20
|
+
shortId: string | null;
|
|
21
|
+
status: string | null;
|
|
22
|
+
openCount: number;
|
|
23
|
+
lastFetch: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const STATUS_ICONS: Record<string, string> = {
|
|
27
|
+
open: "○",
|
|
28
|
+
in_progress: "◐",
|
|
29
|
+
blocked: "●",
|
|
30
|
+
closed: "✓",
|
|
31
|
+
};
|
|
32
|
+
const STATUS_BG: Record<string, string> = {
|
|
33
|
+
open: "\x1b[48;5;238m",
|
|
34
|
+
in_progress: "\x1b[48;5;28m",
|
|
35
|
+
blocked: "\x1b[48;5;88m",
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
let capturedCtx: any = null;
|
|
39
|
+
let beadState: BeadState = { claimId: null, shortId: null, status: null, openCount: 0, lastFetch: 0 };
|
|
40
|
+
let refreshing = false;
|
|
41
|
+
let requestRender: (() => void) | null = null;
|
|
42
|
+
const CACHE_TTL = 5000;
|
|
43
|
+
|
|
44
|
+
const getCwd = () => capturedCtx?.cwd || process.cwd();
|
|
45
|
+
const isBeadsProject = (cwd: string) => fs.existsSync(path.join(cwd, ".beads"));
|
|
46
|
+
const getShortId = (id: string) => id.split("-").pop() ?? id;
|
|
47
|
+
|
|
48
|
+
const refreshBeadState = async () => {
|
|
49
|
+
if (refreshing || Date.now() - beadState.lastFetch < CACHE_TTL) return;
|
|
50
|
+
const cwd = getCwd();
|
|
51
|
+
if (!isBeadsProject(cwd)) return;
|
|
52
|
+
refreshing = true;
|
|
53
|
+
try {
|
|
54
|
+
const claimResult = await SubprocessRunner.run("bd", ["kv", "get", `claimed:${SESSION_KEY}`], { cwd });
|
|
55
|
+
const claimId = claimResult.code === 0 ? claimResult.stdout.trim() || null : null;
|
|
56
|
+
|
|
57
|
+
let status: string | null = null;
|
|
58
|
+
if (claimId) {
|
|
59
|
+
const showResult = await SubprocessRunner.run("bd", ["show", claimId, "--json"], { cwd });
|
|
60
|
+
if (showResult.code === 0) {
|
|
61
|
+
try { status = JSON.parse(showResult.stdout).status ?? null; } catch {}
|
|
66
62
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if (m) openCount = parseInt(m[1], 10);
|
|
63
|
+
if (status === "closed") {
|
|
64
|
+
await SubprocessRunner.run("bd", ["kv", "clear", `claimed:${SESSION_KEY}`], { cwd });
|
|
65
|
+
beadState = { claimId: null, shortId: null, status: null, openCount: beadState.openCount, lastFetch: Date.now() };
|
|
66
|
+
requestRender?.();
|
|
67
|
+
return;
|
|
73
68
|
}
|
|
74
|
-
|
|
75
|
-
beadState = { claimId, shortId: claimId ? getShortId(claimId) : null, status, openCount, lastFetch: Date.now() };
|
|
76
|
-
} catch {}
|
|
77
|
-
finally { refreshing = false; }
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
const buildBeadChip = (): string => {
|
|
81
|
-
const { claimId, shortId, status, openCount } = beadState;
|
|
82
|
-
if (claimId && shortId && status) {
|
|
83
|
-
const icon = STATUS_ICONS[status] ?? "?";
|
|
84
|
-
const bg = STATUS_BG[status] ?? "\x1b[48;5;238m";
|
|
85
|
-
return `${bg}\x1b[38;5;15m bd:${shortId}${icon} \x1b[0m`;
|
|
86
69
|
}
|
|
87
|
-
|
|
88
|
-
|
|
70
|
+
|
|
71
|
+
let openCount = 0;
|
|
72
|
+
const listResult = await SubprocessRunner.run("bd", ["list"], { cwd });
|
|
73
|
+
if (listResult.code === 0) {
|
|
74
|
+
const m = listResult.stdout.match(/\((\d+)\s+open/);
|
|
75
|
+
if (m) openCount = parseInt(m[1], 10);
|
|
89
76
|
}
|
|
90
|
-
|
|
91
|
-
|
|
77
|
+
|
|
78
|
+
beadState = { claimId, shortId: claimId ? getShortId(claimId) : null, status, openCount, lastFetch: Date.now() };
|
|
79
|
+
requestRender?.();
|
|
80
|
+
} catch {}
|
|
81
|
+
finally { refreshing = false; }
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const buildBeadChip = (): string => {
|
|
85
|
+
const { claimId, shortId, status, openCount } = beadState;
|
|
86
|
+
if (claimId && shortId && status) {
|
|
87
|
+
const icon = STATUS_ICONS[status] ?? "?";
|
|
88
|
+
const bg = STATUS_BG[status] ?? "\x1b[48;5;238m";
|
|
89
|
+
return `${bg}\x1b[38;5;15m bd:${shortId}${icon} \x1b[0m`;
|
|
90
|
+
}
|
|
91
|
+
if (openCount > 0) {
|
|
92
|
+
return `\x1b[48;5;238m\x1b[38;5;15m bd:${openCount}${STATUS_ICONS.open} \x1b[0m`;
|
|
93
|
+
}
|
|
94
|
+
return "";
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
98
|
+
capturedCtx = ctx;
|
|
92
99
|
|
|
93
100
|
ctx.ui.setFooter((tui, theme, footerData) => {
|
|
101
|
+
requestRender = () => tui.requestRender();
|
|
94
102
|
const unsub = footerData.onBranchChange(() => tui.requestRender());
|
|
95
103
|
|
|
96
104
|
return {
|
|
97
|
-
dispose() { unsub(); },
|
|
105
|
+
dispose() { unsub(); requestRender = null; },
|
|
98
106
|
invalidate() {},
|
|
99
107
|
render(width: number): string[] {
|
|
100
108
|
refreshBeadState().catch(() => {});
|
|
@@ -118,7 +126,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
118
126
|
|
|
119
127
|
const sep = theme.fg("dim", " | ");
|
|
120
128
|
|
|
121
|
-
// Layout: XTRM | model | 10% | ⌂ dir | ⎇ branch | bd:xxx◐
|
|
122
129
|
const leftParts = [brand, modelStr, usageStr, cwdStr];
|
|
123
130
|
if (branchStr) leftParts.push(branchStr);
|
|
124
131
|
|
|
@@ -131,4 +138,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
131
138
|
};
|
|
132
139
|
});
|
|
133
140
|
});
|
|
141
|
+
|
|
142
|
+
// Bust the bead cache immediately after any bd write so the chip reflects the new state
|
|
143
|
+
pi.on("tool_result", async (event: any) => {
|
|
144
|
+
const cmd = event?.input?.command;
|
|
145
|
+
if (cmd && /\bbd\s+(close|update|create|claim)\b/.test(cmd)) {
|
|
146
|
+
beadState.lastFetch = 0;
|
|
147
|
+
setTimeout(() => refreshBeadState().catch(() => {}), 200);
|
|
148
|
+
}
|
|
149
|
+
return undefined;
|
|
150
|
+
});
|
|
134
151
|
}
|