claude-code-session-manager 0.117.0 → 0.119.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 (45) hide show
  1. package/dist/assets/{AgentLibrary-BLRc_YvR.js → AgentLibrary-CdRrehRI.js} +1 -1
  2. package/dist/assets/{Button-DHEnLfDi.js → Button-BTANn6A_.js} +1 -1
  3. package/dist/assets/{DataModel-C2ytUxJm.js → DataModel-D4DhKYDV.js} +1 -1
  4. package/dist/assets/{History-eoVSG_DX.js → History-Dd1SM-AR.js} +1 -1
  5. package/dist/assets/{Hooks-rS9UvMcI.js → Hooks-BaSxmXkk.js} +1 -1
  6. package/dist/assets/{Library-CdUqaZIj.js → Library-DiRx14Qs.js} +1 -1
  7. package/dist/assets/{ListDetail-DhtJPQvm.js → ListDetail-DD12P0m2.js} +1 -1
  8. package/dist/assets/{McpServers-DoOcEGFd.js → McpServers-B5Zhq3uc.js} +1 -1
  9. package/dist/assets/{Memory-BbYwtTBZ.js → Memory-D8Uibw65.js} +1 -1
  10. package/dist/assets/{Panel-M4ouFOKI.js → Panel-cUxN--SY.js} +1 -1
  11. package/dist/assets/{Permissions-DB0DjkgF.js → Permissions-CgocoJzX.js} +1 -1
  12. package/dist/assets/{Plugins-DnBwH9PN.js → Plugins-BJPK6dlg.js} +2 -2
  13. package/dist/assets/{ProvenanceBadge-1MQmqxxO.js → ProvenanceBadge-Cw0cwJ1w.js} +1 -1
  14. package/dist/assets/{RemoteMachines-DnT08vg8.js → RemoteMachines-BIaTLP0C.js} +1 -1
  15. package/dist/assets/{SaveBar-DGBVV6ro.js → SaveBar-DlGYYdsI.js} +1 -1
  16. package/dist/assets/{Scheduler-CGDLYpdc.js → Scheduler-I4qOJsyy.js} +1 -1
  17. package/dist/assets/{ScopeSwitcher-jgpoKdvH.js → ScopeSwitcher-hTMYmy6R.js} +1 -1
  18. package/dist/assets/{Settings-Ch0zc2cZ.js → Settings-C8AGOoqw.js} +1 -1
  19. package/dist/assets/{SkillReferenceGraph-Cd04i1nZ.js → SkillReferenceGraph-D9a_UFm6.js} +1 -1
  20. package/dist/assets/{Skills-DLJLLulV.js → Skills-Bzn5Wasd.js} +1 -1
  21. package/dist/assets/{SystemPrompt-E4VOiMJN.js → SystemPrompt-D0LpRn5u.js} +1 -1
  22. package/dist/assets/{TagLibrary-2sCAw54h.js → TagLibrary-CgqAdpwI.js} +1 -1
  23. package/dist/assets/{TiptapBody-CzqJev6v.js → TiptapBody-u-CUpM2Q.js} +1 -1
  24. package/dist/assets/{Toggle-D5znlR_5.js → Toggle-Bb6BJMNB.js} +1 -1
  25. package/dist/assets/{ViewTabs-BENAWvOs.js → ViewTabs-Ck2NIjLp.js} +1 -1
  26. package/dist/assets/{historyTotals-DsE82z-I.js → historyTotals-vNPWEsnZ.js} +1 -1
  27. package/dist/assets/{index-B_3CV5Po.js → index-Cq-Ppqar.js} +3 -3
  28. package/dist/assets/{settingsSchema-CmuwD4OX.js → settingsSchema-D2dkl1uu.js} +1 -1
  29. package/dist/assets/{skillFrontmatter-CdlHJV9x.js → skillFrontmatter-CW0PqdDX.js} +1 -1
  30. package/dist/assets/{useAgentsDirWatch-RVq9wMFY.js → useAgentsDirWatch-C9ya0GpQ.js} +1 -1
  31. package/dist/assets/{useRemotePeers-Cpt47RWb.js → useRemotePeers-4evTc_IE.js} +1 -1
  32. package/dist/index.html +1 -1
  33. package/package.json +1 -1
  34. package/plugins/session-manager-dev/skills/develop/SKILL.md +13 -10
  35. package/src/main/build-info.json +4 -4
  36. package/src/main/lib/epic/epicSettle.cjs +105 -0
  37. package/src/main/lib/epic/epicSweepValidator.cjs +74 -0
  38. package/src/main/lib/epic/epicValidationHook.cjs +171 -141
  39. package/src/main/lib/prd/prdCreate.cjs +23 -0
  40. package/src/main/lib/prd/prdWorktreeReach.cjs +77 -0
  41. package/src/main/lib/scheduler/rcaReport.cjs +14 -2
  42. package/src/main/promptSessionEvents.cjs +5 -1
  43. package/src/main/scheduler.cjs +38 -61
  44. package/src/main/templates/PRD_AUTHORING.md +5 -1
  45. package/src/main/webRemote.cjs +27 -1
@@ -99,7 +99,8 @@ const sessionsStore = require('./sessionsStore.cjs');
99
99
  const { enqueueExternalPrompt } = require('./chatRunner.cjs');
100
100
  const { appendResponseEventIfKnown, appendResponseEventWithReason } = require('./promptSessionEvents.cjs');
101
101
  const { buildReviewNotice, selectDueReviewNotices, formatReviewNotice, holdMsFromEnv } = require('./lib/scheduler/reviewNotice.cjs');
102
- const { maybeEnqueueValidationPrompt } = require('./lib/epic/epicValidationHook.cjs');
102
+ const epicValidationHook = require('./lib/epic/epicValidationHook.cjs');
103
+ const prdCreate = require('./lib/prd/prdCreate.cjs');
103
104
  const { parseValidationSentinels } = require('./lib/scheduler/validationSentinels.cjs');
104
105
  const { hasDownstreamValidator } = require('./lib/prd/planValidator.cjs');
105
106
  const promptSessionTranscript = require('./promptSessionTranscript.cjs');
@@ -3018,6 +3019,29 @@ async function resolveFixPlanPath(failedJob) {
3018
3019
  return { fixPath, fixSlug, group, livePrdDir };
3019
3020
  }
3020
3021
 
3022
+ /**
3023
+ * defaultScheduleSettle — production wiring for notifyOriginatingTab's
3024
+ * scheduleSettle option: arms the debounced Epic settle runner with live deps.
3025
+ * `remote` is defined later in the module but only read at call time.
3026
+ */
3027
+ function defaultScheduleSettle({ cwd, epicId }) {
3028
+ const deps = {
3029
+ loadJobs: async () => (await readQueue()).jobs || [],
3030
+ loadHistory: () => queueHistory.readHistory({ limit: 500 }),
3031
+ createPrd: (input) => prdCreate.createPrd(input, remote),
3032
+ resolveBaseSha: async (c, commit) => {
3033
+ try {
3034
+ return String(await execGitAt(c, ['rev-parse', `${commit}^`])).trim() || null;
3035
+ } catch {
3036
+ return null;
3037
+ }
3038
+ },
3039
+ sendPrompt: enqueueExternalPrompt,
3040
+ appendResponseEvent: appendResponseEventIfKnown,
3041
+ };
3042
+ epicValidationHook.scheduleEpicSettle({ cwd, epicId }, (args) => epicValidationHook.runEpicSettle(args, deps));
3043
+ }
3044
+
3021
3045
  /**
3022
3046
  * notifyOriginatingTab(job) → void
3023
3047
  *
@@ -3056,12 +3080,7 @@ async function notifyOriginatingTab(job, {
3056
3080
  appendResponseEvent = appendResponseEventIfKnown,
3057
3081
  appendTranscriptTurn = promptSessionTranscript.appendTurn,
3058
3082
  readResultFromLog = extractResultTextFromLog,
3059
- enqueueValidation = maybeEnqueueValidationPrompt,
3060
- // PRD 1408: same queue rows spawnJob/tickQueue already use — read fresh
3061
- // here (not threaded through the terminal-transition snapshot) since this
3062
- // fires well after dispatch, when the plan may have gained/dropped a
3063
- // trailing validator row since this job was spawned.
3064
- loadJobs = async () => (await readQueue()).jobs || [],
3083
+ scheduleSettle = defaultScheduleSettle,
3065
3084
  } = {}) {
3066
3085
  try {
3067
3086
  const prd = await resolveNotifyPrd(job, parsePrdRaw);
@@ -3174,55 +3193,22 @@ async function notifyOriginatingTab(job, {
3174
3193
  prdSlug: job.slug,
3175
3194
  outcome: job.status,
3176
3195
  validation: 'unvalidated',
3196
+ ...(typeof job.agentType === 'string' ? { agentType: job.agentType } : {}),
3177
3197
  }).catch((e) => {
3178
3198
  console.error('[scheduler] notifyOriginatingTab appendResponseEvent error', job?.slug, e);
3179
3199
  return false;
3180
3200
  });
3181
3201
  if (routed) {
3182
- // PRD 986: a successful check-in append triggers ONE validation
3183
- // prompt into the authoring Epic's own session, asking it to verify
3184
- // every acceptance criterion against the real working tree and reply
3185
- // VERIFIED or REFUTED. Fire-and-forget, same convention as this
3186
- // function's own call site in tickQueue: a failure to enqueue is
3187
- // logged inside the hook, never thrown, and never blocks the job's
3188
- // status transition or the response-event append (which already
3189
- // happened above). All gating (kill-switch SM_EPIC_VALIDATION_DISABLE,
3190
- // active-Epic check, once-per-(epicId, prdSlug), loop guard, slot-pool
3191
- // routing) lives in lib/epic/epicValidationHook.cjs.
3192
- //
3193
- // PRD 1407: a validator job's own check-in never enqueues a
3194
- // validation prompt for itself — it IS the plan's validation pass,
3195
- // not a work-item awaiting one.
3196
- //
3197
- // PRD 1408: nor does an ordinary work-item whose plan already ends in
3198
- // a trailing validator job depending on it — that validator's own
3199
- // check-in (above) is the single validation pass for the whole plan,
3200
- // so a per-PRD prompt here would be a second, redundant ask into the
3201
- // same (Opus/Fable-tier) authoring session.
3202
- if (job.agentType !== 'validator') {
3203
- let skipForDownstreamValidator = false;
3204
- try {
3205
- skipForDownstreamValidator = hasDownstreamValidator(job, await loadJobs());
3206
- } catch (e) {
3207
- console.error('[scheduler] notifyOriginatingTab loadJobs error', job?.slug, e);
3208
- }
3209
- if (!skipForDownstreamValidator) {
3210
- try {
3211
- enqueueValidation(
3212
- {
3213
- cwd: job.cwd || null,
3214
- epicId,
3215
- prdSlug: job.slug,
3216
- prdPath: prd?.path || prdPaths.archivedPrdPathForJob(job) || null,
3217
- outcome: job.status,
3218
- eventValidation: 'unvalidated',
3219
- },
3220
- { sendPrompt },
3221
- );
3222
- } catch (e) {
3223
- console.error('[scheduler] notifyOriginatingTab enqueueValidation error', job?.slug, e);
3224
- }
3225
- }
3202
+ // The debounced Epic settle runner (lib/epic/epicValidationHook.cjs)
3203
+ // owns all validation messaging now (replaces the per-PRD prompts of
3204
+ // PRDs 986/1407/1408): every successful terminal check-in, a
3205
+ // validator's own included (its finish usually settles the Epic),
3206
+ // just re-arms the per-Epic debounce. A finished PRD never sends a
3207
+ // chat prompt into its authoring Epic by itself.
3208
+ try {
3209
+ scheduleSettle({ cwd: job.cwd || null, epicId });
3210
+ } catch (e) {
3211
+ console.error('[scheduler] notifyOriginatingTab scheduleSettle error', job?.slug, e);
3226
3212
  }
3227
3213
  return;
3228
3214
  }
@@ -6456,17 +6442,10 @@ async function spawnJob(job, runId, runDir, defaultCwd, resumeTarget = null, sib
6456
6442
  let mechanicalRecoveryTarget = null;
6457
6443
  let terminalNotifySnapshot = null;
6458
6444
  const newlyCompletedPrds = [];
6459
- // PRD 1408: captured inside the mutate() below (the one in-memory read of
6460
- // `s.jobs` this finalize pass already does) so notifyOriginatingTab's own
6461
- // hasDownstreamValidator check can reuse it instead of a second, redundant
6462
- // readQueue() — that call federates queue.json shards across every known
6463
- // project cwd on the machine, not just this job's own project.
6464
- let jobsSnapshotForNotify = null;
6465
6445
  // Base HEAD for the mechanical-recovery futility check — read here, never inside mutate() (no I/O).
6466
6446
  const finalizeHeadSha = worktreeIntegrationDetail && worktreeIntegrationDetail.failureKind === 'content_conflict'
6467
6447
  ? await gitTreeGuard.gitHead(guardCwd) : null;
6468
6448
  await mutate((s) => {
6469
- jobsSnapshotForNotify = s.jobs.slice();
6470
6449
  const i2 = s.jobs.findIndex((x) => x.slug === job.slug);
6471
6450
  // A job already moved off 'running' by someone else (namely
6472
6451
  // remote.cancelJob, PRD 1024 — it SIGTERMs the process then finalizes
@@ -6883,9 +6862,7 @@ async function spawnJob(job, runId, runDir, defaultCwd, resumeTarget = null, sib
6883
6862
  });
6884
6863
 
6885
6864
  if (terminalNotifySnapshot) {
6886
- notifyOriginatingTab(terminalNotifySnapshot, {
6887
- loadJobs: async () => jobsSnapshotForNotify || [],
6888
- }).catch((e) => {
6865
+ notifyOriginatingTab(terminalNotifySnapshot).catch((e) => {
6889
6866
  console.error('[scheduler] notifyOriginatingTab error', job.slug, e);
6890
6867
  });
6891
6868
  }
@@ -1,4 +1,4 @@
1
- <!-- PRD_AUTHORING.md v5 -->
1
+ <!-- PRD_AUTHORING.md v6 -->
2
2
  # PRD Authoring Guide — Scheduler Safety Rules
3
3
 
4
4
  Rule: every PRD you queue follows the rules below.
@@ -125,6 +125,9 @@ A short Epic-context digest is prepended automatically for orientation only —
125
125
 
126
126
  The executor can't ask you anything: no `ScheduleWakeup`, Cron, Monitor, `AskUserQuestion`, plan mode, or worktree tools; background tasks are off (a `timeout` command is always on PATH — the app installs a shim on macOS). It must never stop to ask a question — it makes the safest reasonable call and reports it, so write the PRD so that call is obvious.
127
127
 
128
+ Worktree reach: every PRD runs in an isolated git worktree holding tracked files plus symlinked `node_modules`/`.venv`/`venv` only. A step that needs gitignored runtime state (databases, `downloads/`, browser profiles, logs) or a live side effect must address the main checkout by absolute path (e.g. `$HOME/Projects/<repo>/...`), be artifact-only (a deliverable artifact + `artifactPaths`, no tracked edits), and `dependsOn` the PRD that lands its tracked edits. Host crontab installs stay human-only (see §15).
129
+ Why: burrow PRD 1516 halted because its relative `.venv` python against `downloads/fb-tracker.db`, `scripts/install-cron.sh` and `pipeline-cron.sh --now` resolved inside the worktree (no runtime state; install-cron would bake a temporary worktree path into crontab), while sibling 1494 succeeded the same day using an absolute main-checkout path and artifact-only output. `scheduler_create_prd` warns on relative gitignored paths.
130
+
128
131
  ---
129
132
 
130
133
  ## §8 Scope sizing — target ≤10 min, ceiling 15
@@ -228,6 +231,7 @@ Before queueing a new PRD, verify each of these:
228
231
  - [ ] **§5 Frontmatter:** `title`, `cwd` (exists on this machine), `estimateMinutes` present; `dependsOn` used for ordering, not `parallelGroup`.
229
232
  - [ ] **§6 Gate and files:** `gate` follows every rule in §6 (or is `["none"]` alone); `files` is 1–50 repo-relative paths, no overlap with a concurrent PRD.
230
233
  - [ ] **§7 Self-contained:** no reference to "the conversation" or outside context; paths/identifiers inline; clean UTF-8, no NUL bytes (`grep -qP '\x00' file && echo BAD`).
234
+ - [ ] **§7 Worktree reach:** no relative path to a gitignored file; live-run steps use absolute main-checkout paths in an artifact-only PRD. Why: the job worktree has no gitignored runtime state, so relative paths fail or bake a temporary path into host config (burrow 1516).
231
235
  - [ ] **§8 Scope:** targets ≤10 min, ceiling 15; e2e/publish sharded to one spec per PRD.
232
236
  - [ ] **§9 Failure surfacing:** errors exit 1 with a diagnostic line; no silent `|| true`.
233
237
  - [ ] **§10 Negative assertions:** every "should find nothing" check is inverted to exit 0 on the clean case.
@@ -53,6 +53,8 @@ const BACKOFF_MULT = 2;
53
53
 
54
54
  // Heartbeat (ADR §2.3)
55
55
  const PING_INTERVAL_MS = 30_000;
56
+ // Relay sends protocol pings every 30s; 100s = just over 3 missed beats.
57
+ const RELAY_PING_SILENCE_MS = 100_000;
56
58
  const PONG_TIMEOUT_MS = 10_000;
57
59
  const MAX_MISSED_PONGS = 3;
58
60
 
@@ -81,6 +83,7 @@ let _backoffMs = BACKOFF_INIT_MS;
81
83
  let _pingTimer = null;
82
84
  let _pongTimer = null;
83
85
  let _missedPongs = 0;
86
+ let _lastRelayPingAt = 0; // last protocol-level relay ping (or socket open)
84
87
  let _configCache = null;
85
88
  let _configCacheAt = 0;
86
89
  let _repairNeeded = false; // relay rejected the device token (invalid_token / 401): terminal until re-pair
@@ -308,10 +311,26 @@ function stopHeartbeat() {
308
311
  if (_pongTimer) { clearTimeout(_pongTimer); _pongTimer = null; }
309
312
  }
310
313
 
314
+ // The relay can evict a live conn from its device map (stale close of a prior
315
+ // socket). App-level pongs still arrive, but protocol pings stop — terminate so
316
+ // the close handler reconnects and re-registers.
317
+ function checkRelayLiveness(now = Date.now()) {
318
+ if (!_ws || _ws.readyState !== WebSocket.OPEN) return false;
319
+ if (_lastRelayPingAt <= 0 || now - _lastRelayPingAt <= RELAY_PING_SILENCE_MS) return false;
320
+ logs.writeLine({
321
+ scope: 'webRemote', level: 'warn',
322
+ message: 'relay heartbeat silent — reconnecting',
323
+ meta: { silentMs: now - _lastRelayPingAt },
324
+ });
325
+ _ws.terminate();
326
+ return true;
327
+ }
328
+
311
329
  function startHeartbeat() {
312
330
  stopHeartbeat();
313
331
  _pingTimer = setInterval(() => {
314
332
  if (!_ws || _ws.readyState !== WebSocket.OPEN) { stopHeartbeat(); return; }
333
+ if (checkRelayLiveness()) return;
315
334
  const pingId = crypto.randomUUID();
316
335
  _ws.send(JSON.stringify({ type: 'ping', id: pingId, ts: Date.now() }));
317
336
  _pongTimer = setTimeout(() => {
@@ -429,6 +448,7 @@ async function connect() {
429
448
  logs.writeLine({ scope: 'webRemote', level: 'info', message: 'connected to relay' });
430
449
  _backoffMs = BACKOFF_INIT_MS;
431
450
  _missedPongs = 0;
451
+ _lastRelayPingAt = Date.now();
432
452
  startHeartbeat();
433
453
  // Update lastConnectedAt without exposing token
434
454
  invalidateConfigCache(); // a pin written since the last cached read must not be overwritten
@@ -481,7 +501,10 @@ async function connect() {
481
501
  // too. This keeps the session up even if the relay ever stops answering the
482
502
  // app-level pings (the exact failure that silently dropped sessions every ~100s
483
503
  // before the relay learned to pong). node 'ws' auto-replies to the ping itself.
484
- ws.on('ping', () => { _missedPongs = 0; });
504
+ // It is also the orphaned-conn signal: the relay's stale close of a previous
505
+ // socket can evict this live conn from its map, after which only app-level
506
+ // pongs arrive and relay protocol pings stop (see checkRelayLiveness).
507
+ ws.on('ping', () => { _missedPongs = 0; _lastRelayPingAt = Date.now(); });
485
508
  }
486
509
 
487
510
  async function handleTokenRevoked(deviceId) {
@@ -1550,6 +1573,9 @@ module.exports = {
1550
1573
  getDispatchMap,
1551
1574
  buildRemoteSessionList,
1552
1575
  setWs: (ws) => { _ws = ws; },
1576
+ checkRelayLiveness,
1577
+ setLastRelayPingAt: (ts) => { _lastRelayPingAt = ts; },
1578
+ RELAY_PING_SILENCE_MS,
1553
1579
  connect,
1554
1580
  hasReconnectTimer: () => _reconnectTimer !== null,
1555
1581
  cancelReconnect,