@dungle-scrubs/tallow 0.9.8 → 0.9.9

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/config.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { type RuntimePathProvider } from "./runtime-path-provider.js";
2
2
  export declare const APP_NAME = "tallow";
3
- export declare const TALLOW_VERSION = "0.9.8";
3
+ export declare const TALLOW_VERSION = "0.9.9";
4
4
  export declare const CONFIG_DIR = ".tallow";
5
5
  /** ~/.tallow (or override from ~/.config/tallow-work-dirs) — all user config, sessions, auth, extensions */
6
6
  export declare const TALLOW_HOME: string;
package/dist/config.js CHANGED
@@ -6,7 +6,7 @@ import { fileURLToPath } from "node:url";
6
6
  import { createRuntimePathProvider } from "./runtime-path-provider.js";
7
7
  // ─── Identity ────────────────────────────────────────────────────────────────
8
8
  export const APP_NAME = "tallow";
9
- export const TALLOW_VERSION = "0.9.8"; // x-release-please-version
9
+ export const TALLOW_VERSION = "0.9.9"; // x-release-please-version
10
10
  export const CONFIG_DIR = ".tallow";
11
11
  // ─── Paths ───────────────────────────────────────────────────────────────────
12
12
  /** ~/.tallow (or override from ~/.config/tallow-work-dirs) — all user config, sessions, auth, extensions */
@@ -13,8 +13,17 @@ interface InteractiveModeLike {
13
13
  clear(): void;
14
14
  };
15
15
  pendingTools: Map<string, unknown>;
16
+ rebindCurrentSession?(): Promise<void>;
16
17
  renderInitialMessages(): void;
17
18
  resetExtensionUI(): void;
19
+ runtimeHost?: {
20
+ apply(result: {
21
+ diagnostics: unknown;
22
+ modelFallbackMessage: unknown;
23
+ services: unknown;
24
+ session: unknown;
25
+ }): void;
26
+ };
18
27
  session: AgentSessionLike;
19
28
  showStatus(message: string): void;
20
29
  statusContainer: {
@@ -30,7 +39,7 @@ interface InteractiveModeLike {
30
39
  };
31
40
  unsubscribe?: (() => void) | undefined;
32
41
  updateTerminalTitle(): void;
33
- initExtensions(): Promise<void>;
42
+ initExtensions?(): Promise<void>;
34
43
  }
35
44
  /** Runtime shape needed from AgentSession for transition orchestration. */
36
45
  type AgentSessionLike = TallowSession["session"] & {
@@ -1 +1 @@
1
- {"version":3,"file":"workspace-transition-interactive.d.ts","sourceRoot":"","sources":["../src/workspace-transition-interactive.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"workspace-transition-interactive.d.ts","sourceRoot":"","sources":["../src/workspace-transition-interactive.ts"],"names":[],"mappings":"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"}
@@ -132,12 +132,27 @@ async function shutdownPreviousSession(session) {
132
132
  */
133
133
  async function swapInteractiveModeSession(mode, next, setCleanupSession) {
134
134
  resetInteractiveModeState(mode);
135
- mode.session = next.session;
135
+ if (mode.runtimeHost) {
136
+ mode.runtimeHost.apply({
137
+ diagnostics: next.runtime.diagnostics,
138
+ modelFallbackMessage: next.runtime.modelFallbackMessage,
139
+ services: next.runtime.services,
140
+ session: next.session,
141
+ });
142
+ }
143
+ else {
144
+ mode.session = next.session;
145
+ }
136
146
  setCleanupSession(next.session);
137
- await mode.initExtensions();
147
+ if (mode.rebindCurrentSession) {
148
+ await mode.rebindCurrentSession();
149
+ }
150
+ else {
151
+ await mode.initExtensions?.();
152
+ mode.subscribeToAgent();
153
+ mode.updateTerminalTitle();
154
+ }
138
155
  mode.renderInitialMessages();
139
- mode.subscribeToAgent();
140
- mode.updateTerminalTitle();
141
156
  mode.ui.requestRender(true);
142
157
  }
143
158
  /** Maximum character length for task context carried across a workspace transition. */
@@ -1 +1 @@
1
- {"version":3,"file":"workspace-transition-interactive.js","sourceRoot":"","sources":["../src/workspace-transition-interactive.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"workspace-transition-interactive.js","sourceRoot":"","sources":["../src/workspace-transition-interactive.ts"],"names":[],"mappings":"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"}
@@ -50,13 +50,13 @@ export type WorktreeLifecycleEventName =
50
50
  (typeof WORKTREE_LIFECYCLE_EVENT_NAMES)[keyof typeof WORKTREE_LIFECYCLE_EVENT_NAMES];
51
51
 
52
52
  /** Valid worktree scopes in lifecycle payloads. */
53
- export type WorktreeLifecycleScope = "session" | "subagent";
53
+ export type WorktreeLifecycleScope = "project" | "session" | "subagent";
54
54
 
55
55
  /** TypeBox schema for worktree lifecycle payloads shared across extensions. */
56
56
  export const WorktreeLifecyclePayloadSchema = Type.Object({
57
57
  agentId: Type.Optional(Type.String()),
58
58
  repoRoot: Type.String(),
59
- scope: Type.Union([Type.Literal("session"), Type.Literal("subagent")]),
59
+ scope: Type.Union([Type.Literal("project"), Type.Literal("session"), Type.Literal("subagent")]),
60
60
  timestamp: Type.Number(),
61
61
  worktreePath: Type.String(),
62
62
  });
@@ -366,7 +366,10 @@ export default function customFooterExtension(pi: ExtensionAPI): void {
366
366
  },
367
367
 
368
368
  dispose: (() => {
369
- disposeHandler = footerData.onBranchChange(() => tui.requestRender());
369
+ disposeHandler = footerData.onBranchChange(() => {
370
+ invalidateGitCache();
371
+ tui.requestRender();
372
+ });
370
373
  return disposeHandler;
371
374
  })(),
372
375
  };
@@ -6,6 +6,9 @@
6
6
  */
7
7
 
8
8
  import { afterEach, beforeEach, describe, expect, test } from "bun:test";
9
+ import * as fs from "node:fs";
10
+ import * as os from "node:os";
11
+ import * as path from "node:path";
9
12
  import type { AssistantMessage, ToolResultMessage, Usage } from "@mariozechner/pi-ai";
10
13
  import type {
11
14
  ContextUsage,
@@ -33,10 +36,17 @@ const ZERO_USAGE: Usage = {
33
36
  cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
34
37
  };
35
38
 
39
+ const PROJECT_TRUST_STATUS_ENV = "TALLOW_PROJECT_TRUST_STATUS";
40
+ const PROJECT_TRUST_CWD_ENV = "TALLOW_PROJECT_TRUST_CWD";
41
+
36
42
  let harness: ExtensionHarness;
43
+ let previousTrustCwd: string | undefined;
44
+ let previousTrustStatus: string | undefined;
37
45
  let scheduler: ManualTimerScheduler;
38
46
 
39
47
  beforeEach(async () => {
48
+ previousTrustCwd = process.env[PROJECT_TRUST_CWD_ENV];
49
+ previousTrustStatus = process.env[PROJECT_TRUST_STATUS_ENV];
40
50
  scheduler = new ManualTimerScheduler();
41
51
  setSlashCommandBridgeSchedulerForTests(scheduler.runtime);
42
52
  harness = ExtensionHarness.create();
@@ -44,6 +54,16 @@ beforeEach(async () => {
44
54
  });
45
55
 
46
56
  afterEach(() => {
57
+ if (previousTrustCwd === undefined) {
58
+ delete process.env[PROJECT_TRUST_CWD_ENV];
59
+ } else {
60
+ process.env[PROJECT_TRUST_CWD_ENV] = previousTrustCwd;
61
+ }
62
+ if (previousTrustStatus === undefined) {
63
+ delete process.env[PROJECT_TRUST_STATUS_ENV];
64
+ } else {
65
+ process.env[PROJECT_TRUST_STATUS_ENV] = previousTrustStatus;
66
+ }
47
67
  resetResetDiagnosticsForTests();
48
68
  resetSlashCommandBridgeStateForTests();
49
69
  });
@@ -123,7 +143,10 @@ function buildCompactToolResult(): ToolResultMessage<{ command: string }> {
123
143
  * @param ctx - Extension context for the execution
124
144
  * @returns Tool execution result
125
145
  */
126
- async function executeTool(params: { command: string }, ctx?: ExtensionContext) {
146
+ async function executeTool(
147
+ params: { command: string; arguments?: string },
148
+ ctx?: ExtensionContext
149
+ ) {
127
150
  const tool = harness.tools.get("run_slash_command");
128
151
  if (!tool) {
129
152
  throw new Error("run_slash_command tool not registered");
@@ -213,6 +236,80 @@ describe("context injection", () => {
213
236
  });
214
237
  });
215
238
 
239
+ /**
240
+ * Creates a trusted temporary project command file for model-callable prompt tests.
241
+ *
242
+ * @returns Temporary project directory path
243
+ */
244
+ function createTrustedModelCallableCommandFixture(): string {
245
+ const cwd = fs.mkdtempSync(path.join(os.tmpdir(), "slash-command-bridge-"));
246
+ const commandsDir = path.join(cwd, ".claude", "commands");
247
+ fs.mkdirSync(commandsDir, { recursive: true });
248
+ fs.writeFileSync(
249
+ path.join(commandsDir, "auto-fix.md"),
250
+ [
251
+ "---",
252
+ "name: auto-fix",
253
+ "description: Auto fix test command",
254
+ "model-callable: true",
255
+ "---",
256
+ "# Auto Fix",
257
+ "Task: $ARGUMENTS",
258
+ ].join("\n")
259
+ );
260
+ process.env[PROJECT_TRUST_CWD_ENV] = cwd;
261
+ process.env[PROJECT_TRUST_STATUS_ENV] = "trusted";
262
+ return cwd;
263
+ }
264
+
265
+ /**
266
+ * Returns message details as a loose object for assertions.
267
+ *
268
+ * @param details - Unknown message details payload
269
+ * @returns Details as a record
270
+ */
271
+ function asDetailsRecord(details: unknown): Record<string, unknown> {
272
+ return details && typeof details === "object" ? (details as Record<string, unknown>) : {};
273
+ }
274
+
275
+ describe("model-callable prompt commands", () => {
276
+ test("injects opted-in prompt commands from trusted project command directories", async () => {
277
+ const cwd = createTrustedModelCallableCommandFixture();
278
+ const ctx = buildContext({ cwd });
279
+
280
+ const results = await harness.fireEvent(
281
+ "before_agent_start",
282
+ { type: "before_agent_start", prompt: "hello", systemPrompt: "" },
283
+ ctx
284
+ );
285
+ const result = results.find((entry) => entry != null) as
286
+ | {
287
+ message: { content: string; customType: string; display: boolean };
288
+ }
289
+ | undefined;
290
+
291
+ expect(result?.message.content).toContain("/auto-fix");
292
+ });
293
+
294
+ test("queues opted-in prompt command content with substituted arguments", async () => {
295
+ const cwd = createTrustedModelCallableCommandFixture();
296
+ const ctx = buildContext({ cwd });
297
+
298
+ const result = await executeTool({ command: "auto-fix", arguments: "repair tests" }, ctx);
299
+
300
+ expect(result.details).toEqual({
301
+ command: "auto-fix",
302
+ arguments: "repair tests",
303
+ promptCommand: true,
304
+ });
305
+ expect(harness.sentMessages).toHaveLength(2);
306
+ expect(harness.sentMessages[0]?.content).toBe("🪆 /auto-fix repair tests");
307
+ expect(harness.sentMessages[1]?.content).toContain("Task: repair tests");
308
+ expect(harness.sentMessages[1]?.options).toEqual({ triggerTurn: true });
309
+ expect(asDetailsRecord(harness.sentMessages[1]?.details).commandName).toBe("auto-fix");
310
+ });
311
+ });
312
+
216
313
  describe("compact", () => {
217
314
  test("defers compact instead of calling ctx.compact inline", async () => {
218
315
  let compactCalled = false;
@@ -9,15 +9,20 @@
9
9
  * registration, with auto-generated tool schemas and full command handler access.
10
10
  */
11
11
 
12
+ import * as fs from "node:fs";
13
+ import * as os from "node:os";
14
+ import * as path from "node:path";
12
15
  import type {
13
16
  ContextUsage,
14
17
  ExtensionAPI,
15
18
  ExtensionContext,
16
19
  TurnEndEvent,
17
20
  } from "@mariozechner/pi-coding-agent";
21
+ import { getAgentDir } from "@mariozechner/pi-coding-agent";
18
22
  import { Text } from "@mariozechner/pi-tui";
19
23
  import { Type } from "@sinclair/typebox";
20
24
  import { recordResetDiagnostic } from "../../src/reset-diagnostics.js";
25
+ import { isProjectTrusted } from "../_shared/project-trust.js";
21
26
 
22
27
  /**
23
28
  * Deferred compact request — set by the tool handler, consumed on the first
@@ -224,6 +229,22 @@ const COMMAND_DESCRIPTIONS: ReadonlyMap<string, string> = new Map([
224
229
  ["compact", "Triggers session compaction to free up context window space"],
225
230
  ]);
226
231
 
232
+ /** Frontmatter parsed from a prompt or command markdown file. */
233
+ interface PromptFrontmatter {
234
+ readonly description?: string;
235
+ readonly modelCallable?: unknown;
236
+ readonly "model-callable"?: unknown;
237
+ readonly model_callable?: unknown;
238
+ readonly [key: string]: unknown;
239
+ }
240
+
241
+ /** Model-callable prompt command discovered from local command directories. */
242
+ interface ModelCallablePromptCommand {
243
+ readonly command: string;
244
+ readonly description: string;
245
+ readonly filePath: string;
246
+ }
247
+
227
248
  /** Context usage with a known finite token count. */
228
249
  interface KnownContextUsage extends ContextUsage {
229
250
  readonly tokens: number;
@@ -233,6 +254,208 @@ interface KnownContextUsage extends ContextUsage {
233
254
  const NO_CONTEXT_USAGE_DATA_TEXT =
234
255
  "No context usage data available yet. A message must be processed first.";
235
256
 
257
+ /**
258
+ * Parses simple YAML frontmatter from a prompt markdown file.
259
+ *
260
+ * @param content - Raw markdown file content
261
+ * @returns Frontmatter key-value pairs
262
+ */
263
+ function parsePromptFrontmatter(content: string): PromptFrontmatter {
264
+ const match = content.match(/^---\s*\n([\s\S]*?)\n---/);
265
+ if (!match) return {};
266
+
267
+ const frontmatter: Record<string, unknown> = {};
268
+ for (const line of match[1].split("\n")) {
269
+ const colonIndex = line.indexOf(":");
270
+ if (colonIndex === -1) continue;
271
+
272
+ const key = line.slice(0, colonIndex).trim();
273
+ const rawValue = line.slice(colonIndex + 1).trim();
274
+ frontmatter[key] = rawValue.replace(/^(["'])(.*)\1$/, "$2");
275
+ }
276
+
277
+ return frontmatter;
278
+ }
279
+
280
+ /**
281
+ * Returns whether a frontmatter value opts a prompt into model invocation.
282
+ *
283
+ * @param value - Raw frontmatter value
284
+ * @returns True when the value is the boolean/string true
285
+ */
286
+ function isModelCallableValue(value: unknown): boolean {
287
+ return value === true || (typeof value === "string" && value.toLowerCase() === "true");
288
+ }
289
+
290
+ /**
291
+ * Returns whether prompt frontmatter opts into model invocation.
292
+ *
293
+ * @param frontmatter - Parsed prompt frontmatter
294
+ * @returns True when any supported model-callable key is true
295
+ */
296
+ function isModelCallablePrompt(frontmatter: PromptFrontmatter): boolean {
297
+ return (
298
+ isModelCallableValue(frontmatter.modelCallable) ||
299
+ isModelCallableValue(frontmatter["model-callable"]) ||
300
+ isModelCallableValue(frontmatter.model_callable)
301
+ );
302
+ }
303
+
304
+ /**
305
+ * Removes YAML frontmatter from markdown content.
306
+ *
307
+ * @param content - Raw markdown content
308
+ * @returns Markdown content without leading frontmatter
309
+ */
310
+ function stripPromptFrontmatter(content: string): string {
311
+ return content.replace(/^---\s*\n[\s\S]*?\n---\s*\n?/, "");
312
+ }
313
+
314
+ /**
315
+ * Substitutes prompt argument placeholders with provided slash command arguments.
316
+ *
317
+ * @param content - Prompt content with placeholders
318
+ * @param args - Space-separated argument string
319
+ * @returns Content with argument placeholders expanded
320
+ */
321
+ function substitutePromptArguments(content: string, args: string): string {
322
+ const argList = args.split(/\s+/).filter(Boolean);
323
+ let result = content.replace(/\$(\d+)/g, (_, num: string) => {
324
+ const index = Number.parseInt(num, 10) - 1;
325
+ return argList[index] ?? "";
326
+ });
327
+
328
+ result = result.replace(
329
+ /\$\{@:(\d+)(?::(\d+))?\}/g,
330
+ (_, startStr: string, lengthStr?: string) => {
331
+ const start = Math.max(Number.parseInt(startStr, 10) - 1, 0);
332
+ if (lengthStr) {
333
+ const length = Number.parseInt(lengthStr, 10);
334
+ return argList.slice(start, start + length).join(" ");
335
+ }
336
+ return argList.slice(start).join(" ");
337
+ }
338
+ );
339
+
340
+ result = result.replace(/\$ARGUMENTS/g, args);
341
+ return result.replace(/\$@/g, args);
342
+ }
343
+
344
+ /**
345
+ * Builds the local prompt/command directories that may contain model-callable commands.
346
+ *
347
+ * @param cwd - Current session working directory
348
+ * @returns Ordered directories, highest-precedence first
349
+ */
350
+ function getModelCallablePromptDirs(cwd: string): readonly string[] {
351
+ const agentDir = getAgentDir();
352
+ const globalDirs = [
353
+ path.join(agentDir, "prompts"),
354
+ path.join(agentDir, "commands"),
355
+ path.join(os.homedir(), ".claude", "commands"),
356
+ ];
357
+
358
+ if (!isProjectTrusted(cwd)) {
359
+ return globalDirs;
360
+ }
361
+
362
+ return [
363
+ path.join(cwd, ".tallow", "prompts"),
364
+ path.join(cwd, ".tallow", "commands"),
365
+ path.join(cwd, ".claude", "commands"),
366
+ ...globalDirs,
367
+ ];
368
+ }
369
+
370
+ /**
371
+ * Converts a markdown path below a prompt directory into its slash command name.
372
+ *
373
+ * @param rootDir - Prompt root directory
374
+ * @param filePath - Markdown file path inside the root directory
375
+ * @returns Slash command name without leading slash, or undefined for ignored files
376
+ */
377
+ function getPromptCommandName(rootDir: string, filePath: string): string | undefined {
378
+ const relativePath = path.relative(rootDir, filePath);
379
+ if (relativePath.startsWith("..") || path.isAbsolute(relativePath)) return undefined;
380
+ if (!relativePath.endsWith(".md")) return undefined;
381
+
382
+ const parts = relativePath.slice(0, -".md".length).split(path.sep);
383
+ if (parts.some((part) => part === "" || part.startsWith("_"))) return undefined;
384
+ return parts.join(":");
385
+ }
386
+
387
+ /**
388
+ * Recursively discovers markdown files in a prompt directory.
389
+ *
390
+ * @param rootDir - Prompt root directory to scan
391
+ * @returns Absolute markdown file paths
392
+ */
393
+ function discoverPromptMarkdownFiles(rootDir: string): readonly string[] {
394
+ const files: string[] = [];
395
+ const pending = [rootDir];
396
+
397
+ while (pending.length > 0) {
398
+ const dir = pending.pop();
399
+ if (!dir) continue;
400
+
401
+ let entries: fs.Dirent[];
402
+ try {
403
+ entries = fs.readdirSync(dir, { withFileTypes: true });
404
+ } catch {
405
+ continue;
406
+ }
407
+
408
+ for (const entry of entries) {
409
+ if (entry.name.startsWith("_")) continue;
410
+ const entryPath = path.join(dir, entry.name);
411
+ if (entry.isDirectory()) {
412
+ pending.push(entryPath);
413
+ } else if (entry.isFile() || entry.isSymbolicLink()) {
414
+ files.push(entryPath);
415
+ }
416
+ }
417
+ }
418
+
419
+ return files;
420
+ }
421
+
422
+ /**
423
+ * Discovers prompt commands that explicitly opt into model invocation.
424
+ *
425
+ * @param cwd - Current session working directory
426
+ * @returns Model-callable prompt commands keyed by slash command name
427
+ */
428
+ function discoverModelCallablePromptCommands(
429
+ cwd: string
430
+ ): ReadonlyMap<string, ModelCallablePromptCommand> {
431
+ const commands = new Map<string, ModelCallablePromptCommand>();
432
+
433
+ for (const dir of getModelCallablePromptDirs(cwd)) {
434
+ for (const filePath of discoverPromptMarkdownFiles(dir)) {
435
+ const command = getPromptCommandName(dir, filePath);
436
+ if (!command || commands.has(command)) continue;
437
+
438
+ let content: string;
439
+ try {
440
+ content = fs.readFileSync(filePath, "utf-8");
441
+ } catch {
442
+ continue;
443
+ }
444
+
445
+ const frontmatter = parsePromptFrontmatter(content);
446
+ if (!isModelCallablePrompt(frontmatter)) continue;
447
+
448
+ commands.set(command, {
449
+ command,
450
+ description: frontmatter.description ?? `Run ${command}`,
451
+ filePath,
452
+ });
453
+ }
454
+ }
455
+
456
+ return commands;
457
+ }
458
+
236
459
  /**
237
460
  * Returns true when context usage exists and tokens are known.
238
461
  *
@@ -387,7 +610,8 @@ function startDeferredCompact(
387
610
  * @param pi - Extension API for registering tools and event handlers
388
611
  */
389
612
  export default function slashCommandBridge(pi: ExtensionAPI): void {
390
- // Build the list of available command names for the tool description
613
+ // Build the static command list for the tool description. Prompt commands that
614
+ // opt into model invocation are discovered per cwd and injected at turn time.
391
615
  const commandList = Array.from(ALLOWED_COMMANDS.keys())
392
616
  .map((name) => `- ${name}: ${COMMAND_DESCRIPTIONS.get(name) ?? "No description"}`)
393
617
  .join("\n");
@@ -412,9 +636,15 @@ WHEN NOT TO USE:
412
636
  parameters: Type.Object({
413
637
  command: Type.String({
414
638
  description:
415
- "Slash command name (without the / prefix). " +
416
- `One of: ${Array.from(ALLOWED_COMMANDS.keys()).join(", ")}`,
639
+ "Slash command name without the / prefix. Built-ins: " +
640
+ Array.from(ALLOWED_COMMANDS.keys()).join(", ") +
641
+ ". Model-callable prompt commands are listed in session context.",
417
642
  }),
643
+ arguments: Type.Optional(
644
+ Type.String({
645
+ description: "Optional slash command arguments used for model-callable prompt commands.",
646
+ })
647
+ ),
418
648
  }),
419
649
 
420
650
  /**
@@ -445,10 +675,58 @@ WHEN NOT TO USE:
445
675
  */
446
676
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
447
677
  const { command } = params;
678
+ const args = params.arguments ?? "";
679
+ const modelCallableCommands = discoverModelCallablePromptCommands(ctx.cwd);
680
+ const modelCallableCommand = modelCallableCommands.get(command);
681
+
682
+ if (!ALLOWED_COMMANDS.has(command) && modelCallableCommand) {
683
+ let content: string;
684
+ try {
685
+ content = fs.readFileSync(modelCallableCommand.filePath, "utf-8");
686
+ } catch {
687
+ return {
688
+ content: [
689
+ {
690
+ type: "text",
691
+ text: `Failed to read model-callable command: /${command}`,
692
+ },
693
+ ],
694
+ details: { command, error: "prompt_read_failed" },
695
+ isError: true,
696
+ };
697
+ }
698
+
699
+ const promptContent = substitutePromptArguments(stripPromptFrontmatter(content), args);
700
+ const inputText = args ? `/${command} ${args}` : `/${command}`;
701
+
702
+ pi.sendMessage({
703
+ content: `🪆 ${inputText}`,
704
+ customType: "nested-prompt-summary",
705
+ details: { commandName: command, mode: "compact", source: "slash-command-bridge" },
706
+ display: true,
707
+ });
708
+ pi.sendMessage(
709
+ {
710
+ content: promptContent,
711
+ customType: "nested-prompt-expanded",
712
+ details: { commandName: command, mode: "compact", source: "slash-command-bridge" },
713
+ display: false,
714
+ },
715
+ { triggerTurn: true }
716
+ );
717
+
718
+ return {
719
+ content: [{ type: "text", text: `Queued model-callable command: ${inputText}` }],
720
+ details: { command, arguments: args, promptCommand: true },
721
+ };
722
+ }
448
723
 
449
724
  // Reject unknown commands
450
725
  if (!ALLOWED_COMMANDS.has(command)) {
451
- const available = Array.from(ALLOWED_COMMANDS.keys()).join(", ");
726
+ const available = [
727
+ ...Array.from(ALLOWED_COMMANDS.keys()),
728
+ ...Array.from(modelCallableCommands.keys()),
729
+ ].join(", ");
452
730
  return {
453
731
  content: [
454
732
  {
@@ -564,8 +842,11 @@ WHEN NOT TO USE:
564
842
 
565
843
  // ── Context injection ────────────────────────────────────────
566
844
 
567
- pi.on("before_agent_start", async () => {
568
- const bridgedCommands = Array.from(ALLOWED_COMMANDS.keys())
845
+ pi.on("before_agent_start", async (_event, ctx) => {
846
+ const bridgedCommands = [
847
+ ...Array.from(ALLOWED_COMMANDS.keys()),
848
+ ...Array.from(discoverModelCallablePromptCommands(ctx.cwd).keys()),
849
+ ]
569
850
  .map((name) => `/${name}`)
570
851
  .join(", ");
571
852
 
@@ -13,8 +13,10 @@ import { tmpdir } from "node:os";
13
13
  import { join } from "node:path";
14
14
  import {
15
15
  cleanupStaleWorktrees,
16
+ createProjectWorktree,
16
17
  createWorktree,
17
18
  removeWorktree,
19
+ resolveProjectWorktreeDefaultPath,
18
20
  TALLOW_WORKTREE_MARKER_FILE,
19
21
  validateGitRepo,
20
22
  } from "../lifecycle.js";
@@ -71,6 +73,42 @@ describe("worktree lifecycle", () => {
71
73
  removeWorktree(created.worktreePath);
72
74
  });
73
75
 
76
+ it("creates a persistent project worktree on a new branch", () => {
77
+ const worktreePath = join(tmpdir(), `tallow-project-worktree-${Date.now()}`);
78
+ const created = createProjectWorktree(repoRoot, {
79
+ branch: "feature/test-worktree",
80
+ path: worktreePath,
81
+ });
82
+
83
+ try {
84
+ expect(existsSync(created.worktreePath)).toBe(true);
85
+ expect(created.branch).toBe("feature/test-worktree");
86
+ expect(created.branchExisted).toBe(false);
87
+ expect(git(created.worktreePath, "branch", "--show-current")).toBe("feature/test-worktree");
88
+ expect(resolveProjectWorktreeDefaultPath(repoRoot, created.branch)).toContain(
89
+ "tallow-worktree-test-"
90
+ );
91
+ } finally {
92
+ removeWorktree(created.worktreePath);
93
+ }
94
+ });
95
+
96
+ it("checks out an existing branch in a persistent project worktree", () => {
97
+ git(repoRoot, "branch", "feature/existing");
98
+ const worktreePath = join(tmpdir(), `tallow-project-existing-${Date.now()}`);
99
+ const created = createProjectWorktree(repoRoot, {
100
+ branch: "feature/existing",
101
+ path: worktreePath,
102
+ });
103
+
104
+ try {
105
+ expect(created.branchExisted).toBe(true);
106
+ expect(git(created.worktreePath, "branch", "--show-current")).toBe("feature/existing");
107
+ } finally {
108
+ removeWorktree(created.worktreePath);
109
+ }
110
+ });
111
+
74
112
  it("fails validation outside git repositories", () => {
75
113
  const outside = mkdtempSync(join(tmpdir(), "tallow-worktree-nogit-"));
76
114
  try {
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "worktree",
3
3
  "version": "0.1.0",
4
- "description": "Managed git worktree isolation lifecycle for sessions and subagents.",
5
- "whenToUse": "Use when tallow should run in temporary detached git worktrees with reliable cleanup and lifecycle hooks.",
4
+ "description": "Managed git worktree lifecycle for session isolation, subagents, and branch worktree transitions.",
5
+ "whenToUse": "Use when tallow should run in git worktrees, create persistent branch worktrees, or emit worktree lifecycle hooks.",
6
6
  "capabilities": {
7
7
  "events": ["before_agent_start", "session_shutdown", "session_start"]
8
8
  },
@@ -1,9 +1,20 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type {
2
+ ExtensionAPI,
3
+ ExtensionCommandContext,
4
+ ExtensionContext,
5
+ } from "@mariozechner/pi-coding-agent";
6
+ import { Type } from "@sinclair/typebox";
7
+ import { getWorkspaceTransitionHost } from "../../runtime/workspace-transition.js";
2
8
  import {
3
9
  emitWorktreeLifecycleEvent,
4
10
  type WorktreeLifecycleEventPayload,
5
11
  } from "../_shared/interop-events.js";
6
- import { cleanupStaleWorktrees, removeWorktree, validateGitRepo } from "./lifecycle.js";
12
+ import {
13
+ cleanupStaleWorktrees,
14
+ createProjectWorktree,
15
+ removeWorktree,
16
+ validateGitRepo,
17
+ } from "./lifecycle.js";
7
18
 
8
19
  /** Env var containing active session worktree path (set by CLI when -w/--worktree is used). */
9
20
  const TALLOW_WORKTREE_PATH_ENV = "TALLOW_WORKTREE_PATH";
@@ -21,6 +32,111 @@ interface SessionWorktreeState {
21
32
  readonly worktreePath: string;
22
33
  }
23
34
 
35
+ /** Parameters accepted by the worktree_create tool. */
36
+ interface WorktreeCreateParams {
37
+ readonly baseRef?: string;
38
+ readonly branch: string;
39
+ readonly path?: string;
40
+ }
41
+
42
+ /** Details returned by worktree_create. */
43
+ interface WorktreeCreateDetails {
44
+ readonly baseRef?: string;
45
+ readonly branch?: string;
46
+ readonly branchExisted?: boolean;
47
+ readonly reason?: string;
48
+ readonly repoRoot?: string;
49
+ readonly status: "cancelled" | "completed" | "create_failed" | "unavailable";
50
+ readonly worktreePath?: string;
51
+ }
52
+
53
+ /**
54
+ * Parse `/worktree-create` arguments.
55
+ *
56
+ * @param args - Raw slash-command argument string
57
+ * @returns Worktree creation parameters
58
+ * @throws {Error} When the branch argument is missing
59
+ */
60
+ function parseWorktreeCreateArgs(args: string): WorktreeCreateParams {
61
+ const parts = args.trim().split(/\s+/).filter(Boolean);
62
+ const branch = parts[0];
63
+ if (!branch) {
64
+ throw new Error("Usage: /worktree-create <branch> [path]");
65
+ }
66
+ return {
67
+ branch,
68
+ path: parts[1],
69
+ };
70
+ }
71
+
72
+ /**
73
+ * Create a project worktree and transition the interactive session into it.
74
+ *
75
+ * @param params - Branch, base ref, and optional path
76
+ * @param ctx - Extension context carrying cwd and UI surface
77
+ * @param initiator - Whether the request came from a command or tool
78
+ * @returns Worktree creation and transition result details
79
+ */
80
+ async function createAndEnterProjectWorktree(
81
+ params: WorktreeCreateParams,
82
+ ctx: Pick<ExtensionContext, "cwd" | "ui">,
83
+ initiator: "command" | "tool",
84
+ events: ExtensionAPI["events"]
85
+ ): Promise<WorktreeCreateDetails> {
86
+ const host = getWorkspaceTransitionHost();
87
+ if (!host) {
88
+ return {
89
+ reason: "Workspace transitions are only available in the interactive TUI session right now.",
90
+ status: "unavailable",
91
+ };
92
+ }
93
+
94
+ let created: ReturnType<typeof createProjectWorktree>;
95
+ try {
96
+ created = createProjectWorktree(ctx.cwd, params);
97
+ } catch (error) {
98
+ return {
99
+ reason: error instanceof Error ? error.message : String(error),
100
+ status: "create_failed",
101
+ };
102
+ }
103
+
104
+ emitWorktreeLifecycleEvent(events, "worktree_create", {
105
+ agentId: undefined,
106
+ repoRoot: created.repoRoot,
107
+ scope: "project",
108
+ timestamp: Date.now(),
109
+ worktreePath: created.worktreePath,
110
+ });
111
+
112
+ const transition = await host.requestTransition({
113
+ initiator,
114
+ sourceCwd: ctx.cwd,
115
+ targetCwd: created.worktreePath,
116
+ ui: ctx.ui,
117
+ });
118
+ if (transition.status !== "completed") {
119
+ return {
120
+ baseRef: created.baseRef,
121
+ branch: created.branch,
122
+ branchExisted: created.branchExisted,
123
+ reason: transition.status === "unavailable" ? transition.reason : undefined,
124
+ repoRoot: created.repoRoot,
125
+ status: transition.status,
126
+ worktreePath: created.worktreePath,
127
+ };
128
+ }
129
+
130
+ return {
131
+ baseRef: created.baseRef,
132
+ branch: created.branch,
133
+ branchExisted: created.branchExisted,
134
+ repoRoot: created.repoRoot,
135
+ status: "completed",
136
+ worktreePath: created.worktreePath,
137
+ };
138
+ }
139
+
24
140
  /**
25
141
  * Worktree lifecycle extension.
26
142
  *
@@ -36,6 +152,69 @@ export default function worktreeExtension(pi: ExtensionAPI): void {
36
152
  let sessionState: SessionWorktreeState | undefined;
37
153
  let cleanedUp = false;
38
154
 
155
+ pi.registerCommand("worktree-create", {
156
+ description: "Create a persistent git worktree for a branch and enter it",
157
+ async handler(args: string, ctx: ExtensionCommandContext): Promise<void> {
158
+ let params: WorktreeCreateParams;
159
+ try {
160
+ params = parseWorktreeCreateArgs(args);
161
+ } catch (error) {
162
+ ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
163
+ return;
164
+ }
165
+
166
+ const result = await createAndEnterProjectWorktree(params, ctx, "command", pi.events);
167
+ if (result.status === "completed") {
168
+ ctx.ui.notify(`Created worktree for ${result.branch}: ${result.worktreePath}`, "info");
169
+ return;
170
+ }
171
+ ctx.ui.notify(result.reason ?? `Worktree creation ${result.status}.`, "error");
172
+ },
173
+ });
174
+
175
+ pi.registerTool({
176
+ name: "worktree_create",
177
+ label: "worktree_create",
178
+ description:
179
+ "Create a persistent git worktree for a branch, then transition the interactive Tallow session into it. Use when the user asks to create a new worktree/branch and continue working there.",
180
+ promptGuidelines: [
181
+ "This tool triggers an interactive workspace transition. Call it as the only tool in the response; sibling tool calls may be discarded when the session moves.",
182
+ ],
183
+ parameters: Type.Object({
184
+ baseRef: Type.Optional(
185
+ Type.String({
186
+ description:
187
+ "Git ref to create the branch from when it does not already exist. Defaults to HEAD.",
188
+ })
189
+ ),
190
+ branch: Type.String({
191
+ description: "Local branch name to create or check out in the new worktree.",
192
+ }),
193
+ path: Type.Optional(
194
+ Type.String({
195
+ description:
196
+ "Optional absolute or cwd-relative target worktree path. Defaults to ~/dev/<project>_worktrees/<branch-slug>.",
197
+ })
198
+ ),
199
+ }),
200
+ async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
201
+ const result = await createAndEnterProjectWorktree(params, ctx, "tool", pi.events);
202
+ return {
203
+ content: [
204
+ {
205
+ type: "text" as const,
206
+ text:
207
+ result.status === "completed"
208
+ ? `Created worktree for ${result.branch}: ${result.worktreePath}`
209
+ : (result.reason ?? `Worktree creation ${result.status}.`),
210
+ },
211
+ ],
212
+ details: result,
213
+ isError: result.status !== "completed",
214
+ };
215
+ },
216
+ });
217
+
39
218
  /**
40
219
  * Best-effort session-worktree cleanup routine.
41
220
  *
@@ -1,8 +1,16 @@
1
1
  import { type ExecFileSyncOptions, execFileSync } from "node:child_process";
2
2
  import { randomUUID } from "node:crypto";
3
- import { existsSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
4
- import { tmpdir } from "node:os";
5
- import { join, resolve } from "node:path";
3
+ import {
4
+ existsSync,
5
+ mkdirSync,
6
+ readdirSync,
7
+ readFileSync,
8
+ rmSync,
9
+ statSync,
10
+ writeFileSync,
11
+ } from "node:fs";
12
+ import { homedir, tmpdir } from "node:os";
13
+ import { basename, dirname, join, resolve } from "node:path";
6
14
 
7
15
  /** Managed worktree directory-name prefix in the system temp dir. */
8
16
  export const TALLOW_WORKTREE_PREFIX = "tallow-worktree-";
@@ -24,6 +32,13 @@ export interface CreateWorktreeOptions {
24
32
  readonly timestampMs?: number;
25
33
  }
26
34
 
35
+ /** Options for creating a project branch worktree. */
36
+ export interface CreateProjectWorktreeOptions {
37
+ readonly baseRef?: string;
38
+ readonly branch: string;
39
+ readonly path?: string;
40
+ }
41
+
27
42
  /** Result from createWorktree. */
28
43
  export interface CreatedWorktree {
29
44
  readonly id: string;
@@ -33,6 +48,15 @@ export interface CreatedWorktree {
33
48
  readonly worktreePath: string;
34
49
  }
35
50
 
51
+ /** Result from creating a persistent project branch worktree. */
52
+ export interface CreatedProjectWorktree {
53
+ readonly baseRef: string;
54
+ readonly branch: string;
55
+ readonly branchExisted: boolean;
56
+ readonly repoRoot: string;
57
+ readonly worktreePath: string;
58
+ }
59
+
36
60
  /** Result from removeWorktree. */
37
61
  export interface RemoveWorktreeResult {
38
62
  readonly method: "filesystem" | "git" | "none";
@@ -112,6 +136,62 @@ export function createWorktree(
112
136
  };
113
137
  }
114
138
 
139
+ /**
140
+ * Create a persistent project worktree for a branch.
141
+ *
142
+ * Existing branches are checked out directly. Missing branches are created from
143
+ * the supplied base ref. The worktree is intentionally not marked for automatic
144
+ * cleanup because it represents user-owned project state.
145
+ *
146
+ * @param cwd - Current working directory inside the repository
147
+ * @param options - Branch, base ref, and optional target path
148
+ * @returns Created project worktree metadata
149
+ * @throws {Error} When git rejects the branch, path, or worktree creation
150
+ */
151
+ export function createProjectWorktree(
152
+ cwd: string,
153
+ options: CreateProjectWorktreeOptions
154
+ ): CreatedProjectWorktree {
155
+ const repoRoot = validateGitRepo(cwd).repoRoot;
156
+ const branch = validateBranchName(options.branch, repoRoot);
157
+ const baseRef = options.baseRef?.trim() || "HEAD";
158
+ const branchExisted = branchExists(repoRoot, branch);
159
+ const worktreePath = options.path
160
+ ? resolve(cwd, options.path)
161
+ : resolveProjectWorktreeDefaultPath(repoRoot, branch);
162
+
163
+ if (existsSync(worktreePath)) {
164
+ throw new Error(`Worktree path already exists: ${worktreePath}`);
165
+ }
166
+
167
+ mkdirSync(dirname(worktreePath), { recursive: true });
168
+ const args = branchExisted
169
+ ? ["-C", repoRoot, "worktree", "add", worktreePath, branch]
170
+ : ["-C", repoRoot, "worktree", "add", "-b", branch, worktreePath, baseRef];
171
+ runGit(args, repoRoot);
172
+
173
+ return {
174
+ baseRef,
175
+ branch,
176
+ branchExisted,
177
+ repoRoot,
178
+ worktreePath,
179
+ };
180
+ }
181
+
182
+ /**
183
+ * Resolve the default persistent worktree path for a repository branch.
184
+ *
185
+ * @param repoRoot - Git repository root
186
+ * @param branch - Branch name
187
+ * @returns Default worktree path under ~/dev/<project>_worktrees/<branch-slug>
188
+ */
189
+ export function resolveProjectWorktreeDefaultPath(repoRoot: string, branch: string): string {
190
+ const primaryRoot = resolvePrimaryWorktreeRoot(repoRoot);
191
+ const projectName = basename(primaryRoot);
192
+ return join(homedir(), "dev", `${projectName}_worktrees`, sanitizeSegment(branch));
193
+ }
194
+
115
195
  /**
116
196
  * Remove a managed worktree path.
117
197
  *
@@ -350,6 +430,59 @@ function sanitizeSegment(value: string): string {
350
430
  return normalized.slice(0, 48);
351
431
  }
352
432
 
433
+ /**
434
+ * Validate a git branch name using git's own ref parser.
435
+ *
436
+ * @param branch - Candidate branch name
437
+ * @param repoRoot - Repository root used for the validation command
438
+ * @returns Trimmed branch name
439
+ * @throws {Error} When the branch name is invalid
440
+ */
441
+ function validateBranchName(branch: string, repoRoot: string): string {
442
+ const trimmed = branch.trim();
443
+ if (!trimmed) {
444
+ throw new Error("Branch name is required.");
445
+ }
446
+ runGit(["-C", repoRoot, "check-ref-format", "--branch", trimmed], repoRoot);
447
+ return trimmed;
448
+ }
449
+
450
+ /**
451
+ * Return whether a local branch already exists.
452
+ *
453
+ * @param repoRoot - Git repository root
454
+ * @param branch - Local branch name
455
+ * @returns True when refs/heads/<branch> exists
456
+ */
457
+ function branchExists(repoRoot: string, branch: string): boolean {
458
+ try {
459
+ runGit(["-C", repoRoot, "show-ref", "--verify", `refs/heads/${branch}`], repoRoot);
460
+ return true;
461
+ } catch {
462
+ return false;
463
+ }
464
+ }
465
+
466
+ /**
467
+ * Resolve the primary worktree root for naming persistent worktree groups.
468
+ *
469
+ * @param repoRoot - Current repository root, possibly itself a linked worktree
470
+ * @returns First path from `git worktree list`, falling back to repoRoot
471
+ */
472
+ function resolvePrimaryWorktreeRoot(repoRoot: string): string {
473
+ try {
474
+ const output = runGit(["-C", repoRoot, "worktree", "list", "--porcelain"], repoRoot);
475
+ const firstWorktree = output
476
+ .split("\n")
477
+ .find((line) => line.startsWith("worktree "))
478
+ ?.slice("worktree ".length)
479
+ .trim();
480
+ return firstWorktree ? resolve(firstWorktree) : repoRoot;
481
+ } catch {
482
+ return repoRoot;
483
+ }
484
+ }
485
+
353
486
  /**
354
487
  * Execute a git command and return trimmed stdout.
355
488
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dungle-scrubs/tallow",
3
- "version": "0.9.8",
3
+ "version": "0.9.9",
4
4
  "description": "An opinionated coding agent. Built on pi.",
5
5
  "piConfig": {
6
6
  "name": "tallow",