@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
|
@@ -23,6 +23,7 @@ import * as path from "node:path";
|
|
|
23
23
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
24
24
|
import { stripFrontmatter } from "@mariozechner/pi-coding-agent";
|
|
25
25
|
import { Text } from "@mariozechner/pi-tui";
|
|
26
|
+
import { recordResetDiagnostic } from "../../src/reset-diagnostics.js";
|
|
26
27
|
import { createLazyInitializer } from "../_shared/lazy-init.js";
|
|
27
28
|
import { isProjectTrusted } from "../_shared/project-trust.js";
|
|
28
29
|
import { isShellInterpolationEnabled } from "../_shared/shell-policy.js";
|
|
@@ -326,6 +327,7 @@ export function registerContextForkExtension(
|
|
|
326
327
|
|
|
327
328
|
let frontmatterIndex: FrontmatterIndex = new Map();
|
|
328
329
|
let agents: Map<string, AgentConfig> = new Map();
|
|
330
|
+
let sessionGeneration = 0;
|
|
329
331
|
|
|
330
332
|
const debug = (...args: unknown[]) => {
|
|
331
333
|
if (process.env.DEBUG) {
|
|
@@ -375,9 +377,21 @@ export function registerContextForkExtension(
|
|
|
375
377
|
|
|
376
378
|
// Reset lazy state on each session start so resources reflect on-disk changes.
|
|
377
379
|
pi.on("session_start", async () => {
|
|
380
|
+
sessionGeneration += 1;
|
|
378
381
|
resetResources();
|
|
379
382
|
});
|
|
380
383
|
|
|
384
|
+
// Invalidate any in-flight fork completions before switching sessions.
|
|
385
|
+
pi.on("session_before_switch", async (_event, ctx) => {
|
|
386
|
+
sessionGeneration += 1;
|
|
387
|
+
ctx.ui?.setWorkingMessage?.();
|
|
388
|
+
recordResetDiagnostic({
|
|
389
|
+
kind: "deferred_cancelled",
|
|
390
|
+
reason: "session_before_switch",
|
|
391
|
+
source: "context-fork",
|
|
392
|
+
});
|
|
393
|
+
});
|
|
394
|
+
|
|
381
395
|
// Register custom message renderer for fork results
|
|
382
396
|
pi.registerMessageRenderer<ForkResultDetails>("fork-result", (message, _options, theme) => {
|
|
383
397
|
const details = message.details;
|
|
@@ -505,6 +519,8 @@ export function registerContextForkExtension(
|
|
|
505
519
|
|
|
506
520
|
// Mark as handled — prevent command-prompt/minimal-skill-display from processing
|
|
507
521
|
// We continue the fork asynchronously via the promise below
|
|
522
|
+
const forkGeneration = sessionGeneration;
|
|
523
|
+
recordResetDiagnostic({ kind: "deferred_registered", source: "context-fork" });
|
|
508
524
|
const forkPromise = dependencies.spawnForkSubprocess({
|
|
509
525
|
content,
|
|
510
526
|
cwd: ctx.cwd,
|
|
@@ -516,6 +532,14 @@ export function registerContextForkExtension(
|
|
|
516
532
|
|
|
517
533
|
forkPromise
|
|
518
534
|
.then((result) => {
|
|
535
|
+
if (forkGeneration !== sessionGeneration) {
|
|
536
|
+
recordResetDiagnostic({
|
|
537
|
+
kind: "deferred_dropped",
|
|
538
|
+
reason: "session_generation_mismatch",
|
|
539
|
+
source: "context-fork",
|
|
540
|
+
});
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
519
543
|
ctx.ui.setWorkingMessage();
|
|
520
544
|
|
|
521
545
|
if (result.exitCode !== 0 && !result.output) {
|
|
@@ -550,6 +574,14 @@ export function registerContextForkExtension(
|
|
|
550
574
|
);
|
|
551
575
|
})
|
|
552
576
|
.catch((err: unknown) => {
|
|
577
|
+
if (forkGeneration !== sessionGeneration) {
|
|
578
|
+
recordResetDiagnostic({
|
|
579
|
+
kind: "deferred_dropped",
|
|
580
|
+
reason: "session_generation_mismatch",
|
|
581
|
+
source: "context-fork",
|
|
582
|
+
});
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
553
585
|
ctx.ui.setWorkingMessage();
|
|
554
586
|
const message = err instanceof Error ? err.message : String(err);
|
|
555
587
|
ctx.ui.notify(`Fork /${commandName} error: ${message}`, "error");
|
|
@@ -14,9 +14,10 @@ import {
|
|
|
14
14
|
renderDiff,
|
|
15
15
|
type ThemeColor,
|
|
16
16
|
} from "@mariozechner/pi-coding-agent";
|
|
17
|
-
import {
|
|
17
|
+
import { Text } from "@mariozechner/pi-tui";
|
|
18
18
|
import { getIcon } from "../_icons/index.js";
|
|
19
19
|
import { commandExistsOnPath, runGitCommandSync } from "../_shared/shell-policy.js";
|
|
20
|
+
import { fileLink, hyperlink } from "../_shared/terminal-links.js";
|
|
20
21
|
import {
|
|
21
22
|
appendSection,
|
|
22
23
|
dimProcessOutputLine,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
3
|
-
import gitStatus from "../index.js";
|
|
3
|
+
import gitStatus, { formatStatus, parseGitStatus, parsePullRequestInfo } from "../index.js";
|
|
4
4
|
|
|
5
5
|
describe("git-status extension", () => {
|
|
6
|
-
test("registers session_start, tool_result, and session_shutdown handlers", () => {
|
|
6
|
+
test("registers session_start, agent_end, tool_result, and session_shutdown handlers", () => {
|
|
7
7
|
const events: string[] = [];
|
|
8
8
|
const pi = {
|
|
9
9
|
on: (event: string) => {
|
|
@@ -13,6 +13,7 @@ describe("git-status extension", () => {
|
|
|
13
13
|
|
|
14
14
|
gitStatus(pi);
|
|
15
15
|
expect(events).toContain("session_start");
|
|
16
|
+
expect(events).toContain("agent_end");
|
|
16
17
|
expect(events).toContain("tool_result");
|
|
17
18
|
expect(events).toContain("session_shutdown");
|
|
18
19
|
});
|
|
@@ -30,3 +31,65 @@ describe("git-status extension", () => {
|
|
|
30
31
|
expect(commands).toHaveLength(0);
|
|
31
32
|
});
|
|
32
33
|
});
|
|
34
|
+
|
|
35
|
+
describe("parseGitStatus", () => {
|
|
36
|
+
test("extracts branch, ahead/behind, and dirty state", () => {
|
|
37
|
+
const parsed = parseGitStatus(
|
|
38
|
+
[
|
|
39
|
+
"# branch.oid abcdef",
|
|
40
|
+
"# branch.head main",
|
|
41
|
+
"# branch.upstream origin/main",
|
|
42
|
+
"# branch.ab +2 -3",
|
|
43
|
+
"1 .M N... 100644 100644 100644 abc def file.ts",
|
|
44
|
+
].join("\n")
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
expect(parsed).toEqual({
|
|
48
|
+
branch: "main",
|
|
49
|
+
dirty: true,
|
|
50
|
+
ahead: 2,
|
|
51
|
+
behind: 3,
|
|
52
|
+
prState: null,
|
|
53
|
+
prNumber: null,
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("returns null when branch metadata is missing", () => {
|
|
58
|
+
expect(parseGitStatus("# branch.oid abcdef")).toBeNull();
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe("parsePullRequestInfo", () => {
|
|
63
|
+
test("prefers draft state when isDraft is true", () => {
|
|
64
|
+
expect(parsePullRequestInfo('{"number":42,"isDraft":true,"state":"OPEN"}')).toEqual({
|
|
65
|
+
prState: "draft",
|
|
66
|
+
prNumber: 42,
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("normalizes regular PR state values", () => {
|
|
71
|
+
expect(parsePullRequestInfo('{"number":42,"state":"OPEN"}')).toEqual({
|
|
72
|
+
prState: "open",
|
|
73
|
+
prNumber: 42,
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
describe("formatStatus", () => {
|
|
79
|
+
test("includes dirty, ahead/behind, and PR metadata", () => {
|
|
80
|
+
const formatted = formatStatus({
|
|
81
|
+
branch: "main",
|
|
82
|
+
dirty: true,
|
|
83
|
+
ahead: 1,
|
|
84
|
+
behind: 2,
|
|
85
|
+
prState: "draft",
|
|
86
|
+
prNumber: 42,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
expect(formatted).toContain("main");
|
|
90
|
+
expect(formatted).toContain("*");
|
|
91
|
+
expect(formatted).toContain("↑1");
|
|
92
|
+
expect(formatted).toContain("↓2");
|
|
93
|
+
expect(formatted).toContain("PR#42(draft)");
|
|
94
|
+
});
|
|
95
|
+
});
|
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
* - Current branch name
|
|
6
6
|
* - Dirty state (* if uncommitted changes)
|
|
7
7
|
* - Ahead/behind remote
|
|
8
|
-
* - PR status (if GitHub CLI available)
|
|
8
|
+
* - PR status (if GitHub CLI is available and responsive)
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
12
12
|
import { getIcon } from "../_icons/index.js";
|
|
13
|
-
import {
|
|
13
|
+
import { runCommand, runGitCommand } from "../_shared/shell-policy.js";
|
|
14
14
|
|
|
15
15
|
// Catppuccin Macchiato colors
|
|
16
16
|
const C_TEAL = "\x1b[38;2;139;213;202m"; // teal #8bd5ca
|
|
@@ -21,47 +21,53 @@ const C_MAUVE = "\x1b[38;2;198;160;246m"; // mauve #c6a0f6
|
|
|
21
21
|
const C_GRAY = "\x1b[38;2;128;135;162m"; // overlay1 #8087a2
|
|
22
22
|
const C_RESET = "\x1b[0m";
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
const STATUS_REFRESH_INTERVAL_MS = 10_000;
|
|
25
|
+
const PR_REFRESH_INTERVAL_MS = 60_000;
|
|
26
|
+
const PR_TIMEOUT_MS = 1_500;
|
|
27
|
+
const PR_ERROR_COOLDOWN_MS = 5 * 60_000;
|
|
28
|
+
|
|
29
|
+
type PullRequestState = "open" | "merged" | "closed" | "draft" | null;
|
|
30
|
+
|
|
31
|
+
/** Represents the current state of a git repository. */
|
|
32
|
+
export interface GitState {
|
|
26
33
|
branch: string | null;
|
|
27
34
|
dirty: boolean;
|
|
28
35
|
ahead: number;
|
|
29
36
|
behind: number;
|
|
30
|
-
prState:
|
|
37
|
+
prState: PullRequestState;
|
|
38
|
+
prNumber: number | null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface PullRequestInfo {
|
|
42
|
+
prState: PullRequestState;
|
|
31
43
|
prNumber: number | null;
|
|
32
44
|
}
|
|
33
45
|
|
|
34
|
-
|
|
35
|
-
|
|
46
|
+
interface GitStatusGlobals {
|
|
47
|
+
__piGitStatusInterval?: ReturnType<typeof setInterval> | null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const G = globalThis as typeof globalThis & GitStatusGlobals;
|
|
36
51
|
if (G.__piGitStatusInterval) {
|
|
37
52
|
clearInterval(G.__piGitStatusInterval);
|
|
38
53
|
G.__piGitStatusInterval = null;
|
|
39
54
|
}
|
|
55
|
+
|
|
40
56
|
let lastCwd = "";
|
|
41
57
|
let cachedState: GitState | null = null;
|
|
58
|
+
let activeRefresh: Promise<void> | null = null;
|
|
59
|
+
let queuedRefresh: { ctx: ExtensionContext; revision: number } | null = null;
|
|
60
|
+
let sessionRevision = 0;
|
|
61
|
+
let lastPrRefreshAt = 0;
|
|
62
|
+
let prCooldownUntil = 0;
|
|
42
63
|
|
|
43
64
|
/**
|
|
44
|
-
*
|
|
65
|
+
* Parse `git status --porcelain=v2 --branch` output into a base git state.
|
|
45
66
|
*
|
|
46
|
-
* @param
|
|
47
|
-
* @
|
|
48
|
-
* @returns The trimmed stdout output, or null if the command failed
|
|
49
|
-
*/
|
|
50
|
-
function runGit(args: string[], cwd: string): string | null {
|
|
51
|
-
return runGitCommandSync(args, cwd, 5000);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Retrieves the current git state for a directory.
|
|
56
|
-
* Includes branch name, dirty status, ahead/behind counts, and PR info.
|
|
57
|
-
* @param cwd - The working directory to check
|
|
58
|
-
* @returns The git state object, or null if not a git repository
|
|
67
|
+
* @param raw - Raw porcelain-v2 status output
|
|
68
|
+
* @returns Parsed git state without PR metadata, or null when no branch is found
|
|
59
69
|
*/
|
|
60
|
-
function
|
|
61
|
-
// Single command: branch, ahead/behind, and porcelain status
|
|
62
|
-
const raw = runGit(["status", "--porcelain=v2", "--branch"], cwd);
|
|
63
|
-
if (raw === null) return null;
|
|
64
|
-
|
|
70
|
+
export function parseGitStatus(raw: string): GitState | null {
|
|
65
71
|
let branch: string | null = null;
|
|
66
72
|
let ahead = 0;
|
|
67
73
|
let behind = 0;
|
|
@@ -70,10 +76,6 @@ function getGitState(cwd: string): GitState | null {
|
|
|
70
76
|
for (const line of raw.split("\n")) {
|
|
71
77
|
if (line.startsWith("# branch.head ")) {
|
|
72
78
|
branch = line.slice("# branch.head ".length);
|
|
73
|
-
if (branch === "(detached)") {
|
|
74
|
-
const sha = runGit(["rev-parse", "--short", "HEAD"], cwd);
|
|
75
|
-
branch = sha ? `(${sha})` : branch;
|
|
76
|
-
}
|
|
77
79
|
} else if (line.startsWith("# branch.ab ")) {
|
|
78
80
|
const match = line.match(/\+(\d+) -(\d+)/);
|
|
79
81
|
if (match) {
|
|
@@ -86,58 +88,161 @@ function getGitState(cwd: string): GitState | null {
|
|
|
86
88
|
}
|
|
87
89
|
|
|
88
90
|
if (!branch) return null;
|
|
91
|
+
return { branch, dirty, ahead, behind, prState: null, prNumber: null };
|
|
92
|
+
}
|
|
89
93
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
94
|
+
/**
|
|
95
|
+
* Parse `gh pr view --json state,number,isDraft` output.
|
|
96
|
+
*
|
|
97
|
+
* @param raw - Raw gh JSON output
|
|
98
|
+
* @returns Parsed pull-request metadata, or null when unavailable
|
|
99
|
+
*/
|
|
100
|
+
export function parsePullRequestInfo(raw: string): PullRequestInfo | null {
|
|
101
|
+
const parsed = JSON.parse(raw) as {
|
|
102
|
+
number?: number;
|
|
103
|
+
isDraft?: boolean;
|
|
104
|
+
state?: string;
|
|
105
|
+
};
|
|
106
|
+
if (!parsed.number) return null;
|
|
107
|
+
if (parsed.isDraft) {
|
|
108
|
+
return { prState: "draft", prNumber: parsed.number };
|
|
109
|
+
}
|
|
110
|
+
if (!parsed.state) return null;
|
|
111
|
+
return {
|
|
112
|
+
prState: parsed.state.toLowerCase() as Exclude<PullRequestState, null>,
|
|
113
|
+
prNumber: parsed.number,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Returns whether a gh stderr payload means “no PR” rather than a broken CLI.
|
|
119
|
+
*
|
|
120
|
+
* @param stderr - gh stderr text
|
|
121
|
+
* @returns True when the branch simply has no associated pull request
|
|
122
|
+
*/
|
|
123
|
+
function isNoPullRequestError(stderr: string): boolean {
|
|
124
|
+
return /no pull requests? found/i.test(stderr);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Returns whether a gh failure should trigger a long retry cooldown.
|
|
129
|
+
*
|
|
130
|
+
* @param result - Process result from the gh invocation
|
|
131
|
+
* @returns True when failures are likely environmental or timeout-related
|
|
132
|
+
*/
|
|
133
|
+
function shouldCooldownPullRequestChecks(result: {
|
|
134
|
+
reason?: string;
|
|
135
|
+
stderr: string;
|
|
136
|
+
exitCode: number | null;
|
|
137
|
+
}): boolean {
|
|
138
|
+
if (result.reason?.includes("timed out")) return true;
|
|
139
|
+
if (result.reason?.includes("ENOENT")) return true;
|
|
140
|
+
if (result.exitCode === null && result.reason) return true;
|
|
141
|
+
return /could not resolve to a repository|no git remotes found|not a git repository/i.test(
|
|
142
|
+
result.stderr
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Execute a git command asynchronously.
|
|
148
|
+
*
|
|
149
|
+
* @param args - Git subcommand and arguments
|
|
150
|
+
* @param cwd - Working directory
|
|
151
|
+
* @param timeoutMs - Optional timeout override
|
|
152
|
+
* @returns Trimmed stdout output, or null on failure
|
|
153
|
+
*/
|
|
154
|
+
async function runGit(
|
|
155
|
+
args: readonly string[],
|
|
156
|
+
cwd: string,
|
|
157
|
+
timeoutMs = 3_000
|
|
158
|
+
): Promise<string | null> {
|
|
159
|
+
return await runGitCommand(args, cwd, timeoutMs);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Read branch, ahead/behind, and dirty state without blocking the UI thread.
|
|
164
|
+
*
|
|
165
|
+
* @param cwd - Working directory to inspect
|
|
166
|
+
* @returns Base git state, or null when not inside a git repository
|
|
167
|
+
*/
|
|
168
|
+
async function getBaseGitState(cwd: string): Promise<GitState | null> {
|
|
169
|
+
const raw = await runGit(["status", "--porcelain=v2", "--branch"], cwd);
|
|
170
|
+
if (raw === null) return null;
|
|
171
|
+
|
|
172
|
+
const state = parseGitStatus(raw);
|
|
173
|
+
if (!state) return null;
|
|
174
|
+
if (state.branch !== "(detached)") return state;
|
|
175
|
+
|
|
176
|
+
const sha = await runGit(["rev-parse", "--short", "HEAD"], cwd);
|
|
177
|
+
if (sha) {
|
|
178
|
+
state.branch = `(${sha})`;
|
|
179
|
+
}
|
|
180
|
+
return state;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Resolve pull-request metadata for the current branch.
|
|
185
|
+
*
|
|
186
|
+
* @param cwd - Working directory to inspect
|
|
187
|
+
* @returns PR metadata, null for no PR / unavailable data, and cooldown on repeated failures
|
|
188
|
+
*/
|
|
189
|
+
async function getPullRequestInfoForBranch(cwd: string): Promise<PullRequestInfo | null> {
|
|
190
|
+
const result = await runCommand({
|
|
191
|
+
command: "gh",
|
|
192
|
+
args: ["pr", "view", "--json", "state,number,isDraft"],
|
|
193
|
+
cwd,
|
|
194
|
+
source: "git-helper",
|
|
195
|
+
timeoutMs: PR_TIMEOUT_MS,
|
|
196
|
+
});
|
|
197
|
+
if (result.ok && result.stdout) {
|
|
198
|
+
try {
|
|
199
|
+
return parsePullRequestInfo(result.stdout);
|
|
200
|
+
} catch {
|
|
201
|
+
prCooldownUntil = Date.now() + PR_ERROR_COOLDOWN_MS;
|
|
202
|
+
return null;
|
|
117
203
|
}
|
|
118
|
-
} catch {
|
|
119
|
-
// gh CLI not available, parse failure, or not in a GitHub repo
|
|
120
204
|
}
|
|
121
205
|
|
|
122
|
-
|
|
206
|
+
if (isNoPullRequestError(result.stderr)) {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (shouldCooldownPullRequestChecks(result)) {
|
|
211
|
+
prCooldownUntil = Date.now() + PR_ERROR_COOLDOWN_MS;
|
|
212
|
+
}
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Returns whether PR metadata should be refreshed for the current branch.
|
|
218
|
+
*
|
|
219
|
+
* @param baseState - Freshly computed base git state
|
|
220
|
+
* @param previousState - Previously cached state before the current refresh
|
|
221
|
+
* @returns True when a PR refresh is worth attempting
|
|
222
|
+
*/
|
|
223
|
+
function shouldRefreshPullRequest(baseState: GitState, previousState: GitState | null): boolean {
|
|
224
|
+
if (baseState.branch === null) return false;
|
|
225
|
+
if (Date.now() < prCooldownUntil) return false;
|
|
226
|
+
if (!previousState) return true;
|
|
227
|
+
if (previousState.branch !== baseState.branch) return true;
|
|
228
|
+
return Date.now() - lastPrRefreshAt >= PR_REFRESH_INTERVAL_MS;
|
|
123
229
|
}
|
|
124
230
|
|
|
125
231
|
/**
|
|
126
232
|
* Formats the git state into a colored status string for display.
|
|
233
|
+
*
|
|
127
234
|
* @param state - The git state to format
|
|
128
235
|
* @returns A formatted string with ANSI color codes
|
|
129
236
|
*/
|
|
130
|
-
function formatStatus(state: GitState): string {
|
|
237
|
+
export function formatStatus(state: GitState): string {
|
|
131
238
|
const parts: string[] = [];
|
|
132
239
|
|
|
133
|
-
// Branch name with dirty indicator
|
|
134
240
|
let branchDisplay = `${C_TEAL}${state.branch}${C_RESET}`;
|
|
135
241
|
if (state.dirty) {
|
|
136
242
|
branchDisplay += `${C_YELLOW}*${C_RESET}`;
|
|
137
243
|
}
|
|
138
244
|
parts.push(branchDisplay);
|
|
139
245
|
|
|
140
|
-
// Ahead/behind
|
|
141
246
|
if (state.ahead > 0 || state.behind > 0) {
|
|
142
247
|
const arrows: string[] = [];
|
|
143
248
|
if (state.ahead > 0) arrows.push(`${C_GREEN}↑${state.ahead}${C_RESET}`);
|
|
@@ -145,7 +250,6 @@ function formatStatus(state: GitState): string {
|
|
|
145
250
|
parts.push(arrows.join(""));
|
|
146
251
|
}
|
|
147
252
|
|
|
148
|
-
// PR status
|
|
149
253
|
if (state.prState && state.prNumber) {
|
|
150
254
|
let prDisplay: string;
|
|
151
255
|
switch (state.prState) {
|
|
@@ -171,63 +275,129 @@ function formatStatus(state: GitState): string {
|
|
|
171
275
|
}
|
|
172
276
|
|
|
173
277
|
/**
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
* @param ctx -
|
|
278
|
+
* Push the current cached status into the UI.
|
|
279
|
+
*
|
|
280
|
+
* @param ctx - Extension context providing the UI surface
|
|
281
|
+
* @param revision - Session revision that must still be current
|
|
282
|
+
* @returns Nothing
|
|
283
|
+
*/
|
|
284
|
+
function renderCachedStatus(ctx: ExtensionContext, revision: number): void {
|
|
285
|
+
if (revision !== sessionRevision) return;
|
|
286
|
+
if (ctx.cwd !== lastCwd || !cachedState) {
|
|
287
|
+
ctx.ui.setStatus("git", undefined);
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
ctx.ui.setStatus("git", formatStatus(cachedState));
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Refresh the git status cache without blocking terminal input.
|
|
295
|
+
*
|
|
296
|
+
* Concurrent refreshes are coalesced so timer ticks, agent-end hooks, and bash
|
|
297
|
+
* results cannot stack multiple in-flight `git`/`gh` subprocesses.
|
|
298
|
+
*
|
|
299
|
+
* @param ctx - Extension context providing cwd + ui access
|
|
300
|
+
* @param revision - Session revision that must remain current while refreshing
|
|
301
|
+
* @returns Promise resolving after the latest queued refresh finishes
|
|
177
302
|
*/
|
|
178
|
-
async function
|
|
303
|
+
async function refreshStatus(ctx: ExtensionContext, revision: number): Promise<void> {
|
|
304
|
+
if (activeRefresh) {
|
|
305
|
+
queuedRefresh = { ctx, revision };
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
|
|
179
309
|
const cwd = ctx.cwd;
|
|
310
|
+
activeRefresh = (async () => {
|
|
311
|
+
const baseState = await getBaseGitState(cwd);
|
|
312
|
+
if (revision !== sessionRevision || ctx.cwd !== cwd) return;
|
|
180
313
|
|
|
181
|
-
// Only update if cwd changed or no cache
|
|
182
|
-
if (cwd !== lastCwd || !cachedState) {
|
|
183
314
|
lastCwd = cwd;
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
315
|
+
if (!baseState) {
|
|
316
|
+
cachedState = null;
|
|
317
|
+
renderCachedStatus(ctx, revision);
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
189
320
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
321
|
+
const previousState = cachedState;
|
|
322
|
+
cachedState = {
|
|
323
|
+
...baseState,
|
|
324
|
+
prState: previousState?.branch === baseState.branch ? previousState.prState : null,
|
|
325
|
+
prNumber: previousState?.branch === baseState.branch ? previousState.prNumber : null,
|
|
326
|
+
};
|
|
327
|
+
renderCachedStatus(ctx, revision);
|
|
328
|
+
|
|
329
|
+
if (!shouldRefreshPullRequest(baseState, previousState)) {
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const prInfo = await getPullRequestInfoForBranch(cwd);
|
|
334
|
+
if (revision !== sessionRevision || ctx.cwd !== cwd) return;
|
|
335
|
+
if (!cachedState || cachedState.branch !== baseState.branch) return;
|
|
336
|
+
|
|
337
|
+
cachedState = {
|
|
338
|
+
...cachedState,
|
|
339
|
+
prState: prInfo?.prState ?? null,
|
|
340
|
+
prNumber: prInfo?.prNumber ?? null,
|
|
341
|
+
};
|
|
342
|
+
lastPrRefreshAt = Date.now();
|
|
343
|
+
renderCachedStatus(ctx, revision);
|
|
344
|
+
})().finally(() => {
|
|
345
|
+
activeRefresh = null;
|
|
346
|
+
const nextRefresh = queuedRefresh;
|
|
347
|
+
queuedRefresh = null;
|
|
348
|
+
if (nextRefresh) {
|
|
349
|
+
void refreshStatus(nextRefresh.ctx, nextRefresh.revision);
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
await activeRefresh;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Invalidate caches that should be recomputed on the next refresh.
|
|
358
|
+
*
|
|
359
|
+
* @returns Nothing
|
|
360
|
+
*/
|
|
361
|
+
function invalidateStatusCache(): void {
|
|
362
|
+
cachedState = null;
|
|
363
|
+
lastPrRefreshAt = 0;
|
|
195
364
|
}
|
|
196
365
|
|
|
197
366
|
/**
|
|
198
367
|
* Registers the git status extension with Pi.
|
|
199
|
-
*
|
|
368
|
+
*
|
|
200
369
|
* @param pi - The Pi extension API
|
|
370
|
+
* @returns Nothing
|
|
201
371
|
*/
|
|
202
372
|
export default function gitStatus(pi: ExtensionAPI): void {
|
|
203
|
-
pi.on("session_start",
|
|
204
|
-
|
|
373
|
+
pi.on("session_start", (_event, ctx) => {
|
|
374
|
+
const revision = ++sessionRevision;
|
|
375
|
+
renderCachedStatus(ctx, revision);
|
|
376
|
+
void refreshStatus(ctx, revision);
|
|
205
377
|
|
|
206
|
-
// Update every 10 seconds
|
|
207
378
|
if (G.__piGitStatusInterval) clearInterval(G.__piGitStatusInterval);
|
|
208
|
-
G.__piGitStatusInterval = setInterval(() =>
|
|
379
|
+
G.__piGitStatusInterval = setInterval(() => {
|
|
380
|
+
void refreshStatus(ctx, revision);
|
|
381
|
+
}, STATUS_REFRESH_INTERVAL_MS);
|
|
209
382
|
});
|
|
210
383
|
|
|
211
|
-
pi.on("session_shutdown",
|
|
384
|
+
pi.on("session_shutdown", () => {
|
|
385
|
+
sessionRevision += 1;
|
|
386
|
+
queuedRefresh = null;
|
|
212
387
|
if (G.__piGitStatusInterval) {
|
|
213
388
|
clearInterval(G.__piGitStatusInterval);
|
|
214
389
|
G.__piGitStatusInterval = null;
|
|
215
390
|
}
|
|
216
391
|
});
|
|
217
392
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
cachedState = null;
|
|
222
|
-
await updateStatus(ctx);
|
|
393
|
+
pi.on("agent_end", (_event, ctx) => {
|
|
394
|
+
invalidateStatusCache();
|
|
395
|
+
void refreshStatus(ctx, sessionRevision);
|
|
223
396
|
});
|
|
224
397
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
cachedState = null;
|
|
230
|
-
await updateStatus(ctx);
|
|
231
|
-
}
|
|
398
|
+
pi.on("tool_result", (event, ctx) => {
|
|
399
|
+
if (event.toolName !== "bash") return;
|
|
400
|
+
invalidateStatusCache();
|
|
401
|
+
void refreshStatus(ctx, sessionRevision);
|
|
232
402
|
});
|
|
233
403
|
}
|