gsd-pi 2.64.0-dev.9c14bd0 → 2.64.0-dev.b3ee078
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/headless.js +3 -1
- package/dist/resources/extensions/bg-shell/bg-shell-lifecycle.js +22 -7
- package/dist/resources/extensions/bg-shell/process-manager.js +6 -1
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +24 -13
- package/dist/resources/extensions/gsd/bootstrap/notify-interceptor.js +28 -0
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +8 -0
- package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +16 -0
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +20 -0
- package/dist/resources/extensions/gsd/commands/catalog.js +7 -1
- package/dist/resources/extensions/gsd/commands/handlers/core.js +1 -0
- package/dist/resources/extensions/gsd/commands/handlers/notifications-handler.js +104 -0
- package/dist/resources/extensions/gsd/commands/handlers/ops.js +5 -0
- package/dist/resources/extensions/gsd/notification-overlay.js +223 -0
- package/dist/resources/extensions/gsd/notification-store.js +273 -0
- package/dist/resources/extensions/gsd/notification-widget.js +56 -0
- package/dist/resources/extensions/gsd/workflow-logger.js +8 -0
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +20 -19
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/routes-manifest.json +6 -0
- package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/api/notifications/route.js +3 -0
- package/dist/web/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -0
- package/dist/web/standalone/.next/server/app/api/notifications/route_client-reference-manifest.js +1 -0
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +20 -19
- package/dist/web/standalone/.next/server/functions-config-manifest.json +1 -0
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +2 -2
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/.next/static/chunks/app/_global-error/page-8805a20e15762c3c.js +1 -0
- package/dist/web/standalone/.next/static/chunks/app/api/boot/route-8805a20e15762c3c.js +1 -0
- package/dist/web/standalone/.next/static/chunks/app/api/bridge-terminal/input/route-8805a20e15762c3c.js +1 -0
- package/dist/web/standalone/.next/static/chunks/app/api/bridge-terminal/resize/route-8805a20e15762c3c.js +1 -0
- package/dist/web/standalone/.next/static/chunks/app/api/bridge-terminal/stream/route-8805a20e15762c3c.js +1 -0
- package/dist/web/standalone/.next/static/chunks/app/api/browse-directories/route-8805a20e15762c3c.js +1 -0
- package/dist/web/standalone/.next/static/chunks/app/api/captures/route-8805a20e15762c3c.js +1 -0
- package/dist/web/standalone/.next/static/chunks/app/api/cleanup/route-8805a20e15762c3c.js +1 -0
- package/dist/web/standalone/.next/static/chunks/app/api/dev-mode/route-8805a20e15762c3c.js +1 -0
- package/dist/web/standalone/.next/static/chunks/app/api/doctor/route-8805a20e15762c3c.js +1 -0
- package/dist/web/standalone/.next/static/chunks/app/api/experimental/route-8805a20e15762c3c.js +1 -0
- package/dist/web/standalone/.next/static/chunks/app/api/export-data/route-8805a20e15762c3c.js +1 -0
- package/dist/web/standalone/.next/static/chunks/app/api/files/route-8805a20e15762c3c.js +1 -0
- package/dist/web/standalone/.next/static/chunks/app/api/forensics/route-8805a20e15762c3c.js +1 -0
- package/dist/web/standalone/.next/static/chunks/app/api/git/route-8805a20e15762c3c.js +1 -0
- package/dist/web/standalone/.next/static/chunks/app/api/history/route-8805a20e15762c3c.js +1 -0
- package/dist/web/standalone/.next/static/chunks/app/api/hooks/route-8805a20e15762c3c.js +1 -0
- package/dist/web/standalone/.next/static/chunks/app/api/inspect/route-8805a20e15762c3c.js +1 -0
- package/dist/web/standalone/.next/static/chunks/app/api/knowledge/route-8805a20e15762c3c.js +1 -0
- package/dist/web/standalone/.next/static/chunks/app/api/live-state/route-8805a20e15762c3c.js +1 -0
- package/dist/web/standalone/.next/static/chunks/app/api/notifications/route-8805a20e15762c3c.js +1 -0
- package/dist/web/standalone/.next/static/chunks/app/api/onboarding/route-8805a20e15762c3c.js +1 -0
- package/dist/web/standalone/.next/static/chunks/app/api/preferences/route-8805a20e15762c3c.js +1 -0
- package/dist/web/standalone/.next/static/chunks/app/api/projects/route-8805a20e15762c3c.js +1 -0
- package/dist/web/standalone/.next/static/chunks/app/api/recovery/route-8805a20e15762c3c.js +1 -0
- package/dist/web/standalone/.next/static/chunks/app/api/remote-questions/route-8805a20e15762c3c.js +1 -0
- package/dist/web/standalone/.next/static/chunks/app/api/session/browser/route-8805a20e15762c3c.js +1 -0
- package/dist/web/standalone/.next/static/chunks/app/api/session/command/route-8805a20e15762c3c.js +1 -0
- package/dist/web/standalone/.next/static/chunks/app/api/session/events/route-8805a20e15762c3c.js +1 -0
- package/dist/web/standalone/.next/static/chunks/app/api/session/manage/route-8805a20e15762c3c.js +1 -0
- package/dist/web/standalone/.next/static/chunks/app/api/settings-data/route-8805a20e15762c3c.js +1 -0
- package/dist/web/standalone/.next/static/chunks/app/api/shutdown/route-8805a20e15762c3c.js +1 -0
- package/dist/web/standalone/.next/static/chunks/app/api/skill-health/route-8805a20e15762c3c.js +1 -0
- package/dist/web/standalone/.next/static/chunks/app/api/steer/route-8805a20e15762c3c.js +1 -0
- package/dist/web/standalone/.next/static/chunks/app/api/switch-root/route-8805a20e15762c3c.js +1 -0
- package/dist/web/standalone/.next/static/chunks/app/api/terminal/input/route-8805a20e15762c3c.js +1 -0
- package/dist/web/standalone/.next/static/chunks/app/api/terminal/resize/route-8805a20e15762c3c.js +1 -0
- package/dist/web/standalone/.next/static/chunks/app/api/terminal/sessions/route-8805a20e15762c3c.js +1 -0
- package/dist/web/standalone/.next/static/chunks/app/api/terminal/stream/route-8805a20e15762c3c.js +1 -0
- package/dist/web/standalone/.next/static/chunks/app/api/terminal/upload/route-8805a20e15762c3c.js +1 -0
- package/dist/web/standalone/.next/static/chunks/app/api/undo/route-8805a20e15762c3c.js +1 -0
- package/dist/web/standalone/.next/static/chunks/app/api/update/route-8805a20e15762c3c.js +1 -0
- package/dist/web/standalone/.next/static/chunks/app/api/visualizer/route-8805a20e15762c3c.js +1 -0
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/app-error-8805a20e15762c3c.js +1 -0
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/forbidden-8805a20e15762c3c.js +1 -0
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/not-found-8805a20e15762c3c.js +1 -0
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/unauthorized-8805a20e15762c3c.js +1 -0
- package/dist/web/standalone/.next/static/l7tiSF0KtXOwxxYn0ZAyF/_buildManifest.js +1 -0
- package/package.json +1 -1
- package/packages/pi-agent-core/dist/agent-loop.js +26 -9
- package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/packages/pi-agent-core/src/agent-loop.test.ts +100 -4
- package/packages/pi-agent-core/src/agent-loop.ts +43 -12
- package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js +38 -0
- package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +11 -0
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/resource-loader-cache-reset.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/resource-loader-cache-reset.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/resource-loader-cache-reset.test.js +24 -0
- package/packages/pi-coding-agent/dist/core/resource-loader-cache-reset.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/resource-loader.js +4 -1
- package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +8 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +6 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +36 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/src/core/agent-session-tool-refresh.test.ts +64 -0
- package/packages/pi-coding-agent/src/core/agent-session.ts +10 -0
- package/packages/pi-coding-agent/src/core/resource-loader-cache-reset.test.ts +42 -0
- package/packages/pi-coding-agent/src/core/resource-loader.ts +5 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +9 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +33 -0
- package/packages/pi-tui/dist/__tests__/overlay-layout.test.d.ts +2 -0
- package/packages/pi-tui/dist/__tests__/overlay-layout.test.d.ts.map +1 -0
- package/packages/pi-tui/dist/__tests__/overlay-layout.test.js +66 -0
- package/packages/pi-tui/dist/__tests__/overlay-layout.test.js.map +1 -0
- package/packages/pi-tui/dist/components/loader.d.ts +4 -2
- package/packages/pi-tui/dist/components/loader.d.ts.map +1 -1
- package/packages/pi-tui/dist/components/loader.js +27 -9
- package/packages/pi-tui/dist/components/loader.js.map +1 -1
- package/packages/pi-tui/dist/components/text.d.ts.map +1 -1
- package/packages/pi-tui/dist/components/text.js +2 -0
- package/packages/pi-tui/dist/components/text.js.map +1 -1
- package/packages/pi-tui/dist/overlay-layout.d.ts.map +1 -1
- package/packages/pi-tui/dist/overlay-layout.js +12 -1
- package/packages/pi-tui/dist/overlay-layout.js.map +1 -1
- package/packages/pi-tui/dist/tui.d.ts +4 -0
- package/packages/pi-tui/dist/tui.d.ts.map +1 -1
- package/packages/pi-tui/dist/tui.js +35 -0
- package/packages/pi-tui/dist/tui.js.map +1 -1
- package/packages/pi-tui/src/__tests__/overlay-layout.test.ts +82 -0
- package/packages/pi-tui/src/components/loader.ts +27 -10
- package/packages/pi-tui/src/components/text.ts +1 -0
- package/packages/pi-tui/src/overlay-layout.ts +14 -1
- package/packages/pi-tui/src/tui.ts +34 -0
- package/src/resources/extensions/bg-shell/bg-shell-lifecycle.ts +19 -7
- package/src/resources/extensions/bg-shell/process-manager.ts +8 -2
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +25 -13
- package/src/resources/extensions/gsd/bootstrap/notify-interceptor.ts +34 -0
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +8 -0
- package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +20 -0
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +28 -0
- package/src/resources/extensions/gsd/commands/catalog.ts +7 -1
- package/src/resources/extensions/gsd/commands/handlers/core.ts +1 -0
- package/src/resources/extensions/gsd/commands/handlers/notifications-handler.ts +140 -0
- package/src/resources/extensions/gsd/commands/handlers/ops.ts +5 -0
- package/src/resources/extensions/gsd/notification-overlay.ts +266 -0
- package/src/resources/extensions/gsd/notification-store.ts +293 -0
- package/src/resources/extensions/gsd/notification-widget.ts +68 -0
- package/src/resources/extensions/gsd/tests/complete-slice-string-coercion.test.ts +36 -0
- package/src/resources/extensions/gsd/tests/discuss-tool-scope-leak.test.ts +76 -0
- package/src/resources/extensions/gsd/tests/notification-store.test.ts +282 -0
- package/src/resources/extensions/gsd/tests/unstructured-continue-context-injection.test.ts +163 -0
- package/src/resources/extensions/gsd/workflow-logger.ts +13 -0
- package/dist/web/standalone/.next/static/SoxM61WC_ia7R2gk4VMpJ/_buildManifest.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/_global-error/page-c4cc189e7b117ea2.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/api/boot/route-c4cc189e7b117ea2.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/api/bridge-terminal/input/route-c4cc189e7b117ea2.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/api/bridge-terminal/resize/route-c4cc189e7b117ea2.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/api/bridge-terminal/stream/route-c4cc189e7b117ea2.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/api/browse-directories/route-c4cc189e7b117ea2.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/api/captures/route-c4cc189e7b117ea2.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/api/cleanup/route-c4cc189e7b117ea2.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/api/dev-mode/route-c4cc189e7b117ea2.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/api/doctor/route-c4cc189e7b117ea2.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/api/experimental/route-c4cc189e7b117ea2.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/api/export-data/route-c4cc189e7b117ea2.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/api/files/route-c4cc189e7b117ea2.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/api/forensics/route-c4cc189e7b117ea2.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/api/git/route-c4cc189e7b117ea2.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/api/history/route-c4cc189e7b117ea2.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/api/hooks/route-c4cc189e7b117ea2.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/api/inspect/route-c4cc189e7b117ea2.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/api/knowledge/route-c4cc189e7b117ea2.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/api/live-state/route-c4cc189e7b117ea2.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/api/onboarding/route-c4cc189e7b117ea2.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/api/preferences/route-c4cc189e7b117ea2.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/api/projects/route-c4cc189e7b117ea2.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/api/recovery/route-c4cc189e7b117ea2.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/api/remote-questions/route-c4cc189e7b117ea2.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/api/session/browser/route-c4cc189e7b117ea2.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/api/session/command/route-c4cc189e7b117ea2.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/api/session/events/route-c4cc189e7b117ea2.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/api/session/manage/route-c4cc189e7b117ea2.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/api/settings-data/route-c4cc189e7b117ea2.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/api/shutdown/route-c4cc189e7b117ea2.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/api/skill-health/route-c4cc189e7b117ea2.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/api/steer/route-c4cc189e7b117ea2.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/api/switch-root/route-c4cc189e7b117ea2.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/api/terminal/input/route-c4cc189e7b117ea2.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/api/terminal/resize/route-c4cc189e7b117ea2.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/api/terminal/sessions/route-c4cc189e7b117ea2.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/api/terminal/stream/route-c4cc189e7b117ea2.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/api/terminal/upload/route-c4cc189e7b117ea2.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/api/undo/route-c4cc189e7b117ea2.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/api/update/route-c4cc189e7b117ea2.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/api/visualizer/route-c4cc189e7b117ea2.js +0 -1
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/app-error-c4cc189e7b117ea2.js +0 -1
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/forbidden-c4cc189e7b117ea2.js +0 -1
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/not-found-c4cc189e7b117ea2.js +0 -1
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/unauthorized-c4cc189e7b117ea2.js +0 -1
- /package/dist/web/standalone/.next/static/{SoxM61WC_ia7R2gk4VMpJ → l7tiSF0KtXOwxxYn0ZAyF}/_ssgManifest.js +0 -0
|
@@ -2,13 +2,16 @@ import type { TUI } from "../tui.js";
|
|
|
2
2
|
import { Text } from "./text.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Loader component that updates every 80ms with spinning animation
|
|
5
|
+
* Loader component that updates every 80ms with spinning animation.
|
|
6
|
+
* Frame rotation is isolated from message text to avoid invalidating
|
|
7
|
+
* Text's render cache (wrapTextWithAnsi, visibleWidth) on every tick.
|
|
6
8
|
*/
|
|
7
9
|
export class Loader extends Text {
|
|
8
10
|
private frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
9
11
|
private currentFrame = 0;
|
|
10
12
|
private intervalId: NodeJS.Timeout | null = null;
|
|
11
13
|
private ui: TUI | null = null;
|
|
14
|
+
private _lastMessage: string = "";
|
|
12
15
|
|
|
13
16
|
constructor(
|
|
14
17
|
ui: TUI,
|
|
@@ -22,18 +25,38 @@ export class Loader extends Text {
|
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
render(width: number): string[] {
|
|
25
|
-
|
|
28
|
+
// Only update Text content when message actually changes —
|
|
29
|
+
// frame rotation is prepended below without touching the cache
|
|
30
|
+
if (this.message !== this._lastMessage) {
|
|
31
|
+
this.setText(this.messageColorFn(this.message));
|
|
32
|
+
this._lastMessage = this.message;
|
|
33
|
+
}
|
|
34
|
+
const messageLines = super.render(width);
|
|
35
|
+
// Shallow copy so we don't mutate cachedLines from Text
|
|
36
|
+
const result = ["", ...messageLines];
|
|
37
|
+
// Prepend spinner frame to first content line
|
|
38
|
+
if (result.length > 1) {
|
|
39
|
+
const frame = this.frames[this.currentFrame];
|
|
40
|
+
result[1] = this.spinnerColorFn(frame) + " " + result[1];
|
|
41
|
+
}
|
|
42
|
+
return result;
|
|
26
43
|
}
|
|
27
44
|
|
|
28
45
|
start() {
|
|
29
46
|
if (this.intervalId) {
|
|
30
47
|
clearInterval(this.intervalId);
|
|
31
48
|
}
|
|
32
|
-
this.
|
|
49
|
+
this.currentFrame = 0;
|
|
33
50
|
this.intervalId = setInterval(() => {
|
|
34
51
|
this.currentFrame = (this.currentFrame + 1) % this.frames.length;
|
|
35
|
-
this.
|
|
52
|
+
if (this.ui) {
|
|
53
|
+
this.ui.requestRender();
|
|
54
|
+
}
|
|
36
55
|
}, 80);
|
|
56
|
+
// Trigger initial render
|
|
57
|
+
if (this.ui) {
|
|
58
|
+
this.ui.requestRender();
|
|
59
|
+
}
|
|
37
60
|
}
|
|
38
61
|
|
|
39
62
|
stop() {
|
|
@@ -50,12 +73,6 @@ export class Loader extends Text {
|
|
|
50
73
|
|
|
51
74
|
setMessage(message: string) {
|
|
52
75
|
this.message = message;
|
|
53
|
-
this.updateDisplay();
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
private updateDisplay() {
|
|
57
|
-
const frame = this.frames[this.currentFrame];
|
|
58
|
-
this.setText(`${this.spinnerColorFn(frame)} ${this.messageColorFn(this.message)}`);
|
|
59
76
|
if (this.ui) {
|
|
60
77
|
this.ui.requestRender();
|
|
61
78
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { OverlayAnchor, OverlayOptions, SizeValue } from "./tui.js";
|
|
9
|
-
import { extractSegments, sliceByColumn, sliceWithWidth, truncateToWidth, visibleWidth } from "./utils.js";
|
|
9
|
+
import { applyBackgroundToLine, extractSegments, sliceByColumn, sliceWithWidth, truncateToWidth, visibleWidth } from "./utils.js";
|
|
10
10
|
import { isImageLine } from "./terminal-image.js";
|
|
11
11
|
import { CURSOR_MARKER } from "./tui.js";
|
|
12
12
|
|
|
@@ -324,6 +324,19 @@ export function compositeOverlays(
|
|
|
324
324
|
|
|
325
325
|
const viewportStart = Math.max(0, workingHeight - termHeight);
|
|
326
326
|
|
|
327
|
+
// Apply backdrop dimming if any visible overlay requests it.
|
|
328
|
+
// Uses dim + dark gray background (256-color 233) so the overlay pops visually.
|
|
329
|
+
const hasBackdrop = visibleEntries.some((e) => e.options?.backdrop);
|
|
330
|
+
if (hasBackdrop) {
|
|
331
|
+
const dimFn = (text: string) =>
|
|
332
|
+
`\x1b[2m\x1b[38;5;242m\x1b[48;5;233m${text}\x1b[49m\x1b[39m\x1b[22m`;
|
|
333
|
+
for (let i = viewportStart; i < result.length; i++) {
|
|
334
|
+
if (!isImageLine(result[i]) && result[i].length > 0) {
|
|
335
|
+
result[i] = applyBackgroundToLine(result[i], termWidth, dimFn);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
327
340
|
// Composite each overlay
|
|
328
341
|
for (const { overlayLines, row, col, w } of rendered) {
|
|
329
342
|
for (let i = 0; i < overlayLines.length; i++) {
|
|
@@ -141,6 +141,8 @@ export interface OverlayOptions {
|
|
|
141
141
|
visible?: (termWidth: number, termHeight: number) => boolean;
|
|
142
142
|
/** If true, don't capture keyboard focus when shown */
|
|
143
143
|
nonCapturing?: boolean;
|
|
144
|
+
/** If true, dim the background behind the overlay */
|
|
145
|
+
backdrop?: boolean;
|
|
144
146
|
}
|
|
145
147
|
|
|
146
148
|
/**
|
|
@@ -166,20 +168,33 @@ export interface OverlayHandle {
|
|
|
166
168
|
*/
|
|
167
169
|
export class Container implements Component {
|
|
168
170
|
children: Component[] = [];
|
|
171
|
+
private _prevRender: string[] | null = null;
|
|
169
172
|
|
|
170
173
|
addChild(component: Component): void {
|
|
171
174
|
this.children.push(component);
|
|
175
|
+
this._prevRender = null;
|
|
172
176
|
}
|
|
173
177
|
|
|
174
178
|
removeChild(component: Component): void {
|
|
175
179
|
const index = this.children.indexOf(component);
|
|
176
180
|
if (index !== -1) {
|
|
181
|
+
const child = this.children[index];
|
|
177
182
|
this.children.splice(index, 1);
|
|
183
|
+
if ('dispose' in child && typeof (child as any).dispose === 'function') {
|
|
184
|
+
(child as any).dispose();
|
|
185
|
+
}
|
|
186
|
+
this._prevRender = null;
|
|
178
187
|
}
|
|
179
188
|
}
|
|
180
189
|
|
|
181
190
|
clear(): void {
|
|
191
|
+
for (const child of this.children) {
|
|
192
|
+
if ('dispose' in child && typeof (child as any).dispose === 'function') {
|
|
193
|
+
(child as any).dispose();
|
|
194
|
+
}
|
|
195
|
+
}
|
|
182
196
|
this.children = [];
|
|
197
|
+
this._prevRender = null;
|
|
183
198
|
}
|
|
184
199
|
|
|
185
200
|
invalidate(): void {
|
|
@@ -194,6 +209,17 @@ export class Container implements Component {
|
|
|
194
209
|
const rendered = child.render(width);
|
|
195
210
|
for (let i = 0; i < rendered.length; i++) lines.push(rendered[i]);
|
|
196
211
|
}
|
|
212
|
+
// Return stable reference if output unchanged — allows doRender()
|
|
213
|
+
// to skip ALL post-processing (isImageLine, applyLineResets, diffs)
|
|
214
|
+
const prev = this._prevRender;
|
|
215
|
+
if (prev && prev.length === lines.length) {
|
|
216
|
+
let same = true;
|
|
217
|
+
for (let i = 0; i < lines.length; i++) {
|
|
218
|
+
if (lines[i] !== prev[i]) { same = false; break; }
|
|
219
|
+
}
|
|
220
|
+
if (same) return prev;
|
|
221
|
+
}
|
|
222
|
+
this._prevRender = lines;
|
|
197
223
|
return lines;
|
|
198
224
|
}
|
|
199
225
|
}
|
|
@@ -222,6 +248,7 @@ export class TUI extends Container {
|
|
|
222
248
|
private previousViewportTop = 0; // Track previous viewport top for resize-aware cursor moves
|
|
223
249
|
private fullRedrawCount = 0;
|
|
224
250
|
private stopped = false;
|
|
251
|
+
private _lastRenderedComponents: string[] | null = null;
|
|
225
252
|
|
|
226
253
|
// Overlay stack for modal components rendered on top of base content
|
|
227
254
|
private focusOrderCounter = 0;
|
|
@@ -599,6 +626,13 @@ export class TUI extends Container {
|
|
|
599
626
|
// Render all components to get new lines
|
|
600
627
|
let newLines = this.render(width);
|
|
601
628
|
|
|
629
|
+
// Skip ALL post-processing if component output is unchanged.
|
|
630
|
+
// Container.render() returns the same array reference when stable.
|
|
631
|
+
if (newLines === this._lastRenderedComponents && this.overlayStack.length === 0) {
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
this._lastRenderedComponents = newLines;
|
|
635
|
+
|
|
602
636
|
// Composite overlays into the rendered lines (before differential compare)
|
|
603
637
|
if (this.overlayStack.length > 0) {
|
|
604
638
|
newLines = compositeOverlays(newLines, this.overlayStack, width, height, this.maxLinesRendered);
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
import {
|
|
17
17
|
processes,
|
|
18
18
|
pendingAlerts,
|
|
19
|
+
pushAlert,
|
|
19
20
|
cleanupAll,
|
|
20
21
|
cleanupSessionProcesses,
|
|
21
22
|
persistManifest,
|
|
@@ -37,19 +38,30 @@ export function registerBgShellLifecycle(pi: ExtensionAPI, state: BgShellSharedS
|
|
|
37
38
|
}
|
|
38
39
|
}
|
|
39
40
|
|
|
40
|
-
// Clean up on session shutdown
|
|
41
|
-
pi.on("session_shutdown", async () => {
|
|
42
|
-
cleanupAll();
|
|
43
|
-
});
|
|
44
|
-
|
|
45
41
|
// Register signal handlers to clean up bg processes on unexpected exit (fixes #428)
|
|
46
42
|
const signalCleanup = () => {
|
|
47
43
|
cleanupAll();
|
|
44
|
+
// Also kill bash-tool spawned children that bg-shell doesn't track
|
|
45
|
+
try {
|
|
46
|
+
const { listDescendants } = require("@gsd/native") as typeof import("@gsd/native");
|
|
47
|
+
const descendants = listDescendants(process.pid);
|
|
48
|
+
for (const childPid of descendants) {
|
|
49
|
+
try { process.kill(childPid, "SIGKILL"); } catch {}
|
|
50
|
+
}
|
|
51
|
+
} catch {}
|
|
48
52
|
};
|
|
49
53
|
process.on("SIGTERM", signalCleanup);
|
|
50
54
|
process.on("SIGINT", signalCleanup);
|
|
51
55
|
process.on("beforeExit", signalCleanup);
|
|
52
56
|
|
|
57
|
+
// Clean up on session shutdown — remove signal handlers to prevent accumulation
|
|
58
|
+
pi.on("session_shutdown", async () => {
|
|
59
|
+
process.off("SIGTERM", signalCleanup);
|
|
60
|
+
process.off("SIGINT", signalCleanup);
|
|
61
|
+
process.off("beforeExit", signalCleanup);
|
|
62
|
+
cleanupAll();
|
|
63
|
+
});
|
|
64
|
+
|
|
53
65
|
// ── Compaction Awareness: Survive Context Resets ───────────────
|
|
54
66
|
|
|
55
67
|
/** Build a compact state summary of all alive processes for context re-injection */
|
|
@@ -65,7 +77,7 @@ export function registerBgShellLifecycle(pi: ExtensionAPI, state: BgShellSharedS
|
|
|
65
77
|
return ` - id:${p.id} "${p.label}" [${p.processType}] status:${p.status} uptime:${formatUptime(Date.now() - p.startedAt)}${portInfo}${urlInfo}${errInfo}${groupInfo}`;
|
|
66
78
|
}).join("\n");
|
|
67
79
|
|
|
68
|
-
|
|
80
|
+
pushAlert(null,
|
|
69
81
|
`${reason} ${alive.length} background process(es) are still running:\n${processSummaries}\nUse bg_shell digest/output/kill with these IDs.`
|
|
70
82
|
);
|
|
71
83
|
}
|
|
@@ -150,7 +162,7 @@ export function registerBgShellLifecycle(pi: ExtensionAPI, state: BgShellSharedS
|
|
|
150
162
|
` - ${s.id}: ${s.label} (pid ${s.pid}, type: ${s.processType}${s.group ? `, group: ${s.group}` : ""})`
|
|
151
163
|
).join("\n");
|
|
152
164
|
|
|
153
|
-
|
|
165
|
+
pushAlert(null,
|
|
154
166
|
`${surviving.length} background process(es) from previous session still running:\n${summary}\n Note: These processes are outside bg_shell's control. Kill them manually if needed.`
|
|
155
167
|
);
|
|
156
168
|
}
|
|
@@ -33,6 +33,8 @@ export const processes = new Map<string, BgProcess>();
|
|
|
33
33
|
/** Pending alerts to inject into the next agent context */
|
|
34
34
|
export let pendingAlerts: string[] = [];
|
|
35
35
|
|
|
36
|
+
const MAX_PENDING_ALERTS = 50;
|
|
37
|
+
|
|
36
38
|
/** Replace the pendingAlerts array (used by the extension entry point) */
|
|
37
39
|
export function setPendingAlerts(alerts: string[]): void {
|
|
38
40
|
pendingAlerts = alerts;
|
|
@@ -58,8 +60,12 @@ export function addEvent(bg: BgProcess, event: Omit<ProcessEvent, "timestamp">):
|
|
|
58
60
|
}
|
|
59
61
|
}
|
|
60
62
|
|
|
61
|
-
export function pushAlert(bg: BgProcess, message: string): void {
|
|
62
|
-
|
|
63
|
+
export function pushAlert(bg: BgProcess | null, message: string): void {
|
|
64
|
+
const prefix = bg ? `[bg:${bg.id} ${bg.label}] ` : "";
|
|
65
|
+
pendingAlerts.push(`${prefix}${message}`);
|
|
66
|
+
if (pendingAlerts.length > MAX_PENDING_ALERTS) {
|
|
67
|
+
pendingAlerts.splice(0, pendingAlerts.length - MAX_PENDING_ALERTS);
|
|
68
|
+
}
|
|
63
69
|
}
|
|
64
70
|
|
|
65
71
|
export function getInfo(p: BgProcess): BgProcessInfo {
|
|
@@ -804,27 +804,39 @@ export function registerDbTools(pi: ExtensionAPI): void {
|
|
|
804
804
|
return m ? [m[1].trim(), m[2].trim()] : [s.trim(), ""];
|
|
805
805
|
};
|
|
806
806
|
const coerced = { ...params };
|
|
807
|
-
|
|
807
|
+
// Coerce simple string-array fields: LLMs sometimes pass a plain string
|
|
808
|
+
// instead of a single-element array (#3585).
|
|
809
|
+
const wrapArray = (v: any): any[] =>
|
|
810
|
+
v == null ? [] : Array.isArray(v) ? v : [v];
|
|
811
|
+
coerced.provides = wrapArray(params.provides);
|
|
812
|
+
coerced.keyFiles = wrapArray(params.keyFiles);
|
|
813
|
+
coerced.keyDecisions = wrapArray(params.keyDecisions);
|
|
814
|
+
coerced.patternsEstablished = wrapArray(params.patternsEstablished);
|
|
815
|
+
coerced.observabilitySurfaces = wrapArray(params.observabilitySurfaces);
|
|
816
|
+
coerced.requirementsSurfaced = wrapArray(params.requirementsSurfaced);
|
|
817
|
+
coerced.drillDownPaths = wrapArray(params.drillDownPaths);
|
|
818
|
+
coerced.affects = wrapArray(params.affects);
|
|
819
|
+
coerced.filesModified = wrapArray(params.filesModified).map((f: any) => {
|
|
808
820
|
if (typeof f !== "string") return f;
|
|
809
821
|
const [path, description] = splitPair(f);
|
|
810
822
|
return { path, description };
|
|
811
823
|
});
|
|
812
|
-
coerced.requires = (params.requires
|
|
824
|
+
coerced.requires = wrapArray(params.requires).map((r: any) => {
|
|
813
825
|
if (typeof r !== "string") return r;
|
|
814
826
|
const [slice, provides] = splitPair(r);
|
|
815
827
|
return { slice, provides };
|
|
816
828
|
});
|
|
817
|
-
coerced.requirementsAdvanced = (params.requirementsAdvanced
|
|
829
|
+
coerced.requirementsAdvanced = wrapArray(params.requirementsAdvanced).map((r: any) => {
|
|
818
830
|
if (typeof r !== "string") return r;
|
|
819
831
|
const [id, how] = splitPair(r);
|
|
820
832
|
return { id, how };
|
|
821
833
|
});
|
|
822
|
-
coerced.requirementsValidated = (params.requirementsValidated
|
|
834
|
+
coerced.requirementsValidated = wrapArray(params.requirementsValidated).map((r: any) => {
|
|
823
835
|
if (typeof r !== "string") return r;
|
|
824
836
|
const [id, proof] = splitPair(r);
|
|
825
837
|
return { id, proof };
|
|
826
838
|
});
|
|
827
|
-
coerced.requirementsInvalidated = (params.requirementsInvalidated
|
|
839
|
+
coerced.requirementsInvalidated = wrapArray(params.requirementsInvalidated).map((r: any) => {
|
|
828
840
|
if (typeof r !== "string") return r;
|
|
829
841
|
const [id, what] = splitPair(r);
|
|
830
842
|
return { id, what };
|
|
@@ -884,14 +896,14 @@ export function registerDbTools(pi: ExtensionAPI): void {
|
|
|
884
896
|
deviations: Type.Optional(Type.String({ description: "Deviations from the slice plan, or 'None.'" })),
|
|
885
897
|
knownLimitations: Type.Optional(Type.String({ description: "Known limitations or gaps, or 'None.'" })),
|
|
886
898
|
followUps: Type.Optional(Type.String({ description: "Follow-up work discovered during execution, or 'None.'" })),
|
|
887
|
-
keyFiles: Type.Optional(Type.Array(Type.String(), { description: "Key files created or modified" })),
|
|
888
|
-
keyDecisions: Type.Optional(Type.Array(Type.String(), { description: "Key decisions made during this slice" })),
|
|
889
|
-
patternsEstablished: Type.Optional(Type.Array(Type.String(), { description: "Patterns established by this slice" })),
|
|
890
|
-
observabilitySurfaces: Type.Optional(Type.Array(Type.String(), { description: "Observability surfaces added" })),
|
|
891
|
-
provides: Type.Optional(Type.Array(Type.String(), { description: "What this slice provides to downstream slices" })),
|
|
892
|
-
requirementsSurfaced: Type.Optional(Type.Array(Type.String(), { description: "New requirements surfaced" })),
|
|
893
|
-
drillDownPaths: Type.Optional(Type.Array(Type.String(), { description: "Paths to task summaries for drill-down" })),
|
|
894
|
-
affects: Type.Optional(Type.Array(Type.String(), { description: "Downstream slices affected" })),
|
|
899
|
+
keyFiles: Type.Optional(Type.Union([Type.Array(Type.String()), Type.String()], { description: "Key files created or modified" })),
|
|
900
|
+
keyDecisions: Type.Optional(Type.Union([Type.Array(Type.String()), Type.String()], { description: "Key decisions made during this slice" })),
|
|
901
|
+
patternsEstablished: Type.Optional(Type.Union([Type.Array(Type.String()), Type.String()], { description: "Patterns established by this slice" })),
|
|
902
|
+
observabilitySurfaces: Type.Optional(Type.Union([Type.Array(Type.String()), Type.String()], { description: "Observability surfaces added" })),
|
|
903
|
+
provides: Type.Optional(Type.Union([Type.Array(Type.String()), Type.String()], { description: "What this slice provides to downstream slices" })),
|
|
904
|
+
requirementsSurfaced: Type.Optional(Type.Union([Type.Array(Type.String()), Type.String()], { description: "New requirements surfaced" })),
|
|
905
|
+
drillDownPaths: Type.Optional(Type.Union([Type.Array(Type.String()), Type.String()], { description: "Paths to task summaries for drill-down" })),
|
|
906
|
+
affects: Type.Optional(Type.Union([Type.Array(Type.String()), Type.String()], { description: "Downstream slices affected" })),
|
|
895
907
|
requirementsAdvanced: Type.Optional(Type.Array(
|
|
896
908
|
Type.Union([
|
|
897
909
|
Type.Object({
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// GSD Extension — Notify Interceptor
|
|
2
|
+
// Wraps ctx.ui.notify() in-place to persist every notification through the
|
|
3
|
+
// notification store. Uses a WeakSet to prevent double-wrapping and handle
|
|
4
|
+
// UI context replacement on /reload gracefully.
|
|
5
|
+
|
|
6
|
+
import type { ExtensionContext } from "@gsd/pi-coding-agent";
|
|
7
|
+
|
|
8
|
+
import { appendNotification, type NotifySeverity } from "../notification-store.js";
|
|
9
|
+
|
|
10
|
+
// Track which ui context objects have been wrapped to prevent double-install.
|
|
11
|
+
// WeakSet allows GC to collect replaced uiContext instances after /reload.
|
|
12
|
+
const _wrappedContexts = new WeakSet<object>();
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Install the notify interceptor on a context's UI object.
|
|
16
|
+
* Mutates ctx.ui.notify in place — the original is called after persistence.
|
|
17
|
+
* Safe to call multiple times; no-ops if already installed on the same ui object.
|
|
18
|
+
*/
|
|
19
|
+
export function installNotifyInterceptor(ctx: ExtensionContext): void {
|
|
20
|
+
if (_wrappedContexts.has(ctx.ui)) return;
|
|
21
|
+
|
|
22
|
+
const originalNotify = ctx.ui.notify.bind(ctx.ui);
|
|
23
|
+
|
|
24
|
+
(ctx.ui as any).notify = (message: string, type?: "info" | "warning" | "error" | "success"): void => {
|
|
25
|
+
try {
|
|
26
|
+
appendNotification(message, (type ?? "info") as NotifySeverity, "notify");
|
|
27
|
+
} catch {
|
|
28
|
+
// Non-fatal — never let persistence break the UI
|
|
29
|
+
}
|
|
30
|
+
originalNotify(message, type);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
_wrappedContexts.add(ctx.ui);
|
|
34
|
+
}
|
|
@@ -21,6 +21,9 @@ import { resetAskUserQuestionsCache } from "../../ask-user-questions.js";
|
|
|
21
21
|
import { recordToolCall as safetyRecordToolCall, recordToolResult as safetyRecordToolResult } from "../safety/evidence-collector.js";
|
|
22
22
|
import { classifyCommand } from "../safety/destructive-guard.js";
|
|
23
23
|
import { logWarning as safetyLogWarning } from "../workflow-logger.js";
|
|
24
|
+
import { installNotifyInterceptor } from "./notify-interceptor.js";
|
|
25
|
+
import { initNotificationStore } from "../notification-store.js";
|
|
26
|
+
import { initNotificationWidget } from "../notification-widget.js";
|
|
24
27
|
|
|
25
28
|
// Skip the welcome screen on the very first session_start — cli.ts already
|
|
26
29
|
// printed it before the TUI launched. Only re-print on /clear (subsequent sessions).
|
|
@@ -33,6 +36,9 @@ async function syncServiceTierStatus(ctx: ExtensionContext): Promise<void> {
|
|
|
33
36
|
|
|
34
37
|
export function registerHooks(pi: ExtensionAPI): void {
|
|
35
38
|
pi.on("session_start", async (_event, ctx) => {
|
|
39
|
+
initNotificationStore(process.cwd());
|
|
40
|
+
installNotifyInterceptor(ctx);
|
|
41
|
+
initNotificationWidget(ctx);
|
|
36
42
|
resetWriteGateState();
|
|
37
43
|
resetToolCallLoopGuard();
|
|
38
44
|
resetAskUserQuestionsCache();
|
|
@@ -70,6 +76,8 @@ export function registerHooks(pi: ExtensionAPI): void {
|
|
|
70
76
|
});
|
|
71
77
|
|
|
72
78
|
pi.on("session_switch", async (_event, ctx) => {
|
|
79
|
+
initNotificationStore(process.cwd());
|
|
80
|
+
installNotifyInterceptor(ctx);
|
|
73
81
|
resetWriteGateState();
|
|
74
82
|
resetToolCallLoopGuard();
|
|
75
83
|
resetAskUserQuestionsCache();
|
|
@@ -5,6 +5,7 @@ import type { ExtensionAPI } from "@gsd/pi-coding-agent";
|
|
|
5
5
|
import { Key } from "@gsd/pi-tui";
|
|
6
6
|
|
|
7
7
|
import { GSDDashboardOverlay } from "../dashboard-overlay.js";
|
|
8
|
+
import { GSDNotificationOverlay } from "../notification-overlay.js";
|
|
8
9
|
import { ParallelMonitorOverlay } from "../parallel-monitor-overlay.js";
|
|
9
10
|
import { shortcutDesc } from "../../shared/mod.js";
|
|
10
11
|
|
|
@@ -31,6 +32,25 @@ export function registerShortcuts(pi: ExtensionAPI): void {
|
|
|
31
32
|
},
|
|
32
33
|
});
|
|
33
34
|
|
|
35
|
+
pi.registerShortcut(Key.ctrlAlt("n"), {
|
|
36
|
+
description: shortcutDesc("Open notification history", "/gsd notifications"),
|
|
37
|
+
handler: async (ctx) => {
|
|
38
|
+
await ctx.ui.custom<void>(
|
|
39
|
+
(tui, theme, _kb, done) => new GSDNotificationOverlay(tui, theme, () => done()),
|
|
40
|
+
{
|
|
41
|
+
overlay: true,
|
|
42
|
+
overlayOptions: {
|
|
43
|
+
width: "80%",
|
|
44
|
+
minWidth: 60,
|
|
45
|
+
maxHeight: "88%",
|
|
46
|
+
anchor: "center",
|
|
47
|
+
backdrop: true,
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
);
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
|
|
34
54
|
pi.registerShortcut(Key.ctrlAlt("p"), {
|
|
35
55
|
description: shortcutDesc("Open parallel worker monitor", "/gsd parallel watch"),
|
|
36
56
|
handler: async (ctx) => {
|
|
@@ -264,6 +264,13 @@ function buildWorktreeContextBlock(): string {
|
|
|
264
264
|
return "";
|
|
265
265
|
}
|
|
266
266
|
|
|
267
|
+
/**
|
|
268
|
+
* Low-entropy resume intent patterns — short phrases a user types to
|
|
269
|
+
* continue work after a pause, rate limit, or context reset (#3615).
|
|
270
|
+
* Tested against the trimmed, lowercased prompt with trailing punctuation stripped.
|
|
271
|
+
*/
|
|
272
|
+
const RESUME_INTENT_PATTERNS = /^(continue|resume|ok|go|go ahead|proceed|keep going|carry on|next|yes|yeah|yep|sure|do it|let's go|pick up where you left off)$/;
|
|
273
|
+
|
|
267
274
|
async function buildGuidedExecuteContextInjection(prompt: string, basePath: string): Promise<string | null> {
|
|
268
275
|
const executeMatch = prompt.match(/Execute the next task:\s+(T\d+)\s+\("([^"]+)"\)\s+in slice\s+(S\d+)\s+of milestone\s+(M\d+(?:-[a-z0-9]{6})?)/i);
|
|
269
276
|
if (executeMatch) {
|
|
@@ -280,6 +287,27 @@ async function buildGuidedExecuteContextInjection(prompt: string, basePath: stri
|
|
|
280
287
|
}
|
|
281
288
|
}
|
|
282
289
|
|
|
290
|
+
// Fallback: low-entropy resume prompt (e.g., "continue", "ok", "go ahead")
|
|
291
|
+
// during an active executing task — inject task context so the agent
|
|
292
|
+
// doesn't rebuild from scratch (#3615).
|
|
293
|
+
// Intent-gated: only fire for short, resume-like prompts to avoid hijacking
|
|
294
|
+
// control/help/diagnostic prompts with unrelated execution context.
|
|
295
|
+
// Phase-gated: only fire during "executing" to avoid misrouting during
|
|
296
|
+
// replanning, gate evaluation, or other non-execution phases.
|
|
297
|
+
const trimmed = prompt.trim().toLowerCase().replace(/[.!?,]+$/g, "");
|
|
298
|
+
if (RESUME_INTENT_PATTERNS.test(trimmed)) {
|
|
299
|
+
const state = await deriveState(basePath);
|
|
300
|
+
if (state.phase === "executing" && state.activeTask && state.activeMilestone && state.activeSlice) {
|
|
301
|
+
return buildTaskExecutionContextInjection(
|
|
302
|
+
basePath,
|
|
303
|
+
state.activeMilestone.id,
|
|
304
|
+
state.activeSlice.id,
|
|
305
|
+
state.activeTask.id,
|
|
306
|
+
state.activeTask.title,
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
283
311
|
return null;
|
|
284
312
|
}
|
|
285
313
|
|
|
@@ -15,7 +15,7 @@ export interface GsdCommandDefinition {
|
|
|
15
15
|
type CompletionMap = Record<string, readonly GsdCommandDefinition[]>;
|
|
16
16
|
|
|
17
17
|
export const GSD_COMMAND_DESCRIPTION =
|
|
18
|
-
"GSD — Get Shit Done: /gsd help|start|templates|next|auto|stop|pause|status|widget|visualize|queue|quick|discuss|capture|triage|dispatch|history|undo|undo-task|reset-slice|rate|skip|export|cleanup|mode|prefs|config|keys|hooks|run-hook|skill-health|doctor|logs|forensics|changelog|migrate|remote|steer|knowledge|new-milestone|parallel|cmux|park|unpark|init|setup|inspect|extensions|update|fast|mcp|rethink|codebase";
|
|
18
|
+
"GSD — Get Shit Done: /gsd help|start|templates|next|auto|stop|pause|status|widget|visualize|queue|quick|discuss|capture|triage|dispatch|history|undo|undo-task|reset-slice|rate|skip|export|cleanup|mode|prefs|config|keys|hooks|run-hook|skill-health|doctor|logs|forensics|changelog|migrate|remote|steer|knowledge|new-milestone|parallel|cmux|park|unpark|init|setup|inspect|extensions|update|fast|mcp|rethink|codebase|notifications";
|
|
19
19
|
|
|
20
20
|
export const TOP_LEVEL_SUBCOMMANDS: readonly GsdCommandDefinition[] = [
|
|
21
21
|
{ cmd: "help", desc: "Categorized command reference with descriptions" },
|
|
@@ -48,6 +48,7 @@ export const TOP_LEVEL_SUBCOMMANDS: readonly GsdCommandDefinition[] = [
|
|
|
48
48
|
{ cmd: "hooks", desc: "Show configured post-unit and pre-dispatch hooks" },
|
|
49
49
|
{ cmd: "run-hook", desc: "Manually trigger a specific hook" },
|
|
50
50
|
{ cmd: "skill-health", desc: "Skill lifecycle dashboard" },
|
|
51
|
+
{ cmd: "notifications", desc: "View, filter, and clear persistent notification history" },
|
|
51
52
|
{ cmd: "doctor", desc: "Runtime health checks with auto-fix" },
|
|
52
53
|
{ cmd: "logs", desc: "Browse activity logs, debug logs, and metrics" },
|
|
53
54
|
{ cmd: "forensics", desc: "Examine execution logs" },
|
|
@@ -110,6 +111,11 @@ const NESTED_COMPLETIONS: CompletionMap = {
|
|
|
110
111
|
{ cmd: "keys", desc: "Manage API keys" },
|
|
111
112
|
{ cmd: "prefs", desc: "Configure global preferences" },
|
|
112
113
|
],
|
|
114
|
+
notifications: [
|
|
115
|
+
{ cmd: "clear", desc: "Clear all notifications" },
|
|
116
|
+
{ cmd: "tail", desc: "Show last N notifications (default: 20)" },
|
|
117
|
+
{ cmd: "filter", desc: "Filter by severity (error|warning|info|success)" },
|
|
118
|
+
],
|
|
113
119
|
logs: [
|
|
114
120
|
{ cmd: "debug", desc: "List or view debug log files" },
|
|
115
121
|
{ cmd: "tail", desc: "Show last N activity log summaries" },
|
|
@@ -29,6 +29,7 @@ export function showHelp(ctx: ExtensionCommandContext): void {
|
|
|
29
29
|
" /gsd queue Show queued/dispatched units and execution order",
|
|
30
30
|
" /gsd history View execution history [--cost] [--phase] [--model] [N]",
|
|
31
31
|
" /gsd changelog Show categorized release notes [version]",
|
|
32
|
+
" /gsd notifications View persistent notification history [clear|tail|filter] (Ctrl+Alt+N)",
|
|
32
33
|
"",
|
|
33
34
|
"COURSE CORRECTION",
|
|
34
35
|
" /gsd steer <desc> Apply user override to active work",
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// GSD Extension — /gsd notifications Command Handler
|
|
2
|
+
// View, filter, and clear the persistent notification history.
|
|
3
|
+
|
|
4
|
+
import type { ExtensionAPI, ExtensionCommandContext } from "@gsd/pi-coding-agent";
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
readNotifications,
|
|
8
|
+
clearNotifications,
|
|
9
|
+
getUnreadCount,
|
|
10
|
+
suppressPersistence,
|
|
11
|
+
unsuppressPersistence,
|
|
12
|
+
type NotifySeverity,
|
|
13
|
+
} from "../../notification-store.js";
|
|
14
|
+
import { GSDNotificationOverlay } from "../../notification-overlay.js";
|
|
15
|
+
|
|
16
|
+
function severityIcon(severity: NotifySeverity): string {
|
|
17
|
+
switch (severity) {
|
|
18
|
+
case "error": return "✗";
|
|
19
|
+
case "warning": return "⚠";
|
|
20
|
+
case "success": return "✓";
|
|
21
|
+
case "info":
|
|
22
|
+
default: return "●";
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function formatTimestamp(ts: string): string {
|
|
27
|
+
try {
|
|
28
|
+
const d = new Date(ts);
|
|
29
|
+
return d.toLocaleString("en-US", { hour12: false, month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" });
|
|
30
|
+
} catch {
|
|
31
|
+
return ts.slice(0, 19);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function handleNotificationsCommand(
|
|
36
|
+
args: string,
|
|
37
|
+
ctx: ExtensionCommandContext,
|
|
38
|
+
pi: ExtensionAPI,
|
|
39
|
+
): Promise<boolean> {
|
|
40
|
+
// /gsd notifications clear
|
|
41
|
+
if (args === "clear") {
|
|
42
|
+
clearNotifications();
|
|
43
|
+
// Suppress persistence so the confirmation toast doesn't re-populate the store
|
|
44
|
+
suppressPersistence();
|
|
45
|
+
try {
|
|
46
|
+
ctx.ui.notify("All notifications cleared.", "success");
|
|
47
|
+
} finally {
|
|
48
|
+
unsuppressPersistence();
|
|
49
|
+
}
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// /gsd notifications tail [N]
|
|
54
|
+
if (args === "tail" || args.startsWith("tail ")) {
|
|
55
|
+
const countStr = args.replace(/^tail\s*/, "").trim();
|
|
56
|
+
const count = countStr ? parseInt(countStr, 10) : 20;
|
|
57
|
+
const n = isNaN(count) || count < 1 ? 20 : Math.min(count, 100);
|
|
58
|
+
const entries = readNotifications().slice(0, n);
|
|
59
|
+
|
|
60
|
+
if (entries.length === 0) {
|
|
61
|
+
ctx.ui.notify("No notifications.", "info");
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const lines = entries.map((e) =>
|
|
66
|
+
`${severityIcon(e.severity)} [${formatTimestamp(e.ts)}] ${e.message}`,
|
|
67
|
+
);
|
|
68
|
+
ctx.ui.notify(`Last ${entries.length} notification(s):\n${lines.join("\n")}`, "info");
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// /gsd notifications filter <severity>
|
|
73
|
+
if (args.startsWith("filter ")) {
|
|
74
|
+
const severity = args.replace(/^filter\s+/, "").trim().toLowerCase();
|
|
75
|
+
if (!["error", "warning", "info", "success"].includes(severity)) {
|
|
76
|
+
ctx.ui.notify("Usage: /gsd notifications filter <error|warning|info|success>", "warning");
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
const entries = readNotifications().filter((e) => e.severity === severity);
|
|
80
|
+
|
|
81
|
+
if (entries.length === 0) {
|
|
82
|
+
ctx.ui.notify(`No ${severity} notifications.`, "info");
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const lines = entries.slice(0, 20).map((e) =>
|
|
87
|
+
`${severityIcon(e.severity)} [${formatTimestamp(e.ts)}] ${e.message}`,
|
|
88
|
+
);
|
|
89
|
+
const suffix = entries.length > 20 ? `\n... and ${entries.length - 20} more` : "";
|
|
90
|
+
ctx.ui.notify(`${severity} notifications (${entries.length}):\n${lines.join("\n")}${suffix}`, "info");
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// /gsd notifications (no args) — open overlay in TUI, or print summary
|
|
95
|
+
if (args === "" || args === "status") {
|
|
96
|
+
// Try overlay first (TUI mode)
|
|
97
|
+
if (ctx.hasUI) {
|
|
98
|
+
try {
|
|
99
|
+
await ctx.ui.custom<void>(
|
|
100
|
+
(tui, theme, _kb, done) => new GSDNotificationOverlay(tui, theme, () => done()),
|
|
101
|
+
{
|
|
102
|
+
overlay: true,
|
|
103
|
+
overlayOptions: {
|
|
104
|
+
width: "80%",
|
|
105
|
+
minWidth: 60,
|
|
106
|
+
maxHeight: "88%",
|
|
107
|
+
anchor: "center",
|
|
108
|
+
backdrop: true,
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
);
|
|
112
|
+
return true;
|
|
113
|
+
} catch {
|
|
114
|
+
// Fall through to text output if overlay fails
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Text fallback (RPC/headless mode)
|
|
119
|
+
const unread = getUnreadCount();
|
|
120
|
+
const entries = readNotifications().slice(0, 10);
|
|
121
|
+
if (entries.length === 0) {
|
|
122
|
+
ctx.ui.notify("No notifications.", "info");
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const lines = entries.map((e) =>
|
|
127
|
+
`${severityIcon(e.severity)} [${formatTimestamp(e.ts)}] ${e.message}`,
|
|
128
|
+
);
|
|
129
|
+
const header = unread > 0 ? `${unread} unread — ` : "";
|
|
130
|
+
ctx.ui.notify(`${header}Recent notifications:\n${lines.join("\n")}`, "info");
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Unknown subcommand
|
|
135
|
+
ctx.ui.notify(
|
|
136
|
+
"Usage: /gsd notifications [clear|tail [N]|filter <severity>]",
|
|
137
|
+
"warning",
|
|
138
|
+
);
|
|
139
|
+
return true;
|
|
140
|
+
}
|