@dungle-scrubs/tallow 0.8.7 → 0.8.8
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/README.md +1 -1
- package/dist/cli.js +244 -54
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js +1 -1
- package/dist/sdk.d.ts +39 -0
- package/dist/sdk.d.ts.map +1 -1
- package/dist/sdk.js +322 -13
- package/dist/sdk.js.map +1 -1
- package/dist/session-utils.d.ts +8 -0
- package/dist/session-utils.d.ts.map +1 -1
- package/dist/session-utils.js +38 -1
- package/dist/session-utils.js.map +1 -1
- package/extensions/__integration__/plan-rejection-feedback.test.ts +272 -0
- package/extensions/__integration__/worktree.test.ts +88 -0
- package/extensions/_icons/index.ts +2 -3
- package/extensions/_shared/inline-preview.ts +2 -4
- package/extensions/_shared/interop-events.ts +48 -0
- package/extensions/_shared/shell-policy.ts +2 -1
- package/extensions/_shared/tallow-paths.ts +48 -0
- package/extensions/agent-commands-tool/__tests__/parsing.test.ts +40 -1
- package/extensions/agent-commands-tool/index.ts +38 -6
- package/extensions/background-task-tool/index.ts +2 -2
- package/extensions/bash-tool-enhanced/index.ts +3 -4
- package/extensions/cd-tool/index.ts +3 -3
- package/extensions/claude-bridge/index.ts +2 -1
- package/extensions/command-prompt/__tests__/plugin-sources.test.ts +49 -0
- package/extensions/command-prompt/index.ts +36 -0
- package/extensions/context-files/index.ts +2 -1
- package/extensions/context-fork/index.ts +2 -1
- package/extensions/context-usage/__tests__/tool-result-memory.test.ts +62 -0
- package/extensions/context-usage/index.ts +169 -8
- package/extensions/debug/__tests__/analysis.test.ts +35 -2
- package/extensions/debug/__tests__/diagnostics-commands.test.ts +11 -2
- package/extensions/debug/analysis.ts +53 -16
- package/extensions/debug/index.ts +84 -10
- package/extensions/debug/logger.ts +2 -2
- package/extensions/health/index.ts +2 -2
- package/extensions/hooks/__tests__/claude-compat.test.ts +31 -0
- package/extensions/hooks/extension.json +3 -1
- package/extensions/hooks/hooks.schema.json +17 -1
- package/extensions/hooks/index.ts +52 -7
- package/extensions/lsp/index.ts +2 -7
- package/extensions/output-styles-tool/index.ts +2 -4
- package/extensions/plan-mode-tool/extension.json +1 -0
- package/extensions/plan-mode-tool/index.ts +119 -4
- package/extensions/prompt-suggestions/index.ts +2 -3
- package/extensions/random-spinner/index.ts +2 -3
- package/extensions/read-tool-enhanced/__tests__/notebook-read.test.ts +100 -0
- package/extensions/read-tool-enhanced/__tests__/notebook.test.ts +136 -0
- package/extensions/read-tool-enhanced/extension.json +4 -4
- package/extensions/read-tool-enhanced/index.ts +112 -10
- package/extensions/read-tool-enhanced/notebook.ts +526 -0
- package/extensions/rewind/__tests__/snapshots.test.ts +43 -1
- package/extensions/rewind/snapshots.ts +18 -6
- package/extensions/session-memory/index.ts +3 -2
- package/extensions/stats/stats-log.ts +2 -3
- package/extensions/subagent-tool/__tests__/discovery-defaults.test.ts +23 -0
- package/extensions/subagent-tool/__tests__/isolation-frontmatter.test.ts +100 -0
- package/extensions/subagent-tool/__tests__/model-router.test.ts +8 -0
- package/extensions/subagent-tool/agents.ts +77 -16
- package/extensions/subagent-tool/index.ts +213 -48
- package/extensions/subagent-tool/model-router.ts +3 -3
- package/extensions/subagent-tool/process.ts +233 -22
- package/extensions/subagent-tool/schema.ts +11 -0
- package/extensions/subagent-tool/widget.ts +15 -2
- package/extensions/tasks/__tests__/widget-subagents.test.ts +28 -7
- package/extensions/tasks/commands/register-tasks-extension.ts +15 -33
- package/extensions/tasks/state/index.ts +2 -2
- package/extensions/theme-selector/index.ts +3 -7
- package/extensions/worktree/__tests__/lifecycle.test.ts +115 -0
- package/extensions/worktree/extension.json +31 -0
- package/extensions/worktree/index.ts +149 -0
- package/extensions/worktree/lifecycle.ts +372 -0
- package/package.json +1 -1
- package/skills/tallow-expert/SKILL.md +1 -1
|
@@ -20,6 +20,9 @@ interface ToolTimingStats {
|
|
|
20
20
|
avgMs: number;
|
|
21
21
|
p50Ms: number;
|
|
22
22
|
p95Ms: number;
|
|
23
|
+
totalPayloadBytes: number;
|
|
24
|
+
avgPayloadBytes: number;
|
|
25
|
+
summarizedCount: number;
|
|
23
26
|
}
|
|
24
27
|
|
|
25
28
|
/**
|
|
@@ -35,6 +38,18 @@ function percentile(sorted: number[], p: number): number {
|
|
|
35
38
|
return sorted[Math.max(0, idx)];
|
|
36
39
|
}
|
|
37
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Format bytes with compact units for table output.
|
|
43
|
+
*
|
|
44
|
+
* @param count - Byte count
|
|
45
|
+
* @returns Human-readable byte string
|
|
46
|
+
*/
|
|
47
|
+
function formatBytes(count: number): string {
|
|
48
|
+
if (count < 1024) return `${count}B`;
|
|
49
|
+
if (count < 1024 * 1024) return `${(count / 1024).toFixed(1)}KB`;
|
|
50
|
+
return `${(count / (1024 * 1024)).toFixed(1)}MB`;
|
|
51
|
+
}
|
|
52
|
+
|
|
38
53
|
/**
|
|
39
54
|
* Computes per-tool timing statistics from tool result log entries.
|
|
40
55
|
*
|
|
@@ -45,35 +60,53 @@ function percentile(sorted: number[], p: number): number {
|
|
|
45
60
|
* @returns Array of timing stats sorted by total time descending
|
|
46
61
|
*/
|
|
47
62
|
export function summarizeToolTimings(entries: LogEntry[]): ToolTimingStats[] {
|
|
48
|
-
const timingsByTool = new Map<
|
|
63
|
+
const timingsByTool = new Map<
|
|
64
|
+
string,
|
|
65
|
+
{ durations: number[]; payloadBytes: number; summarizedCount: number }
|
|
66
|
+
>();
|
|
49
67
|
|
|
50
68
|
for (const entry of entries) {
|
|
51
69
|
if (entry.cat !== "tool" || entry.evt !== "result") continue;
|
|
52
|
-
const { name,
|
|
70
|
+
const { durationMs, name, payloadBytes, summarizedByRetention } = entry.data as {
|
|
71
|
+
durationMs?: number;
|
|
72
|
+
name?: string;
|
|
73
|
+
payloadBytes?: number;
|
|
74
|
+
summarizedByRetention?: boolean;
|
|
75
|
+
};
|
|
53
76
|
if (!name || durationMs == null) continue;
|
|
54
77
|
|
|
55
78
|
const existing = timingsByTool.get(name);
|
|
56
79
|
if (existing) {
|
|
57
|
-
existing.push(durationMs);
|
|
80
|
+
existing.durations.push(durationMs);
|
|
81
|
+
existing.payloadBytes += typeof payloadBytes === "number" ? Math.max(0, payloadBytes) : 0;
|
|
82
|
+
existing.summarizedCount += summarizedByRetention ? 1 : 0;
|
|
58
83
|
} else {
|
|
59
|
-
timingsByTool.set(name,
|
|
84
|
+
timingsByTool.set(name, {
|
|
85
|
+
durations: [durationMs],
|
|
86
|
+
payloadBytes: typeof payloadBytes === "number" ? Math.max(0, payloadBytes) : 0,
|
|
87
|
+
summarizedCount: summarizedByRetention ? 1 : 0,
|
|
88
|
+
});
|
|
60
89
|
}
|
|
61
90
|
}
|
|
62
91
|
|
|
63
92
|
const stats: ToolTimingStats[] = [];
|
|
64
|
-
for (const [name,
|
|
65
|
-
durations.sort((a, b) => a - b);
|
|
66
|
-
const totalMs = durations.reduce((sum, d) => sum + d, 0);
|
|
93
|
+
for (const [name, values] of timingsByTool) {
|
|
94
|
+
values.durations.sort((a, b) => a - b);
|
|
95
|
+
const totalMs = values.durations.reduce((sum, d) => sum + d, 0);
|
|
67
96
|
|
|
68
97
|
stats.push({
|
|
69
98
|
name,
|
|
70
|
-
callCount: durations.length,
|
|
99
|
+
callCount: values.durations.length,
|
|
71
100
|
totalMs,
|
|
72
|
-
minMs: durations[0],
|
|
73
|
-
maxMs: durations[durations.length - 1],
|
|
74
|
-
avgMs: Math.round(totalMs / durations.length),
|
|
75
|
-
p50Ms: percentile(durations, 50),
|
|
76
|
-
p95Ms: percentile(durations, 95),
|
|
101
|
+
minMs: values.durations[0],
|
|
102
|
+
maxMs: values.durations[values.durations.length - 1],
|
|
103
|
+
avgMs: Math.round(totalMs / values.durations.length),
|
|
104
|
+
p50Ms: percentile(values.durations, 50),
|
|
105
|
+
p95Ms: percentile(values.durations, 95),
|
|
106
|
+
totalPayloadBytes: values.payloadBytes,
|
|
107
|
+
avgPayloadBytes:
|
|
108
|
+
values.durations.length > 0 ? Math.round(values.payloadBytes / values.durations.length) : 0,
|
|
109
|
+
summarizedCount: values.summarizedCount,
|
|
77
110
|
});
|
|
78
111
|
}
|
|
79
112
|
|
|
@@ -92,13 +125,13 @@ export function formatToolTimings(stats: ToolTimingStats[]): string {
|
|
|
92
125
|
if (stats.length === 0) return "No tool timing data found.";
|
|
93
126
|
|
|
94
127
|
const lines = [
|
|
95
|
-
"| Tool | Calls | Total (ms) | Avg (ms) | p50 (ms) | p95 (ms) | Max (ms) |",
|
|
96
|
-
"
|
|
128
|
+
"| Tool | Calls | Total (ms) | Avg (ms) | p50 (ms) | p95 (ms) | Max (ms) | Avg payload | Summarized |",
|
|
129
|
+
"|------|-------|------------|----------|----------|----------|----------|-------------|------------|",
|
|
97
130
|
];
|
|
98
131
|
|
|
99
132
|
for (const s of stats) {
|
|
100
133
|
lines.push(
|
|
101
|
-
`| ${s.name} | ${s.callCount} | ${s.totalMs} | ${s.avgMs} | ${s.p50Ms} | ${s.p95Ms} | ${s.maxMs} |`
|
|
134
|
+
`| ${s.name} | ${s.callCount} | ${s.totalMs} | ${s.avgMs} | ${s.p50Ms} | ${s.p95Ms} | ${s.maxMs} | ${formatBytes(s.avgPayloadBytes)} | ${s.summarizedCount} |`
|
|
102
135
|
);
|
|
103
136
|
}
|
|
104
137
|
|
|
@@ -281,7 +314,11 @@ export function formatEntries(entries: LogEntry[]): string {
|
|
|
281
314
|
const highlights: string[] = [];
|
|
282
315
|
if (data.name) highlights.push(`name=${data.name}`);
|
|
283
316
|
if (data.durationMs != null) highlights.push(`${data.durationMs}ms`);
|
|
317
|
+
if (typeof data.payloadBytes === "number") {
|
|
318
|
+
highlights.push(`payload=${formatBytes(Math.max(0, data.payloadBytes))}`);
|
|
319
|
+
}
|
|
284
320
|
if (data.ok !== undefined) highlights.push(data.ok ? "ok" : "FAILED");
|
|
321
|
+
if (data.summarizedByRetention === true) highlights.push("summarized");
|
|
285
322
|
if (data.message) highlights.push(`"${String(data.message).slice(0, 80)}"`);
|
|
286
323
|
if (data.agentId) highlights.push(`agent=${data.agentId}`);
|
|
287
324
|
if (data.exitCode !== undefined) highlights.push(`exit=${data.exitCode}`);
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
import { existsSync, readFileSync } from "node:fs";
|
|
17
17
|
import type { ExtensionAPI, ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
|
|
18
18
|
import { Type } from "@sinclair/typebox";
|
|
19
|
+
import { getTallowPath } from "../_shared/tallow-paths.js";
|
|
19
20
|
import {
|
|
20
21
|
calculateTurnMetrics,
|
|
21
22
|
formatEntries,
|
|
@@ -35,19 +36,81 @@ let totalToolCalls = 0;
|
|
|
35
36
|
let totalTurns = 0;
|
|
36
37
|
let sessionStartTime = 0;
|
|
37
38
|
|
|
39
|
+
/** Marker key set by sdk retention when historical results are summarized. */
|
|
40
|
+
const TOOL_RESULT_RETENTION_MARKER = "__tallow_summarized_tool_result__";
|
|
41
|
+
|
|
42
|
+
/** Payload metrics captured for each tool_result event. */
|
|
43
|
+
interface ToolResultPayloadMetrics {
|
|
44
|
+
readonly contentLength: number;
|
|
45
|
+
readonly contentBytes: number;
|
|
46
|
+
readonly detailsBytes: number;
|
|
47
|
+
readonly imageBlocks: number;
|
|
48
|
+
readonly payloadBytes: number;
|
|
49
|
+
readonly summarizedByRetention: boolean;
|
|
50
|
+
readonly textBlocks: number;
|
|
51
|
+
}
|
|
52
|
+
|
|
38
53
|
/**
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* @
|
|
54
|
+
* Estimate UTF-8 bytes for JSON-serializable data.
|
|
55
|
+
*
|
|
56
|
+
* @param value - Arbitrary data payload
|
|
57
|
+
* @returns Byte length of serialized data, or 0 on serialization failure
|
|
42
58
|
*/
|
|
43
|
-
function
|
|
44
|
-
|
|
59
|
+
function safeJsonBytes(value: unknown): number {
|
|
60
|
+
if (value == null) return 0;
|
|
61
|
+
try {
|
|
62
|
+
return Buffer.byteLength(JSON.stringify(value), "utf-8");
|
|
63
|
+
} catch {
|
|
64
|
+
return 0;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Measure text/image/details payload characteristics for a tool result.
|
|
70
|
+
*
|
|
71
|
+
* @param content - Tool result content blocks
|
|
72
|
+
* @param details - Tool result details payload
|
|
73
|
+
* @returns Structured payload metrics for debug logging
|
|
74
|
+
*/
|
|
75
|
+
function measureToolResultPayload(
|
|
76
|
+
content: Array<{ type: string; text?: string; data?: string; mimeType?: string }>,
|
|
77
|
+
details: unknown
|
|
78
|
+
): ToolResultPayloadMetrics {
|
|
79
|
+
let contentLength = 0;
|
|
80
|
+
let contentBytes = 0;
|
|
81
|
+
let imageBlocks = 0;
|
|
82
|
+
let textBlocks = 0;
|
|
83
|
+
|
|
45
84
|
for (const block of content) {
|
|
46
|
-
if (block.type === "text"
|
|
47
|
-
|
|
85
|
+
if (block.type === "text") {
|
|
86
|
+
const text = block.text ?? "";
|
|
87
|
+
contentLength += text.length;
|
|
88
|
+
contentBytes += Buffer.byteLength(text, "utf-8");
|
|
89
|
+
textBlocks += 1;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (block.type === "image") {
|
|
93
|
+
contentBytes += Buffer.byteLength(block.data ?? "", "utf-8");
|
|
94
|
+
contentBytes += Buffer.byteLength(block.mimeType ?? "", "utf-8");
|
|
95
|
+
imageBlocks += 1;
|
|
48
96
|
}
|
|
49
97
|
}
|
|
50
|
-
|
|
98
|
+
|
|
99
|
+
const detailsBytes = safeJsonBytes(details);
|
|
100
|
+
const summarizedByRetention =
|
|
101
|
+
typeof details === "object" &&
|
|
102
|
+
details !== null &&
|
|
103
|
+
(details as Record<string, unknown>)[TOOL_RESULT_RETENTION_MARKER] === true;
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
contentLength,
|
|
107
|
+
contentBytes,
|
|
108
|
+
detailsBytes,
|
|
109
|
+
imageBlocks,
|
|
110
|
+
payloadBytes: contentBytes + detailsBytes,
|
|
111
|
+
summarizedByRetention,
|
|
112
|
+
textBlocks,
|
|
113
|
+
};
|
|
51
114
|
}
|
|
52
115
|
|
|
53
116
|
/**
|
|
@@ -256,12 +319,23 @@ export default function (pi: ExtensionAPI) {
|
|
|
256
319
|
const durationMs = startTime !== undefined ? Math.round(performance.now() - startTime) : null;
|
|
257
320
|
toolTimings.delete(event.toolCallId);
|
|
258
321
|
|
|
322
|
+
const payload = measureToolResultPayload(
|
|
323
|
+
event.content as Array<{ type: string; text?: string; data?: string; mimeType?: string }>,
|
|
324
|
+
event.details
|
|
325
|
+
);
|
|
326
|
+
|
|
259
327
|
logger.log("tool", "result", {
|
|
260
328
|
toolCallId: event.toolCallId,
|
|
261
329
|
name: event.toolName,
|
|
262
330
|
durationMs,
|
|
263
331
|
ok: !event.isError,
|
|
264
|
-
contentLength: contentLength
|
|
332
|
+
contentLength: payload.contentLength,
|
|
333
|
+
contentBytes: payload.contentBytes,
|
|
334
|
+
detailsBytes: payload.detailsBytes,
|
|
335
|
+
payloadBytes: payload.payloadBytes,
|
|
336
|
+
textBlocks: payload.textBlocks,
|
|
337
|
+
imageBlocks: payload.imageBlocks,
|
|
338
|
+
summarizedByRetention: payload.summarizedByRetention,
|
|
265
339
|
});
|
|
266
340
|
});
|
|
267
341
|
|
|
@@ -501,7 +575,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
501
575
|
* @returns Absolute path to the debug log
|
|
502
576
|
*/
|
|
503
577
|
function getLogPath(): string {
|
|
504
|
-
return logger?.logPath ??
|
|
578
|
+
return logger?.logPath ?? getTallowPath("debug.log");
|
|
505
579
|
}
|
|
506
580
|
|
|
507
581
|
/**
|
|
@@ -18,8 +18,8 @@ import {
|
|
|
18
18
|
truncateSync,
|
|
19
19
|
writeFileSync,
|
|
20
20
|
} from "node:fs";
|
|
21
|
-
import { homedir } from "node:os";
|
|
22
21
|
import { join } from "node:path";
|
|
22
|
+
import { getTallowHomeDir } from "../_shared/tallow-paths.js";
|
|
23
23
|
|
|
24
24
|
/** Log entry categories that partition diagnostic output. */
|
|
25
25
|
export type LogCategory =
|
|
@@ -238,7 +238,7 @@ export class DebugLogger {
|
|
|
238
238
|
logDir?: string
|
|
239
239
|
) {
|
|
240
240
|
this.useStderr = process.env.TALLOW_DEBUG === "stderr";
|
|
241
|
-
const dir = logDir ??
|
|
241
|
+
const dir = logDir ?? getTallowHomeDir();
|
|
242
242
|
this.logPath = join(dir, "debug.log");
|
|
243
243
|
|
|
244
244
|
if (!this.useStderr) {
|
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { existsSync, readFileSync } from "node:fs";
|
|
10
|
-
import { homedir } from "node:os";
|
|
11
10
|
import { join } from "node:path";
|
|
12
11
|
import type {
|
|
13
12
|
ContextUsage,
|
|
@@ -16,6 +15,7 @@ import type {
|
|
|
16
15
|
Theme,
|
|
17
16
|
} from "@mariozechner/pi-coding-agent";
|
|
18
17
|
import { BorderedBox, ROUNDED, truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
|
|
18
|
+
import { getTallowHomeDir } from "../_shared/tallow-paths.js";
|
|
19
19
|
|
|
20
20
|
// ── Types ────────────────────────────────────────────────────────────────────
|
|
21
21
|
|
|
@@ -580,7 +580,7 @@ export default function healthExtension(pi: ExtensionAPI): void {
|
|
|
580
580
|
const piVersion = readPackageVersion(
|
|
581
581
|
join(packageDir, "node_modules", "@mariozechner", "pi-coding-agent", "package.json")
|
|
582
582
|
);
|
|
583
|
-
const tallowHome =
|
|
583
|
+
const tallowHome = getTallowHomeDir();
|
|
584
584
|
|
|
585
585
|
// ── Message renderer ─────────────────────────────────────────────────
|
|
586
586
|
|
|
@@ -42,6 +42,35 @@ describe("translateClaudeHooks", () => {
|
|
|
42
42
|
expect(handler?._claudeEventName).toBe("PreToolUse");
|
|
43
43
|
});
|
|
44
44
|
|
|
45
|
+
it("translates worktree aliases and preserves scope matchers", () => {
|
|
46
|
+
const translated = translateClaudeHooks({
|
|
47
|
+
WorktreeCreate: [
|
|
48
|
+
{ matcher: "project|feature", hooks: [{ type: "command", command: "echo create" }] },
|
|
49
|
+
],
|
|
50
|
+
WorktreeRemove: [
|
|
51
|
+
{ matcher: "project", hooks: [{ type: "command", command: "echo remove" }] },
|
|
52
|
+
],
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
expect(translated.worktree_create).toHaveLength(1);
|
|
56
|
+
expect(translated.worktree_remove).toHaveLength(1);
|
|
57
|
+
expect(translated.worktree_create[0]?.matcher).toBe("project|feature");
|
|
58
|
+
expect(translated.worktree_remove[0]?.matcher).toBe("project");
|
|
59
|
+
expect(translated.worktree_remove[0]?.hooks[0]?._claudeEventName).toBe("WorktreeRemove");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("translates worktree lifecycle aliases", () => {
|
|
63
|
+
const translated = translateClaudeHooks({
|
|
64
|
+
WorktreeCreate: [{ hooks: [{ type: "command", command: "echo create" }] }],
|
|
65
|
+
WorktreeRemove: [{ hooks: [{ type: "command", command: "echo remove" }] }],
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
expect(translated.worktree_create).toHaveLength(1);
|
|
69
|
+
expect(translated.worktree_remove).toHaveLength(1);
|
|
70
|
+
expect(translated.worktree_create[0]?.hooks[0]?._claudeEventName).toBe("WorktreeCreate");
|
|
71
|
+
expect(translated.worktree_remove[0]?.hooks[0]?._claudeEventName).toBe("WorktreeRemove");
|
|
72
|
+
});
|
|
73
|
+
|
|
45
74
|
it("skips PermissionRequest with a warning", () => {
|
|
46
75
|
const warnings: string[] = [];
|
|
47
76
|
const originalWarn = console.warn;
|
|
@@ -98,6 +127,8 @@ describe("translateClaudeHooks", () => {
|
|
|
98
127
|
Notification: "notification",
|
|
99
128
|
SubagentStart: "subagent_start",
|
|
100
129
|
SubagentStop: "subagent_stop",
|
|
130
|
+
WorktreeCreate: "worktree_create",
|
|
131
|
+
WorktreeRemove: "worktree_remove",
|
|
101
132
|
Stop: "agent_end",
|
|
102
133
|
TeammateIdle: "teammate_idle",
|
|
103
134
|
TaskCompleted: "task_completed",
|
|
@@ -107,6 +107,14 @@
|
|
|
107
107
|
"$ref": "#/$defs/eventHooks",
|
|
108
108
|
"description": "When a subagent completes. Matcher filters on agent_type."
|
|
109
109
|
},
|
|
110
|
+
"worktree_create": {
|
|
111
|
+
"$ref": "#/$defs/eventHooks",
|
|
112
|
+
"description": "When a managed worktree is created. Matcher filters on scope (session/subagent)."
|
|
113
|
+
},
|
|
114
|
+
"worktree_remove": {
|
|
115
|
+
"$ref": "#/$defs/eventHooks",
|
|
116
|
+
"description": "When a managed worktree is removed. Matcher filters on scope (session/subagent)."
|
|
117
|
+
},
|
|
110
118
|
"notification": {
|
|
111
119
|
"$ref": "#/$defs/eventHooks",
|
|
112
120
|
"description": "When a notification is emitted via the event bus. Matcher filters on type."
|
|
@@ -155,6 +163,14 @@
|
|
|
155
163
|
"$ref": "#/$defs/eventHooks",
|
|
156
164
|
"description": "Claude Code alias for subagent_stop."
|
|
157
165
|
},
|
|
166
|
+
"WorktreeCreate": {
|
|
167
|
+
"$ref": "#/$defs/eventHooks",
|
|
168
|
+
"description": "Claude Code alias for worktree_create."
|
|
169
|
+
},
|
|
170
|
+
"WorktreeRemove": {
|
|
171
|
+
"$ref": "#/$defs/eventHooks",
|
|
172
|
+
"description": "Claude Code alias for worktree_remove."
|
|
173
|
+
},
|
|
158
174
|
"Stop": {
|
|
159
175
|
"$ref": "#/$defs/eventHooks",
|
|
160
176
|
"description": "Claude Code alias for agent_end."
|
|
@@ -196,7 +212,7 @@
|
|
|
196
212
|
"properties": {
|
|
197
213
|
"matcher": {
|
|
198
214
|
"type": "string",
|
|
199
|
-
"description": "Regex pattern to filter events. Empty or '*' matches all.
|
|
215
|
+
"description": "Regex pattern to filter events. Empty or '*' matches all. Uses event-specific fields (for example: tool_call/tool_result=toolName, task_completed=assignee, teammate_idle=teammate, worktree_create/worktree_remove=scope)."
|
|
200
216
|
},
|
|
201
217
|
"hooks": {
|
|
202
218
|
"type": "array",
|
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
} from "../../src/agent-runner.js";
|
|
38
38
|
import { isProjectTrusted } from "../_shared/project-trust.js";
|
|
39
39
|
import { evaluateCommand } from "../_shared/shell-policy.js";
|
|
40
|
+
import { getTallowHomeDir, getTallowPath } from "../_shared/tallow-paths.js";
|
|
40
41
|
import { createHookStateManager, type HookStateManager } from "./state-manager.js";
|
|
41
42
|
|
|
42
43
|
/** Hook execution strategy: shell command, LLM prompt, or agent subprocess. */
|
|
@@ -304,6 +305,8 @@ const MATCHER_FIELDS: Record<string, string> = {
|
|
|
304
305
|
tool_result: "toolName",
|
|
305
306
|
teammate_idle: "teammate",
|
|
306
307
|
task_completed: "assignee",
|
|
308
|
+
worktree_create: "scope",
|
|
309
|
+
worktree_remove: "scope",
|
|
307
310
|
setup: "trigger",
|
|
308
311
|
subagent_start: "agent_type",
|
|
309
312
|
subagent_stop: "agent_type",
|
|
@@ -323,6 +326,8 @@ export const CLAUDE_EVENT_MAP: Readonly<Record<string, string>> = {
|
|
|
323
326
|
Notification: "notification",
|
|
324
327
|
SubagentStart: "subagent_start",
|
|
325
328
|
SubagentStop: "subagent_stop",
|
|
329
|
+
WorktreeCreate: "worktree_create",
|
|
330
|
+
WorktreeRemove: "worktree_remove",
|
|
326
331
|
Stop: "agent_end",
|
|
327
332
|
TeammateIdle: "teammate_idle",
|
|
328
333
|
TaskCompleted: "task_completed",
|
|
@@ -1129,10 +1134,24 @@ export default function (pi: ExtensionAPI) {
|
|
|
1129
1134
|
}
|
|
1130
1135
|
};
|
|
1131
1136
|
|
|
1137
|
+
/**
|
|
1138
|
+
* Dispatches EventBus hook execution without blocking the publisher.
|
|
1139
|
+
*
|
|
1140
|
+
* @param eventName - Hook event name to execute
|
|
1141
|
+
* @param eventData - Event payload forwarded to hook matchers/handlers
|
|
1142
|
+
* @returns Nothing
|
|
1143
|
+
*/
|
|
1144
|
+
const dispatchEventBusHooks = (eventName: string, eventData: Record<string, unknown>): void => {
|
|
1145
|
+
void runHooks(eventName, eventData).catch((error: unknown) => {
|
|
1146
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1147
|
+
console.error(`[hooks] EventBus dispatch failed for ${eventName}: ${message}`);
|
|
1148
|
+
});
|
|
1149
|
+
};
|
|
1150
|
+
|
|
1132
1151
|
/** Forward teammate_idle events to hook handlers. */
|
|
1133
1152
|
const onTeammateIdle = (data: unknown) => {
|
|
1134
1153
|
const event = data as { team: string; teammate: string; role: string };
|
|
1135
|
-
|
|
1154
|
+
dispatchEventBusHooks("teammate_idle", event);
|
|
1136
1155
|
};
|
|
1137
1156
|
|
|
1138
1157
|
/** Forward task_completed events to hook handlers. */
|
|
@@ -1144,7 +1163,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1144
1163
|
assignee: string;
|
|
1145
1164
|
result: string;
|
|
1146
1165
|
};
|
|
1147
|
-
|
|
1166
|
+
dispatchEventBusHooks("task_completed", event);
|
|
1148
1167
|
};
|
|
1149
1168
|
|
|
1150
1169
|
/** Forward subagent_start events from EventBus to hook handlers. */
|
|
@@ -1156,7 +1175,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1156
1175
|
cwd: string;
|
|
1157
1176
|
background: boolean;
|
|
1158
1177
|
};
|
|
1159
|
-
|
|
1178
|
+
dispatchEventBusHooks("subagent_start", event);
|
|
1160
1179
|
};
|
|
1161
1180
|
|
|
1162
1181
|
/** Forward subagent_stop events from EventBus to hook handlers. */
|
|
@@ -1169,7 +1188,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1169
1188
|
result: string;
|
|
1170
1189
|
background: boolean;
|
|
1171
1190
|
};
|
|
1172
|
-
|
|
1191
|
+
dispatchEventBusHooks("subagent_stop", event);
|
|
1173
1192
|
};
|
|
1174
1193
|
|
|
1175
1194
|
/** Forward notification events from EventBus to hook handlers. */
|
|
@@ -1179,7 +1198,29 @@ export default function (pi: ExtensionAPI) {
|
|
|
1179
1198
|
type: string;
|
|
1180
1199
|
source?: string;
|
|
1181
1200
|
};
|
|
1182
|
-
|
|
1201
|
+
dispatchEventBusHooks("notification", event);
|
|
1202
|
+
};
|
|
1203
|
+
|
|
1204
|
+
/**
|
|
1205
|
+
* Forward worktree_create events from EventBus to hook handlers.
|
|
1206
|
+
*
|
|
1207
|
+
* @param data - Event payload from EventBus
|
|
1208
|
+
* @returns Nothing
|
|
1209
|
+
*/
|
|
1210
|
+
const onWorktreeCreate = (data: unknown) => {
|
|
1211
|
+
const event = data as Record<string, unknown>;
|
|
1212
|
+
dispatchEventBusHooks("worktree_create", event);
|
|
1213
|
+
};
|
|
1214
|
+
|
|
1215
|
+
/**
|
|
1216
|
+
* Forward worktree_remove events from EventBus to hook handlers without blocking.
|
|
1217
|
+
*
|
|
1218
|
+
* @param data - Event payload from EventBus
|
|
1219
|
+
* @returns Nothing
|
|
1220
|
+
*/
|
|
1221
|
+
const onWorktreeRemove = (data: unknown) => {
|
|
1222
|
+
const event = data as Record<string, unknown>;
|
|
1223
|
+
dispatchEventBusHooks("worktree_remove", event);
|
|
1183
1224
|
};
|
|
1184
1225
|
|
|
1185
1226
|
// ── Session lifecycle ────────────────────────────────────────
|
|
@@ -1188,10 +1229,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
1188
1229
|
ctx = context;
|
|
1189
1230
|
currentCwd = context.cwd;
|
|
1190
1231
|
hooksConfig = loadHooksConfig(currentCwd);
|
|
1191
|
-
agentsDir =
|
|
1232
|
+
agentsDir = getTallowPath("agents");
|
|
1192
1233
|
|
|
1193
1234
|
// Initialize once-hook state manager
|
|
1194
|
-
const tallowHome =
|
|
1235
|
+
const tallowHome = getTallowHomeDir();
|
|
1195
1236
|
stateManager = createHookStateManager(tallowHome);
|
|
1196
1237
|
|
|
1197
1238
|
// Check for project-local agents dir
|
|
@@ -1215,6 +1256,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
1215
1256
|
const unsub4 = pi.events.on("subagent_start", onSubagentStart);
|
|
1216
1257
|
const unsub5 = pi.events.on("subagent_stop", onSubagentStop);
|
|
1217
1258
|
const unsub6 = pi.events.on("notification", onNotification);
|
|
1259
|
+
const unsub7 = pi.events.on("worktree_create", onWorktreeCreate);
|
|
1260
|
+
const unsub8 = pi.events.on("worktree_remove", onWorktreeRemove);
|
|
1218
1261
|
G.__hooksEventCleanup = () => {
|
|
1219
1262
|
unsub1();
|
|
1220
1263
|
unsub2();
|
|
@@ -1222,6 +1265,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
1222
1265
|
unsub4();
|
|
1223
1266
|
unsub5();
|
|
1224
1267
|
unsub6();
|
|
1268
|
+
unsub7();
|
|
1269
|
+
unsub8();
|
|
1225
1270
|
};
|
|
1226
1271
|
|
|
1227
1272
|
// Run setup hooks if triggered by --init, --init-only, or --maintenance CLI flags.
|
package/extensions/lsp/index.ts
CHANGED
|
@@ -13,7 +13,6 @@
|
|
|
13
13
|
|
|
14
14
|
import { type ChildProcess, spawn } from "node:child_process";
|
|
15
15
|
import * as fs from "node:fs";
|
|
16
|
-
import { homedir } from "node:os";
|
|
17
16
|
import * as path from "node:path";
|
|
18
17
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
19
18
|
import { Type } from "@sinclair/typebox";
|
|
@@ -41,6 +40,7 @@ import {
|
|
|
41
40
|
WorkspaceSymbolRequest,
|
|
42
41
|
} from "vscode-languageserver-protocol";
|
|
43
42
|
import { getIcon } from "../_icons/index.js";
|
|
43
|
+
import { getTallowSettingsPath } from "../_shared/tallow-paths.js";
|
|
44
44
|
|
|
45
45
|
/** Language server binary configuration and project detection markers. */
|
|
46
46
|
interface ServerConfig {
|
|
@@ -280,12 +280,7 @@ function readStartupTimeoutFromSettings(settings: Record<string, unknown>): numb
|
|
|
280
280
|
* @returns Absolute path to the user-level settings file
|
|
281
281
|
*/
|
|
282
282
|
function getGlobalSettingsPath(): string {
|
|
283
|
-
|
|
284
|
-
if (typeof configuredAgentDir === "string" && configuredAgentDir.trim().length > 0) {
|
|
285
|
-
return path.join(configuredAgentDir, "settings.json");
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
return path.join(homedir(), ".tallow", "settings.json");
|
|
283
|
+
return getTallowSettingsPath();
|
|
289
284
|
}
|
|
290
285
|
|
|
291
286
|
/**
|
|
@@ -18,6 +18,7 @@ import * as fs from "node:fs";
|
|
|
18
18
|
import * as path from "node:path";
|
|
19
19
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
20
20
|
import { atomicWriteFileSync } from "../_shared/atomic-write.js";
|
|
21
|
+
import { getTallowPath } from "../_shared/tallow-paths.js";
|
|
21
22
|
import {
|
|
22
23
|
buildReminderContent,
|
|
23
24
|
buildStyledPrompt,
|
|
@@ -29,10 +30,7 @@ import {
|
|
|
29
30
|
// ── Discovery ───────────────────────────────────────
|
|
30
31
|
|
|
31
32
|
/** Standard locations for output style files */
|
|
32
|
-
const USER_STYLES_DIR =
|
|
33
|
-
process.env.PI_CODING_AGENT_DIR || path.join(process.env.HOME || "", ".tallow"),
|
|
34
|
-
"output-styles"
|
|
35
|
-
);
|
|
33
|
+
const USER_STYLES_DIR = getTallowPath("output-styles");
|
|
36
34
|
const PROJECT_STYLES_DIR = path.join(process.cwd(), ".tallow", "output-styles");
|
|
37
35
|
|
|
38
36
|
/**
|