claude-code-session-manager 0.104.2 → 0.106.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/README.md +15 -3
- package/bin/cli.cjs +0 -5
- package/dist/assets/{AgentLibrary-QPw9gC_o.js → AgentLibrary-CKOZzc2W.js} +1 -1
- package/dist/assets/{Button-8RAbgh1d.js → Button-yuqFpQsK.js} +1 -1
- package/dist/assets/{DataModel-BEkuOZuS.js → DataModel-Bwdc_gaJ.js} +1 -1
- package/dist/assets/{History-BLrNSazM.js → History-D1Z0lydo.js} +1 -1
- package/dist/assets/{Hooks-C8P6wQq1.js → Hooks-CyD6l4Is.js} +1 -1
- package/dist/assets/{Library-CWFvBZw-.js → Library-fnod5cPe.js} +1 -1
- package/dist/assets/{ListDetail-B1jVMgzN.js → ListDetail-BKO68SJ4.js} +1 -1
- package/dist/assets/{MarkdownEditor-JrnUbWjW.js → MarkdownEditor-DrYMnMBy.js} +1 -1
- package/dist/assets/{McpServers-BGqTxDBm.js → McpServers-CgfItWhF.js} +1 -1
- package/dist/assets/{Memory-h8bhRNyI.js → Memory-4M3gO3_r.js} +1 -1
- package/dist/assets/{Panel-srf0FT0m.js → Panel-CKbXo-LT.js} +1 -1
- package/dist/assets/{Permissions-BA4oWydI.js → Permissions-Bc9EpjXL.js} +1 -1
- package/dist/assets/{Plugins-DTuTQqUL.js → Plugins-B_-PlEIx.js} +2 -2
- package/dist/assets/{ProvenanceBadge-BW18EquH.js → ProvenanceBadge-Cxev7XSA.js} +1 -1
- package/dist/assets/{SaveBar-Cc6UukFa.js → SaveBar-DiqscG6q.js} +1 -1
- package/dist/assets/{Scheduler-BctPlNL1.js → Scheduler-CCcJzuu8.js} +1 -1
- package/dist/assets/{ScopeSwitcher-DV1OnSuc.js → ScopeSwitcher-BsM4-jHQ.js} +1 -1
- package/dist/assets/{Settings-Bpyt1RGa.js → Settings-C3-VL-T8.js} +1 -1
- package/dist/assets/{SkillReferenceGraph-B28AYo12.js → SkillReferenceGraph-B9YlOuPo.js} +1 -1
- package/dist/assets/{Skills-zXLI5OB_.js → Skills-Dtf-z_zN.js} +1 -1
- package/dist/assets/{SystemPrompt-DKoN0UAV.js → SystemPrompt-HJDn0E8C.js} +1 -1
- package/dist/assets/{TagLibrary-BkDOa8IM.js → TagLibrary-DLGOfauU.js} +1 -1
- package/dist/assets/{TiptapBody-CIY1f6mI.js → TiptapBody-Cdmv1CuM.js} +1 -1
- package/dist/assets/{Toggle-C139wuAK.js → Toggle-CQFhOo1F.js} +1 -1
- package/dist/assets/{ViewTabs-CUEfsMOe.js → ViewTabs-CxjDCHjH.js} +1 -1
- package/dist/assets/index-Blo38vfX.js +3077 -0
- package/dist/assets/{index-D89EUizB.css → index-DkJv4l6L.css} +1 -1
- package/dist/assets/{settingsSchema-CAFckLz6.js → settingsSchema-B0uoL_1R.js} +1 -1
- package/dist/assets/useAgentsDirWatch-e3TJUFPP.js +1 -0
- package/dist/assets/{useConfigDirWatch-QKxfZx9X.js → useConfigDirWatch-Dxv7JbCU.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +10 -12
- package/plugins/session-manager-dev/skills/builder/3-publish/SKILL.md +12 -0
- package/plugins/session-manager-dev/skills/develop/SKILL.md +4 -2
- package/scripts/scheduler-mcp-server.cjs +51 -17
- package/src/main/build-info.json +4 -4
- package/src/main/chatRunner.cjs +8 -6
- package/src/main/crashDiagnostics.cjs +39 -3
- package/src/main/historyAggregator.cjs +7 -1
- package/src/main/index.cjs +73 -4
- package/src/main/ipcSchemas.cjs +27 -0
- package/src/main/lib/agentPersonaSchema.cjs +8 -1
- package/src/main/lib/appRuntime.cjs +57 -0
- package/src/main/lib/childWithLog.cjs +10 -10
- package/src/main/lib/claudeBin.cjs +58 -20
- package/src/main/lib/cleanEnv.cjs +33 -9
- package/src/main/lib/customerFeedbackAdminRoutes.cjs +94 -0
- package/src/main/lib/customerFeedbackClient.cjs +261 -0
- package/src/main/lib/customerFeedbackInbox.cjs +286 -0
- package/src/main/lib/customerFeedbackIpc.cjs +43 -0
- package/src/main/lib/defaultShell.cjs +36 -0
- package/src/main/lib/definitionOfDone.cjs +6 -2
- package/src/main/lib/delegationReadiness.cjs +13 -5
- package/src/main/lib/gitWorktree.cjs +6 -3
- package/src/main/lib/insideHome.cjs +15 -4
- package/src/main/lib/killTree.cjs +80 -0
- package/src/main/lib/loadGate.cjs +16 -0
- package/src/main/lib/loginShellPath.cjs +82 -0
- package/src/main/lib/machineProfile.cjs +4 -2
- package/src/main/lib/mcpStdioServer.cjs +127 -0
- package/src/main/lib/mcpToolCatalog.cjs +26 -1
- package/src/main/lib/openExternalApp.cjs +62 -11
- package/src/main/lib/otlpTraceExporter.cjs +147 -0
- package/src/main/lib/prereqs.cjs +145 -0
- package/src/main/lib/procIdentity.cjs +37 -2
- package/src/main/lib/projectFolder.cjs +89 -0
- package/src/main/lib/rcaReport.cjs +1 -0
- package/src/main/lib/reaperHelpers.cjs +5 -2
- package/src/main/lib/shippedPersonaSeeds.cjs +10 -0
- package/src/main/lib/timeoutShimScript.cjs +14 -2
- package/src/main/lib/updateCheck.cjs +41 -11
- package/src/main/lib/watchdogHelpers.cjs +23 -3
- package/src/main/lib/winSpawn.cjs +67 -0
- package/src/main/lib/winUserData.cjs +22 -0
- package/src/main/otel.cjs +18 -62
- package/src/main/pty.cjs +32 -11
- package/src/main/runVerify.cjs +42 -2
- package/src/main/scheduler.cjs +116 -29
- package/src/main/seedSchedulerMcp.cjs +11 -2
- package/src/preload/api.d.ts +82 -0
- package/src/preload/index.cjs +14 -0
- package/src/seed/agents/validator.md +2 -2
- package/dist/assets/index-DrvxIbmR.js +0 -3073
- package/dist/assets/useAgentsDirWatch-XhQEVlaK.js +0 -1
- package/scripts/postinstall.cjs +0 -52
package/src/main/pty.cjs
CHANGED
|
@@ -20,6 +20,7 @@ const { addAllowedRoot } = require('./config.cjs');
|
|
|
20
20
|
const { aliasedShellBin } = require('./lib/smProcNames.cjs');
|
|
21
21
|
const { cleanChildEnv, pathWithUserBins } = require('./lib/cleanEnv.cjs');
|
|
22
22
|
const { checkInsideHome } = require('./lib/insideHome.cjs');
|
|
23
|
+
const { defaultShell } = require('./lib/defaultShell.cjs');
|
|
23
24
|
const { sendIfAlive } = require('./lib/sendToRenderer.cjs');
|
|
24
25
|
const opsErrorLog = require('./lib/opsErrorLog.cjs');
|
|
25
26
|
const { planEpicSpawn } = require('./lib/epicSpawnPlan.cjs');
|
|
@@ -51,20 +52,39 @@ const PTY_FLUSH_MAX_BYTES = 64 * 1024;
|
|
|
51
52
|
*/
|
|
52
53
|
function nativeModuleHelp(reason) {
|
|
53
54
|
const mac = process.platform === 'darwin';
|
|
54
|
-
const
|
|
55
|
+
const head = [
|
|
55
56
|
'',
|
|
56
57
|
`\x1b[1;33m[session-manager] Terminal could not start.\x1b[0m`,
|
|
57
58
|
`\x1b[33m${reason}\x1b[0m`,
|
|
58
59
|
'',
|
|
59
|
-
|
|
60
|
-
|
|
60
|
+
];
|
|
61
|
+
if (process.platform === 'win32') {
|
|
62
|
+
return (
|
|
63
|
+
[
|
|
64
|
+
...head,
|
|
65
|
+
`This is almost always a damaged install (the node-pty native module`,
|
|
66
|
+
`is missing or does not match this build). Reinstall session-manager:`,
|
|
67
|
+
'',
|
|
68
|
+
` - Installed app: run the latest installer again.`,
|
|
69
|
+
` - npm package: \x1b[36mnpm install -g claude-code-session-manager@latest\x1b[0m`,
|
|
70
|
+
'',
|
|
71
|
+
`Then quit and reopen session-manager.`,
|
|
72
|
+
'',
|
|
73
|
+
].join('\r\n') + '\r\n'
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
const lines = [
|
|
77
|
+
...head,
|
|
78
|
+
`node-pty ships prebuilt binaries for macOS, Linux and Windows on x64 and`,
|
|
79
|
+
`arm64. This machine is \x1b[36m${process.platform}-${process.arch}\x1b[0m. If the module failed to load, the`,
|
|
80
|
+
`install is probably damaged. Reinstall session-manager:`,
|
|
81
|
+
'',
|
|
82
|
+
` \x1b[36mnpm i -g claude-code-session-manager@latest\x1b[0m`,
|
|
83
|
+
` (or re-run the npx command)`,
|
|
61
84
|
'',
|
|
62
|
-
`
|
|
63
|
-
` \x1b[
|
|
85
|
+
`On any other platform node-pty compiles from source and needs build tools:`,
|
|
86
|
+
mac ? ` macOS: \x1b[36mxcode-select --install\x1b[0m` : ` Linux: build-essential + python3`,
|
|
64
87
|
'',
|
|
65
|
-
mac ? `macOS: if the rebuild fails, install the compiler first:` : '',
|
|
66
|
-
mac ? ` \x1b[36mxcode-select --install\x1b[0m` : '',
|
|
67
|
-
mac ? '' : '',
|
|
68
88
|
`Then quit and reopen session-manager.`,
|
|
69
89
|
'',
|
|
70
90
|
].filter((l) => l !== '');
|
|
@@ -210,8 +230,8 @@ class PtyManager {
|
|
|
210
230
|
SM_PROJECT_ROOT: cwd,
|
|
211
231
|
});
|
|
212
232
|
|
|
213
|
-
const shell = process.env.
|
|
214
|
-
console.log('[pty] spawning shell', shell);
|
|
233
|
+
const shell = defaultShell({ platform: process.platform, env: process.env, exists: fs.existsSync });
|
|
234
|
+
console.log('[pty] spawning shell', shell.file);
|
|
215
235
|
// Tab ID = claudeSessionId (see CLAUDE.md), and Chat/Terminal are two views over ONE session,
|
|
216
236
|
// so this asks the same decision function chatRunner.cjs does (epicSpawnPlan.cjs). `cwd`
|
|
217
237
|
// itself is NEVER repointed; only this actual PTY spawn option is (epicSpawnCwd.cjs's
|
|
@@ -255,7 +275,8 @@ class PtyManager {
|
|
|
255
275
|
// job's descendants; no shell in a job's process tree is ever spawned
|
|
256
276
|
// through here, so that detection is unaffected. Non-bash/zsh shells and
|
|
257
277
|
// any alias failure fall back to the real path.
|
|
258
|
-
|
|
278
|
+
const shellFile = process.platform === 'win32' ? shell.file : aliasedShellBin(shell.file);
|
|
279
|
+
proc = pty.spawn(shellFile, shell.args, {
|
|
259
280
|
name: 'xterm-256color',
|
|
260
281
|
cols,
|
|
261
282
|
rows,
|
package/src/main/runVerify.cjs
CHANGED
|
@@ -168,13 +168,14 @@ function parseLog(logPath) {
|
|
|
168
168
|
try {
|
|
169
169
|
text = fs.readFileSync(logPath, 'utf8');
|
|
170
170
|
} catch (e) {
|
|
171
|
-
return { events: [], resultEvent: null, error: e.message };
|
|
171
|
+
return { events: [], resultEvent: null, error: e.message, transcriptCommitShas: [] };
|
|
172
172
|
}
|
|
173
173
|
|
|
174
174
|
const events = [];
|
|
175
175
|
let resultEvent = null;
|
|
176
176
|
let seq = 0;
|
|
177
177
|
let transcriptCommitLanded = false;
|
|
178
|
+
const transcriptCommitShaSet = new Set();
|
|
178
179
|
|
|
179
180
|
for (const line of text.split('\n')) {
|
|
180
181
|
const trimmed = line.trim();
|
|
@@ -194,6 +195,8 @@ function parseLog(logPath) {
|
|
|
194
195
|
}
|
|
195
196
|
if (obj.tool_use_result?.gitOperation?.commit?.kind === 'committed') {
|
|
196
197
|
transcriptCommitLanded = true;
|
|
198
|
+
const sha = obj.tool_use_result.gitOperation.commit.sha;
|
|
199
|
+
if (typeof sha === 'string' && sha) transcriptCommitShaSet.add(sha);
|
|
197
200
|
}
|
|
198
201
|
|
|
199
202
|
// Final result event.
|
|
@@ -245,7 +248,7 @@ function parseLog(logPath) {
|
|
|
245
248
|
}
|
|
246
249
|
}
|
|
247
250
|
|
|
248
|
-
return { events, resultEvent, error: null, transcriptCommitLanded };
|
|
251
|
+
return { events, resultEvent, error: null, transcriptCommitLanded, transcriptCommitShas: [...transcriptCommitShaSet] };
|
|
249
252
|
}
|
|
250
253
|
|
|
251
254
|
// Stable, narrow substrings of the harness's auto-background tool_result text
|
|
@@ -549,6 +552,28 @@ function scanSentinel(resultEvent, events) {
|
|
|
549
552
|
return null;
|
|
550
553
|
}
|
|
551
554
|
|
|
555
|
+
/**
|
|
556
|
+
* One-line reason after `SCHEDULER_VERDICT: FAIL` (same two-source scan order
|
|
557
|
+
* as scanSentinel). Trimmed, capped at 300 chars; '' when absent.
|
|
558
|
+
*/
|
|
559
|
+
function scanSentinelReason(resultEvent, events) {
|
|
560
|
+
const RE = /^SCHEDULER_VERDICT:\s*FAIL\b[ \t]*([^\n]*)/m;
|
|
561
|
+
const clip = (m) => m[1].trim().slice(0, 300);
|
|
562
|
+
if (resultEvent && resultEvent.resultText) {
|
|
563
|
+
const m = resultEvent.resultText.match(RE);
|
|
564
|
+
if (m) return clip(m);
|
|
565
|
+
}
|
|
566
|
+
let lastToolResult = null;
|
|
567
|
+
for (const ev of events) {
|
|
568
|
+
if (ev.kind === 'tool_result') lastToolResult = ev;
|
|
569
|
+
}
|
|
570
|
+
if (lastToolResult && lastToolResult.content) {
|
|
571
|
+
const m = lastToolResult.content.match(RE);
|
|
572
|
+
if (m) return clip(m);
|
|
573
|
+
}
|
|
574
|
+
return '';
|
|
575
|
+
}
|
|
576
|
+
|
|
552
577
|
/**
|
|
553
578
|
* Scan for a `FOREIGN_WIP_PATHS: <comma-separated paths>` evidence line —
|
|
554
579
|
* the mandatory companion to a `SCHEDULER_VERDICT: BLOCKED_BY_FOREIGN_WIP`
|
|
@@ -1079,6 +1104,20 @@ async function verifyRun({
|
|
|
1079
1104
|
// Scan for the SCHEDULER_VERDICT sentinel emitted by the finish protocol.
|
|
1080
1105
|
const sentinel = scanSentinel(resultEvent, events);
|
|
1081
1106
|
|
|
1107
|
+
// An explicit executor FAIL is always blocking — outranks every other
|
|
1108
|
+
// verdict and every override below (early return). Transcript issues are
|
|
1109
|
+
// kept as annotations so nothing is lost.
|
|
1110
|
+
if (sentinel === 'fail') {
|
|
1111
|
+
const failReason = scanSentinelReason(resultEvent, events);
|
|
1112
|
+
const kept = [...annotations, ...issues.map((i) => ({ verdict: i.verdict, reason: i.reason }))];
|
|
1113
|
+
return conclude(
|
|
1114
|
+
'sentinel_fail',
|
|
1115
|
+
'executor reported SCHEDULER_VERDICT: FAIL' + (failReason ? ': ' + failReason : ''),
|
|
1116
|
+
'needs_review',
|
|
1117
|
+
{ sentinel, ...(kept.length ? { annotations: kept } : {}) },
|
|
1118
|
+
);
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1082
1121
|
// Ground truth outranks heuristics: an exit-0 run that landed a commit
|
|
1083
1122
|
// ATTRIBUTABLE TO THIS RUN (spawnJob's own guardHeadBefore/headAtExit
|
|
1084
1123
|
// delta, or the repo-wide committedDuringRun window scan) and never
|
|
@@ -1377,6 +1416,7 @@ module.exports = {
|
|
|
1377
1416
|
checkDeps,
|
|
1378
1417
|
parseLog,
|
|
1379
1418
|
scanSentinel,
|
|
1419
|
+
scanSentinelReason,
|
|
1380
1420
|
scanForeignWipPathsClaim,
|
|
1381
1421
|
isMergeMainSlug,
|
|
1382
1422
|
extractMergeMainPrNumber,
|
package/src/main/scheduler.cjs
CHANGED
|
@@ -82,6 +82,7 @@ const { isResetFresh, bindingWindow, degradedBudget } = require('./lib/usageCirc
|
|
|
82
82
|
const { computeQueueHealth } = require('./lib/queueHealth.cjs');
|
|
83
83
|
const { createLoadGate, topCpuConsumers } = require('./lib/loadGate.cjs');
|
|
84
84
|
const { openLog, withChildAndLog } = require('./lib/childWithLog.cjs');
|
|
85
|
+
const { killTree, detachedSpawnOpts } = require('./lib/killTree.cjs');
|
|
85
86
|
const { sendIfAlive } = require('./lib/sendToRenderer.cjs');
|
|
86
87
|
const { createBroadcastCoalescer } = require('./lib/broadcastCoalescer.cjs');
|
|
87
88
|
const prdParser = require('./scheduler/prdParser.cjs');
|
|
@@ -846,6 +847,51 @@ async function computeCommittedDuringRun(cwd, headBefore, headAfter, startedAt,
|
|
|
846
847
|
return module.exports.committedInWindow(cwd, startedAt, untilIso);
|
|
847
848
|
}
|
|
848
849
|
|
|
850
|
+
/**
|
|
851
|
+
* Which commit does this run get credit for? A worktree-isolated run shares the
|
|
852
|
+
* main checkout with concurrent jobs, so a HEAD move there may be someone else's
|
|
853
|
+
* integration — credit comes only from this run's own integration result. An
|
|
854
|
+
* in-place run keeps the HEAD-delta / time-window behaviour.
|
|
855
|
+
*/
|
|
856
|
+
function resolveRunCommitAttribution({
|
|
857
|
+
ranInWorktree, integratedSha, headBefore, headAtExit, committedInWindow,
|
|
858
|
+
rangeCommits, transcriptCommitShas, siblingOverlap,
|
|
859
|
+
}) {
|
|
860
|
+
if (ranInWorktree) {
|
|
861
|
+
const landedCommit = integratedSha || null;
|
|
862
|
+
return { landedCommit, committedDuringRun: Boolean(landedCommit) };
|
|
863
|
+
}
|
|
864
|
+
const shas = Array.isArray(transcriptCommitShas) ? transcriptCommitShas.filter(Boolean) : [];
|
|
865
|
+
if (shas.length === 0 && !siblingOverlap) {
|
|
866
|
+
const headMoved = Boolean(headBefore && headAtExit && headBefore !== headAtExit);
|
|
867
|
+
return {
|
|
868
|
+
landedCommit: headMoved ? headAtExit : null,
|
|
869
|
+
committedDuringRun: headMoved || Boolean(committedInWindow),
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
// Shared tree with possible siblings (or transcript evidence): credit only a
|
|
873
|
+
// commit in the range that this run's own transcript reported.
|
|
874
|
+
const own = (Array.isArray(rangeCommits) ? rangeCommits : [])
|
|
875
|
+
.find((c) => shas.some((sha) => c.startsWith(sha)));
|
|
876
|
+
const landedCommit = own || null;
|
|
877
|
+
return { landedCommit, committedDuringRun: Boolean(landedCommit) };
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
/** Full shas in `before..after` (newest first); [] on any error. Never throws. */
|
|
881
|
+
function gitRangeCommits(cwd, before, after) {
|
|
882
|
+
return new Promise((resolve) => {
|
|
883
|
+
if (!cwd || !before || !after || before === after) { resolve([]); return; }
|
|
884
|
+
execFile(
|
|
885
|
+
'git', ['-C', cwd, 'rev-list', `${before}..${after}`],
|
|
886
|
+
{ timeout: 10_000, windowsHide: true },
|
|
887
|
+
(err, out) => {
|
|
888
|
+
if (err) { resolve([]); return; }
|
|
889
|
+
resolve(String(out || '').split('\n').map((l) => l.trim()).filter(Boolean));
|
|
890
|
+
},
|
|
891
|
+
);
|
|
892
|
+
});
|
|
893
|
+
}
|
|
894
|
+
|
|
849
895
|
/**
|
|
850
896
|
* Read-only check: is a job's `sm-job/<slug>` worktree branch already fully
|
|
851
897
|
* integrated into `cwd`'s current HEAD — i.e. every commit on the branch is
|
|
@@ -2769,6 +2815,8 @@ function killOrphanClaudePid(pid, recordedIdentity = null) {
|
|
|
2769
2815
|
&& isDifferentProcess(recordedIdentity, procIdentityOf(pid))) {
|
|
2770
2816
|
return 'mismatch';
|
|
2771
2817
|
}
|
|
2818
|
+
// win32 has no /proc or `ps`: the identity heuristic can't run, so never reap.
|
|
2819
|
+
if (process.platform === 'win32') return 'unknown';
|
|
2772
2820
|
let cmdline = '';
|
|
2773
2821
|
try {
|
|
2774
2822
|
cmdline = fs.readFileSync(`/proc/${pid}/cmdline`, 'utf8').replace(/\0/g, ' ');
|
|
@@ -2779,12 +2827,10 @@ function killOrphanClaudePid(pid, recordedIdentity = null) {
|
|
|
2779
2827
|
} catch { return 'unknown'; }
|
|
2780
2828
|
}
|
|
2781
2829
|
if (!/\bclaude\b/.test(cmdline)) return 'mismatch';
|
|
2782
|
-
|
|
2783
|
-
catch { try { process.kill(pid, 'SIGTERM'); } catch { /* race: died between checks */ } }
|
|
2830
|
+
killTree(pid, 'SIGTERM');
|
|
2784
2831
|
setTimeout(() => {
|
|
2785
2832
|
try { process.kill(pid, 0); } catch { return; /* already gone */ }
|
|
2786
|
-
|
|
2787
|
-
catch { try { process.kill(pid, 'SIGKILL'); } catch { /* race */ } }
|
|
2833
|
+
killTree(pid, 'SIGKILL');
|
|
2788
2834
|
}, 5000).unref?.();
|
|
2789
2835
|
return 'killed';
|
|
2790
2836
|
}
|
|
@@ -6276,7 +6322,15 @@ async function executeJob(job, runDir, defaultCwd, onPid, execCwd, resumeTarget
|
|
|
6276
6322
|
|
|
6277
6323
|
// Distinct comm (`sm-claude-job`) + slug-labelled argv0 for System Monitor.
|
|
6278
6324
|
// Both keep the word `claude`, which the /\bclaude\b/ reaper gates need.
|
|
6279
|
-
const
|
|
6325
|
+
const jobArgs = buildClaudeSpawnArgs({
|
|
6326
|
+
prompt,
|
|
6327
|
+
model: personaResolution.model,
|
|
6328
|
+
effort: personaEffort,
|
|
6329
|
+
sessionId,
|
|
6330
|
+
resume: !!resumeTarget,
|
|
6331
|
+
systemPrompt: personaResolution.systemPrompt,
|
|
6332
|
+
});
|
|
6333
|
+
const jobSpawn = claudeSpawnTarget('job', job.slug, claudeBin, jobArgs);
|
|
6280
6334
|
|
|
6281
6335
|
const { child } = withChildAndLog({
|
|
6282
6336
|
fd,
|
|
@@ -6288,24 +6342,18 @@ async function executeJob(job, runDir, defaultCwd, onPid, execCwd, resumeTarget
|
|
|
6288
6342
|
// Resume mode passes `--resume <sessionId>` (reconnect to the SAME
|
|
6289
6343
|
// session) INSTEAD of `--session-id <sessionId>` (mint a new one) —
|
|
6290
6344
|
// never both, see buildClaudeSpawnArgs.
|
|
6291
|
-
args:
|
|
6292
|
-
prompt,
|
|
6293
|
-
model: personaResolution.model,
|
|
6294
|
-
effort: personaEffort,
|
|
6295
|
-
sessionId,
|
|
6296
|
-
resume: !!resumeTarget,
|
|
6297
|
-
systemPrompt: personaResolution.systemPrompt,
|
|
6298
|
-
}),
|
|
6345
|
+
args: jobSpawn.args || jobArgs,
|
|
6299
6346
|
options: {
|
|
6300
6347
|
cwd: spawnCwd,
|
|
6301
6348
|
env: childEnv,
|
|
6302
6349
|
...(jobSpawn.argv0 ? { argv0: jobSpawn.argv0 } : {}),
|
|
6350
|
+
...(jobSpawn.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {}),
|
|
6303
6351
|
// detached:true puts the child in its own process group so we can kill
|
|
6304
6352
|
// the entire descendant tree (including any stray background bashes the
|
|
6305
|
-
// agent spawned) with
|
|
6353
|
+
// agent spawned) with killTree(pid). Without this, child.kill()
|
|
6306
6354
|
// only kills the immediate `claude` process, leaving orphaned subprocs
|
|
6307
6355
|
// that keep the parent alive (the 2026-05-10 cellar-publish hang).
|
|
6308
|
-
|
|
6356
|
+
...detachedSpawnOpts(),
|
|
6309
6357
|
},
|
|
6310
6358
|
},
|
|
6311
6359
|
watchdogs: [resultTailWatchdog, deadmanWatchdog, idleTailWatchdog, budgetWatchdog],
|
|
@@ -7249,6 +7297,7 @@ async function finalizeJobWorktree({ job, runDir, worktree, guardCwd, carriedPat
|
|
|
7249
7297
|
let worktreeIntegrationDetail = null;
|
|
7250
7298
|
let mergeAutoResolved = null;
|
|
7251
7299
|
let mergeAutoResolvedPaths = null;
|
|
7300
|
+
let integratedSha = null;
|
|
7252
7301
|
try {
|
|
7253
7302
|
worktreeLeftoverDirty = (await uncommitted(worktree.dir)) || [];
|
|
7254
7303
|
// Salvage the worktree's full diff (tracked + untracked) to the run
|
|
@@ -7273,6 +7322,7 @@ async function finalizeJobWorktree({ job, runDir, worktree, guardCwd, carriedPat
|
|
|
7273
7322
|
worktreeIntegrationDetail = integration;
|
|
7274
7323
|
console.error(`[scheduler] ${job.slug}: worktree branch integration FAILED (${integration.reason}) — branch ${worktree.branch} preserved in ${guardCwd} for manual recovery`);
|
|
7275
7324
|
} else if (integration.integrated) {
|
|
7325
|
+
integratedSha = integration.sha || null;
|
|
7276
7326
|
console.log(`[scheduler] ${job.slug}: worktree branch ${worktree.branch} integrated into ${guardCwd}${integration.mergeCommit ? ' (merge commit)' : ' (fast-forward)'}`);
|
|
7277
7327
|
if (integration.autoResolved) {
|
|
7278
7328
|
mergeAutoResolved = integration.autoResolved;
|
|
@@ -7294,7 +7344,7 @@ async function finalizeJobWorktree({ job, runDir, worktree, guardCwd, carriedPat
|
|
|
7294
7344
|
await jw.cleanupJobWorktree({ cwd: guardCwd, dir: worktree.dir, branch: worktree.branch, keepBranch: true });
|
|
7295
7345
|
} catch { /* already reported above */ }
|
|
7296
7346
|
}
|
|
7297
|
-
return { worktreeLeftoverDirty, salvagePatch, worktreeIntegrationFailure, worktreeIntegrationDetail, mergeAutoResolved, mergeAutoResolvedPaths };
|
|
7347
|
+
return { worktreeLeftoverDirty, salvagePatch, worktreeIntegrationFailure, worktreeIntegrationDetail, mergeAutoResolved, mergeAutoResolvedPaths, integratedSha };
|
|
7298
7348
|
}
|
|
7299
7349
|
|
|
7300
7350
|
/**
|
|
@@ -7686,6 +7736,7 @@ async function spawnJob(job, runId, runDir, defaultCwd, resumeTarget = null, sib
|
|
|
7686
7736
|
// self-healed rather than silently looking like an ordinary merge.
|
|
7687
7737
|
let mergeAutoResolved = null;
|
|
7688
7738
|
let mergeAutoResolvedPaths = null;
|
|
7739
|
+
let integratedSha = null;
|
|
7689
7740
|
// A job's uncommitted-work patch, whichever isolation mode produced it —
|
|
7690
7741
|
// set by EITHER branch below, never both (worktree.ok picks exactly one
|
|
7691
7742
|
// shape for the whole run). Named generically (not "worktree...") because
|
|
@@ -7721,7 +7772,7 @@ async function spawnJob(job, runId, runDir, defaultCwd, resumeTarget = null, sib
|
|
|
7721
7772
|
}, reviewInRun);
|
|
7722
7773
|
} finally {
|
|
7723
7774
|
if (worktree.ok) {
|
|
7724
|
-
({ worktreeLeftoverDirty, salvagePatch, worktreeIntegrationFailure, worktreeIntegrationDetail, mergeAutoResolved, mergeAutoResolvedPaths } =
|
|
7775
|
+
({ worktreeLeftoverDirty, salvagePatch, worktreeIntegrationFailure, worktreeIntegrationDetail, mergeAutoResolved, mergeAutoResolvedPaths, integratedSha } =
|
|
7725
7776
|
await finalizeJobWorktree({ job, runDir, worktree, guardCwd, carriedPaths }));
|
|
7726
7777
|
} else {
|
|
7727
7778
|
// In-place run (non-git cwd, cap reached, env-disabled, or a carry-over
|
|
@@ -7873,15 +7924,50 @@ async function spawnJob(job, runId, runDir, defaultCwd, resumeTarget = null, sib
|
|
|
7873
7924
|
// Used by the sentinel override: SCHEDULER_VERDICT: PASS + a landed
|
|
7874
7925
|
// commit together override incidental transcript noise verdicts.
|
|
7875
7926
|
const headAtExit = await gitHead(guardCwd);
|
|
7876
|
-
|
|
7877
|
-
|
|
7878
|
-
|
|
7927
|
+
// Worktree runs: attribution comes only from this run's own integration
|
|
7928
|
+
// (a concurrent job's commit moving shared HEAD must not count).
|
|
7929
|
+
const committedInWindow = worktree.ok
|
|
7930
|
+
? false
|
|
7931
|
+
: await computeCommittedDuringRun(
|
|
7932
|
+
guardCwd,
|
|
7933
|
+
guardHeadBefore,
|
|
7934
|
+
headAtExit,
|
|
7935
|
+
job.startedAt,
|
|
7936
|
+
new Date().toISOString(),
|
|
7937
|
+
);
|
|
7938
|
+
let rangeCommits = [];
|
|
7939
|
+
let transcriptCommitShas = [];
|
|
7940
|
+
let siblingOverlap = false;
|
|
7941
|
+
if (!worktree.ok) {
|
|
7942
|
+
rangeCommits = await gitRangeCommits(guardCwd, guardHeadBefore, headAtExit);
|
|
7943
|
+
transcriptCommitShas = parseLog(path.join(runDir, `${job.slug}.log`)).transcriptCommitShas || [];
|
|
7944
|
+
const overlapState = await readQueue().catch(() => ({ jobs: [] }));
|
|
7945
|
+
const myStart = Date.parse(job.startedAt) || 0;
|
|
7946
|
+
const myEnd = Date.now();
|
|
7947
|
+
siblingOverlap = (overlapState.jobs || []).some((j) => {
|
|
7948
|
+
if (j.slug === job.slug || (j.cwd || defaultCwd) !== guardCwd) return false;
|
|
7949
|
+
if (j.status === 'running') return true;
|
|
7950
|
+
const jStart = Date.parse(j.startedAt);
|
|
7951
|
+
const jEnd = Date.parse(j.finishedAt);
|
|
7952
|
+
if (!Number.isFinite(jStart) && !Number.isFinite(jEnd)) return false;
|
|
7953
|
+
const s0 = Number.isFinite(jStart) ? jStart : jEnd;
|
|
7954
|
+
const e0 = Number.isFinite(jEnd) ? jEnd : jStart;
|
|
7955
|
+
return s0 <= myEnd && e0 >= myStart;
|
|
7956
|
+
});
|
|
7957
|
+
}
|
|
7958
|
+
const attribution = resolveRunCommitAttribution({
|
|
7959
|
+
ranInWorktree: Boolean(worktree.ok),
|
|
7960
|
+
integratedSha,
|
|
7961
|
+
headBefore: guardHeadBefore,
|
|
7879
7962
|
headAtExit,
|
|
7880
|
-
|
|
7881
|
-
|
|
7882
|
-
|
|
7883
|
-
|
|
7884
|
-
|
|
7963
|
+
committedInWindow,
|
|
7964
|
+
rangeCommits,
|
|
7965
|
+
transcriptCommitShas,
|
|
7966
|
+
siblingOverlap,
|
|
7967
|
+
});
|
|
7968
|
+
const committedDuringRun = attribution.committedDuringRun;
|
|
7969
|
+
if (attribution.landedCommit) {
|
|
7970
|
+
jobLandedCommitThisRun = attribution.landedCommit;
|
|
7885
7971
|
}
|
|
7886
7972
|
|
|
7887
7973
|
const prdPath = (await resolveVerifyPrdPath(job)) ?? archivedPrdPathForJob(job);
|
|
@@ -7955,7 +8041,9 @@ async function spawnJob(job, runId, runDir, defaultCwd, resumeTarget = null, sib
|
|
|
7955
8041
|
(j) => j.slug !== job.slug && j.status === 'running' && (j.cwd || defaultCwd) === guardCwd,
|
|
7956
8042
|
);
|
|
7957
8043
|
const guardHeadAfter = await gitHead(guardCwd);
|
|
7958
|
-
const jobSelfCommitted =
|
|
8044
|
+
const jobSelfCommitted = worktree.ok
|
|
8045
|
+
? Boolean(jobLandedCommitThisRun)
|
|
8046
|
+
: guardHeadBefore && guardHeadAfter && guardHeadAfter !== guardHeadBefore;
|
|
7959
8047
|
const guardVerdict = commitGuardVerdict({
|
|
7960
8048
|
newlyDirty,
|
|
7961
8049
|
siblingRunning,
|
|
@@ -9703,9 +9791,7 @@ function bootRowPid(j, logPathOf) {
|
|
|
9703
9791
|
}
|
|
9704
9792
|
|
|
9705
9793
|
function signalAdoptedGroup(pgid, signal, pid) {
|
|
9706
|
-
|
|
9707
|
-
try { process.kill(pid, signal); } catch { /* already dead */ }
|
|
9708
|
-
}
|
|
9794
|
+
if (!killTree(pgid, signal)) killTree(pid, signal);
|
|
9709
9795
|
}
|
|
9710
9796
|
|
|
9711
9797
|
async function superviseAdoptedRunsPass(jobs) {
|
|
@@ -13595,6 +13681,7 @@ function registerAdminRoutes(adminHttp, remoteObj = remote) {
|
|
|
13595
13681
|
module.exports = {
|
|
13596
13682
|
reportSchedulerError,
|
|
13597
13683
|
finalizeJobWorktree,
|
|
13684
|
+
resolveRunCommitAttribution,
|
|
13598
13685
|
worktreeIntegrationVerdict,
|
|
13599
13686
|
guardedTick,
|
|
13600
13687
|
rescheduleIntervalTick,
|
|
@@ -38,6 +38,7 @@ const { spawn } = require('node:child_process');
|
|
|
38
38
|
const { resolveClaudeBin, claudeSpawnTarget } = require('./lib/claudeBin.cjs');
|
|
39
39
|
const { cleanChildEnv, pathWithUserBins } = require('./lib/cleanEnv.cjs');
|
|
40
40
|
const { writeJsonSync } = require('./config.cjs');
|
|
41
|
+
const { nodeSpawnSpec } = require('./lib/appRuntime.cjs');
|
|
41
42
|
|
|
42
43
|
const SERVER_NAME = 'session-manager-scheduler';
|
|
43
44
|
const MAX_ATTEMPTS = 3; // give a transient first-boot failure a few chances
|
|
@@ -102,6 +103,14 @@ function scriptPathMissing() {
|
|
|
102
103
|
}
|
|
103
104
|
}
|
|
104
105
|
|
|
106
|
+
/** argv for `claude mcp add`: system `node` in dev/npx, the app binary with
|
|
107
|
+
* ELECTRON_RUN_AS_NODE=1 when packaged (an installer user may have no Node). */
|
|
108
|
+
function mcpAddArgv(serverPath) {
|
|
109
|
+
const spec = nodeSpawnSpec(serverPath);
|
|
110
|
+
const envFlags = Object.entries(spec.env).flatMap(([k, v]) => ['-e', `${k}=${v}`]);
|
|
111
|
+
return ['mcp', 'add', SERVER_NAME, '--scope', 'user', ...envFlags, '--', spec.command, ...spec.args];
|
|
112
|
+
}
|
|
113
|
+
|
|
105
114
|
/** Run `claude mcp add session-manager-scheduler --scope user -- node <serverPath>`.
|
|
106
115
|
* No shell:true — argv array passed directly to spawn. */
|
|
107
116
|
function runClaudeMcpAdd(serverPath) {
|
|
@@ -111,7 +120,7 @@ function runClaudeMcpAdd(serverPath) {
|
|
|
111
120
|
try {
|
|
112
121
|
proc = spawn(
|
|
113
122
|
target.command,
|
|
114
|
-
|
|
123
|
+
mcpAddArgv(serverPath),
|
|
115
124
|
{
|
|
116
125
|
...(target.argv0 ? { argv0: target.argv0 } : {}),
|
|
117
126
|
cwd: os.homedir(),
|
|
@@ -200,4 +209,4 @@ async function seedSchedulerMcp({ logger = console, addFn = runClaudeMcpAdd, rem
|
|
|
200
209
|
}
|
|
201
210
|
}
|
|
202
211
|
|
|
203
|
-
module.exports = { seedSchedulerMcp, markerPath, MAX_ATTEMPTS, serverScriptPath, SERVER_NAME };
|
|
212
|
+
module.exports = { seedSchedulerMcp, mcpAddArgv, markerPath, MAX_ATTEMPTS, serverScriptPath, SERVER_NAME };
|
package/src/preload/api.d.ts
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import type { PromptSession } from '../renderer/state/promptSessions';
|
|
2
2
|
|
|
3
|
+
/** Mirrors the item shape returned by main/lib/prereqs.cjs checkPrereqs(). */
|
|
4
|
+
export interface PrereqItem {
|
|
5
|
+
id: string;
|
|
6
|
+
label: string;
|
|
7
|
+
ok: boolean;
|
|
8
|
+
version: string | null;
|
|
9
|
+
detail: string | null;
|
|
10
|
+
fix: { command: string; shell: 'sh' | 'powershell'; url?: string } | null;
|
|
11
|
+
}
|
|
12
|
+
|
|
3
13
|
export interface ActionOutcome {
|
|
4
14
|
ok: boolean;
|
|
5
15
|
kind?: 'info' | 'warn' | 'error';
|
|
@@ -464,6 +474,44 @@ export interface OtelSetConfigResult {
|
|
|
464
474
|
status: OtelStatus;
|
|
465
475
|
}
|
|
466
476
|
|
|
477
|
+
// Customer feedback — client submissions + owner inbox. Shapes mirror
|
|
478
|
+
// src/main/lib/customerFeedbackClient.cjs and customerFeedbackInbox.cjs.
|
|
479
|
+
export type CustomerFeedbackTag = 'bug' | 'feature' | 'discussion';
|
|
480
|
+
export type CustomerFeedbackStatus = 'open' | 'in_progress' | 'resolved' | 'wontfix';
|
|
481
|
+
|
|
482
|
+
export interface CustomerFeedbackItem {
|
|
483
|
+
id: string;
|
|
484
|
+
receipt: string | null;
|
|
485
|
+
tag: CustomerFeedbackTag;
|
|
486
|
+
title: string;
|
|
487
|
+
body: string;
|
|
488
|
+
submittedAt: number;
|
|
489
|
+
status: CustomerFeedbackStatus;
|
|
490
|
+
statusNote: string | null;
|
|
491
|
+
statusAt: number | string | null;
|
|
492
|
+
seenStatusAt: number | string | null;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
export interface CustomerFeedbackInboxItem {
|
|
496
|
+
id: string;
|
|
497
|
+
receivedAt: string | number | null;
|
|
498
|
+
tag: CustomerFeedbackTag;
|
|
499
|
+
title: string;
|
|
500
|
+
body: string;
|
|
501
|
+
clientVersion: string | null;
|
|
502
|
+
clientPlatform: string | null;
|
|
503
|
+
moderation: string | null;
|
|
504
|
+
status: CustomerFeedbackStatus;
|
|
505
|
+
statusNote: string | null;
|
|
506
|
+
statusAt: string | number | null;
|
|
507
|
+
epicId: string | null;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
export interface CustomerFeedbackOwnerInfo {
|
|
511
|
+
ownerMode: boolean;
|
|
512
|
+
projectCwd: string | null;
|
|
513
|
+
}
|
|
514
|
+
|
|
467
515
|
// Product telemetry (bilko.run) — anonymous, on-by-default, opt-out. See
|
|
468
516
|
// session-manager-operations/architecture/telemetry.md for the data model.
|
|
469
517
|
export interface TelemetryConfig {
|
|
@@ -1530,6 +1578,10 @@ export interface ChatExternalSendEvent {
|
|
|
1530
1578
|
prompt: string;
|
|
1531
1579
|
}
|
|
1532
1580
|
|
|
1581
|
+
export type CreateProjectFolderResult =
|
|
1582
|
+
| { ok: true; path: string }
|
|
1583
|
+
| { ok: false; code: 'invalid-name' | 'invalid-parent' | 'exists' | 'io'; error: string; path?: string };
|
|
1584
|
+
|
|
1533
1585
|
export interface SessionManagerAPI {
|
|
1534
1586
|
app: {
|
|
1535
1587
|
version: () => Promise<string>;
|
|
@@ -1539,6 +1591,8 @@ export interface SessionManagerAPI {
|
|
|
1539
1591
|
cwd: () => Promise<string>;
|
|
1540
1592
|
engageRulesPath: () => Promise<string | null>;
|
|
1541
1593
|
pickDirectory: () => Promise<string | null>;
|
|
1594
|
+
/** Create `<parentDir>/<name>` for a new project; main validates the name. */
|
|
1595
|
+
createProjectFolder: (parentDir: string, name: string) => Promise<CreateProjectFolderResult>;
|
|
1542
1596
|
gitBranch: (cwd: string) => Promise<string | null>;
|
|
1543
1597
|
/** Default: request a drain-first restart. `force: true` restarts immediately, killing running jobs. */
|
|
1544
1598
|
rebootApp: (opts?: { force?: boolean }) => void;
|
|
@@ -1550,6 +1604,10 @@ export interface SessionManagerAPI {
|
|
|
1550
1604
|
/** Boot diagnostic — resolved claude binary path + whether it was found
|
|
1551
1605
|
* on disk (false means spawn will rely on PATH and may ENOENT). */
|
|
1552
1606
|
claudeBinStatus: () => Promise<{ resolved: string; foundOnDisk: boolean }>;
|
|
1607
|
+
/** Fresh prerequisite probe (git, claude CLI, auth, Git Bash on Windows). */
|
|
1608
|
+
prereqs: () => Promise<PrereqItem[]>;
|
|
1609
|
+
/** Opens the OS terminal running the item's official install command (looked up main-side by id). */
|
|
1610
|
+
prereqsRunFix: (id: string) => Promise<{ ok: true; opener: string } | { ok: false; error: string }>;
|
|
1553
1611
|
/** Boot diagnostic — assertCwdInsideHome(os.homedir()) result. ok=false
|
|
1554
1612
|
* on macOS symlinked-/Users mismatch and blocks all session spawns. */
|
|
1555
1613
|
homeSelfCheck: () => Promise<{ ok: boolean; error?: string; realCwd?: string }>;
|
|
@@ -1752,6 +1810,30 @@ export interface SessionManagerAPI {
|
|
|
1752
1810
|
status: () => Promise<OtelStatus>;
|
|
1753
1811
|
configPath: () => Promise<string>;
|
|
1754
1812
|
};
|
|
1813
|
+
customerFeedback: {
|
|
1814
|
+
submit: (p: { title: string; body: string; tag: CustomerFeedbackTag }) => Promise<
|
|
1815
|
+
{ ok: true; item: CustomerFeedbackItem } | { ok: false; error: string }
|
|
1816
|
+
>;
|
|
1817
|
+
list: () => Promise<CustomerFeedbackItem[]>;
|
|
1818
|
+
refreshStatus: () => Promise<
|
|
1819
|
+
{ ok: true; updated: number; unsupported?: true } | { ok: false; error: string }
|
|
1820
|
+
>;
|
|
1821
|
+
/** Omit `ids` to mark everything seen. Resolves to the updated items. */
|
|
1822
|
+
markSeen: (p: { ids?: string[] }) => Promise<CustomerFeedbackItem[]>;
|
|
1823
|
+
ownerInfo: () => Promise<CustomerFeedbackOwnerInfo>;
|
|
1824
|
+
inboxPull: () => Promise<
|
|
1825
|
+
{ ok: true; fetched: number; pages: number } | { ok: false; error: string }
|
|
1826
|
+
>;
|
|
1827
|
+
inboxList: (p: { includeHidden?: boolean }) => Promise<
|
|
1828
|
+
{ ok: true; items: CustomerFeedbackInboxItem[] } | { ok: false; error: string }
|
|
1829
|
+
>;
|
|
1830
|
+
inboxSetStatus: (p: { id: string; status: CustomerFeedbackStatus; note?: string }) => Promise<
|
|
1831
|
+
{ ok: true; item: CustomerFeedbackInboxItem | null } | { ok: false; error: string }
|
|
1832
|
+
>;
|
|
1833
|
+
inboxLinkEpic: (p: { id: string; epicId: string }) => Promise<
|
|
1834
|
+
{ ok: true } | { ok: false; error: string }
|
|
1835
|
+
>;
|
|
1836
|
+
};
|
|
1755
1837
|
telemetry: {
|
|
1756
1838
|
getConfig: () => Promise<TelemetryConfig>;
|
|
1757
1839
|
setConfig: (cfg: TelemetryConfig) => Promise<TelemetrySetConfigResult>;
|
package/src/preload/index.cjs
CHANGED
|
@@ -8,6 +8,7 @@ contextBridge.exposeInMainWorld('api', {
|
|
|
8
8
|
cwd: () => ipcRenderer.invoke('app:cwd'),
|
|
9
9
|
engageRulesPath: () => ipcRenderer.invoke('app:engage-rules-path'),
|
|
10
10
|
pickDirectory: () => ipcRenderer.invoke('app:pick-directory'),
|
|
11
|
+
createProjectFolder: (parentDir, name) => ipcRenderer.invoke('app:create-project-folder', { parentDir, name }),
|
|
11
12
|
gitBranch: (cwd) => ipcRenderer.invoke('app:git-branch', { cwd }),
|
|
12
13
|
rebootApp: (opts) => ipcRenderer.send('app:reboot-app', opts),
|
|
13
14
|
archiveProject: (encoded) => ipcRenderer.invoke('app:archive-project', { encoded }),
|
|
@@ -20,6 +21,8 @@ contextBridge.exposeInMainWorld('api', {
|
|
|
20
21
|
// Boot diagnostics (v0.10.1) — renderer polls at mount to surface
|
|
21
22
|
// missing-claude-bin / home-symlink-mismatch as toasts.
|
|
22
23
|
claudeBinStatus: () => ipcRenderer.invoke('app:claude-bin-status'),
|
|
24
|
+
prereqs: () => ipcRenderer.invoke('app:prereqs'),
|
|
25
|
+
prereqsRunFix: (id) => ipcRenderer.invoke('app:prereqs-run-fix', id),
|
|
23
26
|
homeSelfCheck: () => ipcRenderer.invoke('app:home-self-check'),
|
|
24
27
|
seedStatus: () => ipcRenderer.invoke('app:seed-status'),
|
|
25
28
|
delegationReadiness: (cwd) => ipcRenderer.invoke('app:delegation-readiness', { cwd }),
|
|
@@ -211,6 +214,17 @@ contextBridge.exposeInMainWorld('api', {
|
|
|
211
214
|
recentRecords: () => ipcRenderer.invoke('telemetry:recent-records'),
|
|
212
215
|
flushNow: () => ipcRenderer.invoke('telemetry:flush-now'),
|
|
213
216
|
},
|
|
217
|
+
customerFeedback: {
|
|
218
|
+
submit: (p) => ipcRenderer.invoke('customerFeedback:submit', p),
|
|
219
|
+
list: () => ipcRenderer.invoke('customerFeedback:list'),
|
|
220
|
+
refreshStatus: () => ipcRenderer.invoke('customerFeedback:refresh-status'),
|
|
221
|
+
markSeen: (p) => ipcRenderer.invoke('customerFeedback:mark-seen', p),
|
|
222
|
+
ownerInfo: () => ipcRenderer.invoke('customerFeedback:owner-info'),
|
|
223
|
+
inboxPull: () => ipcRenderer.invoke('customerFeedback:inbox-pull'),
|
|
224
|
+
inboxList: (p) => ipcRenderer.invoke('customerFeedback:inbox-list', p),
|
|
225
|
+
inboxSetStatus: (p) => ipcRenderer.invoke('customerFeedback:inbox-set-status', p),
|
|
226
|
+
inboxLinkEpic: (p) => ipcRenderer.invoke('customerFeedback:inbox-link-epic', p),
|
|
227
|
+
},
|
|
214
228
|
// Diagnostic only — no handler is registered unless the main process was
|
|
215
229
|
// launched with SM_HEAP_SNAPSHOT=1, so this rejects by default.
|
|
216
230
|
diagnostics: {
|
|
@@ -4,14 +4,14 @@ description: Validates a finished PLAN (the PRDs its validate PRD lists) once, a
|
|
|
4
4
|
tools: Read, Grep, Glob, Bash
|
|
5
5
|
model: sonnet
|
|
6
6
|
title: Engineering — Plan Validator
|
|
7
|
-
seedVersion:
|
|
7
|
+
seedVersion: 3
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
You are validator. You judge whether a plan's PRDs actually landed what they promised. You do not fix anything, you do not re-implement, you do not queue PRDs — your output is evidence and verdicts, read by the architect who owns the plan.
|
|
11
11
|
|
|
12
12
|
## Inputs
|
|
13
13
|
|
|
14
|
-
Your PRD's `# Acceptance criteria` lists the plan's PRD slugs
|
|
14
|
+
Your PRD's `# Acceptance criteria` lists the plan's PRD slugs. PRD files live under `$SM_PROJECT_ROOT/session-manager-operations/scheduler/epics/<epic>/` — `prds/` while queued, `prds-archived/` (or a timestamped subfolder of it) once terminal; find by slug, e.g. `find "$SM_PROJECT_ROOT/session-manager-operations/scheduler/epics/<epic>" -name '*<slug>.md'`. This folder is gitignored, so it is never present in the job worktree — don't look for it there. If a PRD file is missing, fall back to `$SM_PROJECT_ROOT/session-manager-operations/scheduler/state/queue.json` / `history.jsonl` for that slug's `landedCommit`, and record the missing file as a Minor (not Important) finding. Everything else comes from the working tree and `git log`.
|
|
15
15
|
|
|
16
16
|
## Procedure (in this order)
|
|
17
17
|
|