xtrm-tools 2.1.29 → 2.1.30
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
package/config/hooks.json
CHANGED
|
@@ -10,10 +10,18 @@ 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
|
-
|
|
14
|
-
const sessionId = process.pid.toString();
|
|
13
|
+
let cachedSessionId: string | null = null;
|
|
15
14
|
|
|
16
|
-
|
|
15
|
+
// Resolve a stable session ID across event types.
|
|
16
|
+
const getSessionId = (ctx: any): string => {
|
|
17
|
+
const fromManager = ctx?.sessionManager?.getSessionId?.();
|
|
18
|
+
const fromContext = ctx?.sessionId ?? ctx?.session_id;
|
|
19
|
+
const resolved = fromManager || fromContext || cachedSessionId || process.pid.toString();
|
|
20
|
+
if (resolved && !cachedSessionId) cachedSessionId = resolved;
|
|
21
|
+
return resolved;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const getSessionClaim = async (sessionId: string, cwd: string): Promise<string | null> => {
|
|
17
25
|
const result = await SubprocessRunner.run("bd", ["kv", "get", `claimed:${sessionId}`], { cwd });
|
|
18
26
|
if (result.code === 0) return result.stdout.trim();
|
|
19
27
|
return null;
|
|
@@ -32,12 +40,18 @@ export default function (pi: ExtensionAPI) {
|
|
|
32
40
|
return false;
|
|
33
41
|
};
|
|
34
42
|
|
|
43
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
44
|
+
cachedSessionId = ctx?.sessionManager?.getSessionId?.() ?? ctx?.sessionId ?? ctx?.session_id ?? cachedSessionId;
|
|
45
|
+
return undefined;
|
|
46
|
+
});
|
|
47
|
+
|
|
35
48
|
pi.on("tool_call", async (event, ctx) => {
|
|
36
49
|
const cwd = getCwd(ctx);
|
|
37
50
|
if (!isBeadsProject(cwd)) return undefined;
|
|
51
|
+
const sessionId = getSessionId(ctx);
|
|
38
52
|
|
|
39
53
|
if (EventAdapter.isMutatingFileTool(event)) {
|
|
40
|
-
const claim = await getSessionClaim(cwd);
|
|
54
|
+
const claim = await getSessionClaim(sessionId, cwd);
|
|
41
55
|
if (!claim) {
|
|
42
56
|
const hasWork = await hasTrackableWork(cwd);
|
|
43
57
|
if (hasWork) {
|
|
@@ -46,7 +60,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
46
60
|
}
|
|
47
61
|
return {
|
|
48
62
|
block: true,
|
|
49
|
-
reason: `No active issue claim for this session (
|
|
63
|
+
reason: `No active issue claim for this session (${sessionId}).\n bd update <id> --claim\n bd kv set "claimed:${sessionId}" "<id>"`,
|
|
50
64
|
};
|
|
51
65
|
}
|
|
52
66
|
}
|
|
@@ -55,7 +69,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
55
69
|
if (isToolCallEventType("bash", event)) {
|
|
56
70
|
const command = event.input.command;
|
|
57
71
|
if (command && /\bgit\s+commit\b/.test(command)) {
|
|
58
|
-
const claim = await getSessionClaim(cwd);
|
|
72
|
+
const claim = await getSessionClaim(sessionId, cwd);
|
|
59
73
|
if (claim) {
|
|
60
74
|
return {
|
|
61
75
|
block: true,
|
|
@@ -71,6 +85,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
71
85
|
pi.on("tool_result", async (event, ctx) => {
|
|
72
86
|
if (isBashToolResult(event)) {
|
|
73
87
|
const command = event.input.command;
|
|
88
|
+
const sessionId = getSessionId(ctx);
|
|
74
89
|
|
|
75
90
|
// Auto-claim on bd update --claim regardless of exit code.
|
|
76
91
|
// bd returns exit 1 with "already in_progress" when status unchanged — still a valid claim intent.
|
|
@@ -80,7 +95,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
80
95
|
const issueId = issueMatch[1];
|
|
81
96
|
const cwd = getCwd(ctx);
|
|
82
97
|
await SubprocessRunner.run("bd", ["kv", "set", `claimed:${sessionId}`, issueId], { cwd });
|
|
83
|
-
const claimNotice = `\n\n✅ **Beads**: Session
|
|
98
|
+
const claimNotice = `\n\n✅ **Beads**: Session \`${sessionId}\` claimed issue \`${issueId}\`. File edits are now unblocked.`;
|
|
84
99
|
return { content: [...event.content, { type: "text", text: claimNotice }] };
|
|
85
100
|
}
|
|
86
101
|
}
|
|
@@ -95,8 +95,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
95
95
|
|
|
96
96
|
pi.on("session_start", async (_event, ctx) => {
|
|
97
97
|
capturedCtx = ctx;
|
|
98
|
-
// Get session ID from sessionManager (UUID, consistent with hooks)
|
|
99
|
-
sessionId = ctx.sessionManager?.getSessionId?.() ?? process.pid.toString();
|
|
98
|
+
// Get session ID from sessionManager/context (prefer UUID, consistent with hooks)
|
|
99
|
+
sessionId = ctx.sessionManager?.getSessionId?.() ?? ctx.sessionId ?? ctx.session_id ?? process.pid.toString();
|
|
100
100
|
|
|
101
101
|
ctx.ui.setFooter((tui, theme, footerData) => {
|
|
102
102
|
requestRender = () => tui.requestRender();
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// beads-claim-sync — PostToolUse hook
|
|
3
3
|
// Auto-sets bd kv claim when bd update --claim is detected.
|
|
4
|
-
// Uses session_id from hook input (UUID, matches Pi's sessionManager.getSessionId())
|
|
5
4
|
|
|
6
5
|
import { spawnSync } from 'node:child_process';
|
|
7
6
|
import { readFileSync, existsSync } from 'node:fs';
|
|
@@ -19,10 +18,28 @@ function isBeadsProject(cwd) {
|
|
|
19
18
|
return existsSync(join(cwd, '.beads'));
|
|
20
19
|
}
|
|
21
20
|
|
|
21
|
+
function isShellTool(toolName) {
|
|
22
|
+
return toolName === 'Bash' || toolName === 'bash' || toolName === 'execute_shell_command';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function commandSucceeded(payload) {
|
|
26
|
+
const tr = payload?.tool_response ?? payload?.tool_result ?? payload?.result;
|
|
27
|
+
if (!tr || typeof tr !== 'object') return true;
|
|
28
|
+
|
|
29
|
+
if (tr.success === false) return false;
|
|
30
|
+
if (tr.error) return false;
|
|
31
|
+
|
|
32
|
+
const numeric = [tr.exit_code, tr.exitCode, tr.status, tr.returncode].find((v) => Number.isInteger(v));
|
|
33
|
+
if (typeof numeric === 'number' && numeric !== 0) return false;
|
|
34
|
+
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
|
|
22
38
|
function main() {
|
|
23
39
|
const input = readInput();
|
|
24
40
|
if (!input || input.hook_event_name !== 'PostToolUse') process.exit(0);
|
|
25
|
-
if (input.tool_name
|
|
41
|
+
if (!isShellTool(input.tool_name)) process.exit(0);
|
|
42
|
+
if (!commandSucceeded(input)) process.exit(0);
|
|
26
43
|
|
|
27
44
|
const cwd = input.cwd || process.cwd();
|
|
28
45
|
if (!isBeadsProject(cwd)) process.exit(0);
|
|
@@ -32,33 +49,33 @@ function main() {
|
|
|
32
49
|
process.exit(0);
|
|
33
50
|
}
|
|
34
51
|
|
|
35
|
-
// Extract issue ID from command
|
|
36
52
|
const match = command.match(/\bbd\s+update\s+(\S+)/);
|
|
37
53
|
if (!match) process.exit(0);
|
|
38
54
|
|
|
39
55
|
const issueId = match[1];
|
|
40
|
-
|
|
41
|
-
const sessionId = input.session_id;
|
|
56
|
+
const sessionId = input.session_id ?? input.sessionId;
|
|
42
57
|
|
|
43
58
|
if (!sessionId) {
|
|
44
59
|
process.stderr.write('Beads claim sync: no session_id in hook input\n');
|
|
45
60
|
process.exit(0);
|
|
46
61
|
}
|
|
47
62
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
})
|
|
57
|
-
process.
|
|
58
|
-
} catch (err) {
|
|
59
|
-
process.stderr.write(`Beads claim sync warning: ${err.message}\n`);
|
|
63
|
+
const result = spawnSync('bd', ['kv', 'set', `claimed:${sessionId}`, issueId], {
|
|
64
|
+
cwd,
|
|
65
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
66
|
+
timeout: 5000,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
if (result.status !== 0) {
|
|
70
|
+
const err = (result.stderr || result.stdout || '').toString().trim();
|
|
71
|
+
if (err) process.stderr.write(`Beads claim sync warning: ${err}\n`);
|
|
72
|
+
process.exit(0);
|
|
60
73
|
}
|
|
61
74
|
|
|
75
|
+
process.stdout.write(JSON.stringify({
|
|
76
|
+
additionalContext: `\n✅ **Beads**: Session \`${sessionId}\` claimed issue \`${issueId}\`.`,
|
|
77
|
+
}));
|
|
78
|
+
process.stdout.write('\n');
|
|
62
79
|
process.exit(0);
|
|
63
80
|
}
|
|
64
81
|
|