aicodeman 0.2.9 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +118 -4
- package/dist/ai-idle-checker.d.ts.map +1 -1
- package/dist/ai-idle-checker.js +3 -2
- package/dist/ai-idle-checker.js.map +1 -1
- package/dist/ai-plan-checker.d.ts.map +1 -1
- package/dist/ai-plan-checker.js +3 -2
- package/dist/ai-plan-checker.js.map +1 -1
- package/dist/bash-tool-parser.d.ts +2 -3
- package/dist/bash-tool-parser.d.ts.map +1 -1
- package/dist/bash-tool-parser.js +14 -31
- package/dist/bash-tool-parser.js.map +1 -1
- package/dist/config/ai-defaults.d.ts +16 -0
- package/dist/config/ai-defaults.d.ts.map +1 -0
- package/dist/config/ai-defaults.js +16 -0
- package/dist/config/ai-defaults.js.map +1 -0
- package/dist/config/auth-config.d.ts +19 -0
- package/dist/config/auth-config.d.ts.map +1 -0
- package/dist/config/auth-config.js +28 -0
- package/dist/config/auth-config.js.map +1 -0
- package/dist/config/exec-timeout.d.ts +10 -0
- package/dist/config/exec-timeout.d.ts.map +1 -0
- package/dist/config/exec-timeout.js +10 -0
- package/dist/config/exec-timeout.js.map +1 -0
- package/dist/config/map-limits.d.ts +4 -0
- package/dist/config/map-limits.d.ts.map +1 -1
- package/dist/config/map-limits.js +7 -0
- package/dist/config/map-limits.js.map +1 -1
- package/dist/config/server-timing.d.ts +42 -0
- package/dist/config/server-timing.d.ts.map +1 -0
- package/dist/config/server-timing.js +57 -0
- package/dist/config/server-timing.js.map +1 -0
- package/dist/config/team-config.d.ts +16 -0
- package/dist/config/team-config.d.ts.map +1 -0
- package/dist/config/team-config.js +16 -0
- package/dist/config/team-config.js.map +1 -0
- package/dist/config/terminal-limits.d.ts +18 -0
- package/dist/config/terminal-limits.d.ts.map +1 -0
- package/dist/config/terminal-limits.js +18 -0
- package/dist/config/terminal-limits.js.map +1 -0
- package/dist/config/tunnel-config.d.ts +27 -0
- package/dist/config/tunnel-config.d.ts.map +1 -0
- package/dist/config/tunnel-config.js +36 -0
- package/dist/config/tunnel-config.js.map +1 -0
- package/dist/hooks-config.d.ts +21 -6
- package/dist/hooks-config.d.ts.map +1 -1
- package/dist/hooks-config.js +28 -12
- package/dist/hooks-config.js.map +1 -1
- package/dist/image-watcher.d.ts +4 -4
- package/dist/image-watcher.d.ts.map +1 -1
- package/dist/image-watcher.js +17 -30
- package/dist/image-watcher.js.map +1 -1
- package/dist/index.js +1 -2
- package/dist/index.js.map +1 -1
- package/dist/plan-orchestrator.d.ts +2 -24
- package/dist/plan-orchestrator.d.ts.map +1 -1
- package/dist/plan-orchestrator.js.map +1 -1
- package/dist/prompts/planner.d.ts +7 -8
- package/dist/prompts/planner.d.ts.map +1 -1
- package/dist/prompts/planner.js +7 -8
- package/dist/prompts/planner.js.map +1 -1
- package/dist/prompts/research-agent.d.ts +6 -4
- package/dist/prompts/research-agent.d.ts.map +1 -1
- package/dist/prompts/research-agent.js +6 -4
- package/dist/prompts/research-agent.js.map +1 -1
- package/dist/push-store.d.ts +1 -1
- package/dist/push-store.d.ts.map +1 -1
- package/dist/push-store.js +4 -12
- package/dist/push-store.js.map +1 -1
- package/dist/ralph-fix-plan-watcher.d.ts +91 -0
- package/dist/ralph-fix-plan-watcher.d.ts.map +1 -0
- package/dist/ralph-fix-plan-watcher.js +326 -0
- package/dist/ralph-fix-plan-watcher.js.map +1 -0
- package/dist/ralph-loop.d.ts +14 -4
- package/dist/ralph-loop.d.ts.map +1 -1
- package/dist/ralph-loop.js +14 -4
- package/dist/ralph-loop.js.map +1 -1
- package/dist/ralph-plan-tracker.d.ts +201 -0
- package/dist/ralph-plan-tracker.d.ts.map +1 -0
- package/dist/ralph-plan-tracker.js +325 -0
- package/dist/ralph-plan-tracker.js.map +1 -0
- package/dist/ralph-stall-detector.d.ts +84 -0
- package/dist/ralph-stall-detector.d.ts.map +1 -0
- package/dist/ralph-stall-detector.js +139 -0
- package/dist/ralph-stall-detector.js.map +1 -0
- package/dist/ralph-status-parser.d.ts +141 -0
- package/dist/ralph-status-parser.d.ts.map +1 -0
- package/dist/ralph-status-parser.js +478 -0
- package/dist/ralph-status-parser.js.map +1 -0
- package/dist/ralph-tracker.d.ts +218 -692
- package/dist/ralph-tracker.d.ts.map +1 -1
- package/dist/ralph-tracker.js +389 -1723
- package/dist/ralph-tracker.js.map +1 -1
- package/dist/respawn-adaptive-timing.d.ts +61 -0
- package/dist/respawn-adaptive-timing.d.ts.map +1 -0
- package/dist/respawn-adaptive-timing.js +105 -0
- package/dist/respawn-adaptive-timing.js.map +1 -0
- package/dist/respawn-controller.d.ts +35 -115
- package/dist/respawn-controller.d.ts.map +1 -1
- package/dist/respawn-controller.js +167 -607
- package/dist/respawn-controller.js.map +1 -1
- package/dist/respawn-health.d.ts +54 -0
- package/dist/respawn-health.d.ts.map +1 -0
- package/dist/respawn-health.js +183 -0
- package/dist/respawn-health.js.map +1 -0
- package/dist/respawn-metrics.d.ts +81 -0
- package/dist/respawn-metrics.d.ts.map +1 -0
- package/dist/respawn-metrics.js +198 -0
- package/dist/respawn-metrics.js.map +1 -0
- package/dist/respawn-patterns.d.ts +45 -0
- package/dist/respawn-patterns.d.ts.map +1 -0
- package/dist/respawn-patterns.js +125 -0
- package/dist/respawn-patterns.js.map +1 -0
- package/dist/session-auto-ops.d.ts +89 -0
- package/dist/session-auto-ops.d.ts.map +1 -0
- package/dist/session-auto-ops.js +224 -0
- package/dist/session-auto-ops.js.map +1 -0
- package/dist/session-cli-builder.d.ts +62 -0
- package/dist/session-cli-builder.d.ts.map +1 -0
- package/dist/session-cli-builder.js +121 -0
- package/dist/session-cli-builder.js.map +1 -0
- package/dist/session-manager.d.ts +17 -5
- package/dist/session-manager.d.ts.map +1 -1
- package/dist/session-manager.js +17 -5
- package/dist/session-manager.js.map +1 -1
- package/dist/session-task-cache.d.ts +52 -0
- package/dist/session-task-cache.d.ts.map +1 -0
- package/dist/session-task-cache.js +90 -0
- package/dist/session-task-cache.js.map +1 -0
- package/dist/session.d.ts +23 -41
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +79 -317
- package/dist/session.js.map +1 -1
- package/dist/state-store.d.ts +19 -9
- package/dist/state-store.d.ts.map +1 -1
- package/dist/state-store.js +29 -30
- package/dist/state-store.js.map +1 -1
- package/dist/subagent-watcher.d.ts +26 -7
- package/dist/subagent-watcher.d.ts.map +1 -1
- package/dist/subagent-watcher.js +47 -64
- package/dist/subagent-watcher.js.map +1 -1
- package/dist/team-watcher.d.ts.map +1 -1
- package/dist/team-watcher.js +2 -5
- package/dist/team-watcher.js.map +1 -1
- package/dist/tmux-manager.d.ts.map +1 -1
- package/dist/tmux-manager.js +1 -2
- package/dist/tmux-manager.js.map +1 -1
- package/dist/tunnel-manager.d.ts +26 -0
- package/dist/tunnel-manager.d.ts.map +1 -1
- package/dist/tunnel-manager.js +126 -7
- package/dist/tunnel-manager.js.map +1 -1
- package/dist/types/api.d.ts +108 -0
- package/dist/types/api.d.ts.map +1 -0
- package/dist/types/api.js +98 -0
- package/dist/types/api.js.map +1 -0
- package/dist/types/app-state.d.ts +117 -0
- package/dist/types/app-state.d.ts.map +1 -0
- package/dist/types/app-state.js +76 -0
- package/dist/types/app-state.js.map +1 -0
- package/dist/types/common.d.ts +79 -0
- package/dist/types/common.d.ts.map +1 -0
- package/dist/types/common.js +17 -0
- package/dist/types/common.js.map +1 -0
- package/dist/types/index.d.ts +66 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +66 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/lifecycle.d.ts +28 -0
- package/dist/types/lifecycle.d.ts.map +1 -0
- package/dist/types/lifecycle.js +16 -0
- package/dist/types/lifecycle.js.map +1 -0
- package/dist/types/plan.d.ts +45 -0
- package/dist/types/plan.d.ts.map +1 -0
- package/dist/types/plan.js +18 -0
- package/dist/types/plan.js.map +1 -0
- package/dist/types/push.d.ts +36 -0
- package/dist/types/push.d.ts.map +1 -0
- package/dist/types/push.js +18 -0
- package/dist/types/push.js.map +1 -0
- package/dist/types/ralph.d.ts +262 -0
- package/dist/types/ralph.d.ts.map +1 -0
- package/dist/types/ralph.js +70 -0
- package/dist/types/ralph.js.map +1 -0
- package/dist/types/respawn.d.ts +271 -0
- package/dist/types/respawn.d.ts.map +1 -0
- package/dist/types/respawn.js +26 -0
- package/dist/types/respawn.js.map +1 -0
- package/dist/types/run-summary.d.ts +96 -0
- package/dist/types/run-summary.d.ts.map +1 -0
- package/dist/types/run-summary.js +37 -0
- package/dist/types/run-summary.js.map +1 -0
- package/dist/types/session.d.ts +152 -0
- package/dist/types/session.d.ts.map +1 -0
- package/dist/types/session.js +27 -0
- package/dist/types/session.js.map +1 -0
- package/dist/types/task.d.ts +72 -0
- package/dist/types/task.d.ts.map +1 -0
- package/dist/types/task.js +19 -0
- package/dist/types/task.js.map +1 -0
- package/dist/types/teams.d.ts +73 -0
- package/dist/types/teams.d.ts.map +1 -0
- package/dist/types/teams.js +23 -0
- package/dist/types/teams.js.map +1 -0
- package/dist/types/tools.d.ts +61 -0
- package/dist/types/tools.d.ts.map +1 -0
- package/dist/types/tools.js +20 -0
- package/dist/types/tools.js.map +1 -0
- package/dist/types.d.ts +8 -1134
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +8 -210
- package/dist/types.js.map +1 -1
- package/dist/utils/claude-cli-resolver.d.ts.map +1 -1
- package/dist/utils/claude-cli-resolver.js +1 -2
- package/dist/utils/claude-cli-resolver.js.map +1 -1
- package/dist/utils/debouncer.d.ts +111 -0
- package/dist/utils/debouncer.d.ts.map +1 -0
- package/dist/utils/debouncer.js +162 -0
- package/dist/utils/debouncer.js.map +1 -0
- package/dist/utils/index.d.ts +3 -2
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +3 -2
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/opencode-cli-resolver.d.ts.map +1 -1
- package/dist/utils/opencode-cli-resolver.js +1 -2
- package/dist/utils/opencode-cli-resolver.js.map +1 -1
- package/dist/utils/string-similarity.d.ts +0 -57
- package/dist/utils/string-similarity.d.ts.map +1 -1
- package/dist/utils/string-similarity.js +3 -18
- package/dist/utils/string-similarity.js.map +1 -1
- package/dist/web/middleware/auth.d.ts +31 -0
- package/dist/web/middleware/auth.d.ts.map +1 -0
- package/dist/web/middleware/auth.js +154 -0
- package/dist/web/middleware/auth.js.map +1 -0
- package/dist/web/ports/auth-port.d.ts +18 -0
- package/dist/web/ports/auth-port.d.ts.map +1 -0
- package/dist/web/ports/auth-port.js +6 -0
- package/dist/web/ports/auth-port.js.map +1 -0
- package/dist/web/ports/config-port.d.ts +28 -0
- package/dist/web/ports/config-port.d.ts.map +1 -0
- package/dist/web/ports/config-port.js +6 -0
- package/dist/web/ports/config-port.js.map +1 -0
- package/dist/web/ports/event-port.d.ts +13 -0
- package/dist/web/ports/event-port.d.ts.map +1 -0
- package/dist/web/ports/event-port.js +6 -0
- package/dist/web/ports/event-port.js.map +1 -0
- package/dist/web/ports/index.d.ts +14 -0
- package/dist/web/ports/index.d.ts.map +1 -0
- package/dist/web/ports/index.js +9 -0
- package/dist/web/ports/index.js.map +1 -0
- package/dist/web/ports/infra-port.d.ts +36 -0
- package/dist/web/ports/infra-port.d.ts.map +1 -0
- package/dist/web/ports/infra-port.js +6 -0
- package/dist/web/ports/infra-port.js.map +1 -0
- package/dist/web/ports/respawn-port.d.ts +20 -0
- package/dist/web/ports/respawn-port.d.ts.map +1 -0
- package/dist/web/ports/respawn-port.js +6 -0
- package/dist/web/ports/respawn-port.js.map +1 -0
- package/dist/web/ports/session-port.d.ts +15 -0
- package/dist/web/ports/session-port.d.ts.map +1 -0
- package/dist/web/ports/session-port.js +6 -0
- package/dist/web/ports/session-port.js.map +1 -0
- package/dist/web/public/api-client.js +82 -0
- package/dist/web/public/api-client.js.br +0 -0
- package/dist/web/public/api-client.js.gz +0 -0
- package/dist/web/public/app.js +117 -201
- package/dist/web/public/app.js.br +0 -0
- package/dist/web/public/app.js.gz +0 -0
- package/dist/web/public/constants.js +365 -0
- package/dist/web/public/constants.js.br +0 -0
- package/dist/web/public/constants.js.gz +0 -0
- package/dist/web/public/index.html +15 -3
- package/dist/web/public/index.html.br +0 -0
- package/dist/web/public/index.html.gz +0 -0
- package/dist/web/public/keyboard-accessory.js +302 -0
- package/dist/web/public/keyboard-accessory.js.br +0 -0
- package/dist/web/public/keyboard-accessory.js.gz +0 -0
- package/dist/web/public/mobile-handlers.js +491 -0
- package/dist/web/public/mobile-handlers.js.br +0 -0
- package/dist/web/public/mobile-handlers.js.gz +0 -0
- package/dist/web/public/mobile.css.gz +0 -0
- package/dist/web/public/notification-manager.js +472 -0
- package/dist/web/public/notification-manager.js.br +0 -0
- package/dist/web/public/notification-manager.js.gz +0 -0
- package/dist/web/public/ralph-wizard.js +33 -9
- package/dist/web/public/ralph-wizard.js.br +0 -0
- package/dist/web/public/ralph-wizard.js.gz +0 -0
- package/dist/web/public/styles.css.gz +0 -0
- package/dist/web/public/subagent-windows.js +1149 -0
- package/dist/web/public/subagent-windows.js.br +0 -0
- package/dist/web/public/subagent-windows.js.gz +0 -0
- package/dist/web/public/sw.js +15 -0
- package/dist/web/public/sw.js.br +0 -0
- package/dist/web/public/sw.js.gz +0 -0
- package/dist/web/public/upload.html.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-zerolag-input.js +4 -0
- package/dist/web/public/vendor/xterm-zerolag-input.js.br +0 -0
- package/dist/web/public/vendor/xterm-zerolag-input.js.gz +0 -0
- package/dist/web/public/vendor/xterm.css.gz +0 -0
- package/dist/web/public/vendor/xterm.min.js.gz +0 -0
- package/dist/web/public/voice-input.js +882 -0
- package/dist/web/public/voice-input.js.br +0 -0
- package/dist/web/public/voice-input.js.gz +0 -0
- package/dist/web/route-helpers.d.ts +38 -0
- package/dist/web/route-helpers.d.ts.map +1 -0
- package/dist/web/route-helpers.js +144 -0
- package/dist/web/route-helpers.js.map +1 -0
- package/dist/web/routes/case-routes.d.ts +9 -0
- package/dist/web/routes/case-routes.d.ts.map +1 -0
- package/dist/web/routes/case-routes.js +426 -0
- package/dist/web/routes/case-routes.js.map +1 -0
- package/dist/web/routes/file-routes.d.ts +8 -0
- package/dist/web/routes/file-routes.d.ts.map +1 -0
- package/dist/web/routes/file-routes.js +337 -0
- package/dist/web/routes/file-routes.js.map +1 -0
- package/dist/web/routes/hook-event-routes.d.ts +9 -0
- package/dist/web/routes/hook-event-routes.d.ts.map +1 -0
- package/dist/web/routes/hook-event-routes.js +57 -0
- package/dist/web/routes/hook-event-routes.js.map +1 -0
- package/dist/web/routes/index.d.ts +16 -0
- package/dist/web/routes/index.d.ts.map +1 -0
- package/dist/web/routes/index.js +16 -0
- package/dist/web/routes/index.js.map +1 -0
- package/dist/web/routes/mux-routes.d.ts +8 -0
- package/dist/web/routes/mux-routes.d.ts.map +1 -0
- package/dist/web/routes/mux-routes.js +32 -0
- package/dist/web/routes/mux-routes.js.map +1 -0
- package/dist/web/routes/plan-routes.d.ts +9 -0
- package/dist/web/routes/plan-routes.d.ts.map +1 -0
- package/dist/web/routes/plan-routes.js +385 -0
- package/dist/web/routes/plan-routes.js.map +1 -0
- package/dist/web/routes/push-routes.d.ts +8 -0
- package/dist/web/routes/push-routes.d.ts.map +1 -0
- package/dist/web/routes/push-routes.js +49 -0
- package/dist/web/routes/push-routes.js.map +1 -0
- package/dist/web/routes/ralph-routes.d.ts +9 -0
- package/dist/web/routes/ralph-routes.d.ts.map +1 -0
- package/dist/web/routes/ralph-routes.js +485 -0
- package/dist/web/routes/ralph-routes.js.map +1 -0
- package/dist/web/routes/respawn-routes.d.ts +8 -0
- package/dist/web/routes/respawn-routes.d.ts.map +1 -0
- package/dist/web/routes/respawn-routes.js +270 -0
- package/dist/web/routes/respawn-routes.js.map +1 -0
- package/dist/web/routes/scheduled-routes.d.ts +8 -0
- package/dist/web/routes/scheduled-routes.d.ts.map +1 -0
- package/dist/web/routes/scheduled-routes.js +51 -0
- package/dist/web/routes/scheduled-routes.js.map +1 -0
- package/dist/web/routes/session-routes.d.ts +9 -0
- package/dist/web/routes/session-routes.d.ts.map +1 -0
- package/dist/web/routes/session-routes.js +751 -0
- package/dist/web/routes/session-routes.js.map +1 -0
- package/dist/web/routes/system-routes.d.ts +9 -0
- package/dist/web/routes/system-routes.d.ts.map +1 -0
- package/dist/web/routes/system-routes.js +699 -0
- package/dist/web/routes/system-routes.js.map +1 -0
- package/dist/web/routes/team-routes.d.ts +8 -0
- package/dist/web/routes/team-routes.d.ts.map +1 -0
- package/dist/web/routes/team-routes.js +14 -0
- package/dist/web/routes/team-routes.js.map +1 -0
- package/dist/web/schemas.d.ts +43 -3
- package/dist/web/schemas.d.ts.map +1 -1
- package/dist/web/schemas.js +6 -2
- package/dist/web/schemas.js.map +1 -1
- package/dist/web/server.d.ts +35 -15
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +563 -3971
- package/dist/web/server.js.map +1 -1
- package/dist/web/sse-events.d.ts +361 -0
- package/dist/web/sse-events.d.ts.map +1 -0
- package/dist/web/sse-events.js +396 -0
- package/dist/web/sse-events.js.map +1 -0
- package/package.json +2 -1
- package/scripts/postinstall.js +58 -0
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Five-layer notification system for session events and alerts.
|
|
3
|
+
*
|
|
4
|
+
* The NotificationManager class implements five notification layers:
|
|
5
|
+
* 1. In-app notification drawer (slide-out panel with grouped notifications)
|
|
6
|
+
* 2. Tab title flash (alternating "(*) Codeman" when tab is hidden)
|
|
7
|
+
* 3. Browser Notification API (desktop push with auto-close after 8s)
|
|
8
|
+
* 4. Web Push via service worker (OS-level notifications when tab is closed)
|
|
9
|
+
* 5. Audio alerts (Web Audio API beep, user-opt-in)
|
|
10
|
+
*
|
|
11
|
+
* Features:
|
|
12
|
+
* - Per-event-type preferences (enabled, browser, audio, push) with v1→v4 migration
|
|
13
|
+
* - Device-specific defaults (notifications disabled on mobile by default)
|
|
14
|
+
* - 5s notification grouping window to batch rapid-fire events
|
|
15
|
+
* - 100-notification cap with oldest eviction
|
|
16
|
+
* - Rate limiting: 3s between browser notifications
|
|
17
|
+
* - Visibility tracking (pauses title flash when tab becomes visible)
|
|
18
|
+
* - iOS Safari bfcache support via pageshow event
|
|
19
|
+
*
|
|
20
|
+
* @class NotificationManager
|
|
21
|
+
* @param {CodemanApp} app - Reference to the main app instance
|
|
22
|
+
*
|
|
23
|
+
* @dependency constants.js (STUCK_THRESHOLD_DEFAULT_MS, timing constants)
|
|
24
|
+
* @dependency mobile-handlers.js (MobileDetection.getDeviceType for device-specific defaults)
|
|
25
|
+
* @loadorder 4 of 9 — loaded after voice-input.js, before keyboard-accessory.js
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
// Codeman — Multi-layer notification system
|
|
29
|
+
// Loaded after mobile-handlers.js, before app.js
|
|
30
|
+
|
|
31
|
+
// Notification Manager - Multi-layer browser notification system
|
|
32
|
+
class NotificationManager {
|
|
33
|
+
constructor(app) {
|
|
34
|
+
this.app = app;
|
|
35
|
+
this.notifications = [];
|
|
36
|
+
this.unreadCount = 0;
|
|
37
|
+
this.isTabVisible = !document.hidden;
|
|
38
|
+
this.isDrawerOpen = false;
|
|
39
|
+
this.originalTitle = document.title;
|
|
40
|
+
this.titleFlashInterval = null;
|
|
41
|
+
this.titleFlashState = false;
|
|
42
|
+
this.lastBrowserNotifTime = 0;
|
|
43
|
+
this.audioCtx = null;
|
|
44
|
+
this.renderScheduled = false;
|
|
45
|
+
|
|
46
|
+
// Debounce grouping: Map<key, {notification, timeout}>
|
|
47
|
+
this.groupingMap = new Map();
|
|
48
|
+
|
|
49
|
+
// Load preferences
|
|
50
|
+
this.preferences = this.loadPreferences();
|
|
51
|
+
|
|
52
|
+
// Visibility tracking
|
|
53
|
+
document.addEventListener('visibilitychange', () => {
|
|
54
|
+
this.isTabVisible = !document.hidden;
|
|
55
|
+
if (this.isTabVisible) {
|
|
56
|
+
this.onTabVisible();
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
// iOS Safari: pageshow fires on back-forward cache restore (bfcache)
|
|
60
|
+
window.addEventListener('pageshow', (e) => {
|
|
61
|
+
if (e.persisted) {
|
|
62
|
+
this.isTabVisible = true;
|
|
63
|
+
this.onTabVisible();
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
loadPreferences() {
|
|
69
|
+
const defaultEventTypes = {
|
|
70
|
+
permission_prompt: { enabled: true, browser: true, audio: true, push: false },
|
|
71
|
+
elicitation_dialog: { enabled: true, browser: true, audio: true, push: false },
|
|
72
|
+
idle_prompt: { enabled: true, browser: true, audio: false, push: false },
|
|
73
|
+
stop: { enabled: true, browser: false, audio: false, push: false },
|
|
74
|
+
session_error: { enabled: true, browser: true, audio: false, push: false },
|
|
75
|
+
respawn_cycle: { enabled: true, browser: false, audio: false, push: false },
|
|
76
|
+
token_milestone: { enabled: true, browser: false, audio: false, push: false },
|
|
77
|
+
ralph_complete: { enabled: true, browser: true, audio: true, push: false },
|
|
78
|
+
subagent_spawn: { enabled: false, browser: false, audio: false, push: false },
|
|
79
|
+
subagent_complete: { enabled: false, browser: false, audio: false, push: false },
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// Device-specific defaults: mobile has notifications disabled by default
|
|
83
|
+
const isMobile = MobileDetection.getDeviceType() === 'mobile';
|
|
84
|
+
const defaults = {
|
|
85
|
+
enabled: !isMobile, // Disabled on mobile by default
|
|
86
|
+
browserNotifications: !isMobile,
|
|
87
|
+
audioAlerts: false,
|
|
88
|
+
stuckThresholdMs: STUCK_THRESHOLD_DEFAULT_MS,
|
|
89
|
+
// Legacy urgency muting (keep for backwards compat)
|
|
90
|
+
muteCritical: false,
|
|
91
|
+
muteWarning: false,
|
|
92
|
+
muteInfo: false,
|
|
93
|
+
// Per-event-type preferences
|
|
94
|
+
eventTypes: defaultEventTypes,
|
|
95
|
+
_version: 4,
|
|
96
|
+
};
|
|
97
|
+
try {
|
|
98
|
+
const storageKey = this.getStorageKey();
|
|
99
|
+
const saved = localStorage.getItem(storageKey);
|
|
100
|
+
if (saved) {
|
|
101
|
+
const prefs = JSON.parse(saved);
|
|
102
|
+
// Migrate: v1 had browserNotifications defaulting to false
|
|
103
|
+
if (!prefs._version || prefs._version < 2) {
|
|
104
|
+
prefs.browserNotifications = true;
|
|
105
|
+
prefs._version = 2;
|
|
106
|
+
}
|
|
107
|
+
// Migrate: v2 -> v3 adds eventTypes
|
|
108
|
+
if (prefs._version < 3) {
|
|
109
|
+
prefs.eventTypes = defaultEventTypes;
|
|
110
|
+
prefs._version = 3;
|
|
111
|
+
localStorage.setItem(storageKey, JSON.stringify(prefs));
|
|
112
|
+
}
|
|
113
|
+
// Migrate: v3 -> v4 adds push field to all eventTypes
|
|
114
|
+
if (prefs._version < 4) {
|
|
115
|
+
if (prefs.eventTypes) {
|
|
116
|
+
for (const key of Object.keys(prefs.eventTypes)) {
|
|
117
|
+
if (prefs.eventTypes[key] && prefs.eventTypes[key].push === undefined) {
|
|
118
|
+
prefs.eventTypes[key].push = false;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
prefs._version = 4;
|
|
123
|
+
localStorage.setItem(storageKey, JSON.stringify(prefs));
|
|
124
|
+
}
|
|
125
|
+
// Merge with defaults to ensure all eventTypes exist
|
|
126
|
+
return {
|
|
127
|
+
...defaults,
|
|
128
|
+
...prefs,
|
|
129
|
+
eventTypes: { ...defaultEventTypes, ...prefs.eventTypes },
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
} catch (_e) { /* ignore */ }
|
|
133
|
+
return defaults;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Get storage key for notification prefs (device-specific)
|
|
137
|
+
getStorageKey() {
|
|
138
|
+
const isMobile = MobileDetection.getDeviceType() === 'mobile';
|
|
139
|
+
return isMobile ? 'codeman-notification-prefs-mobile' : 'codeman-notification-prefs';
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
savePreferences() {
|
|
143
|
+
localStorage.setItem(this.getStorageKey(), JSON.stringify(this.preferences));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
notify({ urgency, category, sessionId, sessionName, title, message }) {
|
|
147
|
+
if (!this.preferences.enabled) return;
|
|
148
|
+
|
|
149
|
+
// Map notification categories to eventType preference keys
|
|
150
|
+
const categoryToEventType = {
|
|
151
|
+
'hook-permission': 'permission_prompt',
|
|
152
|
+
'hook-elicitation': 'elicitation_dialog',
|
|
153
|
+
'hook-idle': 'idle_prompt',
|
|
154
|
+
'hook-stop': 'stop',
|
|
155
|
+
'session-error': 'session_error',
|
|
156
|
+
'session-crash': 'session_error',
|
|
157
|
+
'session-stuck': 'idle_prompt',
|
|
158
|
+
'respawn-blocked': 'respawn_cycle',
|
|
159
|
+
'auto-accept': 'respawn_cycle',
|
|
160
|
+
'auto-clear': 'respawn_cycle',
|
|
161
|
+
'ralph-complete': 'ralph_complete',
|
|
162
|
+
'circuit-breaker': 'respawn_cycle',
|
|
163
|
+
'exit-gate': 'ralph_complete',
|
|
164
|
+
'subagent-spawn': 'subagent_spawn',
|
|
165
|
+
'subagent-complete': 'subagent_complete',
|
|
166
|
+
'hook-teammate-idle': 'idle_prompt',
|
|
167
|
+
'hook-task-completed': 'stop',
|
|
168
|
+
};
|
|
169
|
+
const eventTypeKey = categoryToEventType[category] || category;
|
|
170
|
+
|
|
171
|
+
// Check per-event-type preferences first
|
|
172
|
+
const eventPref = this.preferences.eventTypes?.[eventTypeKey];
|
|
173
|
+
let shouldBrowserNotify = false;
|
|
174
|
+
let shouldAudioAlert = false;
|
|
175
|
+
|
|
176
|
+
if (eventPref) {
|
|
177
|
+
// Event type found - use its specific preferences
|
|
178
|
+
if (!eventPref.enabled) return;
|
|
179
|
+
shouldBrowserNotify = eventPref.browser && this.preferences.browserNotifications;
|
|
180
|
+
shouldAudioAlert = eventPref.audio && this.preferences.audioAlerts;
|
|
181
|
+
} else {
|
|
182
|
+
// Fall back to urgency-based muting for unknown categories
|
|
183
|
+
if (urgency === 'critical' && this.preferences.muteCritical) return;
|
|
184
|
+
if (urgency === 'warning' && this.preferences.muteWarning) return;
|
|
185
|
+
if (urgency === 'info' && this.preferences.muteInfo) return;
|
|
186
|
+
// Default browser/audio behavior based on urgency
|
|
187
|
+
shouldBrowserNotify = this.preferences.browserNotifications &&
|
|
188
|
+
(urgency === 'critical' || urgency === 'warning' || !this.isTabVisible);
|
|
189
|
+
shouldAudioAlert = urgency === 'critical' && this.preferences.audioAlerts;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Grouping: same category+session within 5s updates count instead of new entry
|
|
193
|
+
const groupKey = `${category}:${sessionId || 'global'}`;
|
|
194
|
+
const existing = this.groupingMap.get(groupKey);
|
|
195
|
+
if (existing) {
|
|
196
|
+
existing.notification.count = (existing.notification.count || 1) + 1;
|
|
197
|
+
existing.notification.message = message;
|
|
198
|
+
existing.notification.timestamp = Date.now();
|
|
199
|
+
clearTimeout(existing.timeout);
|
|
200
|
+
existing.timeout = setTimeout(() => this.groupingMap.delete(groupKey), GROUPING_TIMEOUT_MS);
|
|
201
|
+
this.scheduleRender();
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const notification = {
|
|
206
|
+
id: Date.now() + '-' + Math.random().toString(36).slice(2, 7),
|
|
207
|
+
urgency,
|
|
208
|
+
category,
|
|
209
|
+
sessionId,
|
|
210
|
+
sessionName,
|
|
211
|
+
title,
|
|
212
|
+
message,
|
|
213
|
+
timestamp: Date.now(),
|
|
214
|
+
read: false,
|
|
215
|
+
count: 1,
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
// Add to log (cap at NOTIFICATION_LIST_CAP)
|
|
219
|
+
this.notifications.unshift(notification);
|
|
220
|
+
if (this.notifications.length > NOTIFICATION_LIST_CAP) this.notifications.pop();
|
|
221
|
+
|
|
222
|
+
// Track for grouping
|
|
223
|
+
const timeout = setTimeout(() => this.groupingMap.delete(groupKey), GROUPING_TIMEOUT_MS);
|
|
224
|
+
this.groupingMap.set(groupKey, { notification, timeout });
|
|
225
|
+
|
|
226
|
+
// Update unread
|
|
227
|
+
this.unreadCount++;
|
|
228
|
+
this.updateBadge();
|
|
229
|
+
this.scheduleRender();
|
|
230
|
+
|
|
231
|
+
// Layer 2: Tab title (when tab unfocused)
|
|
232
|
+
if (!this.isTabVisible) {
|
|
233
|
+
this.updateTabTitle();
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Layer 3: Browser notification
|
|
237
|
+
if (shouldBrowserNotify) {
|
|
238
|
+
this.sendBrowserNotif(title, message, category, sessionId);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Layer 4: Audio alert
|
|
242
|
+
if (shouldAudioAlert) {
|
|
243
|
+
this.playAudioAlert();
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Layer 1: Drawer rendering
|
|
248
|
+
scheduleRender() {
|
|
249
|
+
if (this.renderScheduled) return;
|
|
250
|
+
this.renderScheduled = true;
|
|
251
|
+
requestAnimationFrame(() => {
|
|
252
|
+
this.renderScheduled = false;
|
|
253
|
+
this.renderDrawer();
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
renderDrawer() {
|
|
258
|
+
const list = document.getElementById('notifList');
|
|
259
|
+
const empty = document.getElementById('notifEmpty');
|
|
260
|
+
if (!list || !empty) return;
|
|
261
|
+
|
|
262
|
+
if (this.notifications.length === 0) {
|
|
263
|
+
list.style.display = 'none';
|
|
264
|
+
empty.style.display = 'flex';
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
list.style.display = 'block';
|
|
269
|
+
empty.style.display = 'none';
|
|
270
|
+
|
|
271
|
+
list.innerHTML = this.notifications.map(n => {
|
|
272
|
+
const urgencyClass = `notif-item-${n.urgency}`;
|
|
273
|
+
const readClass = n.read ? '' : ' unread';
|
|
274
|
+
const countLabel = n.count > 1 ? `<span class="notif-item-count">×${n.count}</span>` : '';
|
|
275
|
+
const sessionChip = n.sessionName ? `<span class="notif-item-session">${escapeHtml(n.sessionName)}</span>` : '';
|
|
276
|
+
return `<div class="notif-item ${urgencyClass}${readClass}" data-notif-id="${n.id}" data-session-id="${n.sessionId || ''}" onclick="app.notificationManager.clickNotification('${escapeHtml(n.id)}')">
|
|
277
|
+
<div class="notif-item-header">
|
|
278
|
+
<span class="notif-item-title">${escapeHtml(n.title)}${countLabel}</span>
|
|
279
|
+
<span class="notif-item-time">${this.relativeTime(n.timestamp)}</span>
|
|
280
|
+
</div>
|
|
281
|
+
<div class="notif-item-message">${escapeHtml(n.message)}</div>
|
|
282
|
+
${sessionChip}
|
|
283
|
+
</div>`;
|
|
284
|
+
}).join('');
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Layer 2: Tab title with unread count
|
|
288
|
+
updateTabTitle() {
|
|
289
|
+
if (this.unreadCount > 0 && !this.isTabVisible) {
|
|
290
|
+
if (!this.titleFlashInterval) {
|
|
291
|
+
this.titleFlashInterval = setInterval(() => {
|
|
292
|
+
this.titleFlashState = !this.titleFlashState;
|
|
293
|
+
document.title = this.titleFlashState
|
|
294
|
+
? `\u26A0\uFE0F (${this.unreadCount}) Codeman`
|
|
295
|
+
: this.originalTitle;
|
|
296
|
+
}, TITLE_FLASH_INTERVAL_MS);
|
|
297
|
+
// Set immediately
|
|
298
|
+
document.title = `\u26A0\uFE0F (${this.unreadCount}) Codeman`;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
stopTitleFlash() {
|
|
304
|
+
if (this.titleFlashInterval) {
|
|
305
|
+
clearInterval(this.titleFlashInterval);
|
|
306
|
+
this.titleFlashInterval = null;
|
|
307
|
+
this.titleFlashState = false;
|
|
308
|
+
document.title = this.originalTitle;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Layer 3: Web Notification API
|
|
313
|
+
sendBrowserNotif(title, body, tag, sessionId) {
|
|
314
|
+
if (!this.preferences.browserNotifications) return;
|
|
315
|
+
if (typeof Notification === 'undefined') return;
|
|
316
|
+
if (Notification.permission === 'default') {
|
|
317
|
+
// Auto-request on first notification attempt
|
|
318
|
+
Notification.requestPermission().then(result => {
|
|
319
|
+
if (result === 'granted') {
|
|
320
|
+
// Re-send this notification now that we have permission
|
|
321
|
+
this.sendBrowserNotif(title, body, tag, sessionId);
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
if (Notification.permission !== 'granted') return;
|
|
327
|
+
|
|
328
|
+
// Rate limit
|
|
329
|
+
const now = Date.now();
|
|
330
|
+
if (now - this.lastBrowserNotifTime < BROWSER_NOTIF_RATE_LIMIT_MS) return;
|
|
331
|
+
this.lastBrowserNotifTime = now;
|
|
332
|
+
|
|
333
|
+
const notif = new Notification(`Codeman: ${title}`, {
|
|
334
|
+
body,
|
|
335
|
+
tag, // Groups same-tag notifications
|
|
336
|
+
icon: '/favicon.ico',
|
|
337
|
+
silent: true, // We handle audio ourselves
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
notif.onclick = () => {
|
|
341
|
+
window.focus();
|
|
342
|
+
if (sessionId && this.app.sessions.has(sessionId)) {
|
|
343
|
+
this.app.selectSession(sessionId);
|
|
344
|
+
}
|
|
345
|
+
notif.close();
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
// Auto-close
|
|
349
|
+
setTimeout(() => notif.close(), AUTO_CLOSE_NOTIFICATION_MS);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
async requestPermission() {
|
|
353
|
+
if (typeof Notification === 'undefined') {
|
|
354
|
+
this.app.showToast('Browser notifications not supported', 'warning');
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
const result = await Notification.requestPermission();
|
|
358
|
+
const statusEl = document.getElementById('notifPermissionStatus');
|
|
359
|
+
if (statusEl) statusEl.textContent = `Status: ${result}`;
|
|
360
|
+
if (result === 'granted') {
|
|
361
|
+
this.preferences.browserNotifications = true;
|
|
362
|
+
this.savePreferences();
|
|
363
|
+
this.app.showToast('Notifications enabled', 'success');
|
|
364
|
+
} else {
|
|
365
|
+
this.app.showToast(`Permission ${result}`, 'warning');
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Layer 4: Audio alert via Web Audio API
|
|
370
|
+
playAudioAlert() {
|
|
371
|
+
try {
|
|
372
|
+
if (!this.audioCtx) {
|
|
373
|
+
this.audioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
|
374
|
+
}
|
|
375
|
+
if (this.audioCtx.state === 'suspended') {
|
|
376
|
+
this.audioCtx.resume();
|
|
377
|
+
}
|
|
378
|
+
const ctx = this.audioCtx;
|
|
379
|
+
const oscillator = ctx.createOscillator();
|
|
380
|
+
const gain = ctx.createGain();
|
|
381
|
+
oscillator.connect(gain);
|
|
382
|
+
gain.connect(ctx.destination);
|
|
383
|
+
oscillator.type = 'sine';
|
|
384
|
+
oscillator.frequency.setValueAtTime(660, ctx.currentTime);
|
|
385
|
+
gain.gain.setValueAtTime(0.15, ctx.currentTime);
|
|
386
|
+
gain.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.15);
|
|
387
|
+
oscillator.start(ctx.currentTime);
|
|
388
|
+
oscillator.stop(ctx.currentTime + 0.15);
|
|
389
|
+
} catch (_e) { /* Audio not available */ }
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// UI interactions
|
|
393
|
+
toggleDrawer() {
|
|
394
|
+
const drawer = document.getElementById('notifDrawer');
|
|
395
|
+
if (!drawer) return;
|
|
396
|
+
this.isDrawerOpen = !this.isDrawerOpen;
|
|
397
|
+
drawer.classList.toggle('open', this.isDrawerOpen);
|
|
398
|
+
if (this.isDrawerOpen) {
|
|
399
|
+
this.renderDrawer();
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
clickNotification(notifId) {
|
|
404
|
+
const notif = this.notifications.find(n => n.id === notifId);
|
|
405
|
+
if (!notif) return;
|
|
406
|
+
|
|
407
|
+
// Mark as read
|
|
408
|
+
if (!notif.read) {
|
|
409
|
+
notif.read = true;
|
|
410
|
+
this.unreadCount = Math.max(0, this.unreadCount - 1);
|
|
411
|
+
this.updateBadge();
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Switch to session if available
|
|
415
|
+
if (notif.sessionId && this.app.sessions.has(notif.sessionId)) {
|
|
416
|
+
this.app.selectSession(notif.sessionId);
|
|
417
|
+
this.toggleDrawer();
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
this.scheduleRender();
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
markAllRead() {
|
|
424
|
+
this.notifications.forEach(n => { n.read = true; });
|
|
425
|
+
this.unreadCount = 0;
|
|
426
|
+
this.updateBadge();
|
|
427
|
+
this.stopTitleFlash();
|
|
428
|
+
this.scheduleRender();
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
clearAll() {
|
|
432
|
+
this.notifications = [];
|
|
433
|
+
this.unreadCount = 0;
|
|
434
|
+
this.updateBadge();
|
|
435
|
+
this.stopTitleFlash();
|
|
436
|
+
this.scheduleRender();
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
updateBadge() {
|
|
440
|
+
const badge = document.getElementById('notifBadge');
|
|
441
|
+
if (!badge) return;
|
|
442
|
+
if (this.unreadCount > 0) {
|
|
443
|
+
badge.style.display = 'flex';
|
|
444
|
+
badge.textContent = this.unreadCount > 99 ? '99+' : String(this.unreadCount);
|
|
445
|
+
} else {
|
|
446
|
+
badge.style.display = 'none';
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
onTabVisible() {
|
|
451
|
+
this.stopTitleFlash();
|
|
452
|
+
// If drawer is open, mark all as read
|
|
453
|
+
if (this.isDrawerOpen) {
|
|
454
|
+
this.markAllRead();
|
|
455
|
+
}
|
|
456
|
+
// Re-fit terminal and send resize to PTY so this client's dimensions win.
|
|
457
|
+
// Fixes broken layout when switching between desktop and mobile on the same session.
|
|
458
|
+
if (this.app?.fitAddon && this.app?.activeSessionId) {
|
|
459
|
+
this.app.fitAddon.fit();
|
|
460
|
+
this.app.sendResize(this.app.activeSessionId);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Utilities
|
|
465
|
+
relativeTime(ts) {
|
|
466
|
+
const diff = Math.floor((Date.now() - ts) / 1000);
|
|
467
|
+
if (diff < 60) return 'now';
|
|
468
|
+
if (diff < 3600) return `${Math.floor(diff / 60)}m ago`;
|
|
469
|
+
if (diff < 86400) return `${Math.floor(diff / 3600)}h ago`;
|
|
470
|
+
return `${Math.floor(diff / 86400)}d ago`;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
Binary file
|
|
Binary file
|
|
@@ -1,10 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Ralph Loop Wizard —
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* @fileoverview Ralph Loop Wizard — multi-step modal for configuring autonomous task loops.
|
|
3
|
+
*
|
|
4
|
+
* Extends CodemanApp.prototype with wizard methods for the Ralph Loop setup flow:
|
|
5
|
+
* Step 1: Task description, completion phrase, iteration limit, case selection
|
|
6
|
+
* Step 2: AI-powered plan generation (optional) with research agent → planner agent pipeline
|
|
7
|
+
* Step 3: Respawn configuration (idle timeout, kickstart prompt, auto-clear/init)
|
|
8
|
+
* Step 4: Review and launch
|
|
9
|
+
*
|
|
10
|
+
* Features:
|
|
11
|
+
* - Plan generation via POST /api/sessions/:id/plan/generate with SSE progress streaming
|
|
12
|
+
* - Existing @fix_plan.md detection and reuse
|
|
13
|
+
* - Plan detail level selection (brief/detailed/comprehensive)
|
|
14
|
+
* - Case selector population from /api/cases
|
|
15
|
+
* - Focus trap for modal accessibility
|
|
16
|
+
* - Abort controller for cancelling in-flight plan generation
|
|
17
|
+
*
|
|
18
|
+
* @mixin Extends CodemanApp.prototype via Object.assign
|
|
19
|
+
* @dependency app.js (CodemanApp class must be defined)
|
|
20
|
+
* @dependency keyboard-accessory.js (FocusTrap class for modal focus management)
|
|
21
|
+
* @dependency constants.js (escapeHtml)
|
|
22
|
+
* @loadorder 7 of 9 — loaded after app.js, before api-client.js
|
|
5
23
|
*/
|
|
6
24
|
|
|
7
|
-
//
|
|
25
|
+
// ═══════════════════════════════════════════════════════════════
|
|
26
|
+
// Ralph Loop Wizard
|
|
27
|
+
// ═══════════════════════════════════════════════════════════════
|
|
8
28
|
|
|
9
29
|
Object.assign(CodemanApp.prototype, {
|
|
10
30
|
|
|
@@ -361,7 +381,7 @@ Object.assign(CodemanApp.prototype, {
|
|
|
361
381
|
prompt += 'Output `<promise>BLOCKED</promise>` with explanation';
|
|
362
382
|
|
|
363
383
|
// Show preview with highlighting (escape first, then apply formatting)
|
|
364
|
-
const escapedPrompt =
|
|
384
|
+
const escapedPrompt = escapeHtml(prompt);
|
|
365
385
|
const highlightedPrompt = escapedPrompt
|
|
366
386
|
.replace(/<promise>/g, '<span class="preview-highlight"><promise>')
|
|
367
387
|
.replace(/<\/promise>/g, '</promise></span>')
|
|
@@ -388,7 +408,9 @@ Object.assign(CodemanApp.prototype, {
|
|
|
388
408
|
}
|
|
389
409
|
},
|
|
390
410
|
|
|
391
|
-
//
|
|
411
|
+
// ═══════════════════════════════════════════════════════════════
|
|
412
|
+
// Plan Generation
|
|
413
|
+
// ═══════════════════════════════════════════════════════════════
|
|
392
414
|
|
|
393
415
|
resetPlanGenerationUI() {
|
|
394
416
|
// Hide all plan generation states
|
|
@@ -724,7 +746,7 @@ Object.assign(CodemanApp.prototype, {
|
|
|
724
746
|
<input type="checkbox" class="plan-item-checkbox" ${item.enabled ? 'checked' : ''}
|
|
725
747
|
onchange="app.togglePlanItem(${index})">
|
|
726
748
|
${item.priority ? `<span class="plan-item-priority-badge">${item.priority}</span>` : ''}
|
|
727
|
-
<span class="plan-item-text">${
|
|
749
|
+
<span class="plan-item-text">${escapeHtml(item.content)}</span>
|
|
728
750
|
`;
|
|
729
751
|
fragment.appendChild(row);
|
|
730
752
|
});
|
|
@@ -780,7 +802,9 @@ Object.assign(CodemanApp.prototype, {
|
|
|
780
802
|
this.closePlanSubagentWindows();
|
|
781
803
|
},
|
|
782
804
|
|
|
783
|
-
//
|
|
805
|
+
// ═══════════════════════════════════════════════════════════════
|
|
806
|
+
// Plan Subagent Windows
|
|
807
|
+
// ═══════════════════════════════════════════════════════════════
|
|
784
808
|
|
|
785
809
|
handlePlanSubagentEvent(event) {
|
|
786
810
|
if (this.planGenerationStopped) return;
|
|
@@ -847,7 +871,7 @@ Object.assign(CodemanApp.prototype, {
|
|
|
847
871
|
<div class="plan-subagent-header">
|
|
848
872
|
<span>
|
|
849
873
|
<span class="plan-subagent-icon">${typeIcons[agentType] || '🤖'}</span>
|
|
850
|
-
<span class="plan-subagent-title">${typeLabels[agentType] ||
|
|
874
|
+
<span class="plan-subagent-title">${typeLabels[agentType] || escapeHtml(agentType)}</span>
|
|
851
875
|
</span>
|
|
852
876
|
<span class="plan-subagent-model">${model}</span>
|
|
853
877
|
</div>
|
|
Binary file
|
|
Binary file
|
|
Binary file
|