@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"
|
|
17
|
-
<a href="https://www.npmjs.com/package/tallow"
|
|
18
|
-
<a href="https://
|
|
19
|
-
<a href="https://
|
|
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.
|
|
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.
|
|
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
|
-
|
|
265
|
-
//
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
80
|
-
"@mariozechner/pi-ai": "0.54.
|
|
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",
|