@dungle-scrubs/tallow 0.8.8 → 0.8.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/README.md CHANGED
@@ -1,7 +1,3 @@
1
- <p align="center">
2
- <img src="assets/logo.jpg" width="200" alt="Tallow logo" />
3
- </p>
4
-
5
1
  <h1 align="center">Tallow</h1>
6
2
 
7
3
  <p align="center">
@@ -13,21 +9,14 @@
13
9
  </p>
14
10
 
15
11
  <p align="center">
16
- <a href="https://github.com/dungle-scrubs/tallow/actions/workflows/ci.yml"><img src="https://github.com/dungle-scrubs/tallow/actions/workflows/ci.yml/badge.svg" alt="CI" /></a>
17
- <a href="https://www.npmjs.com/package/tallow"><img src="https://img.shields.io/npm/v/tallow" alt="npm version" /></a>
18
- <a href="https://img.shields.io/badge/Bun-%E2%89%A51.3.9-f9f1e1?logo=bun"><img src="https://img.shields.io/badge/Bun-%E2%89%A51.3.9-f9f1e1?logo=bun" alt="Bun ≥1.3.9" /></a>
19
- <a href="https://tallow.dungle-scrubs.com"><img src="https://img.shields.io/badge/docs-tallow-blue" alt="Docs" /></a>
20
- <a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT" /></a>
12
+ <a href="https://github.com/dungle-scrubs/tallow/actions/workflows/ci.yml">CI</a> ·
13
+ <a href="https://www.npmjs.com/package/tallow">npm</a> ·
14
+ <a href="https://tallow.dungle-scrubs.com">Docs</a> ·
15
+ <a href="https://opensource.org/licenses/MIT">MIT</a>
21
16
  </p>
22
17
 
23
18
  ---
24
19
 
25
- <p align="center">
26
- <img src="assets/screenshot-annotated.png" alt="Tallow session showing status bar, auto-named session, and model selection" />
27
- <br />
28
- <sub>Shown with a customized <a href="https://wezfurlong.org/wezterm/">WezTerm</a> configuration.</sub>
29
- </p>
30
-
31
20
  Tallow is a terminal coding agent that starts minimal and scales up. Install only the
32
21
  extensions, themes, and agents your project needs, or enable everything. It drops into
33
22
  existing Claude Code projects via `.claude/` bridging, so nothing breaks when you switch.
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.8.8";
3
+ export declare const TALLOW_VERSION = "0.8.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.8.8"; // x-release-please-version
9
+ export const TALLOW_VERSION = "0.8.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 */
@@ -217,6 +217,70 @@ describe("compact", () => {
217
217
  expect(statusUpdates.at(-1)).toEqual({ key: "compact", text: undefined });
218
218
  });
219
219
 
220
+ test("onComplete sends continuation message when agent is idle", async () => {
221
+ let compactOptions: Parameters<ExtensionContext["compact"]>[0];
222
+ const toolCtx = buildContext({ compact: () => {} });
223
+ const agentEndCtx = buildContext({
224
+ compact: (options) => {
225
+ compactOptions = options;
226
+ },
227
+ isIdle: () => true,
228
+ });
229
+
230
+ await executeTool({ command: "compact" }, toolCtx);
231
+ await harness.fireEvent("agent_end", { type: "agent_end", messages: [] }, agentEndCtx);
232
+
233
+ // Trigger onComplete and wait for the setTimeout(50) to fire
234
+ compactOptions?.onComplete?.();
235
+ await new Promise((resolve) => setTimeout(resolve, 100));
236
+
237
+ const continuation = harness.sentMessages.find((m) => m.customType === "compact-continue");
238
+ expect(continuation).toBeDefined();
239
+ expect(continuation?.display).toBe(false);
240
+ expect(continuation?.options?.triggerTurn).toBe(true);
241
+ expect(continuation?.content).toContain("compaction is complete");
242
+ });
243
+
244
+ test("onComplete skips continuation when agent is not idle", async () => {
245
+ let compactOptions: Parameters<ExtensionContext["compact"]>[0];
246
+ const toolCtx = buildContext({ compact: () => {} });
247
+ const agentEndCtx = buildContext({
248
+ compact: (options) => {
249
+ compactOptions = options;
250
+ },
251
+ isIdle: () => false,
252
+ });
253
+
254
+ await executeTool({ command: "compact" }, toolCtx);
255
+ await harness.fireEvent("agent_end", { type: "agent_end", messages: [] }, agentEndCtx);
256
+
257
+ compactOptions?.onComplete?.();
258
+ await new Promise((resolve) => setTimeout(resolve, 100));
259
+
260
+ const continuation = harness.sentMessages.find((m) => m.customType === "compact-continue");
261
+ expect(continuation).toBeUndefined();
262
+ });
263
+
264
+ test("onError does not send continuation message", async () => {
265
+ let compactOptions: Parameters<ExtensionContext["compact"]>[0];
266
+ const toolCtx = buildContext({ compact: () => {} });
267
+ const agentEndCtx = buildContext({
268
+ compact: (options) => {
269
+ compactOptions = options;
270
+ },
271
+ isIdle: () => true,
272
+ });
273
+
274
+ await executeTool({ command: "compact" }, toolCtx);
275
+ await harness.fireEvent("agent_end", { type: "agent_end", messages: [] }, agentEndCtx);
276
+
277
+ compactOptions?.onError?.();
278
+ await new Promise((resolve) => setTimeout(resolve, 100));
279
+
280
+ const continuation = harness.sentMessages.find((m) => m.customType === "compact-continue");
281
+ expect(continuation).toBeUndefined();
282
+ });
283
+
220
284
  test("agent_end hook is a no-op when no compact is pending", async () => {
221
285
  let compactCalled = false;
222
286
  const ctx = buildContext({
@@ -244,6 +244,20 @@ WHEN NOT TO USE:
244
244
  * Fires compact after the agent finishes its turn. This avoids the
245
245
  * spinner-hang caused by aborting the agent mid-tool-execution.
246
246
  * The tool sets `pendingCompact`, then agent_end picks it up.
247
+ *
248
+ * After compaction completes, checks whether the agent is idle. When the
249
+ * model triggered compaction (vs. the user typing during it), the
250
+ * framework's `flushCompactionQueue` finds no queued messages and the
251
+ * agent sits at the input prompt — even though the model promised to
252
+ * continue. We fix this by sending a hidden continuation message via
253
+ * `pi.sendMessage()` with `triggerTurn: true` to re-prompt the agent.
254
+ *
255
+ * A short `setTimeout` allows `flushCompactionQueue`'s fire-and-forget
256
+ * async path to settle first, so we don't conflict with user-queued
257
+ * messages that already restarted the agent.
258
+ *
259
+ * @see Plan 98 — deferred compact to agent_end (introduced idle-after-compact)
260
+ * @see Plan 157 — auto-continue after model-triggered compaction
247
261
  */
248
262
  pi.on("agent_end", (_event, ctx) => {
249
263
  if (!pendingCompact) return;
@@ -261,14 +275,30 @@ WHEN NOT TO USE:
261
275
  onComplete: () => {
262
276
  ctx.ui?.setWorkingMessage?.();
263
277
  ctx.ui?.setStatus?.("compact", undefined);
264
- // Framework's executeCompaction rebuilds the UI and
265
- // shows the compaction summary. No extra action needed.
278
+
279
+ // After compaction, re-prompt the agent if no user messages
280
+ // triggered a turn via flushCompactionQueue. The setTimeout
281
+ // lets flushCompactionQueue's async work settle first.
282
+ setTimeout(() => {
283
+ if (ctx.isIdle()) {
284
+ pi.sendMessage(
285
+ {
286
+ customType: "compact-continue",
287
+ content:
288
+ "Session compaction is complete. Continue with the task " +
289
+ "you were working on before compaction was triggered.",
290
+ display: false,
291
+ },
292
+ { triggerTurn: true }
293
+ );
294
+ }
295
+ }, 50);
266
296
  },
267
297
  onError: () => {
268
298
  ctx.ui?.setWorkingMessage?.();
269
299
  ctx.ui?.setStatus?.("compact", undefined);
270
300
  // Framework's executeCompaction handles error/cancel
271
- // display. No extra handling needed.
301
+ // display. No continuation on failure — user decides.
272
302
  },
273
303
  });
274
304
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dungle-scrubs/tallow",
3
- "version": "0.8.8",
3
+ "version": "0.8.9",
4
4
  "description": "An opinionated coding agent. Built on pi.",
5
5
  "piConfig": {
6
6
  "name": "tallow",
@@ -67,7 +67,7 @@
67
67
  "dependencies": {
68
68
  "@clack/prompts": "^1.0.0",
69
69
  "@dungle-scrubs/synapse": "0.1.4",
70
- "@mariozechner/pi-coding-agent": "0.54.0",
70
+ "@mariozechner/pi-coding-agent": "^0.54.2",
71
71
  "@sinclair/typebox": "0.34.48",
72
72
  "ai": "^6.0.86",
73
73
  "commander": "^14.0.3",
@@ -76,8 +76,8 @@
76
76
  },
77
77
  "devDependencies": {
78
78
  "@biomejs/biome": "2.4.2",
79
- "@mariozechner/pi-agent-core": "0.54.0",
80
- "@mariozechner/pi-ai": "0.54.0",
79
+ "@mariozechner/pi-agent-core": "^0.54.2",
80
+ "@mariozechner/pi-ai": "^0.54.2",
81
81
  "@mariozechner/pi-tui": "workspace:*",
82
82
  "@types/node": "25.2.3",
83
83
  "husky": "^9.1.7",