gsd-pi 2.64.0 → 2.65.0
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/auto-dashboard.js +5 -5
- package/dist/resources/extensions/gsd/auto-post-unit.js +98 -1
- package/dist/resources/extensions/gsd/auto-verification.js +138 -1
- package/dist/resources/extensions/gsd/auto.js +5 -0
- 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 +256 -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/post-execution-checks.js +407 -0
- package/dist/resources/extensions/gsd/pre-execution-checks.js +464 -0
- package/dist/resources/extensions/gsd/preferences-types.js +4 -0
- package/dist/resources/extensions/gsd/preferences-validation.js +33 -0
- package/dist/resources/extensions/gsd/preferences.js +4 -0
- package/dist/resources/extensions/gsd/verification-evidence.js +18 -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 +7 -6
- 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/required-server-files.json +1 -1
- 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 +7 -6
- 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/MRM3OSYIAa4HMDqVGQ9nt/_buildManifest.js +1 -0
- 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/server.js +1 -1
- 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/package.json +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 +13 -1
- package/packages/pi-tui/src/tui.ts +34 -0
- package/pkg/package.json +1 -1
- 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/auto-dashboard.ts +5 -4
- package/src/resources/extensions/gsd/auto-post-unit.ts +122 -0
- package/src/resources/extensions/gsd/auto-verification.ts +190 -2
- package/src/resources/extensions/gsd/auto.ts +4 -0
- 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 +295 -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/post-execution-checks.ts +539 -0
- package/src/resources/extensions/gsd/pre-execution-checks.ts +573 -0
- package/src/resources/extensions/gsd/preferences-types.ts +28 -0
- package/src/resources/extensions/gsd/preferences-validation.ts +33 -0
- package/src/resources/extensions/gsd/preferences.ts +4 -0
- package/src/resources/extensions/gsd/tests/auto-start-time-persistence.test.ts +50 -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/enhanced-verification-integration.test.ts +526 -0
- package/src/resources/extensions/gsd/tests/notification-overlay.test.ts +73 -0
- package/src/resources/extensions/gsd/tests/notification-store.test.ts +282 -0
- package/src/resources/extensions/gsd/tests/post-exec-retry-bypass.test.ts +312 -0
- package/src/resources/extensions/gsd/tests/post-execution-checks.test.ts +813 -0
- package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +999 -0
- package/src/resources/extensions/gsd/tests/pre-execution-fail-closed.test.ts +266 -0
- package/src/resources/extensions/gsd/tests/pre-execution-pause-wiring.test.ts +457 -0
- package/src/resources/extensions/gsd/tests/unstructured-continue-context-injection.test.ts +163 -0
- package/src/resources/extensions/gsd/verification-evidence.ts +68 -0
- package/src/resources/extensions/gsd/workflow-logger.ts +13 -0
- 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/eebXKteM9EaWyseHKTjqp/_buildManifest.js +0 -1
- /package/dist/web/standalone/.next/static/{eebXKteM9EaWyseHKTjqp → MRM3OSYIAa4HMDqVGQ9nt}/_ssgManifest.js +0 -0
|
@@ -4,7 +4,7 @@ import { join } from "node:path";
|
|
|
4
4
|
import { loadRegistry } from "../workflow-templates.js";
|
|
5
5
|
import { resolveProjectRoot } from "../worktree.js";
|
|
6
6
|
const gsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
|
|
7
|
-
export const GSD_COMMAND_DESCRIPTION = "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";
|
|
7
|
+
export const GSD_COMMAND_DESCRIPTION = "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";
|
|
8
8
|
export const TOP_LEVEL_SUBCOMMANDS = [
|
|
9
9
|
{ cmd: "help", desc: "Categorized command reference with descriptions" },
|
|
10
10
|
{ cmd: "next", desc: "Explicit step mode (same as /gsd)" },
|
|
@@ -36,6 +36,7 @@ export const TOP_LEVEL_SUBCOMMANDS = [
|
|
|
36
36
|
{ cmd: "hooks", desc: "Show configured post-unit and pre-dispatch hooks" },
|
|
37
37
|
{ cmd: "run-hook", desc: "Manually trigger a specific hook" },
|
|
38
38
|
{ cmd: "skill-health", desc: "Skill lifecycle dashboard" },
|
|
39
|
+
{ cmd: "notifications", desc: "View, filter, and clear persistent notification history" },
|
|
39
40
|
{ cmd: "doctor", desc: "Runtime health checks with auto-fix" },
|
|
40
41
|
{ cmd: "logs", desc: "Browse activity logs, debug logs, and metrics" },
|
|
41
42
|
{ cmd: "forensics", desc: "Examine execution logs" },
|
|
@@ -97,6 +98,11 @@ const NESTED_COMPLETIONS = {
|
|
|
97
98
|
{ cmd: "keys", desc: "Manage API keys" },
|
|
98
99
|
{ cmd: "prefs", desc: "Configure global preferences" },
|
|
99
100
|
],
|
|
101
|
+
notifications: [
|
|
102
|
+
{ cmd: "clear", desc: "Clear all notifications" },
|
|
103
|
+
{ cmd: "tail", desc: "Show last N notifications (default: 20)" },
|
|
104
|
+
{ cmd: "filter", desc: "Filter by severity (error|warning|info|success)" },
|
|
105
|
+
],
|
|
100
106
|
logs: [
|
|
101
107
|
{ cmd: "debug", desc: "List or view debug log files" },
|
|
102
108
|
{ cmd: "tail", desc: "Show last N activity log summaries" },
|
|
@@ -25,6 +25,7 @@ export function showHelp(ctx) {
|
|
|
25
25
|
" /gsd queue Show queued/dispatched units and execution order",
|
|
26
26
|
" /gsd history View execution history [--cost] [--phase] [--model] [N]",
|
|
27
27
|
" /gsd changelog Show categorized release notes [version]",
|
|
28
|
+
" /gsd notifications View persistent notification history [clear|tail|filter] (Ctrl+Alt+N)",
|
|
28
29
|
"",
|
|
29
30
|
"COURSE CORRECTION",
|
|
30
31
|
" /gsd steer <desc> Apply user override to active work",
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// GSD Extension — /gsd notifications Command Handler
|
|
2
|
+
// View, filter, and clear the persistent notification history.
|
|
3
|
+
import { readNotifications, clearNotifications, getUnreadCount, suppressPersistence, unsuppressPersistence, } from "../../notification-store.js";
|
|
4
|
+
import { GSDNotificationOverlay } from "../../notification-overlay.js";
|
|
5
|
+
function severityIcon(severity) {
|
|
6
|
+
switch (severity) {
|
|
7
|
+
case "error": return "✗";
|
|
8
|
+
case "warning": return "⚠";
|
|
9
|
+
case "success": return "✓";
|
|
10
|
+
case "info":
|
|
11
|
+
default: return "●";
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function formatTimestamp(ts) {
|
|
15
|
+
try {
|
|
16
|
+
const d = new Date(ts);
|
|
17
|
+
return d.toLocaleString("en-US", { hour12: false, month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" });
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return ts.slice(0, 19);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export async function handleNotificationsCommand(args, ctx, pi) {
|
|
24
|
+
// /gsd notifications clear
|
|
25
|
+
if (args === "clear") {
|
|
26
|
+
clearNotifications();
|
|
27
|
+
// Suppress persistence so the confirmation toast doesn't re-populate the store
|
|
28
|
+
suppressPersistence();
|
|
29
|
+
try {
|
|
30
|
+
ctx.ui.notify("All notifications cleared.", "success");
|
|
31
|
+
}
|
|
32
|
+
finally {
|
|
33
|
+
unsuppressPersistence();
|
|
34
|
+
}
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
// /gsd notifications tail [N]
|
|
38
|
+
if (args === "tail" || args.startsWith("tail ")) {
|
|
39
|
+
const countStr = args.replace(/^tail\s*/, "").trim();
|
|
40
|
+
const count = countStr ? parseInt(countStr, 10) : 20;
|
|
41
|
+
const n = isNaN(count) || count < 1 ? 20 : Math.min(count, 100);
|
|
42
|
+
const entries = readNotifications().slice(0, n);
|
|
43
|
+
if (entries.length === 0) {
|
|
44
|
+
ctx.ui.notify("No notifications.", "info");
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
const lines = entries.map((e) => `${severityIcon(e.severity)} [${formatTimestamp(e.ts)}] ${e.message}`);
|
|
48
|
+
ctx.ui.notify(`Last ${entries.length} notification(s):\n${lines.join("\n")}`, "info");
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
// /gsd notifications filter <severity>
|
|
52
|
+
if (args.startsWith("filter ")) {
|
|
53
|
+
const severity = args.replace(/^filter\s+/, "").trim().toLowerCase();
|
|
54
|
+
if (!["error", "warning", "info", "success"].includes(severity)) {
|
|
55
|
+
ctx.ui.notify("Usage: /gsd notifications filter <error|warning|info|success>", "warning");
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
const entries = readNotifications().filter((e) => e.severity === severity);
|
|
59
|
+
if (entries.length === 0) {
|
|
60
|
+
ctx.ui.notify(`No ${severity} notifications.`, "info");
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
const lines = entries.slice(0, 20).map((e) => `${severityIcon(e.severity)} [${formatTimestamp(e.ts)}] ${e.message}`);
|
|
64
|
+
const suffix = entries.length > 20 ? `\n... and ${entries.length - 20} more` : "";
|
|
65
|
+
ctx.ui.notify(`${severity} notifications (${entries.length}):\n${lines.join("\n")}${suffix}`, "info");
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
// /gsd notifications (no args) — open overlay in TUI, or print summary
|
|
69
|
+
if (args === "" || args === "status") {
|
|
70
|
+
// Try overlay first (TUI mode)
|
|
71
|
+
if (ctx.hasUI) {
|
|
72
|
+
try {
|
|
73
|
+
await ctx.ui.custom((tui, theme, _kb, done) => new GSDNotificationOverlay(tui, theme, () => done()), {
|
|
74
|
+
overlay: true,
|
|
75
|
+
overlayOptions: {
|
|
76
|
+
width: "80%",
|
|
77
|
+
minWidth: 60,
|
|
78
|
+
maxHeight: "88%",
|
|
79
|
+
anchor: "center",
|
|
80
|
+
backdrop: true,
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
// Fall through to text output if overlay fails
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// Text fallback (RPC/headless mode)
|
|
90
|
+
const unread = getUnreadCount();
|
|
91
|
+
const entries = readNotifications().slice(0, 10);
|
|
92
|
+
if (entries.length === 0) {
|
|
93
|
+
ctx.ui.notify("No notifications.", "info");
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
const lines = entries.map((e) => `${severityIcon(e.severity)} [${formatTimestamp(e.ts)}] ${e.message}`);
|
|
97
|
+
const header = unread > 0 ? `${unread} unread — ` : "";
|
|
98
|
+
ctx.ui.notify(`${header}Recent notifications:\n${lines.join("\n")}`, "info");
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
// Unknown subcommand
|
|
102
|
+
ctx.ui.notify("Usage: /gsd notifications [clear|tail [N]|filter <severity>]", "warning");
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
@@ -175,6 +175,11 @@ Examples:
|
|
|
175
175
|
await dispatchDirectPhase(ctx, pi, phase, projectRoot());
|
|
176
176
|
return true;
|
|
177
177
|
}
|
|
178
|
+
if (trimmed === "notifications" || trimmed.startsWith("notifications ")) {
|
|
179
|
+
const { handleNotificationsCommand } = await import("./notifications-handler.js");
|
|
180
|
+
await handleNotificationsCommand(trimmed.replace(/^notifications\s*/, "").trim(), ctx, pi);
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
178
183
|
if (trimmed === "inspect") {
|
|
179
184
|
await handleInspect(ctx);
|
|
180
185
|
return true;
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
// GSD Extension — Notification History Overlay
|
|
2
|
+
// Scrollable panel showing all persisted notifications with severity filtering.
|
|
3
|
+
// Toggled with Ctrl+Alt+N or opened from /gsd notifications.
|
|
4
|
+
import { truncateToWidth, visibleWidth, matchesKey, Key } from "@gsd/pi-tui";
|
|
5
|
+
import { readNotifications, markAllRead, clearNotifications, } from "./notification-store.js";
|
|
6
|
+
import { padRight, joinColumns } from "../shared/mod.js";
|
|
7
|
+
const FILTER_CYCLE = ["all", "error", "warning", "info"];
|
|
8
|
+
function severityIcon(severity) {
|
|
9
|
+
switch (severity) {
|
|
10
|
+
case "error": return "✗";
|
|
11
|
+
case "warning": return "⚠";
|
|
12
|
+
case "success": return "✓";
|
|
13
|
+
case "info":
|
|
14
|
+
default: return "●";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/** Word-wrap plain text to fit within maxWidth columns. */
|
|
18
|
+
function wrapText(text, maxWidth) {
|
|
19
|
+
if (text.length <= maxWidth)
|
|
20
|
+
return [text];
|
|
21
|
+
const words = text.split(/\s+/);
|
|
22
|
+
const lines = [];
|
|
23
|
+
let current = "";
|
|
24
|
+
for (const word of words) {
|
|
25
|
+
if (current.length === 0) {
|
|
26
|
+
current = word;
|
|
27
|
+
}
|
|
28
|
+
else if (current.length + 1 + word.length <= maxWidth) {
|
|
29
|
+
current += " " + word;
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
lines.push(current);
|
|
33
|
+
current = word;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (current.length > 0)
|
|
37
|
+
lines.push(current);
|
|
38
|
+
// If a single word exceeds maxWidth, truncate it
|
|
39
|
+
return lines.map((l) => l.length > maxWidth ? l.slice(0, maxWidth - 1) + "…" : l);
|
|
40
|
+
}
|
|
41
|
+
function formatTimestamp(ts) {
|
|
42
|
+
try {
|
|
43
|
+
const d = new Date(ts);
|
|
44
|
+
const now = Date.now();
|
|
45
|
+
const diffMs = now - d.getTime();
|
|
46
|
+
if (diffMs < 60_000)
|
|
47
|
+
return "just now";
|
|
48
|
+
if (diffMs < 3600_000)
|
|
49
|
+
return `${Math.floor(diffMs / 60_000)}m ago`;
|
|
50
|
+
if (diffMs < 86400_000)
|
|
51
|
+
return `${Math.floor(diffMs / 3600_000)}h ago`;
|
|
52
|
+
return `${Math.floor(diffMs / 86400_000)}d ago`;
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return ts.slice(11, 19); // fallback: HH:MM:SS
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export class GSDNotificationOverlay {
|
|
59
|
+
tui;
|
|
60
|
+
theme;
|
|
61
|
+
onClose;
|
|
62
|
+
cachedWidth;
|
|
63
|
+
cachedLines;
|
|
64
|
+
scrollOffset = 0;
|
|
65
|
+
filterIndex = 0;
|
|
66
|
+
entries = [];
|
|
67
|
+
refreshTimer;
|
|
68
|
+
disposed = false;
|
|
69
|
+
resizeHandler = null;
|
|
70
|
+
constructor(tui, theme, onClose) {
|
|
71
|
+
this.tui = tui;
|
|
72
|
+
this.theme = theme;
|
|
73
|
+
this.onClose = onClose;
|
|
74
|
+
// Mark all as read on open
|
|
75
|
+
markAllRead();
|
|
76
|
+
this.entries = readNotifications();
|
|
77
|
+
// Resize handler
|
|
78
|
+
this.resizeHandler = () => {
|
|
79
|
+
if (this.disposed)
|
|
80
|
+
return;
|
|
81
|
+
this.invalidate();
|
|
82
|
+
this.tui.requestRender();
|
|
83
|
+
};
|
|
84
|
+
process.stdout.on("resize", this.resizeHandler);
|
|
85
|
+
// Refresh every 3s for new notifications
|
|
86
|
+
this.refreshTimer = setInterval(() => {
|
|
87
|
+
if (this.disposed)
|
|
88
|
+
return;
|
|
89
|
+
const fresh = readNotifications();
|
|
90
|
+
if (fresh.length !== this.entries.length) {
|
|
91
|
+
this.entries = fresh;
|
|
92
|
+
markAllRead();
|
|
93
|
+
this.invalidate();
|
|
94
|
+
this.tui.requestRender();
|
|
95
|
+
}
|
|
96
|
+
}, 3000);
|
|
97
|
+
}
|
|
98
|
+
get filter() {
|
|
99
|
+
return FILTER_CYCLE[this.filterIndex];
|
|
100
|
+
}
|
|
101
|
+
get filteredEntries() {
|
|
102
|
+
if (this.filter === "all")
|
|
103
|
+
return this.entries;
|
|
104
|
+
return this.entries.filter((e) => e.severity === this.filter);
|
|
105
|
+
}
|
|
106
|
+
handleInput(data) {
|
|
107
|
+
if (matchesKey(data, Key.escape) || matchesKey(data, Key.ctrl("c")) || matchesKey(data, Key.ctrlAlt("n"))) {
|
|
108
|
+
this.dispose();
|
|
109
|
+
this.onClose();
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
// Scroll
|
|
113
|
+
if (matchesKey(data, Key.down) || matchesKey(data, "j")) {
|
|
114
|
+
this.scrollOffset++;
|
|
115
|
+
this.invalidate();
|
|
116
|
+
this.tui.requestRender();
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (matchesKey(data, Key.up) || matchesKey(data, "k")) {
|
|
120
|
+
this.scrollOffset = Math.max(0, this.scrollOffset - 1);
|
|
121
|
+
this.invalidate();
|
|
122
|
+
this.tui.requestRender();
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (data === "g") {
|
|
126
|
+
this.scrollOffset = 0;
|
|
127
|
+
this.invalidate();
|
|
128
|
+
this.tui.requestRender();
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (data === "G") {
|
|
132
|
+
this.scrollOffset = 999;
|
|
133
|
+
this.invalidate();
|
|
134
|
+
this.tui.requestRender();
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
// Filter cycle
|
|
138
|
+
if (data === "f") {
|
|
139
|
+
this.filterIndex = (this.filterIndex + 1) % FILTER_CYCLE.length;
|
|
140
|
+
this.scrollOffset = 0;
|
|
141
|
+
this.invalidate();
|
|
142
|
+
this.tui.requestRender();
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
// Clear all
|
|
146
|
+
if (data === "c") {
|
|
147
|
+
clearNotifications();
|
|
148
|
+
this.entries = [];
|
|
149
|
+
this.scrollOffset = 0;
|
|
150
|
+
this.invalidate();
|
|
151
|
+
this.tui.requestRender();
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
render(width) {
|
|
156
|
+
if (this.cachedLines && this.cachedWidth === width) {
|
|
157
|
+
return this.cachedLines;
|
|
158
|
+
}
|
|
159
|
+
const content = this.buildContentLines(width);
|
|
160
|
+
const maxVisibleRows = Math.max(5, process.stdout.rows ? process.stdout.rows - 8 : 24) - 2;
|
|
161
|
+
const visibleContentRows = Math.min(content.length, maxVisibleRows);
|
|
162
|
+
const maxScroll = Math.max(0, content.length - visibleContentRows);
|
|
163
|
+
this.scrollOffset = Math.min(this.scrollOffset, maxScroll);
|
|
164
|
+
const visibleContent = content.slice(this.scrollOffset, this.scrollOffset + visibleContentRows);
|
|
165
|
+
const lines = this.wrapInBox(visibleContent, width);
|
|
166
|
+
this.cachedWidth = width;
|
|
167
|
+
this.cachedLines = lines;
|
|
168
|
+
return lines;
|
|
169
|
+
}
|
|
170
|
+
invalidate() {
|
|
171
|
+
this.cachedLines = undefined;
|
|
172
|
+
this.cachedWidth = undefined;
|
|
173
|
+
}
|
|
174
|
+
dispose() {
|
|
175
|
+
this.disposed = true;
|
|
176
|
+
clearInterval(this.refreshTimer);
|
|
177
|
+
if (this.resizeHandler) {
|
|
178
|
+
process.stdout.removeListener("resize", this.resizeHandler);
|
|
179
|
+
this.resizeHandler = null;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
wrapInBox(inner, width) {
|
|
183
|
+
const th = this.theme;
|
|
184
|
+
const border = (s) => th.fg("borderAccent", s);
|
|
185
|
+
const innerWidth = width - 4;
|
|
186
|
+
const lines = [];
|
|
187
|
+
lines.push(border("╭" + "─".repeat(width - 2) + "╮"));
|
|
188
|
+
for (const line of inner) {
|
|
189
|
+
const truncated = truncateToWidth(line, innerWidth);
|
|
190
|
+
const padWidth = Math.max(0, innerWidth - visibleWidth(truncated));
|
|
191
|
+
lines.push(border("│") + " " + truncated + " ".repeat(padWidth) + " " + border("│"));
|
|
192
|
+
}
|
|
193
|
+
lines.push(border("╰" + "─".repeat(width - 2) + "╯"));
|
|
194
|
+
return lines;
|
|
195
|
+
}
|
|
196
|
+
buildContentLines(width) {
|
|
197
|
+
const th = this.theme;
|
|
198
|
+
const shellWidth = width - 4;
|
|
199
|
+
const contentWidth = Math.min(shellWidth, 128);
|
|
200
|
+
const sidePad = Math.max(0, Math.floor((shellWidth - contentWidth) / 2));
|
|
201
|
+
const leftMargin = " ".repeat(sidePad);
|
|
202
|
+
const lines = [];
|
|
203
|
+
const row = (content = "") => {
|
|
204
|
+
const truncated = truncateToWidth(content, contentWidth);
|
|
205
|
+
return leftMargin + padRight(truncated, contentWidth);
|
|
206
|
+
};
|
|
207
|
+
const blank = () => row("");
|
|
208
|
+
const hr = () => row(th.fg("dim", "─".repeat(contentWidth)));
|
|
209
|
+
// Header
|
|
210
|
+
const title = th.fg("accent", th.bold("Notifications"));
|
|
211
|
+
const filterLabel = this.filter === "all"
|
|
212
|
+
? th.fg("dim", "all")
|
|
213
|
+
: th.fg(this.filter === "error" ? "error" : this.filter === "warning" ? "warning" : "dim", this.filter);
|
|
214
|
+
const count = `${this.filteredEntries.length} entries`;
|
|
215
|
+
lines.push(row(joinColumns(`${title} ${th.fg("dim", "filter:")} ${filterLabel}`, th.fg("dim", count), contentWidth)));
|
|
216
|
+
lines.push(hr());
|
|
217
|
+
// Controls
|
|
218
|
+
lines.push(row(th.fg("dim", "↑/↓ scroll f filter c clear Esc close")));
|
|
219
|
+
lines.push(blank());
|
|
220
|
+
// Entries
|
|
221
|
+
const filtered = this.filteredEntries;
|
|
222
|
+
if (filtered.length === 0) {
|
|
223
|
+
lines.push(blank());
|
|
224
|
+
lines.push(row(th.fg("dim", this.entries.length === 0
|
|
225
|
+
? "No notifications yet."
|
|
226
|
+
: `No ${this.filter} notifications.`)));
|
|
227
|
+
lines.push(blank());
|
|
228
|
+
return lines;
|
|
229
|
+
}
|
|
230
|
+
for (const entry of filtered) {
|
|
231
|
+
const icon = severityIcon(entry.severity);
|
|
232
|
+
const coloredIcon = entry.severity === "error" ? th.fg("error", icon)
|
|
233
|
+
: entry.severity === "warning" ? th.fg("warning", icon)
|
|
234
|
+
: entry.severity === "success" ? th.fg("success", icon)
|
|
235
|
+
: th.fg("dim", icon);
|
|
236
|
+
const time = th.fg("dim", formatTimestamp(entry.ts));
|
|
237
|
+
const source = entry.source === "workflow-logger" ? th.fg("dim", " [engine]") : "";
|
|
238
|
+
// Measure actual prefix width for wrapping
|
|
239
|
+
const prefix = `${coloredIcon} ${time}${source} `;
|
|
240
|
+
const prefixWidth = visibleWidth(prefix);
|
|
241
|
+
const msgMaxWidth = Math.max(10, contentWidth - prefixWidth);
|
|
242
|
+
// Wrap long messages onto continuation lines indented to align with message start
|
|
243
|
+
const msgLines = wrapText(entry.message, msgMaxWidth);
|
|
244
|
+
const indent = " ".repeat(prefixWidth);
|
|
245
|
+
for (let i = 0; i < msgLines.length; i++) {
|
|
246
|
+
if (i === 0) {
|
|
247
|
+
lines.push(row(`${prefix}${msgLines[i]}`));
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
lines.push(row(`${indent}${msgLines[i]}`));
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return lines;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
// GSD Extension — Persistent Notification Store
|
|
2
|
+
// Captures all ctx.ui.notify() calls and workflow-logger warnings to
|
|
3
|
+
// .gsd/notifications.jsonl so they survive context resets and session restarts.
|
|
4
|
+
// Rotates at MAX_ENTRIES to prevent unbounded growth.
|
|
5
|
+
import { appendFileSync, existsSync, mkdirSync, openSync, closeSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { randomUUID } from "node:crypto";
|
|
8
|
+
// ─── Constants ──────────────────────────────────────────────────────────
|
|
9
|
+
const MAX_ENTRIES = 500;
|
|
10
|
+
const FILENAME = "notifications.jsonl";
|
|
11
|
+
const LOCKFILE = "notifications.lock";
|
|
12
|
+
// ─── Module State ───────────────────────────────────────────────────────
|
|
13
|
+
let _basePath = null;
|
|
14
|
+
let _lineCount = 0; // Hint for rotation — not authoritative for public API
|
|
15
|
+
let _suppressCount = 0;
|
|
16
|
+
// ─── Public API ─────────────────────────────────────────────────────────
|
|
17
|
+
/**
|
|
18
|
+
* Initialize the notification store. Call once at session start with the
|
|
19
|
+
* project root. Seeds in-memory counters from the existing file on disk.
|
|
20
|
+
*/
|
|
21
|
+
export function initNotificationStore(basePath) {
|
|
22
|
+
_basePath = basePath;
|
|
23
|
+
// Seed line count hint for rotation — public counters read from disk
|
|
24
|
+
_lineCount = _readEntriesFromDisk(basePath).length;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Append a notification entry to the store. Synchronous — safe to call
|
|
28
|
+
* from the notify() shim which is declared void (not async).
|
|
29
|
+
*/
|
|
30
|
+
export function appendNotification(message, severity, source = "notify") {
|
|
31
|
+
if (!_basePath)
|
|
32
|
+
return;
|
|
33
|
+
if (_suppressCount > 0)
|
|
34
|
+
return;
|
|
35
|
+
const entry = {
|
|
36
|
+
id: randomUUID(),
|
|
37
|
+
ts: new Date().toISOString(),
|
|
38
|
+
severity,
|
|
39
|
+
message: message.length > 500 ? message.slice(0, 500) + "…" : message,
|
|
40
|
+
source,
|
|
41
|
+
read: false,
|
|
42
|
+
};
|
|
43
|
+
try {
|
|
44
|
+
const dir = join(_basePath, ".gsd");
|
|
45
|
+
mkdirSync(dir, { recursive: true });
|
|
46
|
+
appendFileSync(join(dir, FILENAME), JSON.stringify(entry) + "\n", "utf-8");
|
|
47
|
+
_lineCount++;
|
|
48
|
+
// Rotate if hint suggests we're over limit
|
|
49
|
+
if (_lineCount > MAX_ENTRIES) {
|
|
50
|
+
_rotate();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// Non-fatal — never let persistence break the caller
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Read all notification entries from disk. Returns newest-first.
|
|
59
|
+
*/
|
|
60
|
+
export function readNotifications(basePath) {
|
|
61
|
+
const bp = basePath ?? _basePath;
|
|
62
|
+
if (!bp)
|
|
63
|
+
return [];
|
|
64
|
+
return _readEntriesFromDisk(bp).reverse();
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Mark all notifications as read. Atomic rewrite via temp-file + rename.
|
|
68
|
+
* Resyncs in-memory counters from disk after mutation.
|
|
69
|
+
*/
|
|
70
|
+
export function markAllRead(basePath) {
|
|
71
|
+
const bp = basePath ?? _basePath;
|
|
72
|
+
if (!bp)
|
|
73
|
+
return;
|
|
74
|
+
const entries = _readEntriesFromDisk(bp);
|
|
75
|
+
if (entries.length === 0)
|
|
76
|
+
return;
|
|
77
|
+
const hasUnread = entries.some((e) => !e.read);
|
|
78
|
+
if (!hasUnread)
|
|
79
|
+
return;
|
|
80
|
+
try {
|
|
81
|
+
_withLock(bp, () => {
|
|
82
|
+
// Re-read inside lock to get freshest state
|
|
83
|
+
const fresh = _readEntriesFromDisk(bp);
|
|
84
|
+
if (fresh.length === 0 || !fresh.some((e) => !e.read))
|
|
85
|
+
return;
|
|
86
|
+
const lines = fresh.map((e) => JSON.stringify({ ...e, read: true }));
|
|
87
|
+
_atomicWrite(bp, lines.join("\n") + "\n");
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
// Non-fatal
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Clear all notifications. Atomic write of empty content under lock.
|
|
96
|
+
*/
|
|
97
|
+
export function clearNotifications(basePath) {
|
|
98
|
+
const bp = basePath ?? _basePath;
|
|
99
|
+
if (!bp)
|
|
100
|
+
return;
|
|
101
|
+
try {
|
|
102
|
+
_withLock(bp, () => {
|
|
103
|
+
_atomicWrite(bp, "");
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
// Non-fatal
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Get the current unread count. Reads from disk to stay accurate across
|
|
112
|
+
* processes (web subprocess can clear/modify the file independently).
|
|
113
|
+
*/
|
|
114
|
+
export function getUnreadCount() {
|
|
115
|
+
if (!_basePath)
|
|
116
|
+
return 0;
|
|
117
|
+
try {
|
|
118
|
+
const entries = _readEntriesFromDisk(_basePath);
|
|
119
|
+
return entries.filter((e) => !e.read).length;
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return 0;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Get the total notification count. Reads from disk for cross-process accuracy.
|
|
127
|
+
*/
|
|
128
|
+
export function getLineCount() {
|
|
129
|
+
if (!_basePath)
|
|
130
|
+
return 0;
|
|
131
|
+
try {
|
|
132
|
+
return _readEntriesFromDisk(_basePath).length;
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
return 0;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Temporarily suppress persistence. Use around ctx.ui.notify calls that
|
|
140
|
+
* should NOT be persisted (e.g., confirmation toasts after clear).
|
|
141
|
+
* Calls are ref-counted — nest safely.
|
|
142
|
+
*/
|
|
143
|
+
export function suppressPersistence() {
|
|
144
|
+
_suppressCount++;
|
|
145
|
+
}
|
|
146
|
+
export function unsuppressPersistence() {
|
|
147
|
+
_suppressCount = Math.max(0, _suppressCount - 1);
|
|
148
|
+
}
|
|
149
|
+
// ─── Test Helpers ───────────────────────────────────────────────────────
|
|
150
|
+
/**
|
|
151
|
+
* Reset module state. Only for use in tests.
|
|
152
|
+
*/
|
|
153
|
+
export function _resetNotificationStore() {
|
|
154
|
+
_basePath = null;
|
|
155
|
+
_lineCount = 0;
|
|
156
|
+
_suppressCount = 0;
|
|
157
|
+
}
|
|
158
|
+
// ─── Internal ───────────────────────────────────────────────────────────
|
|
159
|
+
function _readEntriesFromDisk(basePath) {
|
|
160
|
+
const filePath = join(basePath, ".gsd", FILENAME);
|
|
161
|
+
if (!existsSync(filePath))
|
|
162
|
+
return [];
|
|
163
|
+
try {
|
|
164
|
+
const content = readFileSync(filePath, "utf-8");
|
|
165
|
+
return content
|
|
166
|
+
.split("\n")
|
|
167
|
+
.filter((l) => l.length > 0)
|
|
168
|
+
.map((l) => {
|
|
169
|
+
try {
|
|
170
|
+
return JSON.parse(l);
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
})
|
|
176
|
+
.filter((e) => e !== null);
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
return [];
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
function _rotate() {
|
|
183
|
+
if (!_basePath)
|
|
184
|
+
return;
|
|
185
|
+
try {
|
|
186
|
+
_withLock(_basePath, () => {
|
|
187
|
+
// Re-read inside lock to get freshest state
|
|
188
|
+
const entries = _readEntriesFromDisk(_basePath);
|
|
189
|
+
if (entries.length <= MAX_ENTRIES)
|
|
190
|
+
return;
|
|
191
|
+
const trimmed = entries.slice(entries.length - MAX_ENTRIES);
|
|
192
|
+
const lines = trimmed.map((e) => JSON.stringify(e));
|
|
193
|
+
_atomicWrite(_basePath, lines.join("\n") + "\n");
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
// Non-fatal
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Atomic file rewrite via temp-file + rename. Prevents partial reads
|
|
202
|
+
* by other processes (web API subprocess, parallel workers).
|
|
203
|
+
* Must be called inside _withLock for cross-process safety.
|
|
204
|
+
*/
|
|
205
|
+
function _atomicWrite(basePath, content) {
|
|
206
|
+
const dir = join(basePath, ".gsd");
|
|
207
|
+
mkdirSync(dir, { recursive: true });
|
|
208
|
+
const target = join(dir, FILENAME);
|
|
209
|
+
const tmp = target + ".tmp." + process.pid;
|
|
210
|
+
writeFileSync(tmp, content, "utf-8");
|
|
211
|
+
renameSync(tmp, target);
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Acquire an exclusive lockfile for rewrite operations.
|
|
215
|
+
* Uses O_CREAT|O_EXCL for atomic creation — if the file exists, another
|
|
216
|
+
* process holds the lock. Retries briefly, then proceeds anyway (best-effort)
|
|
217
|
+
* to avoid deadlocking the UI on a stale lock.
|
|
218
|
+
*/
|
|
219
|
+
function _withLock(basePath, fn) {
|
|
220
|
+
const lockPath = join(basePath, ".gsd", LOCKFILE);
|
|
221
|
+
let fd = null;
|
|
222
|
+
const maxAttempts = 5;
|
|
223
|
+
const retryMs = 20;
|
|
224
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
225
|
+
try {
|
|
226
|
+
mkdirSync(join(basePath, ".gsd"), { recursive: true });
|
|
227
|
+
fd = openSync(lockPath, "wx");
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
catch (err) {
|
|
231
|
+
if (err?.code === "EEXIST") {
|
|
232
|
+
// Check if lock is stale (older than 5s)
|
|
233
|
+
try {
|
|
234
|
+
const stat = readFileSync(lockPath, "utf-8");
|
|
235
|
+
const lockTime = parseInt(stat, 10);
|
|
236
|
+
if (Date.now() - lockTime > 5000) {
|
|
237
|
+
try {
|
|
238
|
+
unlinkSync(lockPath);
|
|
239
|
+
}
|
|
240
|
+
catch { /* race ok */ }
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
catch { /* can't read lock, retry */ }
|
|
245
|
+
// Wait and retry
|
|
246
|
+
const start = Date.now();
|
|
247
|
+
while (Date.now() - start < retryMs) { /* spin */ }
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
// Other error — proceed without lock
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
// Only run the mutation if we actually own the lock
|
|
255
|
+
const ownsLock = fd !== null;
|
|
256
|
+
try {
|
|
257
|
+
if (ownsLock && fd !== null) {
|
|
258
|
+
// Write our PID timestamp into the lock for stale detection
|
|
259
|
+
writeFileSync(lockPath, String(Date.now()), "utf-8");
|
|
260
|
+
closeSync(fd);
|
|
261
|
+
}
|
|
262
|
+
return fn();
|
|
263
|
+
}
|
|
264
|
+
finally {
|
|
265
|
+
// Only delete the lock if we created it — never remove another process's lock
|
|
266
|
+
if (ownsLock) {
|
|
267
|
+
try {
|
|
268
|
+
unlinkSync(lockPath);
|
|
269
|
+
}
|
|
270
|
+
catch { /* best-effort cleanup */ }
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|