claude-code-session-manager 0.38.1 → 0.38.3
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/{TiptapBody-DlPTesio.js → TiptapBody-R9igz9Iz.js} +1 -1
- package/dist/assets/{index-CBSyk1aI.js → index-BJRUaZ96.js} +163 -163
- package/dist/index.html +1 -1
- package/package.json +1 -1
- package/plugins/session-manager-dev/skills/pr-review-sweep/1-classify/SKILL.md +11 -0
- package/plugins/session-manager-dev/skills/pr-review-sweep/3-queue/SKILL.md +8 -1
- package/src/main/__tests__/browserAgentServer.test.cjs +50 -1
- package/src/main/__tests__/scheduler-notify-originating-tab.test.cjs +99 -0
- package/src/main/browserAgentServer.cjs +26 -5
- package/src/main/chatRunner.cjs +44 -0
- package/src/main/index.cjs +2 -1
- package/src/main/ipcSchemas.cjs +20 -0
- package/src/main/lib/__tests__/localAdminHttp.test.cjs +72 -1
- package/src/main/lib/localAdminHttp.cjs +78 -5
- package/src/main/lib/prdCreate.cjs +4 -1
- package/src/main/scheduler/prdParser.cjs +5 -0
- package/src/main/scheduler.cjs +69 -1
- package/src/main/webRemote.cjs +10 -0
- package/src/preload/api.d.ts +11 -0
- package/src/preload/index.cjs +7 -0
package/src/main/scheduler.cjs
CHANGED
|
@@ -60,6 +60,8 @@ const { openLog, withChildAndLog } = require('./lib/childWithLog.cjs');
|
|
|
60
60
|
const { sendIfAlive } = require('./lib/sendToRenderer.cjs');
|
|
61
61
|
const { createBroadcastCoalescer } = require('./lib/broadcastCoalescer.cjs');
|
|
62
62
|
const prdParser = require('./scheduler/prdParser.cjs');
|
|
63
|
+
const sessionsStore = require('./sessionsStore.cjs');
|
|
64
|
+
const { enqueueExternalPrompt } = require('./chatRunner.cjs');
|
|
63
65
|
const { verifyRun } = require('./runVerify.cjs');
|
|
64
66
|
const logs = require('./logs.cjs');
|
|
65
67
|
const { schemas, validated } = require('./ipcSchemas.cjs');
|
|
@@ -1185,6 +1187,62 @@ function applyOrphanOutcome(job, outcome, killNote = '') {
|
|
|
1185
1187
|
}
|
|
1186
1188
|
}
|
|
1187
1189
|
|
|
1190
|
+
/**
|
|
1191
|
+
* isNotifiableTerminalStatus(effectiveStatus) → boolean
|
|
1192
|
+
*
|
|
1193
|
+
* Gates notifyOriginatingTab to true terminal transitions only: 'completed'
|
|
1194
|
+
* and 'failed'. Excludes 'needs_review' (not yet truly done — may still
|
|
1195
|
+
* auto-fix) and, implicitly, the rateLimited/paused-queue path, which never
|
|
1196
|
+
* reaches effectiveStatus computation at all (it takes the treatAsPending
|
|
1197
|
+
* branch in spawnJob and resets the job to pending instead).
|
|
1198
|
+
*/
|
|
1199
|
+
function isNotifiableTerminalStatus(effectiveStatus) {
|
|
1200
|
+
return effectiveStatus === 'completed' || effectiveStatus === 'failed';
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
/**
|
|
1204
|
+
* notifyOriginatingTab(job) → void
|
|
1205
|
+
*
|
|
1206
|
+
* On a true terminal transition (completed/failed — never the benign
|
|
1207
|
+
* rateLimited auto-pause, which resets the job to pending instead), push a
|
|
1208
|
+
* short status prompt into the chat tab that queued this PRD via
|
|
1209
|
+
* enqueueExternalPrompt (PRD 753). Resolution order: (1) the PRD's own
|
|
1210
|
+
* `sourceTabId` frontmatter, captured at creation time; (2) the first open
|
|
1211
|
+
* tab (per sessionsStore's persisted tabs.json) whose cwd matches the job's
|
|
1212
|
+
* cwd — first match only, no fan-out to multiple matching tabs; (3) no-op.
|
|
1213
|
+
* Never throws to the caller (fire-and-forget from spawnJob). Deps are
|
|
1214
|
+
* injectable (mirrors partitionBootOrphans's isAlive param) so unit tests can
|
|
1215
|
+
* exercise the resolution logic without touching disk/electron.
|
|
1216
|
+
*/
|
|
1217
|
+
async function notifyOriginatingTab(job, {
|
|
1218
|
+
parsePrdRaw = prdParser.parsePrdRaw,
|
|
1219
|
+
loadSessions = sessionsStore.load,
|
|
1220
|
+
sendPrompt = enqueueExternalPrompt,
|
|
1221
|
+
} = {}) {
|
|
1222
|
+
try {
|
|
1223
|
+
const prdPath = path.join(PRDS_DIR, `${job.slug}.md`);
|
|
1224
|
+
const prd = await parsePrdRaw(prdPath).catch(() => null);
|
|
1225
|
+
|
|
1226
|
+
let targetTabId = prd?.sourceTabId || null;
|
|
1227
|
+
if (!targetTabId) {
|
|
1228
|
+
const jobCwd = job.cwd || null;
|
|
1229
|
+
if (jobCwd) {
|
|
1230
|
+
const { tabs } = await loadSessions();
|
|
1231
|
+
const match = (tabs || []).find((t) => t && t.cwd === jobCwd);
|
|
1232
|
+
targetTabId = match?.id || null;
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
if (!targetTabId) {
|
|
1236
|
+
console.log(`[scheduler] notifyOriginatingTab: no target tab for ${job.slug}, skipping`);
|
|
1237
|
+
return;
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
sendPrompt(targetTabId, `PRD ${job.slug} finished: ${job.status}. Check Scheduler for details.`);
|
|
1241
|
+
} catch (e) {
|
|
1242
|
+
console.error('[scheduler] notifyOriginatingTab error', job?.slug, e);
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1188
1246
|
/** Scan the tail of a job's log for the canonical rate-limit signal. We look
|
|
1189
1247
|
* at the last 16 KB — final result event always lands at the end.
|
|
1190
1248
|
* Uses readTail() so no raw fd lifecycle is needed here. */
|
|
@@ -1941,6 +1999,7 @@ async function spawnJob(job, runId, runDir, defaultCwd) {
|
|
|
1941
1999
|
let needsInvestigationNow = false;
|
|
1942
2000
|
let investigationJobSnapshot = null;
|
|
1943
2001
|
let needsReviewRcaSnapshot = null;
|
|
2002
|
+
let terminalNotifySnapshot = null;
|
|
1944
2003
|
await mutate((s) => {
|
|
1945
2004
|
const i2 = s.jobs.findIndex((x) => x.slug === job.slug);
|
|
1946
2005
|
if (i2 >= 0) {
|
|
@@ -1994,6 +2053,9 @@ async function spawnJob(job, runId, runDir, defaultCwd) {
|
|
|
1994
2053
|
}
|
|
1995
2054
|
delete s.jobs[i2].runtime;
|
|
1996
2055
|
|
|
2056
|
+
if (isNotifiableTerminalStatus(effectiveStatus)) {
|
|
2057
|
+
terminalNotifySnapshot = { ...s.jobs[i2] };
|
|
2058
|
+
}
|
|
1997
2059
|
if (effectiveStatus === 'failed') {
|
|
1998
2060
|
actuallyFailed = true;
|
|
1999
2061
|
failedJobSnapshot = { ...s.jobs[i2] };
|
|
@@ -2052,6 +2114,12 @@ async function spawnJob(job, runId, runDir, defaultCwd) {
|
|
|
2052
2114
|
});
|
|
2053
2115
|
await broadcast({ flush: true });
|
|
2054
2116
|
|
|
2117
|
+
if (terminalNotifySnapshot) {
|
|
2118
|
+
notifyOriginatingTab(terminalNotifySnapshot).catch((e) => {
|
|
2119
|
+
console.error('[scheduler] notifyOriginatingTab error', job.slug, e);
|
|
2120
|
+
});
|
|
2121
|
+
}
|
|
2122
|
+
|
|
2055
2123
|
if (needsReviewRcaSnapshot) {
|
|
2056
2124
|
// Fire-and-forget, mirroring the DoD drain hook: never blocks the status
|
|
2057
2125
|
// transition, never throws to this caller.
|
|
@@ -3386,4 +3454,4 @@ function registerAdminRoutes(adminHttp, remoteObj = remote) {
|
|
|
3386
3454
|
});
|
|
3387
3455
|
}
|
|
3388
3456
|
|
|
3389
|
-
module.exports = { registerScheduleHandlers, attachWindow, init, ROOT, PRDS_DIR, allocateParallelGroup, selectHistoryJobs, parsePorcelain, FINISH_PROTOCOL, remote, pickNextBatch, pickForProject, reapDeadRunningJobs, pollRecoveryClearSource, memoryLimitedBatchSize, availableForJobs, reverifyNeedsReview, isRescanCandidate, isPromotableOriginal, selectAutoFixTargets, isEligibleForImmediateAutoFix, resolveRunId, isUnresolvableNeedsReview, healTargetForFix, buildInvestigationPrompt, committedInWindow, computeCommittedDuringRun, isFixPlanSlug, isFixPlanBeyondDepthCap, MAX_INVESTIGATION_DEPTH, forceTickOutcome, applyPauseCleared, detectNetworkErrorInLog, detectRateLimitInLog, classifyFailureOutcome, TRANSIENT_RETRY_CAP, buildScheduleStatePayload, partitionBootOrphans, applyOrphanOutcome, BOOT_ORPHAN_KILL_GRACE_MS, feedbackSweepDue, FEEDBACK_SWEEP_TICK_INTERVAL, sweepFeedback, registerAdminRoutes };
|
|
3457
|
+
module.exports = { registerScheduleHandlers, attachWindow, init, ROOT, PRDS_DIR, allocateParallelGroup, selectHistoryJobs, parsePorcelain, FINISH_PROTOCOL, remote, pickNextBatch, pickForProject, reapDeadRunningJobs, pollRecoveryClearSource, memoryLimitedBatchSize, availableForJobs, reverifyNeedsReview, isRescanCandidate, isPromotableOriginal, selectAutoFixTargets, isEligibleForImmediateAutoFix, resolveRunId, isUnresolvableNeedsReview, healTargetForFix, buildInvestigationPrompt, committedInWindow, computeCommittedDuringRun, isFixPlanSlug, isFixPlanBeyondDepthCap, MAX_INVESTIGATION_DEPTH, forceTickOutcome, applyPauseCleared, detectNetworkErrorInLog, detectRateLimitInLog, classifyFailureOutcome, TRANSIENT_RETRY_CAP, buildScheduleStatePayload, partitionBootOrphans, applyOrphanOutcome, BOOT_ORPHAN_KILL_GRACE_MS, feedbackSweepDue, FEEDBACK_SWEEP_TICK_INTERVAL, sweepFeedback, registerAdminRoutes, notifyOriginatingTab, isNotifiableTerminalStatus };
|
package/src/main/webRemote.cjs
CHANGED
|
@@ -1056,6 +1056,7 @@ function getDispatchMap() {
|
|
|
1056
1056
|
const sessionsStore = require('./sessionsStore.cjs');
|
|
1057
1057
|
const scheduler = require('./scheduler.cjs');
|
|
1058
1058
|
const { remote: histRemote } = require('./historyAggregator.cjs');
|
|
1059
|
+
const chatRunner = require('./chatRunner.cjs');
|
|
1059
1060
|
|
|
1060
1061
|
_dispatchMap = {
|
|
1061
1062
|
'cmd:sessions:load': async () =>
|
|
@@ -1146,6 +1147,15 @@ function getDispatchMap() {
|
|
|
1146
1147
|
stopSessionWatch(parsed.tabId);
|
|
1147
1148
|
return { ok: true };
|
|
1148
1149
|
},
|
|
1150
|
+
|
|
1151
|
+
// Push a prompt into an open tab's chat queue from the paired phone
|
|
1152
|
+
// client — in-process call, no admin HTTP hop needed since webRemote
|
|
1153
|
+
// already runs inside the Electron main process (PRD 753).
|
|
1154
|
+
'cmd:chat:send': async (payload) => {
|
|
1155
|
+
const parsed = schemas.chatExternalSend.parse(payload);
|
|
1156
|
+
chatRunner.enqueueExternalPrompt(parsed.tabId, parsed.prompt);
|
|
1157
|
+
return { ok: true };
|
|
1158
|
+
},
|
|
1149
1159
|
};
|
|
1150
1160
|
|
|
1151
1161
|
return _dispatchMap;
|
package/src/preload/api.d.ts
CHANGED
|
@@ -1102,6 +1102,13 @@ export interface ChatRunNoticeEvent {
|
|
|
1102
1102
|
message: string;
|
|
1103
1103
|
}
|
|
1104
1104
|
|
|
1105
|
+
/** Pushed by enqueueExternalPrompt (PRD 753) — Web Remote, the admin HTTP
|
|
1106
|
+
* route, or an MCP caller asking to enqueue a prompt into an open tab. */
|
|
1107
|
+
export interface ChatExternalSendEvent {
|
|
1108
|
+
tabId: string;
|
|
1109
|
+
prompt: string;
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1105
1112
|
export interface SessionManagerAPI {
|
|
1106
1113
|
app: {
|
|
1107
1114
|
version: () => Promise<string>;
|
|
@@ -1475,6 +1482,10 @@ export interface SessionManagerAPI {
|
|
|
1475
1482
|
onComplete: (handler: (e: ChatRunCompleteEvent) => void) => () => void;
|
|
1476
1483
|
onError: (handler: (e: ChatRunErrorEvent) => void) => () => void;
|
|
1477
1484
|
onNotice: (handler: (e: ChatRunNoticeEvent) => void) => () => void;
|
|
1485
|
+
/** Fires when a main-process caller (Web Remote, admin HTTP route, MCP
|
|
1486
|
+
* tool) pushes a prompt into an open tab's queue from outside the
|
|
1487
|
+
* renderer (PRD 753). */
|
|
1488
|
+
onExternalSend: (handler: (e: ChatExternalSendEvent) => void) => () => void;
|
|
1478
1489
|
/** Classify a queued PromptTicket's text as 'inline' (run through
|
|
1479
1490
|
* chatRunner) or 'develop' (dispatch to /develop for PRD decomposition).
|
|
1480
1491
|
* A single bounded `claude -p` call, never a scheduler job. */
|
package/src/preload/index.cjs
CHANGED
|
@@ -436,6 +436,13 @@ contextBridge.exposeInMainWorld('api', {
|
|
|
436
436
|
ipcRenderer.on('chat:run:notice', listener);
|
|
437
437
|
return () => ipcRenderer.removeListener('chat:run:notice', listener);
|
|
438
438
|
},
|
|
439
|
+
/** Fires when a main-process caller pushes a prompt into an open tab's
|
|
440
|
+
* queue from outside the renderer (Web Remote / admin HTTP / MCP). */
|
|
441
|
+
onExternalSend: (handler) => {
|
|
442
|
+
const listener = (_e, payload) => handler(payload);
|
|
443
|
+
ipcRenderer.on('chat:external-send', listener);
|
|
444
|
+
return () => ipcRenderer.removeListener('chat:external-send', listener);
|
|
445
|
+
},
|
|
439
446
|
/** Classify a queued PromptTicket's text as 'inline' or 'develop'. */
|
|
440
447
|
classifyTicket: (payload) => ipcRenderer.invoke('chat:classify-ticket', payload),
|
|
441
448
|
},
|