claude-code-session-manager 0.106.0 → 0.108.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.
Files changed (50) hide show
  1. package/dist/assets/{AgentLibrary-CKOZzc2W.js → AgentLibrary-Co54GThg.js} +1 -1
  2. package/dist/assets/{Button-yuqFpQsK.js → Button-DC6VzovQ.js} +1 -1
  3. package/dist/assets/{DataModel-Bwdc_gaJ.js → DataModel-DZ0vyJau.js} +1 -1
  4. package/dist/assets/{History-D1Z0lydo.js → History-Df3YwpPe.js} +1 -1
  5. package/dist/assets/{Hooks-CyD6l4Is.js → Hooks-DplG6roH.js} +1 -1
  6. package/dist/assets/{Library-fnod5cPe.js → Library-DLmnlfQt.js} +1 -1
  7. package/dist/assets/{ListDetail-BKO68SJ4.js → ListDetail-YylbvUj_.js} +1 -1
  8. package/dist/assets/{MarkdownEditor-DrYMnMBy.js → MarkdownEditor-DMPKWHTF.js} +1 -1
  9. package/dist/assets/{McpServers-CgfItWhF.js → McpServers-Da-Ty5nc.js} +1 -1
  10. package/dist/assets/{Memory-4M3gO3_r.js → Memory-C3rUQ_cg.js} +1 -1
  11. package/dist/assets/{Panel-CKbXo-LT.js → Panel-CC3JTnOI.js} +1 -1
  12. package/dist/assets/{Permissions-Bc9EpjXL.js → Permissions-XvBiMj1-.js} +1 -1
  13. package/dist/assets/{Plugins-B_-PlEIx.js → Plugins-CzHY1mgS.js} +2 -2
  14. package/dist/assets/{ProvenanceBadge-Cxev7XSA.js → ProvenanceBadge-DaJIWVcg.js} +1 -1
  15. package/dist/assets/{SaveBar-DiqscG6q.js → SaveBar-C06rFT34.js} +1 -1
  16. package/dist/assets/Scheduler-BehadU-0.js +16 -0
  17. package/dist/assets/{ScopeSwitcher-BsM4-jHQ.js → ScopeSwitcher-Cp5mxblN.js} +1 -1
  18. package/dist/assets/Settings-Cg9LIni0.js +4 -0
  19. package/dist/assets/{SkillReferenceGraph-B9YlOuPo.js → SkillReferenceGraph-DuWRmT_f.js} +1 -1
  20. package/dist/assets/{Skills-Dtf-z_zN.js → Skills-CMmCZfIv.js} +1 -1
  21. package/dist/assets/{SystemPrompt-HJDn0E8C.js → SystemPrompt-D_-MV025.js} +1 -1
  22. package/dist/assets/{TagLibrary-DLGOfauU.js → TagLibrary-D9Tv3yOJ.js} +1 -1
  23. package/dist/assets/{TiptapBody-Cdmv1CuM.js → TiptapBody-1r4ODLJ7.js} +1 -1
  24. package/dist/assets/{Toggle-CQFhOo1F.js → Toggle-BGT68XHP.js} +1 -1
  25. package/dist/assets/{ViewTabs-CxjDCHjH.js → ViewTabs-T0NBc8uv.js} +1 -1
  26. package/dist/assets/{index-Blo38vfX.js → index-3_AtCAyd.js} +3 -3
  27. package/dist/assets/{index-DkJv4l6L.css → index-tr1w3hHB.css} +1 -1
  28. package/dist/assets/{settingsSchema-B0uoL_1R.js → settingsSchema-CvoNk3Ye.js} +1 -1
  29. package/dist/assets/useAgentsDirWatch-1C2CBBya.js +1 -0
  30. package/dist/assets/{useConfigDirWatch-Dxv7JbCU.js → useConfigDirWatch-Cd5PnTkB.js} +1 -1
  31. package/dist/index.html +2 -2
  32. package/package.json +2 -1
  33. package/scripts/scheduler-mcp-server.cjs +52 -7
  34. package/src/main/build-info.json +4 -4
  35. package/src/main/index.cjs +8 -0
  36. package/src/main/ipcSchemas.cjs +93 -0
  37. package/src/main/lib/builtinMacros.cjs +2 -2
  38. package/src/main/lib/e2eStateMachine.cjs +39 -0
  39. package/src/main/lib/mcpToolCatalog.cjs +8 -6
  40. package/src/main/lib/shippedPersonaSeeds.cjs +11 -0
  41. package/src/main/lib/usageCircuit.cjs +15 -0
  42. package/src/main/scheduler.cjs +57 -11
  43. package/src/main/usage.cjs +10 -2
  44. package/src/main/webRemote.cjs +1364 -0
  45. package/src/preload/api.d.ts +64 -1
  46. package/src/preload/index.cjs +39 -0
  47. package/src/seed/agents/demo-video-builder.md +69 -48
  48. package/dist/assets/Scheduler-CCcJzuu8.js +0 -16
  49. package/dist/assets/Settings-C3-VL-T8.js +0 -3
  50. package/dist/assets/useAgentsDirWatch-e3TJUFPP.js +0 -1
@@ -758,7 +758,7 @@ export interface ScheduleJobStatusHistoryEntry {
758
758
  at: string;
759
759
  }
760
760
 
761
- export type SchedulePauseReason = 'rate_limit' | 'auth' | 'network' | 'manual' | 'reset_failure';
761
+ export type SchedulePauseReason = 'rate_limit' | 'auth' | 'network' | 'manual' | 'agent' | 'reset_failure';
762
762
 
763
763
  export interface SchedulePauseInfo {
764
764
  reason: SchedulePauseReason;
@@ -767,6 +767,8 @@ export interface SchedulePauseInfo {
767
767
  /** ISO timestamp at which auto-resume will fire (typically next 5h reset).
768
768
  * null means "indefinite — wait for manual Run now". */
769
769
  resumeAt: string | null;
770
+ /** Present when reason is 'agent': who paused and why. */
771
+ by?: { originClaudeSessionId: string | null; cwd: string | null; reason: string | null } | null;
770
772
  }
771
773
 
772
774
  export interface ScheduleHealthSnapshot {
@@ -1582,6 +1584,46 @@ export type CreateProjectFolderResult =
1582
1584
  | { ok: true; path: string }
1583
1585
  | { ok: false; code: 'invalid-name' | 'invalid-parent' | 'exists' | 'io'; error: string; path?: string };
1584
1586
 
1587
+ // ────────────────────────────────────────────── Web Remote (PRD 08)
1588
+
1589
+ export interface WebRemoteDevice {
1590
+ deviceId: string;
1591
+ deviceName: string;
1592
+ issuedAt: string;
1593
+ lastConnectedAt: string | null;
1594
+ }
1595
+
1596
+ export interface WebRemoteStatus {
1597
+ enabled: boolean;
1598
+ remoteControlEnabled: boolean;
1599
+ connected: boolean;
1600
+ e2eActive: boolean;
1601
+ /** True once the user has confirmed the SAS on the desktop. */
1602
+ e2eAuthenticated: boolean;
1603
+ /** Explicit E2E session state for UI rendering. */
1604
+ e2eState: 'idle' | 'pending_sas' | 'authenticated' | 'failed';
1605
+ /** 6-digit Short Authentication String pending user confirmation, or null. */
1606
+ pendingSas: string | null;
1607
+ devices: WebRemoteDevice[];
1608
+ }
1609
+
1610
+ export interface WebRemotePairResult {
1611
+ ok: boolean;
1612
+ deviceId?: string;
1613
+ error?: string;
1614
+ }
1615
+
1616
+ export interface WebRemoteMutationResult {
1617
+ ok: boolean;
1618
+ error?: string;
1619
+ }
1620
+
1621
+ export interface WebRemoteAuditTailResult {
1622
+ ok: boolean;
1623
+ lines?: string[];
1624
+ error?: string;
1625
+ }
1626
+
1585
1627
  export interface SessionManagerAPI {
1586
1628
  app: {
1587
1629
  version: () => Promise<string>;
@@ -2069,6 +2111,27 @@ export interface SessionManagerAPI {
2069
2111
  * renderer needs per-row badges without a separate git call per file. */
2070
2112
  fileStatus: (cwd: string) => Promise<GitFileStatusMap>;
2071
2113
  };
2114
+ webRemote: {
2115
+ getStatus: () => Promise<WebRemoteStatus>;
2116
+ enable: () => Promise<WebRemoteMutationResult>;
2117
+ disable: () => Promise<WebRemoteMutationResult>;
2118
+ /** Allow MUTATE-tier commands (pty spawn/write, scheduler writes). Default off. */
2119
+ enableControl: () => Promise<WebRemoteMutationResult>;
2120
+ /** Block MUTATE-tier commands — mobile becomes read-only mirror. */
2121
+ disableControl: () => Promise<WebRemoteMutationResult>;
2122
+ pair: (otp: string) => Promise<WebRemotePairResult>;
2123
+ revokeDevice: (deviceId: string) => Promise<WebRemoteMutationResult>;
2124
+ auditTail: (lines?: number) => Promise<WebRemoteAuditTailResult>;
2125
+ onStatus: (handler: (status: WebRemoteStatus) => void) => () => void;
2126
+ onTokenRevoked: (handler: (ev: { deviceId: string }) => void) => () => void;
2127
+ /** Revoke ALL paired devices and tear down every session immediately (panic). */
2128
+ revokeAll: () => Promise<WebRemoteMutationResult>;
2129
+ /** Subscribe to the completion event broadcast after revokeAll. */
2130
+ onRevokedAll: (handler: (ev: { revokedCount: number }) => void) => () => void;
2131
+ /** Confirm that the SAS shown on the desktop matches the browser — marks the
2132
+ * E2E session as authenticated and unblocks MUTATE-tier commands. */
2133
+ confirmSas: () => Promise<WebRemoteMutationResult>;
2134
+ };
2072
2135
  chat: {
2073
2136
  /** Spawn a headless `claude -p` run for a tab. Results arrive via the on* listeners. */
2074
2137
  run: (payload: ChatRunPayload) => Promise<{ ok: boolean }>;
@@ -392,6 +392,45 @@ contextBridge.exposeInMainWorld('api', {
392
392
  status: (cwd) => ipcRenderer.invoke('git:status', { cwd }),
393
393
  fileStatus: (cwd) => ipcRenderer.invoke('git:file-status', { cwd }),
394
394
  },
395
+ webRemote: {
396
+ /** Current connection state (no token values). */
397
+ getStatus: () => ipcRenderer.invoke('webRemote:get-status'),
398
+ /** Turn remote control on. Initiates relay connection if a device is paired. */
399
+ enable: () => ipcRenderer.invoke('webRemote:enable'),
400
+ /** Turn remote control off. Immediately drops relay connection. */
401
+ disable: () => ipcRenderer.invoke('webRemote:disable'),
402
+ /** Allow MUTATE-tier commands (pty spawn/write, scheduler writes). Default off. */
403
+ enableControl: () => ipcRenderer.invoke('webRemote:enable-control'),
404
+ /** Block MUTATE-tier commands — mobile becomes read-only mirror. */
405
+ disableControl: () => ipcRenderer.invoke('webRemote:disable-control'),
406
+ /** Pair a new device using the 8-character OTP shown in the web UI. */
407
+ pair: (otp) => ipcRenderer.invoke('webRemote:pair', { otp }),
408
+ /** Revoke a paired device by its deviceId. */
409
+ revokeDevice: (deviceId) => ipcRenderer.invoke('webRemote:revoke-device', { deviceId }),
410
+ /** Read the last N lines of today's audit log (default 50). */
411
+ auditTail: (lines) => ipcRenderer.invoke('webRemote:audit-tail', lines ? { lines } : {}),
412
+ /** Push event from main when connection status changes. */
413
+ onStatus: (handler) => {
414
+ const listener = (_e, payload) => handler(payload);
415
+ ipcRenderer.on('webRemote:status', listener);
416
+ return () => ipcRenderer.removeListener('webRemote:status', listener);
417
+ },
418
+ /** Push event when the relay revokes this device's token. */
419
+ onTokenRevoked: (handler) => {
420
+ const listener = (_e, payload) => handler(payload);
421
+ ipcRenderer.on('webRemote:token-revoked', listener);
422
+ return () => ipcRenderer.removeListener('webRemote:token-revoked', listener);
423
+ },
424
+ /** Revoke ALL paired devices and tear down every session immediately. */
425
+ revokeAll: () => ipcRenderer.invoke('webRemote:revoke-all'),
426
+ confirmSas: () => ipcRenderer.invoke('webRemote:confirm-sas'),
427
+ /** Push event when revokeAll completes (main broadcasts webRemote:revoked-all). */
428
+ onRevokedAll: (handler) => {
429
+ const listener = (_e, payload) => handler(payload);
430
+ ipcRenderer.on('webRemote:revoked-all', listener);
431
+ return () => ipcRenderer.removeListener('webRemote:revoked-all', listener);
432
+ },
433
+ },
395
434
  chat: {
396
435
  /** Spawn a headless claude -p job. Results arrive via the on* listeners. */
397
436
  run: (payload) => ipcRenderer.invoke('chat:run', payload),
@@ -5,7 +5,7 @@ tools: Read, Grep, Glob, Bash, Write, Edit
5
5
  title: 'Project Home — Demo Video'
6
6
  model: sonnet
7
7
  effort: medium
8
- seedVersion: 2
8
+ seedVersion: 3
9
9
  ---
10
10
 
11
11
  You are the demo-video-builder. You make a 30-second animated demo that shows someone what this
@@ -52,54 +52,73 @@ Produce exactly one complete HTML document:
52
52
  - Expose `window.smDemo = { duration: 30, seek(t), play(), pause() }`, where `seek(t)` renders
53
53
  the exact frame for time `t` in seconds, independent of whatever played before it.
54
54
  - Autoplays once on load; shows play/pause, restart, and a progress bar.
55
- - Optional narration audio (see `<audio_policy>`); the video must still make sense muted.
56
- - No network: no `fetch`, `XMLHttpRequest`, `WebSocket`, `import()`, or `<iframe>`. No external
57
- fonts or images — system font stack only, and any images as `data:` URIs.
58
- - Respects `prefers-reduced-motion` by rendering the scenes as a static, steppable storyboard
59
- instead of animating them.
60
- - Under 2 MB total.
61
- - A light/dark neutral palette with sufficient contrast in both.
62
- </output_contract>
55
+ - Narration audio built per `<audio_policy>
56
+ Narration is the default deliverable: every video ships with a natural-sounding on-device voice
57
+ track reading the storyboard captions. Generate it at build time, never at playback time; the
58
+ shipped document never calls out to a network TTS service. A silent video is allowed ONLY when
59
+ the install or model download below genuinely fails — then your final report must state the exact
60
+ failing command and its error. Generating a track and then saving the HTML without it is a failed run.
63
61
 
64
- <audio_policy>
65
- Narrate the storyboard captions with a short, pleasant on-device voice track — optional, never
66
- block the video on it. Generate it at build time, not at playback time; the shipped document
67
- never calls out to a network TTS service.
62
+ - **TTS: Kokoro-82M via `kokoro-onnx`** (Apache-2.0, ONNX runtime, no PyTorch). Install with
63
+ `python3 -m pip install --user --break-system-packages kokoro-onnx soundfile`. If
64
+ `python3 -m pip` is missing, bootstrap it first:
65
+ `curl -sS https://bootstrap.pypa.io/get-pip.py | python3 - --user --break-system-packages`.
66
+ `ffmpeg` must be on PATH.
67
+ - **Shared model cache:** `MODEL_DIR=~/.cache/kokoro-onnx`, reused across projects. Only if
68
+ `kokoro-v1.0.onnx` (~325 MB) or `voices-v1.0.bin` (~28 MB) is missing there, download it with
69
+ `curl -L --max-time 600 -o "$MODEL_DIR/<file>" https://github.com/thewh1teagle/kokoro-onnx/releases/download/model-files-v1.0/<file>`.
70
+ Never download into /tmp or the project tree.
71
+ - **Voices** (all `lang='en-us'`, speed 1.0; raise speed only to fit a line into its scene, max 1.15):
72
+ female narrator `af_heart` (Kokoro's top-graded voice; alt `af_bella`) is the default; male
73
+ narrator `am_michael` (alt `am_fenrir`) when the human asks for it or the project's audience or
74
+ brand suggests it. A two-voice video may alternate `af_heart` / `am_michael` per scene. Never use
75
+ Piper `-medium` or espeak (robotic). Piper `en_US-lessac-high` is the fallback only when Kokoro
76
+ cannot be installed.
77
+ - **Build recipe.** Work in a fresh `mktemp -d` directory outside the project. Save this as
78
+ `build.py` there, fill in `SCENES` and `TEMPLATE`, and run it:
68
79
 
69
- - Use **Kokoro-82M via the `kokoro-onnx` package** (`pip install kokoro-onnx`, Apache-2.0,
70
- ONNX runtime only — no PyTorch needed despite earlier guidance here claiming otherwise;
71
- confirmed by running it for real). If `python3 -m pip` isn't on PATH, bootstrap it first:
72
- `curl -sS https://bootstrap.pypa.io/get-pip.py | python3 - --user --break-system-packages`,
73
- then `python3 -m pip install --user --break-system-packages kokoro-onnx soundfile`. Pull the
74
- model once: `kokoro-v1.0.onnx` (~325 MB) + `voices-v1.0.bin` (~28 MB) from
75
- `https://github.com/thewh1teagle/kokoro-onnx/releases/download/model-files-v1.0/`. Use the
76
- **`af_heart`** or **`af_bella`** voice (both are Kokoro's top-rated English voices and read
77
- as clearly more natural than Piper, including Piper's `-high` tier — but which one of the
78
- two sounds better is a taste call, not a settled fact; if the human reacts to a generated
79
- video with "still not it," try the other one before reaching for a different library).
80
- `Kokoro(model, voices).create(line, voice='af_heart', speed=1.0, lang='en-us')` returns
81
- `(samples, sample_rate)` (24000 Hz); write each scene's line with `soundfile.write`. If
82
- `kokoro-onnx`/its model download genuinely isn't available (offline, no bandwidth for a
83
- ~350 MB pull), fall back to **Piper** (`pip install piper-tts`) with a `-high` voice tier
84
- (e.g. `en_US-lessac-high`, not `-medium` — `-medium` reads as noticeably robotic) from
85
- `https://huggingface.co/rhasspy/piper-voices`.
86
- - Synthesize one short line per scene, sized to fit that scene's on-screen hold time. Lay the
87
- scene clips onto one mono track (at the model's native sample rate) at each scene's start
88
- offset, encode the result with `ffmpeg -c:a libmp3lame -b:a 64k` **at that native sample
89
- rate — do not downsample or drop below ~48 kbps; both make speech sound crushed/robotic for
90
- only a small size win** (a 30 s mono track at 64 kbps is still only ~235 KB). Base64 it into
91
- a single `<audio src="data:audio/mpeg;base64,...">` tag. Keep the whole document — markup
92
- plus audio — under the 2 MB cap (plenty of headroom at this bitrate).
80
+ ```python
81
+ import base64, os, subprocess
82
+ import numpy as np, soundfile
83
+ from kokoro_onnx import Kokoro
84
+
85
+ MODEL_DIR = os.path.expanduser('~/.cache/kokoro-onnx')
86
+ SCENES = [ # (start_s, end_s, line, voice) — one per scene, from the storyboard captions
87
+ (0.0, 4.0, 'Project name. One-line purpose.', 'af_heart'),
88
+ ]
89
+ k = Kokoro(f'{MODEL_DIR}/kokoro-v1.0.onnx', f'{MODEL_DIR}/voices-v1.0.bin')
90
+ buf, sr = None, None
91
+ for start, end, line, voice in SCENES:
92
+ samples, sr = k.create(line, voice=voice, speed=1.0, lang='en-us')
93
+ room = end - start - 0.3
94
+ if len(samples) / sr > room: # overruns its scene: speed up, never past 1.15
95
+ speed = min(1.15, len(samples) / sr / room)
96
+ samples, sr = k.create(line, voice=voice, speed=speed, lang='en-us')
97
+ if buf is None:
98
+ buf = np.zeros(30 * sr, dtype=np.float32)
99
+ at = int((start + 0.15) * sr)
100
+ clip = samples[: len(buf) - at]
101
+ buf[at : at + len(clip)] += clip
102
+ soundfile.write('narration.wav', buf, sr)
103
+ subprocess.run(['ffmpeg', '-y', '-i', 'narration.wav', '-ac', '1', '-ar', '24000',
104
+ '-c:a', 'libmp3lame', '-b:a', '64k', 'narration.mp3'], check=True)
105
+ b64 = base64.b64encode(open('narration.mp3', 'rb').read()).decode()
106
+ html = open('template.html').read() # your finished HTML with the placeholder below
107
+ html = html.replace('AUDIO_B64_PLACEHOLDER', b64) # <audio src="data:audio/mpeg;base64,AUDIO_B64_PLACEHOLDER">
108
+ open('demo.html', 'w').write(html)
109
+ ```
110
+
111
+ Keep the mono track at the model's native 24000 Hz and ~64 kbps — downsampling or dropping below
112
+ ~48 kbps makes speech sound crushed (a 30 s track is only ~235 KB). Keep the whole document
113
+ under the 2 MB cap.
93
114
  - Wire `play()`/`pause()`/`seek(t)` to the `<audio>` element too (`audio.currentTime = t` on
94
- seek) so the narration never drifts from the visual clock, and start muted-fallback: call
95
- `.play()` on load, and on rejection (autoplay-blocked browsers) show a small "tap for sound"
96
- button instead of failing silently forever.
115
+ seek) so narration never drifts from the visual clock; call `.play()` on load and, on rejection
116
+ (autoplay-blocked), show a small "tap for sound" button instead of failing silently.
97
117
  - Never write the literal pattern `function (` or `function(` in the inline `<script>` —
98
- `project_demo_video_write`'s safety scanner blocks anything matching `/\bFunction\s*\(/i` to
99
- stop dynamic `Function(...)` eval, and it cannot tell your IIFE apart from that. Use arrow
100
- functions (`() => { ... }`) throughout instead.
101
- - If pip/network/piper isn't available in this environment, skip audio entirely and ship the
102
- silent video rather than failing the whole run — audio is a nice-to-have, never a blocker.
118
+ `project_demo_video_write`'s safety scanner blocks anything matching `/\bFunction\s*\(/i`.
119
+ Use arrow functions (`() => { ... }`) throughout.
120
+ - Save the built file by path (see `<process>` step 6). Never retype the base64 audio into a tool
121
+ argument — a ~300 KB payload retyped by the model is exactly how a run once shipped silent.
103
122
  </audio_policy>
104
123
 
105
124
  <process>
@@ -109,11 +128,13 @@ never calls out to a network TTS service.
109
128
  2. Write the claims list: for every goal or feature you plan to show, note the source you read
110
129
  it from. Drop anything without a source.
111
130
  3. Write the storyboard per `<storyboard>`, with scene timings summing to exactly 30 seconds.
112
- 4. Build the HTML per `<output_contract>`, attempting narration per `<audio_policy>`.
131
+ 4. Build the HTML per `<output_contract>` and the narration track per `<audio_policy>`, ending
132
+ with the final document written to a file (`demo.html`) that contains the `<audio>` tag.
113
133
  5. Self-check before saving: scene durations sum to 30, every caption traces to a claim in your
114
134
  list, and the document contains none of the forbidden network APIs.
115
- 6. Call `project_demo_video_write` exactly once with `{ html }`. It validates the document and
116
- writes it as the project's Demo Video.
135
+ 6. Call `project_demo_video_write` exactly once with `{ htmlPath }` — the absolute path of the
136
+ built file. Never pass the document in `html` (retyping base64 is how narration got lost). It
137
+ validates the document and writes it as the project's Demo Video.
117
138
  7. Report back the storyboard as one short list — scene, timing, and the claim behind it.
118
139
  </process>
119
140