claude-code-session-manager 0.86.0 → 0.87.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 (128) hide show
  1. package/dist/assets/AgentLibrary-DyLWzZDf.js +3 -0
  2. package/dist/assets/{DataModel-Q4jhl24R.js → DataModel--mISIJ6h.js} +1 -1
  3. package/dist/assets/{History-Cj2FejEo.js → History-C2ahUXTg.js} +2 -2
  4. package/dist/assets/{Hooks-CaelQI6t.js → Hooks-BiC6oyR2.js} +3 -3
  5. package/dist/assets/{HostBilko--v7cMR8I.js → HostBilko-BPleEOld.js} +1 -1
  6. package/dist/assets/{Library-DgI9oCCZ.js → Library-Dc8Qst1R.js} +1 -1
  7. package/dist/assets/{ListDetail-DYUZN-x-.js → ListDetail-DIXh-OLX.js} +1 -1
  8. package/dist/assets/MarkdownEditor-C90bkLXK.js +1 -0
  9. package/dist/assets/{McpServers-ypCYURh3.js → McpServers-DqcbLOLZ.js} +2 -2
  10. package/dist/assets/{Memory-C2qYp-3M.js → Memory-CW62MXlh.js} +4 -4
  11. package/dist/assets/{Panel-Cj2kw-Zv.js → Panel-Bw1FhRuF.js} +1 -1
  12. package/dist/assets/Permissions-BcUC-5y8.js +3 -0
  13. package/dist/assets/{Plugins-C1Vj8_dU.js → Plugins-BnKx9flD.js} +2 -2
  14. package/dist/assets/{ProvenanceBadge-DczPNM5U.js → ProvenanceBadge-Bw5vNVPT.js} +1 -1
  15. package/dist/assets/{SaveBar-Cd_7U6Gb.js → SaveBar-CWr0O_w-.js} +1 -1
  16. package/dist/assets/Scheduler-DYdLuUqq.js +14 -0
  17. package/dist/assets/{ScopeSwitcher-DVSyI44-.js → ScopeSwitcher-CrBLbg8s.js} +1 -1
  18. package/dist/assets/Settings-DluB-vN1.js +3 -0
  19. package/dist/assets/{SkillReferenceGraph-CUv1_Q2c.js → SkillReferenceGraph-CHLSseay.js} +1 -1
  20. package/dist/assets/{Skills-C_YHkAy-.js → Skills-gNdo_HNK.js} +2 -2
  21. package/dist/assets/{SystemPrompt-B8R7T9xn.js → SystemPrompt-Cru05-Ia.js} +1 -1
  22. package/dist/assets/{TagLibrary-dj9YHWyy.js → TagLibrary-DNHY0xou.js} +1 -1
  23. package/dist/assets/{TiptapBody-DnSBUjHE.js → TiptapBody-I4lmbCgP.js} +1 -1
  24. package/dist/assets/{Toggle-CjV_BJn6.js → Toggle-bWMHjmRh.js} +1 -1
  25. package/dist/assets/{index-CDo9xBR9.css → index-DV3PorRY.css} +1 -1
  26. package/dist/assets/{index-CXFQIPhO.js → index-fc_JjdxL.js} +724 -724
  27. package/dist/assets/settingsSchema-BfhtZnGD.js +3 -0
  28. package/dist/index.html +2 -2
  29. package/package.json +14 -14
  30. package/plugins/CLAUDE.md +61 -0
  31. package/plugins/session-manager-dev/.claude-plugin/plugin.json +1 -1
  32. package/plugins/session-manager-dev/skills/builder/4-manual/SKILL.md +1 -1
  33. package/plugins/session-manager-dev/skills/ops-sweep/SKILL.md +1 -1
  34. package/scripts/scheduler-mcp-server.cjs +7 -0
  35. package/src/main/__tests__/agentModelResolve.test.cjs +100 -9
  36. package/src/main/__tests__/broadcastCoalescer.test.cjs +18 -0
  37. package/src/main/__tests__/epicMint.test.cjs +2 -2
  38. package/src/main/__tests__/health-delegation-chain.test.cjs +2 -1
  39. package/src/main/__tests__/needsReviewLedger.test.cjs +162 -0
  40. package/src/main/__tests__/opsErrorLogTelemetryTap.test.cjs +3 -3
  41. package/src/main/__tests__/pollLoop-dispatch-on-failure.test.cjs +15 -1
  42. package/src/main/__tests__/prdCreateDisposition.test.cjs +201 -0
  43. package/src/main/__tests__/prdFrontmatterDisposition.test.cjs +125 -0
  44. package/src/main/__tests__/prdLocations.test.cjs +100 -2
  45. package/src/main/__tests__/prdLocationsArchived.test.cjs +43 -1
  46. package/src/main/__tests__/prdSetDisposition.test.cjs +222 -0
  47. package/src/main/__tests__/queue-health-verdict.test.cjs +170 -0
  48. package/src/main/__tests__/queue-starvation-per-project.test.cjs +14 -2
  49. package/src/main/__tests__/queueHistory.test.cjs +63 -0
  50. package/src/main/__tests__/reconcileTiming.test.cjs +135 -0
  51. package/src/main/__tests__/scheduleJobTransitions.test.cjs +101 -1
  52. package/src/main/__tests__/scheduler-boot-orphans.test.cjs +2 -2
  53. package/src/main/__tests__/scheduler-broadcast-reconcile.test.cjs +121 -0
  54. package/src/main/__tests__/scheduler-cross-project-batch.test.cjs +43 -0
  55. package/src/main/__tests__/scheduler-guard-verdict-autoresolve.test.cjs +344 -0
  56. package/src/main/__tests__/scheduler-job-budget.test.cjs +172 -0
  57. package/src/main/__tests__/scheduler-looks-done.test.cjs +93 -3
  58. package/src/main/__tests__/scheduler-porcelain-rename.test.cjs +164 -0
  59. package/src/main/__tests__/scheduler-reap-dead-running-jobs.test.cjs +160 -0
  60. package/src/main/__tests__/scheduler-reaper-helpers-basics.test.cjs +87 -0
  61. package/src/main/__tests__/scheduler-shared-tree-guard.test.cjs +88 -0
  62. package/src/main/__tests__/scheduler-starve-escalation.test.cjs +14 -4
  63. package/src/main/__tests__/scheduler-worktree-cap-defer.test.cjs +14 -0
  64. package/src/main/chatRunner.cjs +8 -5
  65. package/src/main/health.cjs +1 -1
  66. package/src/main/historyAggregator.cjs +5 -0
  67. package/src/main/index.cjs +95 -44
  68. package/src/main/ipcSchemas.cjs +47 -0
  69. package/src/main/lib/__tests__/active-sessions.test.cjs +251 -0
  70. package/src/main/lib/__tests__/bootSelfHeal.test.cjs +107 -0
  71. package/src/main/lib/__tests__/delegationReadiness.test.cjs +322 -43
  72. package/src/main/lib/__tests__/effectiveModelInfo.test.cjs +239 -0
  73. package/src/main/lib/__tests__/gitWorktree.test.cjs +89 -0
  74. package/src/main/lib/__tests__/guardShims.test.cjs +151 -0
  75. package/src/main/lib/__tests__/opsRootAbsoluteCwd.test.cjs +5 -5
  76. package/src/main/lib/__tests__/prdDisposition.test.cjs +224 -0
  77. package/src/main/lib/__tests__/reaperHelpers.test.cjs +179 -1
  78. package/src/main/lib/__tests__/usageCircuit.test.cjs +224 -0
  79. package/src/main/lib/__tests__/watchdog-helpers.test.cjs +312 -0
  80. package/src/main/lib/__tests__/watchdog-relaunch.test.cjs +193 -0
  81. package/{scripts → src/main}/lib/activeSessions.cjs +50 -4
  82. package/src/main/lib/agentModelResolve.cjs +65 -27
  83. package/src/main/lib/bootSelfHeal.cjs +88 -0
  84. package/src/main/lib/delegationReadiness.cjs +290 -225
  85. package/src/main/lib/effectiveModelInfo.cjs +333 -0
  86. package/src/main/lib/ephemeralCwd.cjs +1 -1
  87. package/src/main/lib/epicMint.cjs +3 -3
  88. package/src/main/lib/gitWorktree.cjs +42 -12
  89. package/src/main/lib/guardShims.cjs +156 -0
  90. package/src/main/lib/jobDirtFilter.cjs +7 -2
  91. package/src/main/lib/launchFailure.cjs +2 -1
  92. package/src/main/lib/mcpToolCatalog.cjs +4 -1
  93. package/src/main/lib/needsReviewLedger.cjs +205 -0
  94. package/src/main/lib/opsErrorLog.cjs +1 -1
  95. package/src/main/lib/opsOwnership.cjs +1 -1
  96. package/src/main/lib/prdCreate.cjs +56 -1
  97. package/src/main/lib/prdDisposition.cjs +199 -0
  98. package/src/main/lib/prdFrontmatter.cjs +8 -2
  99. package/src/main/lib/prdLocations.cjs +167 -45
  100. package/src/main/lib/projectHomeAdminRoutes.cjs +4 -4
  101. package/src/main/lib/projectPageSummarySchema.cjs +1 -1
  102. package/src/main/lib/projectRootResolve.cjs +1 -1
  103. package/src/main/lib/queueHistory.cjs +19 -1
  104. package/src/main/lib/queueStore.cjs +6 -1
  105. package/src/main/lib/reaperHelpers.cjs +181 -15
  106. package/src/main/lib/scheduleJobSchema.cjs +8 -0
  107. package/src/main/lib/scheduleJobTransitions.cjs +33 -0
  108. package/src/main/lib/schedulerConfig.cjs +24 -0
  109. package/src/main/lib/usageCircuit.cjs +159 -0
  110. package/{scripts → src/main}/lib/watchdogHelpers.cjs +1 -1
  111. package/src/main/scheduler/prdParser.cjs +13 -0
  112. package/src/main/scheduler.cjs +1285 -143
  113. package/src/main/templates/PRD_AUTHORING.md +50 -0
  114. package/src/main/templates/project-pages-catalog.json +1 -1
  115. package/src/main/usage.cjs +21 -3
  116. package/src/preload/api.d.ts +92 -1
  117. package/src/preload/index.cjs +10 -0
  118. package/web/README.md +41 -0
  119. package/{scripts/render-project-pages.cjs → web/project-pages/render.cjs} +4 -4
  120. package/{scripts/render-project-pages → web/project-pages/renderer}/dist/renderer.cjs +1 -1
  121. package/{scripts/validate-project-pages-summary.cjs → web/project-pages/validate-summary.cjs} +5 -5
  122. package/dist/assets/AgentLibrary-DTFL7y8G.js +0 -3
  123. package/dist/assets/MarkdownEditor-DCIubYWf.js +0 -1
  124. package/dist/assets/Permissions-BiYZNGYW.js +0 -3
  125. package/dist/assets/Scheduler-DcLBiJBq.js +0 -14
  126. package/dist/assets/Settings-Cv-pRyms.js +0 -3
  127. package/dist/assets/settingsSchema-BJVciriw.js +0 -3
  128. /package/{scripts/project-pages-logic → web/project-pages/logic}/dist/logic.cjs +0 -0
@@ -3,14 +3,19 @@
3
3
  /**
4
4
  * agentModelResolve.cjs — resolves the `--model` an Epic's session should
5
5
  * launch a headless `claude -p` (or Terminal `claude`) process with: its
6
- * agentType persona's own `model` field (Agent Library) when the Epic has
7
- * one set and it isn't 'inherit', else `fallbackModel`.
6
+ * agentType persona's own `model` field, project-overlay-then-global
7
+ * (`epicMint.cjs`'s `resolvePersonaPaths`), when the Epic has one set and
8
+ * it isn't 'inherit', else `fallbackModel`.
8
9
  *
9
10
  * Extracted from PRD agent-model-default-terminal (which wired this for the
10
11
  * Terminal-view launch in EpicTerminalPane.tsx) so chatRunner.cjs's headless
11
12
  * Chat-view launch resolves the SAME persona-derived model — Chat and
12
13
  * Terminal are two VIEWS over one Epic session (CLAUDE.md) and must agree on
13
- * which model that session launches with.
14
+ * which model that session launches with. Terminal reaches `resolveEpicModel`
15
+ * through the `agents:resolve-epic-model` IPC (main-process only; the
16
+ * renderer has no fs access) rather than a second implementation, so the two
17
+ * views literally call the same function instead of two functions that are
18
+ * merely supposed to agree.
14
19
  *
15
20
  * Plain Node module (sync fs) — no Electron deps, mirrors epicMint.cjs's
16
21
  * readActiveIndex so this can be called from chatRunner.cjs's synchronous
@@ -21,7 +26,7 @@ const fs = require('node:fs');
21
26
  const path = require('node:path');
22
27
  const os = require('node:os');
23
28
  const { splitFrontmatter } = require('./prdFrontmatter.cjs');
24
- const { readActiveIndex } = require('./epicMint.cjs');
29
+ const { readActiveIndex, resolvePersonaPaths } = require('./epicMint.cjs');
25
30
  const configMgr = require('../config.cjs');
26
31
 
27
32
  /** The hardcoded floor every branch of resolveEpicModel falls back to — --model must never be omitted (CLAUDE.md "Automation model pinning"). */
@@ -73,30 +78,57 @@ function findAgentTypeByClaudeSessionId(cwd, claudeSessionId, deps = {}) {
73
78
  }
74
79
 
75
80
  /**
76
- * Reads a global agent persona's `model` frontmatter field by name
77
- * (`~/.claude/agents/<agentType>.md`), sync. Returns null on any miss
78
- * (no such file, unreadable, no `model` key) — never throws.
81
+ * Shared miss-tolerant reader `readOverlayAwarePersonaModel` below is a thin
82
+ * wrapper over: tries each candidate path in order (`validatePath` then sync
83
+ * `readFileSync`), and returns the first readable file's `model` frontmatter
84
+ * field — even if that field is absent, which is why an empty-`model`
85
+ * overlay file does NOT fall through to a later candidate (matches
86
+ * `getPersonaBody`'s own semantics). Logs the dangling-persona warning at
87
+ * most once per `(deps.cwd, agentType)` only when EVERY candidate misses.
88
+ * Never throws.
79
89
  */
80
- function readPersonaModel(agentType, deps = {}) {
81
- if (!agentType) return null;
82
- const globalDir = deps.globalDir || path.join(os.homedir(), '.claude', 'agents');
90
+ function readModelFromCandidatePaths(candidates, agentType, deps) {
83
91
  const validatePath = deps.validatePath || configMgr.validatePath;
84
- let real;
85
- try {
86
- real = validatePath(path.join(globalDir, `${agentType}.md`));
87
- } catch {
88
- logDanglingPersonaOnce(deps.cwd, agentType);
89
- return null;
90
- }
91
- let text;
92
- try {
93
- text = fs.readFileSync(real, 'utf8');
94
- } catch {
95
- logDanglingPersonaOnce(deps.cwd, agentType);
96
- return null;
92
+ for (const candidate of candidates) {
93
+ let real;
94
+ try {
95
+ real = validatePath(candidate);
96
+ } catch {
97
+ continue;
98
+ }
99
+ try {
100
+ const text = fs.readFileSync(real, 'utf8');
101
+ const { fm } = splitFrontmatter(text);
102
+ return fm.model || null;
103
+ } catch {
104
+ continue;
105
+ }
97
106
  }
98
- const { fm } = splitFrontmatter(text);
99
- return fm.model || null;
107
+ logDanglingPersonaOnce(deps.cwd, agentType);
108
+ return null;
109
+ }
110
+
111
+ /**
112
+ * Reads an Epic's agentType persona's `model` frontmatter field with the SAME
113
+ * project-overlay-then-global precedence `agentLibrary.cjs`'s `getPersonaBody`
114
+ * (and, through it, `resolvePrdPersonaForSpawn` below) already applies for a
115
+ * scheduled PRD — `epicMint.cjs`'s `resolvePersonaPaths` is the shared path
116
+ * resolver both readers go through, so there is exactly one place that decides
117
+ * which of the two files wins.
118
+ *
119
+ * Sync (not `getPersonaBody`'s async `fsp.readFile`) because `resolveEpicModel`
120
+ * must stay synchronous: `chatRunner.cjs`'s `executeRun()` registers its
121
+ * cancel handle into `inFlight` synchronously, and its caller (`pump()`) reads
122
+ * that entry back immediately after invoking the executor — an `await`
123
+ * inserted ahead of that registration would run it a tick late and silently
124
+ * break `cancel()`. Returns null on a total miss (neither location resolves)
125
+ * — never throws.
126
+ */
127
+ function readOverlayAwarePersonaModel(agentType, deps = {}) {
128
+ if (!agentType) return null;
129
+ const resolvePaths = deps.resolvePersonaPaths || resolvePersonaPaths;
130
+ const { projectPath, globalPath } = resolvePaths(deps.cwd, agentType, deps);
131
+ return readModelFromCandidatePaths([projectPath, globalPath], agentType, deps);
100
132
  }
101
133
 
102
134
  /**
@@ -105,6 +137,12 @@ function readPersonaModel(agentType, deps = {}) {
105
137
  * file) falls back to `fallbackModel` (default: 'sonnet', the same literal
106
138
  * every claude -p call site pins per CLAUDE.md's model-pinning rule).
107
139
  *
140
+ * This is the SINGLE authority both Chat (`chatRunner.cjs`, in-process) and
141
+ * Terminal (`EpicTerminalPane.tsx`, via the `agents:resolve-epic-model` IPC
142
+ * added in the same change) call for an Epic-backed launch, so the two views
143
+ * of one Epic agree on which model that session launches with — see this
144
+ * module's header.
145
+ *
108
146
  * @param {{ cwd: string, claudeSessionId: string, fallbackModel?: string, deps?: object }} opts
109
147
  * @returns {string}
110
148
  */
@@ -112,7 +150,7 @@ function resolveEpicModel({ cwd, claudeSessionId, fallbackModel = FALLBACK_MODEL
112
150
  try {
113
151
  const agentType = findAgentTypeByClaudeSessionId(cwd, claudeSessionId, deps);
114
152
  if (!agentType) return fallbackModel;
115
- const model = readPersonaModel(agentType, { ...deps, cwd });
153
+ const model = readOverlayAwarePersonaModel(agentType, { ...deps, cwd });
116
154
  if (!model || model === 'inherit') return fallbackModel;
117
155
  return model;
118
156
  } catch {
@@ -181,6 +219,6 @@ module.exports = {
181
219
  FALLBACK_MODEL,
182
220
  resolveEpicModel,
183
221
  findAgentTypeByClaudeSessionId,
184
- readPersonaModel,
222
+ readOverlayAwarePersonaModel,
185
223
  resolvePrdPersonaForSpawn,
186
224
  };
@@ -0,0 +1,88 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * bootSelfHeal.cjs — the epic-index self-heal scan, split out of
5
+ * app.whenReady() so it can run off the boot critical path.
6
+ *
7
+ * A project whose active-index.json is MISSING or fails to parse has lost
8
+ * every open Epic's status unless the per-Epic status mirror
9
+ * (epicStatusMirror.cjs) can rebuild it. `allProjectCwds()` is a synchronous
10
+ * ~270ms directory scan; looping it straight through every project inside
11
+ * app.whenReady()'s synchronous body blocked the event loop long enough that
12
+ * the renderer's first IPC round trips (schedule.state, billing.fetch,
13
+ * teams.list) hit their 5s deadlines before any handler could dispatch.
14
+ *
15
+ * `scheduleBootSelfHeal` defers `runEpicIndexSelfHeal` until the main window
16
+ * has painted (`did-finish-load`), with an unconditional `setImmediate`
17
+ * fallback for the window-destroyed-before-load and no-window (headless)
18
+ * cases, so the scan still runs exactly once per boot either way. It yields
19
+ * to the event loop between projects so a machine with thousands of
20
+ * projects can't stall it for a second straight.
21
+ */
22
+
23
+ async function runEpicIndexSelfHeal(deps) {
24
+ const { fs, allProjectCwds, promptSessionsActiveIndexPath, rebuildActiveIndex, logs } = deps;
25
+ try {
26
+ for (const projectCwd of allProjectCwds()) {
27
+ const indexPath = promptSessionsActiveIndexPath(projectCwd);
28
+ let unclean = false;
29
+ let parseError = null;
30
+ if (!fs.existsSync(indexPath)) {
31
+ unclean = true;
32
+ } else {
33
+ try {
34
+ JSON.parse(fs.readFileSync(indexPath, 'utf8'));
35
+ } catch (e) {
36
+ unclean = true;
37
+ parseError = e.message;
38
+ }
39
+ }
40
+ if (unclean) {
41
+ try {
42
+ const { rows, skipped } = rebuildActiveIndex(projectCwd);
43
+ logs.writeLine({
44
+ scope: 'active-index-rebuild',
45
+ level: 'info',
46
+ message: 'rebuilt active-index.json from status mirrors',
47
+ meta: { cwd: projectCwd, parseError, restored: rows.length, skipped: skipped.length },
48
+ });
49
+ } catch (e) {
50
+ logs.writeLine({ scope: 'active-index-rebuild', level: 'error', message: 'rebuild failed', meta: { cwd: projectCwd, error: e?.message } });
51
+ }
52
+ }
53
+ // Yield between projects so a multi-thousand-project machine can't
54
+ // stall the event loop for a second at a time.
55
+ await new Promise((resolve) => { setImmediate(resolve); });
56
+ }
57
+ } catch (e) {
58
+ logs.writeLine({ scope: 'active-index-rebuild', level: 'error', message: 'boot sweep failed', meta: { error: e?.message } });
59
+ }
60
+ }
61
+
62
+ function scheduleBootSelfHeal(mainWindow, deps) {
63
+ let ran = false;
64
+ const runOnce = () => {
65
+ if (ran) return;
66
+ ran = true;
67
+ runEpicIndexSelfHeal(deps);
68
+ };
69
+
70
+ const webContents = mainWindow && typeof mainWindow.isDestroyed === 'function' && !mainWindow.isDestroyed()
71
+ ? mainWindow.webContents
72
+ : null;
73
+
74
+ if (webContents && !webContents.isDestroyed()) {
75
+ webContents.once('did-finish-load', runOnce);
76
+ // Edge case: the window can be destroyed before it ever finishes
77
+ // loading (closed during boot, crashed renderer). did-finish-load would
78
+ // then never fire and the self-heal would silently never run — fall
79
+ // back to an unconditional setImmediate so it still runs exactly once.
80
+ webContents.once('destroyed', () => setImmediate(runOnce));
81
+ } else {
82
+ // Headless / no-visible-window boot — nothing will ever emit
83
+ // did-finish-load, so run on the very next event-loop turn instead.
84
+ setImmediate(runOnce);
85
+ }
86
+ }
87
+
88
+ module.exports = { scheduleBootSelfHeal, runEpicIndexSelfHeal };