@dungle-scrubs/tallow 0.9.4 → 0.9.7
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/cli.js +8 -5
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js +1 -1
- package/dist/interactive-mode-patch.d.ts +24 -12
- package/dist/interactive-mode-patch.d.ts.map +1 -1
- package/dist/interactive-mode-patch.js +229 -146
- package/dist/interactive-mode-patch.js.map +1 -1
- package/dist/interactive-reset.d.ts +49 -0
- package/dist/interactive-reset.d.ts.map +1 -0
- package/dist/interactive-reset.js +40 -0
- package/dist/interactive-reset.js.map +1 -0
- package/dist/pi-tui-editor-patch.d.ts +10 -0
- package/dist/pi-tui-editor-patch.d.ts.map +1 -0
- package/dist/pi-tui-editor-patch.js +159 -0
- package/dist/pi-tui-editor-patch.js.map +1 -0
- package/dist/pi-tui-patch.d.ts +2 -0
- package/dist/pi-tui-patch.d.ts.map +1 -0
- package/dist/pi-tui-patch.js +563 -0
- package/dist/pi-tui-patch.js.map +1 -0
- package/dist/pi-tui-settings-list-patch.d.ts +11 -0
- package/dist/pi-tui-settings-list-patch.d.ts.map +1 -0
- package/dist/pi-tui-settings-list-patch.js +38 -0
- package/dist/pi-tui-settings-list-patch.js.map +1 -0
- package/dist/process-cleanup.js +1 -1
- package/dist/process-cleanup.js.map +1 -1
- package/dist/reset-diagnostics.d.ts +69 -0
- package/dist/reset-diagnostics.d.ts.map +1 -0
- package/dist/reset-diagnostics.js +41 -0
- package/dist/reset-diagnostics.js.map +1 -0
- package/dist/sdk.d.ts +7 -23
- package/dist/sdk.d.ts.map +1 -1
- package/dist/sdk.js +211 -174
- package/dist/sdk.js.map +1 -1
- package/dist/workspace-transition-interactive.d.ts +1 -0
- package/dist/workspace-transition-interactive.d.ts.map +1 -1
- package/dist/workspace-transition-interactive.js +8 -18
- package/dist/workspace-transition-interactive.js.map +1 -1
- package/extensions/__integration__/audit-findings.test.ts +4 -5
- package/extensions/_icons/index.ts +2 -4
- package/extensions/_shared/__tests__/image-metadata.test.ts +33 -0
- package/extensions/_shared/__tests__/shell-policy.test.ts +19 -0
- package/extensions/_shared/__tests__/terminal-links.test.ts +18 -0
- package/extensions/_shared/image-metadata.ts +99 -0
- package/extensions/_shared/inline-preview.ts +1 -1
- package/extensions/_shared/shell-policy.ts +121 -1
- package/extensions/_shared/terminal-links.ts +22 -0
- package/extensions/ask-user-question-tool/index.ts +0 -3
- package/extensions/clear/__tests__/clear.test.ts +269 -2
- package/extensions/command-expansion/index.ts +9 -3
- package/extensions/context-files/index.ts +5 -1
- package/extensions/context-fork/__tests__/context-fork.test.ts +94 -1
- package/extensions/context-fork/extension.json +1 -1
- package/extensions/context-fork/frontmatter-index.ts +6 -1
- package/extensions/context-fork/index.ts +32 -0
- package/extensions/edit-tool-enhanced/index.ts +2 -1
- package/extensions/git-status/__tests__/git-status.test.ts +65 -2
- package/extensions/git-status/index.ts +268 -98
- package/extensions/hooks/index.ts +33 -11
- package/extensions/loop/index.ts +14 -1
- package/extensions/lsp/index.ts +64 -13
- package/extensions/lsp/package.json +2 -2
- package/extensions/minimal-skill-display/index.ts +7 -1
- package/extensions/random-spinner/index.ts +7 -642
- package/extensions/read-tool-enhanced/index.ts +13 -10
- package/extensions/render-stabilizer/__tests__/render-stabilizer.test.ts +2 -3
- package/extensions/render-stabilizer/index.ts +6 -6
- package/extensions/rewind/__tests__/session-files.test.ts +115 -0
- package/extensions/rewind/__tests__/snapshots.test.ts +23 -0
- package/extensions/rewind/index.ts +5 -0
- package/extensions/rewind/session-files.ts +138 -0
- package/extensions/rewind/snapshots.ts +104 -5
- package/extensions/skill-commands/index.ts +6 -1
- package/extensions/slash-command-bridge/__tests__/slash-command-bridge.test.ts +26 -0
- package/extensions/slash-command-bridge/index.ts +14 -2
- package/extensions/subagent-tool/model-resolver.ts +274 -7
- package/extensions/subagent-tool/schema.ts +1 -2
- package/extensions/tasks/commands/register-tasks-extension.ts +9 -9
- package/extensions/teams-tool/tools/register-extension.ts +1 -3
- package/extensions/teams-tool/tools/teammate-tools.ts +1 -2
- package/extensions/web-search-tool/index.ts +2 -1
- package/extensions/wezterm-pane-control/index.ts +1 -2
- package/extensions/write-tool-enhanced/index.ts +2 -1
- package/node_modules/@mariozechner/pi-tui/README.md +56 -34
- package/node_modules/@mariozechner/pi-tui/dist/autocomplete.d.ts +18 -13
- package/node_modules/@mariozechner/pi-tui/dist/autocomplete.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/autocomplete.js +182 -113
- package/node_modules/@mariozechner/pi-tui/dist/autocomplete.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/cancellable-loader.js +3 -3
- package/node_modules/@mariozechner/pi-tui/dist/components/cancellable-loader.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/editor.d.ts +45 -36
- package/node_modules/@mariozechner/pi-tui/dist/components/editor.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/editor.js +489 -325
- package/node_modules/@mariozechner/pi-tui/dist/components/editor.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/image.d.ts +1 -99
- package/node_modules/@mariozechner/pi-tui/dist/components/image.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/image.js +17 -192
- package/node_modules/@mariozechner/pi-tui/dist/components/image.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/input.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/input.js +57 -60
- package/node_modules/@mariozechner/pi-tui/dist/components/input.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/loader.d.ts +2 -69
- package/node_modules/@mariozechner/pi-tui/dist/components/loader.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/loader.js +5 -102
- package/node_modules/@mariozechner/pi-tui/dist/components/loader.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/markdown.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/markdown.js +111 -53
- package/node_modules/@mariozechner/pi-tui/dist/components/markdown.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/select-list.d.ts +19 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/select-list.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/select-list.js +78 -67
- package/node_modules/@mariozechner/pi-tui/dist/components/select-list.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/settings-list.d.ts +1 -25
- package/node_modules/@mariozechner/pi-tui/dist/components/settings-list.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/settings-list.js +13 -50
- package/node_modules/@mariozechner/pi-tui/dist/components/settings-list.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/index.d.ts +8 -10
- package/node_modules/@mariozechner/pi-tui/dist/index.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/index.js +6 -9
- package/node_modules/@mariozechner/pi-tui/dist/index.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/keybindings.d.ts +108 -238
- package/node_modules/@mariozechner/pi-tui/dist/keybindings.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/keybindings.js +108 -365
- package/node_modules/@mariozechner/pi-tui/dist/keybindings.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/keys.d.ts +33 -48
- package/node_modules/@mariozechner/pi-tui/dist/keys.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/keys.js +239 -155
- package/node_modules/@mariozechner/pi-tui/dist/keys.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/terminal-image.d.ts +14 -94
- package/node_modules/@mariozechner/pi-tui/dist/terminal-image.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/terminal-image.js +44 -186
- package/node_modules/@mariozechner/pi-tui/dist/terminal-image.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/terminal.d.ts +13 -58
- package/node_modules/@mariozechner/pi-tui/dist/terminal.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/terminal.js +78 -111
- package/node_modules/@mariozechner/pi-tui/dist/terminal.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/tui.d.ts +24 -110
- package/node_modules/@mariozechner/pi-tui/dist/tui.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/tui.js +188 -435
- package/node_modules/@mariozechner/pi-tui/dist/tui.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/utils.d.ts +0 -18
- package/node_modules/@mariozechner/pi-tui/dist/utils.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/utils.js +251 -119
- package/node_modules/@mariozechner/pi-tui/dist/utils.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/package.json +6 -6
- package/node_modules/@mariozechner/pi-tui/src/__tests__/__snapshots__/render.test.ts.snap +3 -40
- package/node_modules/@mariozechner/pi-tui/src/__tests__/image-component.test.ts +71 -81
- package/node_modules/@mariozechner/pi-tui/src/__tests__/render.test.ts +0 -33
- package/node_modules/@mariozechner/pi-tui/src/__tests__/terminal-image.test.ts +93 -334
- package/node_modules/@mariozechner/pi-tui/src/__tests__/tui-render-scheduling.test.ts +1 -1
- package/node_modules/@mariozechner/pi-tui/src/__tests__/utils.test.ts +11 -196
- package/node_modules/@mariozechner/pi-tui/src/autocomplete.ts +228 -142
- package/node_modules/@mariozechner/pi-tui/src/components/cancellable-loader.ts +3 -3
- package/node_modules/@mariozechner/pi-tui/src/components/editor.ts +624 -390
- package/node_modules/@mariozechner/pi-tui/src/components/image.ts +17 -227
- package/node_modules/@mariozechner/pi-tui/src/components/input.ts +71 -63
- package/node_modules/@mariozechner/pi-tui/src/components/loader.ts +5 -137
- package/node_modules/@mariozechner/pi-tui/src/components/markdown.ts +143 -52
- package/node_modules/@mariozechner/pi-tui/src/components/select-list.ts +136 -70
- package/node_modules/@mariozechner/pi-tui/src/components/settings-list.ts +12 -51
- package/node_modules/@mariozechner/pi-tui/src/index.ts +17 -36
- package/node_modules/@mariozechner/pi-tui/src/keybindings.ts +148 -421
- package/node_modules/@mariozechner/pi-tui/src/keys.ts +253 -181
- package/node_modules/@mariozechner/pi-tui/src/terminal-image.ts +51 -252
- package/node_modules/@mariozechner/pi-tui/src/terminal.ts +78 -133
- package/node_modules/@mariozechner/pi-tui/src/tui.ts +202 -478
- package/node_modules/@mariozechner/pi-tui/src/utils.ts +289 -125
- package/node_modules/@mariozechner/pi-tui/tsconfig.build.json +1 -0
- package/package.json +13 -13
- package/packages/tallow-tui/node_modules/@types/mime-types/README.md +8 -2
- package/packages/tallow-tui/node_modules/@types/mime-types/index.d.ts +6 -0
- package/packages/tallow-tui/node_modules/@types/mime-types/package.json +9 -3
- package/packages/tallow-tui/node_modules/get-east-asian-width/lookup-data.js +18 -0
- package/packages/tallow-tui/node_modules/get-east-asian-width/lookup.js +116 -384
- package/packages/tallow-tui/node_modules/get-east-asian-width/package.json +5 -4
- package/packages/tallow-tui/node_modules/get-east-asian-width/utilities.js +24 -0
- package/packages/tallow-tui/node_modules/marked/README.md +5 -4
- package/packages/tallow-tui/node_modules/marked/bin/main.js +10 -8
- package/packages/tallow-tui/node_modules/marked/bin/marked.js +2 -1
- package/packages/tallow-tui/node_modules/marked/lib/marked.d.ts +156 -125
- package/packages/tallow-tui/node_modules/marked/lib/marked.esm.js +67 -2179
- package/packages/tallow-tui/node_modules/marked/lib/marked.esm.js.map +3 -3
- package/packages/tallow-tui/node_modules/marked/lib/marked.umd.js +67 -2201
- package/packages/tallow-tui/node_modules/marked/lib/marked.umd.js.map +3 -3
- package/packages/tallow-tui/node_modules/marked/man/marked.1 +4 -2
- package/packages/tallow-tui/node_modules/marked/man/marked.1.md +2 -1
- package/packages/tallow-tui/node_modules/marked/package.json +26 -34
- package/skills/tallow-expert/SKILL.md +3 -5
- package/node_modules/@mariozechner/pi-tui/dist/border-styles.d.ts +0 -32
- package/node_modules/@mariozechner/pi-tui/dist/border-styles.d.ts.map +0 -1
- package/node_modules/@mariozechner/pi-tui/dist/border-styles.js +0 -46
- package/node_modules/@mariozechner/pi-tui/dist/border-styles.js.map +0 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/bordered-box.d.ts +0 -52
- package/node_modules/@mariozechner/pi-tui/dist/components/bordered-box.d.ts.map +0 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/bordered-box.js +0 -89
- package/node_modules/@mariozechner/pi-tui/dist/components/bordered-box.js.map +0 -1
- package/node_modules/@mariozechner/pi-tui/dist/test-utils/capability-env.d.ts +0 -14
- package/node_modules/@mariozechner/pi-tui/dist/test-utils/capability-env.d.ts.map +0 -1
- package/node_modules/@mariozechner/pi-tui/dist/test-utils/capability-env.js +0 -55
- package/node_modules/@mariozechner/pi-tui/dist/test-utils/capability-env.js.map +0 -1
- package/node_modules/@mariozechner/pi-tui/src/__tests__/editor-change-listener.test.ts +0 -121
- package/node_modules/@mariozechner/pi-tui/src/__tests__/editor-ghost-text.test.ts +0 -112
- package/node_modules/@mariozechner/pi-tui/src/__tests__/mouse-events.test.ts +0 -134
- package/node_modules/@mariozechner/pi-tui/src/__tests__/settings-list.test.ts +0 -81
- package/node_modules/@mariozechner/pi-tui/src/__tests__/tui-diff-regression.test.ts +0 -555
- package/node_modules/@mariozechner/pi-tui/src/border-styles.ts +0 -60
- package/node_modules/@mariozechner/pi-tui/src/components/bordered-box.ts +0 -113
- package/node_modules/@mariozechner/pi-tui/src/test-utils/capability-env.ts +0 -56
- package/packages/tallow-tui/node_modules/marked/lib/marked.cjs +0 -2211
- package/packages/tallow-tui/node_modules/marked/lib/marked.cjs.map +0 -7
- package/packages/tallow-tui/node_modules/marked/lib/marked.d.cts +0 -728
- package/packages/tallow-tui/node_modules/marked/marked.min.js +0 -69
|
@@ -2,7 +2,7 @@ import { describe, expect, it } from "bun:test";
|
|
|
2
2
|
import renderStabilizerExtension from "../index.js";
|
|
3
3
|
|
|
4
4
|
describe("render-stabilizer extension", () => {
|
|
5
|
-
it("registers
|
|
5
|
+
it("registers only the legacy session_before_switch hook", () => {
|
|
6
6
|
const handlers = new Map<string, unknown[]>();
|
|
7
7
|
|
|
8
8
|
const mockPi = {
|
|
@@ -14,8 +14,7 @@ describe("render-stabilizer extension", () => {
|
|
|
14
14
|
|
|
15
15
|
renderStabilizerExtension(mockPi as never);
|
|
16
16
|
|
|
17
|
-
expect(handlers.has("session_start")).toBe(
|
|
18
|
-
expect(handlers.get("session_start")?.length).toBe(1);
|
|
17
|
+
expect(handlers.has("session_start")).toBe(false);
|
|
19
18
|
expect(handlers.has("session_before_switch")).toBe(true);
|
|
20
19
|
expect(handlers.get("session_before_switch")?.length).toBe(1);
|
|
21
20
|
});
|
|
@@ -43,15 +43,15 @@ function captureTuiRef(ui: {
|
|
|
43
43
|
/**
|
|
44
44
|
* Register render stabilization hooks.
|
|
45
45
|
*
|
|
46
|
+
* The shared reset helper now owns reset-grace handling for the main
|
|
47
|
+
* interactive reset paths. This extension remains as a compatibility shim
|
|
48
|
+
* for any remaining session-switch surfaces that still depend on an
|
|
49
|
+
* extension-side hook.
|
|
50
|
+
*
|
|
46
51
|
* @param pi - Extension API
|
|
47
52
|
*/
|
|
48
53
|
export default function renderStabilizerExtension(pi: ExtensionAPI): void {
|
|
49
|
-
//
|
|
50
|
-
pi.on("session_start", async (_event, ctx) => {
|
|
51
|
-
captureTuiRef(ctx.ui);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
// Reset the render grace period before a session switch so the
|
|
54
|
+
// Reset the render grace period before a legacy session switch so the
|
|
55
55
|
// chatContainer.clear() → renderInitialMessages() transition uses
|
|
56
56
|
// gentle line-by-line redraws instead of screen-clearing redraws.
|
|
57
57
|
pi.on("session_before_switch", async (_event, ctx) => {
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it } from "bun:test";
|
|
2
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { listLiveSessionIdsForCwd } from "../session-files.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Encode a cwd into the session directory name used by tallow.
|
|
9
|
+
*
|
|
10
|
+
* @param cwd - Absolute working directory path
|
|
11
|
+
* @returns Encoded directory name
|
|
12
|
+
*/
|
|
13
|
+
function encodeSessionDirName(cwd: string): string {
|
|
14
|
+
const withoutLeadingSlash = cwd.startsWith("/") || cwd.startsWith("\\") ? cwd.slice(1) : cwd;
|
|
15
|
+
const safeName = withoutLeadingSlash
|
|
16
|
+
.replaceAll("/", "-")
|
|
17
|
+
.replaceAll("\\", "-")
|
|
18
|
+
.replaceAll(":", "-");
|
|
19
|
+
return `--${safeName}--`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Create a minimal JSONL session file for a test home/cwd pair.
|
|
24
|
+
*
|
|
25
|
+
* @param homeDir - Tallow home directory
|
|
26
|
+
* @param cwd - Session working directory
|
|
27
|
+
* @param sessionId - Session id to persist in the header
|
|
28
|
+
* @param fileName - Session filename to create
|
|
29
|
+
* @returns Absolute path to the created session file
|
|
30
|
+
*/
|
|
31
|
+
function createSessionFile(
|
|
32
|
+
homeDir: string,
|
|
33
|
+
cwd: string,
|
|
34
|
+
sessionId: string,
|
|
35
|
+
fileName: string
|
|
36
|
+
): string {
|
|
37
|
+
const sessionDir = join(homeDir, "sessions", encodeSessionDirName(cwd));
|
|
38
|
+
mkdirSync(sessionDir, { recursive: true });
|
|
39
|
+
const filePath = join(sessionDir, fileName);
|
|
40
|
+
writeFileSync(
|
|
41
|
+
filePath,
|
|
42
|
+
`${JSON.stringify({ cwd, id: sessionId, timestamp: new Date().toISOString(), type: "session", version: 3 })}\n`
|
|
43
|
+
);
|
|
44
|
+
return filePath;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
describe("listLiveSessionIdsForCwd", () => {
|
|
48
|
+
const originalHome = process.env.HOME;
|
|
49
|
+
const originalTallowHome = process.env.TALLOW_CODING_AGENT_DIR;
|
|
50
|
+
const originalPiHome = process.env.PI_CODING_AGENT_DIR;
|
|
51
|
+
let tmpRoot: string;
|
|
52
|
+
|
|
53
|
+
beforeEach(() => {
|
|
54
|
+
tmpRoot = mkdtempSync(join(tmpdir(), "rewind-session-files-"));
|
|
55
|
+
process.env.HOME = tmpRoot;
|
|
56
|
+
delete process.env.PI_CODING_AGENT_DIR;
|
|
57
|
+
delete process.env.TALLOW_CODING_AGENT_DIR;
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
afterEach(() => {
|
|
61
|
+
if (originalHome === undefined) {
|
|
62
|
+
delete process.env.HOME;
|
|
63
|
+
} else {
|
|
64
|
+
process.env.HOME = originalHome;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (originalTallowHome === undefined) {
|
|
68
|
+
delete process.env.TALLOW_CODING_AGENT_DIR;
|
|
69
|
+
} else {
|
|
70
|
+
process.env.TALLOW_CODING_AGENT_DIR = originalTallowHome;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (originalPiHome === undefined) {
|
|
74
|
+
delete process.env.PI_CODING_AGENT_DIR;
|
|
75
|
+
} else {
|
|
76
|
+
process.env.PI_CODING_AGENT_DIR = originalPiHome;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
rmSync(tmpRoot, { force: true, recursive: true });
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("finds live session ids across the default and active tallow homes", () => {
|
|
83
|
+
const cwd = "/Users/kevin/dev/tallow";
|
|
84
|
+
const defaultHome = join(tmpRoot, ".tallow");
|
|
85
|
+
const activeHome = join(tmpRoot, ".tallow-project");
|
|
86
|
+
process.env.TALLOW_CODING_AGENT_DIR = activeHome;
|
|
87
|
+
|
|
88
|
+
createSessionFile(defaultHome, cwd, "default-session", "2026-04-20_default-session.jsonl");
|
|
89
|
+
createSessionFile(activeHome, cwd, "active-session", "2026-04-20_active-session.jsonl");
|
|
90
|
+
createSessionFile(
|
|
91
|
+
activeHome,
|
|
92
|
+
"/Users/kevin/dev/other",
|
|
93
|
+
"other-project",
|
|
94
|
+
"2026-04-20_other.jsonl"
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
const ids = listLiveSessionIdsForCwd(cwd, [defaultHome, activeHome]);
|
|
98
|
+
expect([...ids].sort((a, b) => a.localeCompare(b))).toEqual([
|
|
99
|
+
"active-session",
|
|
100
|
+
"default-session",
|
|
101
|
+
]);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("ignores unreadable or corrupt session files", () => {
|
|
105
|
+
const cwd = "/Users/kevin/dev/tallow";
|
|
106
|
+
const defaultHome = join(tmpRoot, ".tallow");
|
|
107
|
+
const sessionDir = join(defaultHome, "sessions", encodeSessionDirName(cwd));
|
|
108
|
+
mkdirSync(sessionDir, { recursive: true });
|
|
109
|
+
writeFileSync(join(sessionDir, "corrupt.jsonl"), "not-json\n");
|
|
110
|
+
createSessionFile(defaultHome, cwd, "valid-session", "2026-04-20_valid-session.jsonl");
|
|
111
|
+
|
|
112
|
+
const ids = listLiveSessionIdsForCwd(cwd, [defaultHome]);
|
|
113
|
+
expect([...ids]).toEqual(["valid-session"]);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
@@ -247,6 +247,29 @@ describe("SnapshotManager", () => {
|
|
|
247
247
|
otherMgr.cleanup();
|
|
248
248
|
});
|
|
249
249
|
|
|
250
|
+
it("should prune stale session refs while preserving live sessions", () => {
|
|
251
|
+
const otherMgr = new SnapshotManager(tmpDir, "other-session");
|
|
252
|
+
const staleMgr = new SnapshotManager(tmpDir, "stale-session");
|
|
253
|
+
|
|
254
|
+
writeFileSync(join(tmpDir, "a.txt"), "live-1");
|
|
255
|
+
mgr.createSnapshot(1);
|
|
256
|
+
writeFileSync(join(tmpDir, "a.txt"), "live-2");
|
|
257
|
+
otherMgr.createSnapshot(1);
|
|
258
|
+
writeFileSync(join(tmpDir, "a.txt"), "stale");
|
|
259
|
+
staleMgr.createSnapshot(1);
|
|
260
|
+
|
|
261
|
+
const deletedRefs = mgr.cleanupStaleSessions(new Set(["other-session", "test-session"]));
|
|
262
|
+
expect(deletedRefs).toBe(1);
|
|
263
|
+
|
|
264
|
+
const staleRefs = git(["for-each-ref", "refs/tallow/rewind/stale-session/"], tmpDir);
|
|
265
|
+
expect(staleRefs.trim()).toBe("");
|
|
266
|
+
|
|
267
|
+
const liveRefs = git(["for-each-ref", "refs/tallow/rewind/test-session/"], tmpDir);
|
|
268
|
+
expect(liveRefs.trim()).not.toBe("");
|
|
269
|
+
const otherRefs = git(["for-each-ref", "refs/tallow/rewind/other-session/"], tmpDir);
|
|
270
|
+
expect(otherRefs.trim()).not.toBe("");
|
|
271
|
+
});
|
|
272
|
+
|
|
250
273
|
it("should return empty list when no snapshots exist", () => {
|
|
251
274
|
expect(mgr.listSnapshots()).toHaveLength(0);
|
|
252
275
|
});
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import type { CustomEntry, ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
17
|
+
import { listLiveSessionIdsForCwd } from "./session-files.js";
|
|
17
18
|
import { SnapshotManager } from "./snapshots.js";
|
|
18
19
|
import type { RewindSnapshotEntry } from "./tracker.js";
|
|
19
20
|
import { FileTracker } from "./tracker.js";
|
|
@@ -50,6 +51,10 @@ export default function rewind(pi: ExtensionAPI): void {
|
|
|
50
51
|
snapshots = mgr;
|
|
51
52
|
tracker.reset();
|
|
52
53
|
|
|
54
|
+
const liveSessionIds = listLiveSessionIdsForCwd(context.cwd);
|
|
55
|
+
liveSessionIds.add(sessionId);
|
|
56
|
+
mgr.cleanupStaleSessions(liveSessionIds);
|
|
57
|
+
|
|
53
58
|
// Restore tracker state from persisted session entries
|
|
54
59
|
const entries = context.sessionManager.getEntries();
|
|
55
60
|
const snapshotEntries = entries
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import type { SessionHeader } from "@mariozechner/pi-coding-agent";
|
|
5
|
+
import { getDefaultTallowHomeDir, getTallowHomeDir } from "../_shared/tallow-paths.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Encode a cwd into the per-project session directory name used by tallow.
|
|
9
|
+
*
|
|
10
|
+
* @param cwd - Absolute working directory path
|
|
11
|
+
* @returns Encoded directory name (for example `--Users-kevin-dev-tallow--`)
|
|
12
|
+
*/
|
|
13
|
+
function encodeSessionDirName(cwd: string): string {
|
|
14
|
+
const withoutLeadingSlash = cwd.startsWith("/") || cwd.startsWith("\\") ? cwd.slice(1) : cwd;
|
|
15
|
+
const safeName = withoutLeadingSlash
|
|
16
|
+
.replaceAll("/", "-")
|
|
17
|
+
.replaceAll("\\", "-")
|
|
18
|
+
.replaceAll(":", "-");
|
|
19
|
+
return `--${safeName}--`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Read additional tallow home directories from the maintainer's work-dir config.
|
|
24
|
+
*
|
|
25
|
+
* @returns Extra configured tallow home directories
|
|
26
|
+
*/
|
|
27
|
+
function readConfiguredHomeDirs(): string[] {
|
|
28
|
+
const workDirsPath = join(homedir(), ".config", "tallow-work-dirs");
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
const content = readFileSync(workDirsPath, "utf-8");
|
|
32
|
+
return content
|
|
33
|
+
.split("\n")
|
|
34
|
+
.map((line) => line.trim())
|
|
35
|
+
.filter((line) => line.length > 0 && !line.startsWith("#"))
|
|
36
|
+
.map((line) => {
|
|
37
|
+
const colonIndex = line.indexOf(":");
|
|
38
|
+
return colonIndex === -1 ? "" : line.slice(colonIndex + 1).trim();
|
|
39
|
+
})
|
|
40
|
+
.filter((configDir) => configDir.length > 0);
|
|
41
|
+
} catch {
|
|
42
|
+
// Missing or unreadable work-dir config means there are no extra homes to scan.
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Build the set of tallow homes that should be searched for session files.
|
|
49
|
+
*
|
|
50
|
+
* @param homeDirs - Optional explicit home directories (used by tests to avoid scanning real homes)
|
|
51
|
+
* @returns Unique tallow home directories to inspect
|
|
52
|
+
*/
|
|
53
|
+
function resolveHomeDirs(homeDirs?: readonly string[]): Set<string> {
|
|
54
|
+
if (homeDirs) {
|
|
55
|
+
return new Set(homeDirs);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return new Set<string>([
|
|
59
|
+
getDefaultTallowHomeDir(),
|
|
60
|
+
getTallowHomeDir(),
|
|
61
|
+
...readConfiguredHomeDirs(),
|
|
62
|
+
]);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Discover every session directory that can contain sessions for a specific cwd.
|
|
67
|
+
*
|
|
68
|
+
* Tallow can store sessions under the default home, the active runtime home,
|
|
69
|
+
* and any per-project homes listed in `~/.config/tallow-work-dirs`.
|
|
70
|
+
*
|
|
71
|
+
* @param cwd - Working directory whose session subdirectory should be resolved
|
|
72
|
+
* @param homeDirs - Optional explicit home directories (used by tests to avoid scanning real homes)
|
|
73
|
+
* @returns Existing session directory paths across all known tallow homes
|
|
74
|
+
*/
|
|
75
|
+
function discoverSessionDirsForCwd(cwd: string, homeDirs?: readonly string[]): string[] {
|
|
76
|
+
const dirName = encodeSessionDirName(cwd);
|
|
77
|
+
const dirs = new Set<string>();
|
|
78
|
+
|
|
79
|
+
for (const home of resolveHomeDirs(homeDirs)) {
|
|
80
|
+
const sessionsDir = join(home, "sessions", dirName);
|
|
81
|
+
if (existsSync(sessionsDir)) {
|
|
82
|
+
dirs.add(sessionsDir);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return [...dirs];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Read the session id from a JSONL session file.
|
|
91
|
+
*
|
|
92
|
+
* The conventional filename already contains the id, but parsing the header is
|
|
93
|
+
* more robust for renamed or migrated session files.
|
|
94
|
+
*
|
|
95
|
+
* @param filePath - Absolute path to the session JSONL file
|
|
96
|
+
* @returns Session id when readable and valid, otherwise null
|
|
97
|
+
*/
|
|
98
|
+
function readSessionId(filePath: string): string | null {
|
|
99
|
+
try {
|
|
100
|
+
const content = readFileSync(filePath, "utf-8");
|
|
101
|
+
const firstNewline = content.indexOf("\n");
|
|
102
|
+
const headerLine = firstNewline === -1 ? content : content.slice(0, firstNewline);
|
|
103
|
+
const header = JSON.parse(headerLine) as SessionHeader;
|
|
104
|
+
if (header.type !== "session") return null;
|
|
105
|
+
return typeof header.id === "string" && header.id.length > 0 ? header.id : null;
|
|
106
|
+
} catch {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* List every live session id for the current cwd across all known tallow homes.
|
|
113
|
+
*
|
|
114
|
+
* @param cwd - Working directory whose sessions should be considered live
|
|
115
|
+
* @param homeDirs - Optional explicit home directories to scan instead of runtime discovery
|
|
116
|
+
* @returns Set of live session ids
|
|
117
|
+
*/
|
|
118
|
+
export function listLiveSessionIdsForCwd(cwd: string, homeDirs?: readonly string[]): Set<string> {
|
|
119
|
+
const ids = new Set<string>();
|
|
120
|
+
|
|
121
|
+
for (const sessionsDir of discoverSessionDirsForCwd(cwd, homeDirs)) {
|
|
122
|
+
let files: string[];
|
|
123
|
+
try {
|
|
124
|
+
files = readdirSync(sessionsDir).filter((file) => file.endsWith(".jsonl"));
|
|
125
|
+
} catch {
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
for (const file of files) {
|
|
130
|
+
const sessionId = readSessionId(join(sessionsDir, file));
|
|
131
|
+
if (sessionId) {
|
|
132
|
+
ids.add(sessionId);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return ids;
|
|
138
|
+
}
|
|
@@ -277,13 +277,112 @@ export class SnapshotManager {
|
|
|
277
277
|
}
|
|
278
278
|
|
|
279
279
|
/**
|
|
280
|
-
*
|
|
280
|
+
* Remove all refs for the current session.
|
|
281
|
+
*
|
|
282
|
+
* @returns Number of refs deleted for this session
|
|
283
|
+
*/
|
|
284
|
+
cleanup(): number {
|
|
285
|
+
return this.cleanupSession(this.getSessionIdFromPrefix(this.refPrefix));
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Remove rewind refs whose session ids no longer exist on disk.
|
|
290
|
+
*
|
|
291
|
+
* @param liveSessionIds - Session ids that still have backing session files
|
|
292
|
+
* @returns Count of deleted refs across all stale sessions
|
|
293
|
+
*/
|
|
294
|
+
cleanupStaleSessions(liveSessionIds: ReadonlySet<string>): number {
|
|
295
|
+
let deletedRefs = 0;
|
|
296
|
+
|
|
297
|
+
for (const sessionId of this.listSessionIds()) {
|
|
298
|
+
if (liveSessionIds.has(sessionId)) continue;
|
|
299
|
+
deletedRefs += this.cleanupSession(sessionId);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return deletedRefs;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* List every session id that currently has rewind refs in this repository.
|
|
307
|
+
*
|
|
308
|
+
* @returns Ordered unique session ids extracted from `refs/tallow/rewind/*`
|
|
309
|
+
*/
|
|
310
|
+
private listSessionIds(): string[] {
|
|
311
|
+
const raw = this.git(["for-each-ref", "--format=%(refname)", "refs/tallow/rewind/"]);
|
|
312
|
+
if (!raw) return [];
|
|
313
|
+
|
|
314
|
+
const ids = new Set<string>();
|
|
315
|
+
for (const line of raw.split("\n")) {
|
|
316
|
+
const ref = this.normalizeRefName(line);
|
|
317
|
+
if (!ref) continue;
|
|
318
|
+
const sessionId = this.parseSessionIdFromRef(ref);
|
|
319
|
+
if (sessionId) {
|
|
320
|
+
ids.add(sessionId);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return [...ids].sort((a, b) => a.localeCompare(b));
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Remove every rewind ref for a single session id.
|
|
329
|
+
*
|
|
330
|
+
* @param sessionId - Session id whose namespaced rewind refs should be deleted
|
|
331
|
+
* @returns Count of deleted refs
|
|
281
332
|
*/
|
|
282
|
-
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
|
|
333
|
+
private cleanupSession(sessionId: string): number {
|
|
334
|
+
const raw = this.git([
|
|
335
|
+
"for-each-ref",
|
|
336
|
+
"--format=%(refname)",
|
|
337
|
+
`refs/tallow/rewind/${sessionId}/`,
|
|
338
|
+
]);
|
|
339
|
+
if (!raw) return 0;
|
|
340
|
+
|
|
341
|
+
let deletedRefs = 0;
|
|
342
|
+
for (const line of raw.split("\n")) {
|
|
343
|
+
const ref = this.normalizeRefName(line);
|
|
344
|
+
if (!ref) continue;
|
|
345
|
+
if (this.git(["update-ref", "-d", ref]) !== null) {
|
|
346
|
+
deletedRefs++;
|
|
347
|
+
}
|
|
286
348
|
}
|
|
349
|
+
|
|
350
|
+
return deletedRefs;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Parse a session id from a full rewind ref name.
|
|
355
|
+
*
|
|
356
|
+
* @param ref - Full rewind ref name
|
|
357
|
+
* @returns Session id when the ref matches the rewind namespace, otherwise null
|
|
358
|
+
*/
|
|
359
|
+
private parseSessionIdFromRef(ref: string): string | null {
|
|
360
|
+
const match = /^refs\/tallow\/rewind\/([^/]+)\/turn-\d+$/.exec(ref);
|
|
361
|
+
return match?.[1] ?? null;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Normalize a raw ref name line from git output.
|
|
366
|
+
*
|
|
367
|
+
* @param line - Raw line returned by `git for-each-ref`
|
|
368
|
+
* @returns Trimmed ref name without surrounding quotes
|
|
369
|
+
*/
|
|
370
|
+
private normalizeRefName(line: string): string {
|
|
371
|
+
const trimmed = line.trim();
|
|
372
|
+
if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
|
|
373
|
+
return trimmed.slice(1, -1);
|
|
374
|
+
}
|
|
375
|
+
return trimmed;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Extract the current manager's session id from its ref prefix.
|
|
380
|
+
*
|
|
381
|
+
* @param refPrefix - Session-scoped rewind ref prefix
|
|
382
|
+
* @returns Session id portion of the prefix
|
|
383
|
+
*/
|
|
384
|
+
private getSessionIdFromPrefix(refPrefix: string): string {
|
|
385
|
+
return refPrefix.replace(/^refs\/tallow\/rewind\//, "");
|
|
287
386
|
}
|
|
288
387
|
|
|
289
388
|
/**
|
|
@@ -199,7 +199,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
199
199
|
// Load skills synchronously during extension init for autocomplete to work.
|
|
200
200
|
// includeDefaults: true picks up ~/.tallow/skills/ and ./skills/ (project).
|
|
201
201
|
// extraSkillPaths adds shared dirs + Claude bridge paths.
|
|
202
|
-
const { skills } = loadSkills({
|
|
202
|
+
const { skills } = loadSkills({
|
|
203
|
+
cwd: process.cwd(),
|
|
204
|
+
agentDir,
|
|
205
|
+
skillPaths: extraSkillPaths,
|
|
206
|
+
includeDefaults: true,
|
|
207
|
+
});
|
|
203
208
|
|
|
204
209
|
for (const skill of skills) {
|
|
205
210
|
// Validate name before registration — invalid names produce broken commands
|
|
@@ -13,6 +13,10 @@ import type {
|
|
|
13
13
|
ExtensionUIContext,
|
|
14
14
|
TurnEndEvent,
|
|
15
15
|
} from "@mariozechner/pi-coding-agent";
|
|
16
|
+
import {
|
|
17
|
+
getResetDiagnosticsForTests,
|
|
18
|
+
resetResetDiagnosticsForTests,
|
|
19
|
+
} from "../../../src/reset-diagnostics.js";
|
|
16
20
|
import { ExtensionHarness } from "../../../test-utils/extension-harness.js";
|
|
17
21
|
import { ManualTimerScheduler } from "../../../test-utils/manual-timer-scheduler.js";
|
|
18
22
|
import slashCommandBridge, {
|
|
@@ -40,6 +44,7 @@ beforeEach(async () => {
|
|
|
40
44
|
});
|
|
41
45
|
|
|
42
46
|
afterEach(() => {
|
|
47
|
+
resetResetDiagnosticsForTests();
|
|
43
48
|
resetSlashCommandBridgeStateForTests();
|
|
44
49
|
});
|
|
45
50
|
|
|
@@ -314,6 +319,11 @@ describe("compact", () => {
|
|
|
314
319
|
const continuation = harness.sentMessages.find(
|
|
315
320
|
(message) => message.customType === "compact-continue"
|
|
316
321
|
);
|
|
322
|
+
expect(
|
|
323
|
+
getResetDiagnosticsForTests().some(
|
|
324
|
+
(event) => event.kind === "deferred_registered" && event.source === "slash-command-bridge"
|
|
325
|
+
)
|
|
326
|
+
).toBe(true);
|
|
317
327
|
expect(continuation?.display).toBe(false);
|
|
318
328
|
expect(continuation?.options?.triggerTurn).toBe(true);
|
|
319
329
|
expect(continuation?.content).toContain("compaction is complete");
|
|
@@ -344,6 +354,14 @@ describe("compact", () => {
|
|
|
344
354
|
await harness.fireEvent("turn_start", { type: "turn_start", turnIndex: 0, timestamp: 0 }, ctx);
|
|
345
355
|
scheduler.advanceBy(200);
|
|
346
356
|
|
|
357
|
+
expect(
|
|
358
|
+
getResetDiagnosticsForTests().some(
|
|
359
|
+
(event) =>
|
|
360
|
+
event.kind === "deferred_cancelled" &&
|
|
361
|
+
event.source === "slash-command-bridge" &&
|
|
362
|
+
event.reason === "turn_start"
|
|
363
|
+
)
|
|
364
|
+
).toBe(true);
|
|
347
365
|
expect(harness.sentMessages).toHaveLength(0);
|
|
348
366
|
expect(widgetUpdates.at(-1)).toEqual({ key: "compact-progress", content: undefined });
|
|
349
367
|
});
|
|
@@ -374,6 +392,14 @@ describe("compact", () => {
|
|
|
374
392
|
compactOptions?.onComplete?.();
|
|
375
393
|
scheduler.advanceBy(200);
|
|
376
394
|
|
|
395
|
+
expect(
|
|
396
|
+
getResetDiagnosticsForTests().some(
|
|
397
|
+
(event) =>
|
|
398
|
+
event.kind === "deferred_dropped" &&
|
|
399
|
+
event.source === "slash-command-bridge" &&
|
|
400
|
+
event.reason === "session_not_idle"
|
|
401
|
+
)
|
|
402
|
+
).toBe(true);
|
|
377
403
|
expect(harness.sentMessages).toHaveLength(0);
|
|
378
404
|
expect(widgetUpdates.at(-1)).toEqual({ key: "compact-progress", content: undefined });
|
|
379
405
|
expect(workingMessages.at(-1)).toBeUndefined();
|
|
@@ -17,6 +17,7 @@ import type {
|
|
|
17
17
|
} from "@mariozechner/pi-coding-agent";
|
|
18
18
|
import { Text } from "@mariozechner/pi-tui";
|
|
19
19
|
import { Type } from "@sinclair/typebox";
|
|
20
|
+
import { recordResetDiagnostic } from "../../src/reset-diagnostics.js";
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* Deferred compact request — set by the tool handler, consumed on the first
|
|
@@ -154,13 +155,18 @@ function stopCompactProgress(ctx?: ExtensionContext): void {
|
|
|
154
155
|
*
|
|
155
156
|
* @returns Nothing
|
|
156
157
|
*/
|
|
157
|
-
function clearContinuationTimer(): void {
|
|
158
|
+
function clearContinuationTimer(reason?: string): void {
|
|
158
159
|
if (!continuationTimer) {
|
|
159
160
|
return;
|
|
160
161
|
}
|
|
161
162
|
|
|
162
163
|
timerScheduler.clearTimeout(continuationTimer);
|
|
163
164
|
continuationTimer = null;
|
|
165
|
+
recordResetDiagnostic({
|
|
166
|
+
kind: "deferred_cancelled",
|
|
167
|
+
reason: reason ?? "clear_continuation_timer",
|
|
168
|
+
source: "slash-command-bridge",
|
|
169
|
+
});
|
|
164
170
|
}
|
|
165
171
|
|
|
166
172
|
/**
|
|
@@ -338,6 +344,7 @@ function startDeferredCompact(
|
|
|
338
344
|
// 200ms gives session.prompt()'s async setup (API key resolution,
|
|
339
345
|
// compaction check) time to settle. The turn_start listener cancels
|
|
340
346
|
// this timer if a turn starts before it fires (defense-in-depth).
|
|
347
|
+
recordResetDiagnostic({ kind: "deferred_registered", source: "slash-command-bridge" });
|
|
341
348
|
continuationTimer = timerScheduler.setTimeout(() => {
|
|
342
349
|
continuationTimer = null;
|
|
343
350
|
if (ctx.isIdle()) {
|
|
@@ -354,6 +361,11 @@ function startDeferredCompact(
|
|
|
354
361
|
} else {
|
|
355
362
|
// User sent a message during compaction — their turn is
|
|
356
363
|
// handling things, clean up our indicators.
|
|
364
|
+
recordResetDiagnostic({
|
|
365
|
+
kind: "deferred_dropped",
|
|
366
|
+
reason: "session_not_idle",
|
|
367
|
+
source: "slash-command-bridge",
|
|
368
|
+
});
|
|
357
369
|
resumingAfterCompact = false;
|
|
358
370
|
ctx.ui?.setWidget?.("compact-progress", undefined);
|
|
359
371
|
ctx.ui?.setWorkingMessage?.();
|
|
@@ -601,7 +613,7 @@ WHEN NOT TO USE:
|
|
|
601
613
|
* now active and showing the pending working message ("Resuming task…").
|
|
602
614
|
*/
|
|
603
615
|
pi.on("turn_start", (_event, ctx) => {
|
|
604
|
-
clearContinuationTimer();
|
|
616
|
+
clearContinuationTimer("turn_start");
|
|
605
617
|
if (!resumingAfterCompact) return;
|
|
606
618
|
resumingAfterCompact = false;
|
|
607
619
|
ctx.ui?.setWidget?.("compact-progress", undefined);
|