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.
- package/dist/assets/{AgentLibrary-BLRc_YvR.js → AgentLibrary-CdRrehRI.js} +1 -1
- package/dist/assets/{Button-DHEnLfDi.js → Button-BTANn6A_.js} +1 -1
- package/dist/assets/{DataModel-C2ytUxJm.js → DataModel-D4DhKYDV.js} +1 -1
- package/dist/assets/{History-eoVSG_DX.js → History-Dd1SM-AR.js} +1 -1
- package/dist/assets/{Hooks-rS9UvMcI.js → Hooks-BaSxmXkk.js} +1 -1
- package/dist/assets/{Library-CdUqaZIj.js → Library-DiRx14Qs.js} +1 -1
- package/dist/assets/{ListDetail-DhtJPQvm.js → ListDetail-DD12P0m2.js} +1 -1
- package/dist/assets/{McpServers-DoOcEGFd.js → McpServers-B5Zhq3uc.js} +1 -1
- package/dist/assets/{Memory-BbYwtTBZ.js → Memory-D8Uibw65.js} +1 -1
- package/dist/assets/{Panel-M4ouFOKI.js → Panel-cUxN--SY.js} +1 -1
- package/dist/assets/{Permissions-DB0DjkgF.js → Permissions-CgocoJzX.js} +1 -1
- package/dist/assets/{Plugins-DnBwH9PN.js → Plugins-BJPK6dlg.js} +2 -2
- package/dist/assets/{ProvenanceBadge-1MQmqxxO.js → ProvenanceBadge-Cw0cwJ1w.js} +1 -1
- package/dist/assets/{RemoteMachines-DnT08vg8.js → RemoteMachines-BIaTLP0C.js} +1 -1
- package/dist/assets/{SaveBar-DGBVV6ro.js → SaveBar-DlGYYdsI.js} +1 -1
- package/dist/assets/{Scheduler-CGDLYpdc.js → Scheduler-I4qOJsyy.js} +1 -1
- package/dist/assets/{ScopeSwitcher-jgpoKdvH.js → ScopeSwitcher-hTMYmy6R.js} +1 -1
- package/dist/assets/{Settings-Ch0zc2cZ.js → Settings-C8AGOoqw.js} +1 -1
- package/dist/assets/{SkillReferenceGraph-Cd04i1nZ.js → SkillReferenceGraph-D9a_UFm6.js} +1 -1
- package/dist/assets/{Skills-DLJLLulV.js → Skills-Bzn5Wasd.js} +1 -1
- package/dist/assets/{SystemPrompt-E4VOiMJN.js → SystemPrompt-D0LpRn5u.js} +1 -1
- package/dist/assets/{TagLibrary-2sCAw54h.js → TagLibrary-CgqAdpwI.js} +1 -1
- package/dist/assets/{TiptapBody-CzqJev6v.js → TiptapBody-u-CUpM2Q.js} +1 -1
- package/dist/assets/{Toggle-D5znlR_5.js → Toggle-Bb6BJMNB.js} +1 -1
- package/dist/assets/{ViewTabs-BENAWvOs.js → ViewTabs-Ck2NIjLp.js} +1 -1
- package/dist/assets/{historyTotals-DsE82z-I.js → historyTotals-vNPWEsnZ.js} +1 -1
- package/dist/assets/{index-B_3CV5Po.js → index-Cq-Ppqar.js} +3 -3
- package/dist/assets/{settingsSchema-CmuwD4OX.js → settingsSchema-D2dkl1uu.js} +1 -1
- package/dist/assets/{skillFrontmatter-CdlHJV9x.js → skillFrontmatter-CW0PqdDX.js} +1 -1
- package/dist/assets/{useAgentsDirWatch-RVq9wMFY.js → useAgentsDirWatch-C9ya0GpQ.js} +1 -1
- package/dist/assets/{useRemotePeers-Cpt47RWb.js → useRemotePeers-4evTc_IE.js} +1 -1
- package/dist/index.html +1 -1
- package/package.json +1 -1
- package/plugins/session-manager-dev/skills/develop/SKILL.md +13 -10
- package/src/main/build-info.json +4 -4
- package/src/main/lib/epic/epicSettle.cjs +105 -0
- package/src/main/lib/epic/epicSweepValidator.cjs +74 -0
- package/src/main/lib/epic/epicValidationHook.cjs +171 -141
- package/src/main/lib/prd/prdCreate.cjs +23 -0
- package/src/main/lib/prd/prdWorktreeReach.cjs +77 -0
- package/src/main/lib/scheduler/rcaReport.cjs +14 -2
- package/src/main/promptSessionEvents.cjs +5 -1
- package/src/main/scheduler.cjs +38 -61
- package/src/main/templates/PRD_AUTHORING.md +5 -1
- package/src/main/webRemote.cjs +27 -1
package/src/main/scheduler.cjs
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
//
|
|
3183
|
-
//
|
|
3184
|
-
//
|
|
3185
|
-
//
|
|
3186
|
-
//
|
|
3187
|
-
//
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
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
|
|
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.
|
package/src/main/webRemote.cjs
CHANGED
|
@@ -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
|
-
|
|
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,
|