claude-code-session-manager 0.56.0 → 0.57.1
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-cI3xlWjr.js → TiptapBody-CJDxVF7X.js} +1 -1
- package/dist/assets/index-DCa3QvAi.js +3227 -0
- package/dist/assets/{index-BktVTpqc.css → index-WUc_GHMf.css} +1 -1
- package/dist/index.html +2 -2
- package/package.json +1 -1
- package/plugins/session-manager-dev/skills/ops-sweep/SKILL.md +136 -0
- package/src/main/__tests__/queueHistory.test.cjs +14 -0
- package/src/main/__tests__/scheduler-reconcile-history-backfill.test.cjs +105 -0
- package/src/main/agentLibrary.cjs +5 -2
- package/src/main/config.cjs +0 -8
- package/src/main/lib/epicMint.cjs +6 -0
- package/src/main/lib/opsOwnership.cjs +0 -2
- package/src/main/lib/queueHistory.cjs +14 -2
- package/src/main/scheduler.cjs +33 -2
- package/src/preload/api.d.ts +5 -1
- package/dist/assets/index-1KHaygYh.js +0 -3223
|
@@ -42,13 +42,14 @@ const PERSONA_NAME_RE = /^[a-z][a-z0-9-]*$/;
|
|
|
42
42
|
* assign/remove the relationship from either side. Stored the same
|
|
43
43
|
* comma-list way as `tools` for one parser to cover both.
|
|
44
44
|
*/
|
|
45
|
-
function serializePersona({ name, description, tools, model, color, tags, body }) {
|
|
45
|
+
function serializePersona({ name, description, tools, model, color, tags, title, body }) {
|
|
46
46
|
const lines = ['---', `name: ${name}`];
|
|
47
47
|
if (description) lines.push(`description: ${description}`);
|
|
48
48
|
if (tools && tools.length) lines.push(`tools: ${tools.join(', ')}`);
|
|
49
49
|
if (model && model !== 'inherit') lines.push(`model: ${model}`);
|
|
50
50
|
if (color) lines.push(`color: ${color}`);
|
|
51
51
|
if (tags && tags.length) lines.push(`tags: ${tags.join(', ')}`);
|
|
52
|
+
if (title) lines.push(`title: ${title}`);
|
|
52
53
|
lines.push('---', '');
|
|
53
54
|
return lines.join('\n') + (body || '').trim() + '\n';
|
|
54
55
|
}
|
|
@@ -66,6 +67,7 @@ async function savePersona({
|
|
|
66
67
|
model,
|
|
67
68
|
color,
|
|
68
69
|
tags,
|
|
70
|
+
title,
|
|
69
71
|
body,
|
|
70
72
|
globalDir = path.join(os.homedir(), '.claude', 'agents'),
|
|
71
73
|
validatePath = configMgr.validatePath,
|
|
@@ -75,7 +77,7 @@ async function savePersona({
|
|
|
75
77
|
throw new Error('agent name must be lowercase, hyphenated (e.g. "my-agent")');
|
|
76
78
|
}
|
|
77
79
|
const target = validatePath(path.join(globalDir, `${name}.md`));
|
|
78
|
-
const text = serializePersona({ name, description, tools, model, color, tags, body });
|
|
80
|
+
const text = serializePersona({ name, description, tools, model, color, tags, title, body });
|
|
79
81
|
await writeTextAtomic(target, text);
|
|
80
82
|
if (originalName && originalName !== name) {
|
|
81
83
|
const oldReal = validatePath(path.join(globalDir, `${originalName}.md`));
|
|
@@ -201,6 +203,7 @@ async function listPersonas({
|
|
|
201
203
|
model: fm.model || null,
|
|
202
204
|
color: fm.color || null,
|
|
203
205
|
tags: parseTools(fm.tags),
|
|
206
|
+
title: fm.title || null,
|
|
204
207
|
path: real,
|
|
205
208
|
body: body.trim(),
|
|
206
209
|
overridingProjects,
|
package/src/main/config.cjs
CHANGED
|
@@ -136,14 +136,6 @@ function validateWrite(realAbs) {
|
|
|
136
136
|
if (realAbs === browserSub || realAbs.startsWith(browserSub + path.sep)) {
|
|
137
137
|
return;
|
|
138
138
|
}
|
|
139
|
-
// Feedback inbox writes (scheduler RCA hook — rcaFeedbackHook.cjs — and
|
|
140
|
-
// any interactive feedback filing): narrowly scoped to
|
|
141
|
-
// session-manager-operations/feedback/, this repo's existing
|
|
142
|
-
// per-project intake convention.
|
|
143
|
-
const feedbackSub = path.join(realRoot, 'session-manager-operations', 'feedback');
|
|
144
|
-
if (realAbs === feedbackSub || realAbs.startsWith(feedbackSub + path.sep)) {
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
139
|
// PromptSession persistence (active-index.json + per-session archives,
|
|
148
140
|
// promptSessions.ts) and the scheduler's own read-modify-write of that
|
|
149
141
|
// same active index (promptSessionEvents.cjs, PRD 814) — narrowly
|
|
@@ -215,6 +215,12 @@ function withPathLock(lockPath, task) {
|
|
|
215
215
|
*/
|
|
216
216
|
function ensureEpic(cwd, { goalText, tag, reuseByGoal = false, epicId: explicitEpicId, status = 'proposed', openingPrompt = null, mintIfMissing = true, source = null, forceNewEpic = false } = {}) {
|
|
217
217
|
if (!cwd || typeof cwd !== 'string') throw new Error('ensureEpic: cwd is required');
|
|
218
|
+
// A relative cwd (e.g. a caller passing '.') would otherwise get stored
|
|
219
|
+
// verbatim on the minted Epic's `cwd` field — the renderer's EpicsWorkspace
|
|
220
|
+
// matches Epics to a tab by exact `s.cwd === effectiveCwd` string equality
|
|
221
|
+
// against the tab's absolute path, so a relative value silently produces an
|
|
222
|
+
// Epic that exists on disk but never renders in any project's queue.
|
|
223
|
+
cwd = path.resolve(cwd);
|
|
218
224
|
return withPathLock(activeIndexPath(cwd), () => {
|
|
219
225
|
const index = readActiveIndex(cwd);
|
|
220
226
|
|
|
@@ -48,8 +48,6 @@ const OWNERS = Object.freeze({
|
|
|
48
48
|
'scheduler': 'scheduler',
|
|
49
49
|
// Project Home owns the synthesized Brief (generate + hand-edit).
|
|
50
50
|
'project-brief': 'project-home',
|
|
51
|
-
// The feedback intake folder — written by the RCA hook and feedback filing.
|
|
52
|
-
'feedback': 'feedback',
|
|
53
51
|
// Browser tab scratch saves (DOM captures, screenshots, recorded flows).
|
|
54
52
|
'browser': 'browser',
|
|
55
53
|
// Structured per-tab error log lines (JSONL), tagged for tracing/analysis.
|
|
@@ -21,10 +21,22 @@ const { projectHistoryPath, stateCwds } = require('./queueStore.cjs');
|
|
|
21
21
|
// resolved via queueStore.cjs). Old rows stay readable from here; new appends
|
|
22
22
|
// go to the owning project's shard (entries without a cwd still land here so
|
|
23
23
|
// nothing is ever dropped).
|
|
24
|
-
|
|
24
|
+
//
|
|
25
|
+
// Overridable via SM_HISTORY_PATH_OVERRIDE so queueHistory.test.cjs can point
|
|
26
|
+
// this at an isolated tmpdir path instead of racing a live Electron
|
|
27
|
+
// instance's scheduler.cjs, which writes this same machine-global file.
|
|
28
|
+
const HISTORY_PATH = process.env.SM_HISTORY_PATH_OVERRIDE
|
|
29
|
+
|| path.join(os.homedir(), '.claude', 'session-manager', 'scheduled-plans', 'history.jsonl');
|
|
25
30
|
|
|
26
|
-
/**
|
|
31
|
+
/**
|
|
32
|
+
* Every history file currently in play: each project's shard + the legacy
|
|
33
|
+
* global. Under SM_HISTORY_PATH_OVERRIDE (queueHistory.test.cjs), skip the
|
|
34
|
+
* federated per-project shards entirely — they're real files under whatever
|
|
35
|
+
* projects exist on this machine's ~/.claude/projects, so including them
|
|
36
|
+
* would leak real production history rows into an otherwise-isolated test.
|
|
37
|
+
*/
|
|
27
38
|
function historyPaths() {
|
|
39
|
+
if (process.env.SM_HISTORY_PATH_OVERRIDE) return [HISTORY_PATH];
|
|
28
40
|
const paths = [];
|
|
29
41
|
for (const cwd of stateCwds()) {
|
|
30
42
|
try { paths.push(projectHistoryPath(cwd)); } catch { /* unusable cwd */ }
|
package/src/main/scheduler.cjs
CHANGED
|
@@ -1138,11 +1138,26 @@ async function reconcile(state) {
|
|
|
1138
1138
|
|
|
1139
1139
|
const next = [];
|
|
1140
1140
|
const seen = new Set();
|
|
1141
|
+
// Terminal (completed/failed) jobs whose .md vanished from disk BEFORE
|
|
1142
|
+
// their row ever crossed HISTORY_RETENTION_MS in partitionJobs — i.e. the
|
|
1143
|
+
// common case, since archiveCompletedPrd() (called right when a job
|
|
1144
|
+
// finishes, scheduler.cjs's `newlyCompletedPrds` handling) moves the PRD
|
|
1145
|
+
// file immediately, while partitionJobs' appendHistory only fires after 7
|
|
1146
|
+
// days. Bug found 2026-08-02: the old code below dropped these rows on the
|
|
1147
|
+
// very next reconcile() pass with NO history write at all — the file-gone
|
|
1148
|
+
// check couldn't tell "already recorded to history.jsonl on a prior pass"
|
|
1149
|
+
// apart from "never recorded anywhere", so a job that completed and had
|
|
1150
|
+
// its file auto-archived within minutes lost its history entirely. Both
|
|
1151
|
+
// Queue and History tabs then read as empty even for projects with real,
|
|
1152
|
+
// successfully-completed work. Collected here; reconciled against
|
|
1153
|
+
// historyTerminalBySlug() below and backfilled before being dropped.
|
|
1154
|
+
const terminalDroppedNeedingHistoryCheck = [];
|
|
1141
1155
|
for (const job of state.jobs) {
|
|
1142
1156
|
const p = onDisk.get(job.slug);
|
|
1143
1157
|
if (!p) {
|
|
1144
1158
|
// A terminal job whose .md is gone was archived on purpose — dropping
|
|
1145
|
-
// its row is the intended end of the auto-archive flow
|
|
1159
|
+
// its row is the intended end of the auto-archive flow, PROVIDED it's
|
|
1160
|
+
// already durably recorded in history.jsonl (checked below).
|
|
1146
1161
|
//
|
|
1147
1162
|
// A PENDING or RUNNING job whose .md is merely not VISIBLE is a
|
|
1148
1163
|
// different thing entirely, and dropping it destroys queued work: the
|
|
@@ -1163,6 +1178,8 @@ async function reconcile(state) {
|
|
|
1163
1178
|
seen.add(job.slug);
|
|
1164
1179
|
next.push({ ...job });
|
|
1165
1180
|
console.warn(`[scheduler] reconcile: keeping ${job.status} job ${job.slug} — PRD source not visible in any candidate dir`);
|
|
1181
|
+
} else if (job.status === 'completed' || job.status === 'failed') {
|
|
1182
|
+
terminalDroppedNeedingHistoryCheck.push(job);
|
|
1166
1183
|
}
|
|
1167
1184
|
continue;
|
|
1168
1185
|
}
|
|
@@ -1198,10 +1215,24 @@ async function reconcile(state) {
|
|
|
1198
1215
|
for (const [slug] of onDisk) {
|
|
1199
1216
|
if (!seen.has(slug)) unmatchedSlugs.push(slug);
|
|
1200
1217
|
}
|
|
1201
|
-
const historyBySlug = unmatchedSlugs.length > 0
|
|
1218
|
+
const historyBySlug = (unmatchedSlugs.length > 0 || terminalDroppedNeedingHistoryCheck.length > 0)
|
|
1202
1219
|
? await queueHistory.historyTerminalBySlug()
|
|
1203
1220
|
: new Map();
|
|
1204
1221
|
|
|
1222
|
+
// Backfill: any terminal job dropped above whose slug isn't already in
|
|
1223
|
+
// history.jsonl gets written now, before its row is gone for good. This is
|
|
1224
|
+
// the fix for the bug described where the loop collects
|
|
1225
|
+
// terminalDroppedNeedingHistoryCheck — without it, a job whose PRD file was
|
|
1226
|
+
// auto-archived immediately on completion (the normal case) is lost from
|
|
1227
|
+
// both jobs[] and history.jsonl in the same reconcile pass.
|
|
1228
|
+
if (terminalDroppedNeedingHistoryCheck.length > 0) {
|
|
1229
|
+
const missing = terminalDroppedNeedingHistoryCheck.filter((j) => !historyBySlug.has(j.slug));
|
|
1230
|
+
if (missing.length > 0) {
|
|
1231
|
+
await queueHistory.appendHistory(missing);
|
|
1232
|
+
console.log(`[scheduler] reconcile: backfilled ${missing.length} completed/failed job(s) into history.jsonl (PRD file already archived, row about to drop)`);
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1205
1236
|
// Terminal-in-history slugs whose .md file is still on disk: fed into the
|
|
1206
1237
|
// auto-archive selection pass below (as synthetic completed entries) so
|
|
1207
1238
|
// their file can still be swept, without ever creating a live job row
|
package/src/preload/api.d.ts
CHANGED
|
@@ -133,7 +133,7 @@ export interface ImportRef {
|
|
|
133
133
|
/** Declared owner ids for the single-writer law over a project's
|
|
134
134
|
* session-manager-operations/ root. Mirrors OWNERS in
|
|
135
135
|
* src/main/lib/opsOwnership.cjs — one writer per namespace; everyone reads. */
|
|
136
|
-
export type OpsWriter = 'epics' | 'scheduler' | 'project-home' | '
|
|
136
|
+
export type OpsWriter = 'epics' | 'scheduler' | 'project-home' | 'browser';
|
|
137
137
|
|
|
138
138
|
export interface WriteResult {
|
|
139
139
|
ok: boolean;
|
|
@@ -267,6 +267,9 @@ export interface AgentPersona {
|
|
|
267
267
|
color: string | null;
|
|
268
268
|
/** Epic intent tags (tagLibrary.ts's TAG_LIBRARY) this persona is associated with. */
|
|
269
269
|
tags: string[];
|
|
270
|
+
/** "<Department> — <Human title>" (e.g. "Engineering — Architect"), free text — drives
|
|
271
|
+
* department grouping in the New Epic Agent picker. Optional/null when not set. */
|
|
272
|
+
title?: string | null;
|
|
270
273
|
path: string;
|
|
271
274
|
body: string;
|
|
272
275
|
/** Project names (basename of cwd) whose currently-open tab overlays this agent. */
|
|
@@ -283,6 +286,7 @@ export interface AgentPersonaSaveInput {
|
|
|
283
286
|
model: string;
|
|
284
287
|
color: string;
|
|
285
288
|
tags: string[];
|
|
289
|
+
title?: string;
|
|
286
290
|
body: string;
|
|
287
291
|
}
|
|
288
292
|
|