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.
- package/dist/assets/AgentLibrary-DyLWzZDf.js +3 -0
- package/dist/assets/{DataModel-Q4jhl24R.js → DataModel--mISIJ6h.js} +1 -1
- package/dist/assets/{History-Cj2FejEo.js → History-C2ahUXTg.js} +2 -2
- package/dist/assets/{Hooks-CaelQI6t.js → Hooks-BiC6oyR2.js} +3 -3
- package/dist/assets/{HostBilko--v7cMR8I.js → HostBilko-BPleEOld.js} +1 -1
- package/dist/assets/{Library-DgI9oCCZ.js → Library-Dc8Qst1R.js} +1 -1
- package/dist/assets/{ListDetail-DYUZN-x-.js → ListDetail-DIXh-OLX.js} +1 -1
- package/dist/assets/MarkdownEditor-C90bkLXK.js +1 -0
- package/dist/assets/{McpServers-ypCYURh3.js → McpServers-DqcbLOLZ.js} +2 -2
- package/dist/assets/{Memory-C2qYp-3M.js → Memory-CW62MXlh.js} +4 -4
- package/dist/assets/{Panel-Cj2kw-Zv.js → Panel-Bw1FhRuF.js} +1 -1
- package/dist/assets/Permissions-BcUC-5y8.js +3 -0
- package/dist/assets/{Plugins-C1Vj8_dU.js → Plugins-BnKx9flD.js} +2 -2
- package/dist/assets/{ProvenanceBadge-DczPNM5U.js → ProvenanceBadge-Bw5vNVPT.js} +1 -1
- package/dist/assets/{SaveBar-Cd_7U6Gb.js → SaveBar-CWr0O_w-.js} +1 -1
- package/dist/assets/Scheduler-DYdLuUqq.js +14 -0
- package/dist/assets/{ScopeSwitcher-DVSyI44-.js → ScopeSwitcher-CrBLbg8s.js} +1 -1
- package/dist/assets/Settings-DluB-vN1.js +3 -0
- package/dist/assets/{SkillReferenceGraph-CUv1_Q2c.js → SkillReferenceGraph-CHLSseay.js} +1 -1
- package/dist/assets/{Skills-C_YHkAy-.js → Skills-gNdo_HNK.js} +2 -2
- package/dist/assets/{SystemPrompt-B8R7T9xn.js → SystemPrompt-Cru05-Ia.js} +1 -1
- package/dist/assets/{TagLibrary-dj9YHWyy.js → TagLibrary-DNHY0xou.js} +1 -1
- package/dist/assets/{TiptapBody-DnSBUjHE.js → TiptapBody-I4lmbCgP.js} +1 -1
- package/dist/assets/{Toggle-CjV_BJn6.js → Toggle-bWMHjmRh.js} +1 -1
- package/dist/assets/{index-CDo9xBR9.css → index-DV3PorRY.css} +1 -1
- package/dist/assets/{index-CXFQIPhO.js → index-fc_JjdxL.js} +724 -724
- package/dist/assets/settingsSchema-BfhtZnGD.js +3 -0
- package/dist/index.html +2 -2
- package/package.json +14 -14
- package/plugins/CLAUDE.md +61 -0
- package/plugins/session-manager-dev/.claude-plugin/plugin.json +1 -1
- package/plugins/session-manager-dev/skills/builder/4-manual/SKILL.md +1 -1
- package/plugins/session-manager-dev/skills/ops-sweep/SKILL.md +1 -1
- package/scripts/scheduler-mcp-server.cjs +7 -0
- package/src/main/__tests__/agentModelResolve.test.cjs +100 -9
- package/src/main/__tests__/broadcastCoalescer.test.cjs +18 -0
- package/src/main/__tests__/epicMint.test.cjs +2 -2
- package/src/main/__tests__/health-delegation-chain.test.cjs +2 -1
- package/src/main/__tests__/needsReviewLedger.test.cjs +162 -0
- package/src/main/__tests__/opsErrorLogTelemetryTap.test.cjs +3 -3
- package/src/main/__tests__/pollLoop-dispatch-on-failure.test.cjs +15 -1
- package/src/main/__tests__/prdCreateDisposition.test.cjs +201 -0
- package/src/main/__tests__/prdFrontmatterDisposition.test.cjs +125 -0
- package/src/main/__tests__/prdLocations.test.cjs +100 -2
- package/src/main/__tests__/prdLocationsArchived.test.cjs +43 -1
- package/src/main/__tests__/prdSetDisposition.test.cjs +222 -0
- package/src/main/__tests__/queue-health-verdict.test.cjs +170 -0
- package/src/main/__tests__/queue-starvation-per-project.test.cjs +14 -2
- package/src/main/__tests__/queueHistory.test.cjs +63 -0
- package/src/main/__tests__/reconcileTiming.test.cjs +135 -0
- package/src/main/__tests__/scheduleJobTransitions.test.cjs +101 -1
- package/src/main/__tests__/scheduler-boot-orphans.test.cjs +2 -2
- package/src/main/__tests__/scheduler-broadcast-reconcile.test.cjs +121 -0
- package/src/main/__tests__/scheduler-cross-project-batch.test.cjs +43 -0
- package/src/main/__tests__/scheduler-guard-verdict-autoresolve.test.cjs +344 -0
- package/src/main/__tests__/scheduler-job-budget.test.cjs +172 -0
- package/src/main/__tests__/scheduler-looks-done.test.cjs +93 -3
- package/src/main/__tests__/scheduler-porcelain-rename.test.cjs +164 -0
- package/src/main/__tests__/scheduler-reap-dead-running-jobs.test.cjs +160 -0
- package/src/main/__tests__/scheduler-reaper-helpers-basics.test.cjs +87 -0
- package/src/main/__tests__/scheduler-shared-tree-guard.test.cjs +88 -0
- package/src/main/__tests__/scheduler-starve-escalation.test.cjs +14 -4
- package/src/main/__tests__/scheduler-worktree-cap-defer.test.cjs +14 -0
- package/src/main/chatRunner.cjs +8 -5
- package/src/main/health.cjs +1 -1
- package/src/main/historyAggregator.cjs +5 -0
- package/src/main/index.cjs +95 -44
- package/src/main/ipcSchemas.cjs +47 -0
- package/src/main/lib/__tests__/active-sessions.test.cjs +251 -0
- package/src/main/lib/__tests__/bootSelfHeal.test.cjs +107 -0
- package/src/main/lib/__tests__/delegationReadiness.test.cjs +322 -43
- package/src/main/lib/__tests__/effectiveModelInfo.test.cjs +239 -0
- package/src/main/lib/__tests__/gitWorktree.test.cjs +89 -0
- package/src/main/lib/__tests__/guardShims.test.cjs +151 -0
- package/src/main/lib/__tests__/opsRootAbsoluteCwd.test.cjs +5 -5
- package/src/main/lib/__tests__/prdDisposition.test.cjs +224 -0
- package/src/main/lib/__tests__/reaperHelpers.test.cjs +179 -1
- package/src/main/lib/__tests__/usageCircuit.test.cjs +224 -0
- package/src/main/lib/__tests__/watchdog-helpers.test.cjs +312 -0
- package/src/main/lib/__tests__/watchdog-relaunch.test.cjs +193 -0
- package/{scripts → src/main}/lib/activeSessions.cjs +50 -4
- package/src/main/lib/agentModelResolve.cjs +65 -27
- package/src/main/lib/bootSelfHeal.cjs +88 -0
- package/src/main/lib/delegationReadiness.cjs +290 -225
- package/src/main/lib/effectiveModelInfo.cjs +333 -0
- package/src/main/lib/ephemeralCwd.cjs +1 -1
- package/src/main/lib/epicMint.cjs +3 -3
- package/src/main/lib/gitWorktree.cjs +42 -12
- package/src/main/lib/guardShims.cjs +156 -0
- package/src/main/lib/jobDirtFilter.cjs +7 -2
- package/src/main/lib/launchFailure.cjs +2 -1
- package/src/main/lib/mcpToolCatalog.cjs +4 -1
- package/src/main/lib/needsReviewLedger.cjs +205 -0
- package/src/main/lib/opsErrorLog.cjs +1 -1
- package/src/main/lib/opsOwnership.cjs +1 -1
- package/src/main/lib/prdCreate.cjs +56 -1
- package/src/main/lib/prdDisposition.cjs +199 -0
- package/src/main/lib/prdFrontmatter.cjs +8 -2
- package/src/main/lib/prdLocations.cjs +167 -45
- package/src/main/lib/projectHomeAdminRoutes.cjs +4 -4
- package/src/main/lib/projectPageSummarySchema.cjs +1 -1
- package/src/main/lib/projectRootResolve.cjs +1 -1
- package/src/main/lib/queueHistory.cjs +19 -1
- package/src/main/lib/queueStore.cjs +6 -1
- package/src/main/lib/reaperHelpers.cjs +181 -15
- package/src/main/lib/scheduleJobSchema.cjs +8 -0
- package/src/main/lib/scheduleJobTransitions.cjs +33 -0
- package/src/main/lib/schedulerConfig.cjs +24 -0
- package/src/main/lib/usageCircuit.cjs +159 -0
- package/{scripts → src/main}/lib/watchdogHelpers.cjs +1 -1
- package/src/main/scheduler/prdParser.cjs +13 -0
- package/src/main/scheduler.cjs +1285 -143
- package/src/main/templates/PRD_AUTHORING.md +50 -0
- package/src/main/templates/project-pages-catalog.json +1 -1
- package/src/main/usage.cjs +21 -3
- package/src/preload/api.d.ts +92 -1
- package/src/preload/index.cjs +10 -0
- package/web/README.md +41 -0
- package/{scripts/render-project-pages.cjs → web/project-pages/render.cjs} +4 -4
- package/{scripts/render-project-pages → web/project-pages/renderer}/dist/renderer.cjs +1 -1
- package/{scripts/validate-project-pages-summary.cjs → web/project-pages/validate-summary.cjs} +5 -5
- package/dist/assets/AgentLibrary-DTFL7y8G.js +0 -3
- package/dist/assets/MarkdownEditor-DCIubYWf.js +0 -1
- package/dist/assets/Permissions-BiYZNGYW.js +0 -3
- package/dist/assets/Scheduler-DcLBiJBq.js +0 -14
- package/dist/assets/Settings-Cv-pRyms.js +0 -3
- package/dist/assets/settingsSchema-BJVciriw.js +0 -3
- /package/{scripts/project-pages-logic → web/project-pages/logic}/dist/logic.cjs +0 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Ported from the retired top-level test-root directory (a node:test suite,
|
|
4
|
+
// never runnable by this repo's vitest-only setup — see CLAUDE.md's Commands
|
|
5
|
+
// section). The existing src/main/lib/__tests__/reaperHelpers.test.cjs
|
|
6
|
+
// exercises classifyRunOutcome extensively via selectReapableJobs and the
|
|
7
|
+
// rate-limit paths, but never asserts claudePidAlive directly (only ever
|
|
8
|
+
// injects a stand-in `pidAlive` function), and never pins the bare
|
|
9
|
+
// no-result/empty-file/malformed-event edge cases below. These fill that
|
|
10
|
+
// gap.
|
|
11
|
+
//
|
|
12
|
+
// Run: timeout 120 npx vitest run src/main/__tests__/scheduler-reaper-helpers-basics.test.cjs
|
|
13
|
+
|
|
14
|
+
const assert = require('node:assert/strict');
|
|
15
|
+
const os = require('node:os');
|
|
16
|
+
const path = require('node:path');
|
|
17
|
+
const fs = require('node:fs');
|
|
18
|
+
const { claudePidAlive, classifyRunOutcome } = require('../lib/reaperHelpers.cjs');
|
|
19
|
+
|
|
20
|
+
// ─────────────────────────────────────────── claudePidAlive
|
|
21
|
+
|
|
22
|
+
test('claudePidAlive: nonexistent pid (999999) returns false', () => {
|
|
23
|
+
assert.equal(claudePidAlive(999999), false);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test('claudePidAlive: pid 0 returns false', () => {
|
|
27
|
+
assert.equal(claudePidAlive(0), false);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('claudePidAlive: pid 1 returns false', () => {
|
|
31
|
+
assert.equal(claudePidAlive(1), false);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('claudePidAlive: null returns false', () => {
|
|
35
|
+
assert.equal(claudePidAlive(null), false);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('claudePidAlive: undefined returns false', () => {
|
|
39
|
+
assert.equal(claudePidAlive(undefined), false);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// ─────────────────────────────────────────── classifyRunOutcome
|
|
43
|
+
|
|
44
|
+
function writeTempLog(content) {
|
|
45
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'reaper-basics-test-'));
|
|
46
|
+
const filePath = path.join(dir, 'test.log');
|
|
47
|
+
fs.writeFileSync(filePath, content, 'utf8');
|
|
48
|
+
return filePath;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
test('classifyRunOutcome: success without an is_error field → success', () => {
|
|
52
|
+
const p = writeTempLog('{"type":"result","subtype":"success","result":"ok"}\n');
|
|
53
|
+
assert.equal(classifyRunOutcome(p), 'success');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('classifyRunOutcome: subtype success but is_error:true → failed (error wins)', () => {
|
|
57
|
+
const p = writeTempLog('{"type":"result","subtype":"success","is_error":true}\n');
|
|
58
|
+
assert.equal(classifyRunOutcome(p), 'failed');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('classifyRunOutcome: no result event in the tail → no_result', () => {
|
|
62
|
+
const log = [
|
|
63
|
+
'{"type":"assistant","message":"working"}',
|
|
64
|
+
'{"type":"tool_use","name":"bash"}',
|
|
65
|
+
'some plain text log line',
|
|
66
|
+
].join('\n');
|
|
67
|
+
const p = writeTempLog(log);
|
|
68
|
+
assert.equal(classifyRunOutcome(p), 'no_result');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('classifyRunOutcome: empty file → no_result', () => {
|
|
72
|
+
const p = writeTempLog('');
|
|
73
|
+
assert.equal(classifyRunOutcome(p), 'no_result');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('classifyRunOutcome: last result event wins when multiple exist', () => {
|
|
77
|
+
const log = [
|
|
78
|
+
'{"type":"result","subtype":"success"}',
|
|
79
|
+
'{"type":"result","subtype":"error","is_error":true}',
|
|
80
|
+
].join('\n');
|
|
81
|
+
const p = writeTempLog(log);
|
|
82
|
+
assert.equal(classifyRunOutcome(p), 'failed');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('classifyRunOutcome: nonexistent file → no_result (readTail swallows I/O errors)', () => {
|
|
86
|
+
assert.equal(classifyRunOutcome('/nonexistent/path/that/cannot/exist.log'), 'no_result');
|
|
87
|
+
});
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
18
|
import { test, expect, vi, afterEach } from 'vitest';
|
|
19
|
+
const fs = require('node:fs');
|
|
19
20
|
const scheduler = require('../scheduler.cjs');
|
|
20
21
|
const { evaluateSharedTreeGuard, checkSharedTreeGuard } = scheduler;
|
|
21
22
|
|
|
@@ -210,3 +211,90 @@ test('checkSharedTreeGuard never throws when the underlying git calls reject', a
|
|
|
210
211
|
});
|
|
211
212
|
expect(result).toBeNull();
|
|
212
213
|
});
|
|
214
|
+
|
|
215
|
+
// --- 2026-09-12 false-positive: an untracked path that merely became
|
|
216
|
+
// IGNORED during the run (a .gitignore edit) is not a revert. See the
|
|
217
|
+
// evaluateSharedTreeGuard doc comment for the full incident writeup. Each
|
|
218
|
+
// test below is written to FAIL against the pre-fix implementation (which
|
|
219
|
+
// computed `reverted = dirtyBefore - dirtyAfter - committed` with no
|
|
220
|
+
// existence/tracked-status distinction at all) and PASS after it.
|
|
221
|
+
|
|
222
|
+
test('evaluateSharedTreeGuard: an untracked path that disappears from git status but STILL EXISTS on disk is nowIgnored, not reverted', () => {
|
|
223
|
+
// Pre-fix behavior: this path would land in `reverted` (dirtyBefore minus
|
|
224
|
+
// dirtyAfter minus committed, no existence check at all) — FAILS pre-fix.
|
|
225
|
+
const result = evaluateSharedTreeGuard({
|
|
226
|
+
stashBefore: [],
|
|
227
|
+
stashAfter: [],
|
|
228
|
+
dirtyBefore: [{ code: '??', path: 'session-manager-operations/logs/errors-2026-09-12.jsonl' }],
|
|
229
|
+
dirtyAfter: [],
|
|
230
|
+
pathsCommittedDuringRun: ['.gitignore'],
|
|
231
|
+
existsAfter: ['session-manager-operations/logs/errors-2026-09-12.jsonl'],
|
|
232
|
+
});
|
|
233
|
+
expect(result.reverted).toEqual([]);
|
|
234
|
+
expect(result.nowIgnored).toEqual(['session-manager-operations/logs/errors-2026-09-12.jsonl']);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
test('evaluateSharedTreeGuard: an untracked path that disappears from git status AND no longer exists on disk is still reverted', () => {
|
|
238
|
+
// Proves the fix is not a blanket "stop reporting reverted": a genuinely
|
|
239
|
+
// deleted untracked file (the guard's real purpose — catching a job that
|
|
240
|
+
// discarded uncommitted work) must still surface. If the fix had simply
|
|
241
|
+
// stopped reporting any untracked-and-missing path as reverted, this test
|
|
242
|
+
// would fail (reverted would come back empty instead of containing the
|
|
243
|
+
// path).
|
|
244
|
+
const result = evaluateSharedTreeGuard({
|
|
245
|
+
stashBefore: [],
|
|
246
|
+
stashAfter: [],
|
|
247
|
+
dirtyBefore: [{ code: '??', path: 'data/scratch.txt' }],
|
|
248
|
+
dirtyAfter: [],
|
|
249
|
+
pathsCommittedDuringRun: [],
|
|
250
|
+
existsAfter: [], // file no longer exists on disk
|
|
251
|
+
});
|
|
252
|
+
expect(result.reverted).toEqual(['data/scratch.txt']);
|
|
253
|
+
expect(result.nowIgnored).toEqual([]);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
test('evaluateSharedTreeGuard: a TRACKED path clean after the run is still reverted even though it exists on disk', () => {
|
|
257
|
+
// A tracked file leaving the dirty set means its content was restored to
|
|
258
|
+
// HEAD — exactly the discard this guard exists to catch — regardless of
|
|
259
|
+
// whether the file is still present. Pre-fix this already worked by
|
|
260
|
+
// accident (no existence check existed at all); this test pins the
|
|
261
|
+
// behavior now that existence checks exist for the untracked branch, so a
|
|
262
|
+
// future change can't accidentally start trusting `existsAfter` for
|
|
263
|
+
// tracked paths too.
|
|
264
|
+
const result = evaluateSharedTreeGuard({
|
|
265
|
+
stashBefore: [],
|
|
266
|
+
stashAfter: [],
|
|
267
|
+
dirtyBefore: [{ code: ' M', path: 'src/config.js' }],
|
|
268
|
+
dirtyAfter: [],
|
|
269
|
+
pathsCommittedDuringRun: [],
|
|
270
|
+
existsAfter: ['src/config.js'], // file exists, but content was reset to HEAD
|
|
271
|
+
});
|
|
272
|
+
expect(result.reverted).toEqual(['src/config.js']);
|
|
273
|
+
expect(result.nowIgnored).toEqual([]);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
test('checkSharedTreeGuard end-to-end: the real 2026-09-12 incident shape — untracked logs path ignored by a committed .gitignore change, file still on disk — must NOT verdict shared_tree_reverted', async () => {
|
|
277
|
+
// PRD 1181's exact shape: a bare `logs/` pattern lands in .gitignore during
|
|
278
|
+
// the run (committed), eleven untracked log paths vanish from `git status`,
|
|
279
|
+
// and the files are untouched on disk. Pre-fix, checkSharedTreeGuard's
|
|
280
|
+
// `reverted` would contain this path (no existsAfter computation existed),
|
|
281
|
+
// driving the caller's `verdict: 'shared_tree_reverted'` downgrade — FAILS
|
|
282
|
+
// pre-fix.
|
|
283
|
+
const logPath = 'session-manager-operations/logs/errors-2026-09-12.jsonl';
|
|
284
|
+
vi.spyOn(scheduler, 'stashList').mockResolvedValue([]);
|
|
285
|
+
vi.spyOn(scheduler, 'gitHead').mockResolvedValue('sha-after');
|
|
286
|
+
vi.spyOn(scheduler, 'pathsChangedSince').mockResolvedValue(['.gitignore']);
|
|
287
|
+
vi.spyOn(scheduler, 'uncommittedChanges').mockResolvedValue([]); // the log path no longer shows up at all
|
|
288
|
+
vi.spyOn(fs, 'existsSync').mockImplementation((p) => String(p).includes('errors-2026-09-12.jsonl'));
|
|
289
|
+
|
|
290
|
+
const result = await checkSharedTreeGuard({
|
|
291
|
+
cwd: '/repo',
|
|
292
|
+
stashBaseline: [],
|
|
293
|
+
dirtyBaseline: [{ code: '??', path: logPath }],
|
|
294
|
+
headBefore: 'sha-before',
|
|
295
|
+
slug: 'job-1181',
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
expect(result.reverted).toBeUndefined();
|
|
299
|
+
expect(result.nowIgnored).toEqual([logPath]);
|
|
300
|
+
});
|
|
@@ -12,21 +12,26 @@
|
|
|
12
12
|
* Exercises selectStarveEscalations() (pure) directly, and
|
|
13
13
|
* runStarveEscalationSweep() (the side-effecting wrapper) against a real,
|
|
14
14
|
* scratch audit-log.jsonl and a fake attached window — matching
|
|
15
|
-
*
|
|
16
|
-
* require()
|
|
15
|
+
* pollLoop-dispatch-on-failure.test.cjs's pattern of setting HOME before
|
|
16
|
+
* require() (beforeAll runs AFTER module evaluation) since every state path
|
|
17
|
+
* is baked from os.homedir() at load, and restoring it in afterAll.
|
|
17
18
|
*
|
|
18
19
|
* Run: timeout 180 npx vitest run src/main/__tests__/scheduler-starve-escalation.test.cjs
|
|
19
20
|
*/
|
|
20
21
|
|
|
21
22
|
'use strict';
|
|
22
23
|
|
|
23
|
-
import { test, beforeEach } from 'vitest';
|
|
24
|
+
import { test, beforeEach, afterAll } from 'vitest';
|
|
24
25
|
const assert = require('node:assert/strict');
|
|
25
26
|
const fs = require('node:fs');
|
|
26
27
|
const os = require('node:os');
|
|
27
28
|
const path = require('node:path');
|
|
28
29
|
|
|
29
|
-
process.env.HOME
|
|
30
|
+
const originalHome = process.env.HOME;
|
|
31
|
+
const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'starve-escalation-test-'));
|
|
32
|
+
process.env.HOME = tmpHome;
|
|
33
|
+
fs.mkdirSync(path.join(tmpHome, '.claude', 'session-manager'), { recursive: true });
|
|
34
|
+
fs.mkdirSync(path.join(tmpHome, '.claude', 'projects'), { recursive: true });
|
|
30
35
|
|
|
31
36
|
const {
|
|
32
37
|
selectStarveEscalations,
|
|
@@ -36,6 +41,11 @@ const {
|
|
|
36
41
|
} = require('../scheduler.cjs');
|
|
37
42
|
const { AUDIT_LOG_PATH } = require('../lib/auditLog.cjs');
|
|
38
43
|
|
|
44
|
+
afterAll(() => {
|
|
45
|
+
process.env.HOME = originalHome;
|
|
46
|
+
fs.rmSync(tmpHome, { recursive: true, force: true });
|
|
47
|
+
});
|
|
48
|
+
|
|
39
49
|
const CWD_A = '/home/bilko/Projects/Bilko';
|
|
40
50
|
const CWD_B = '/home/bilko/Projects/session-manager';
|
|
41
51
|
|
|
@@ -187,6 +187,20 @@ test('a non-git cwd still falls back to running in place (unchanged behaviour
|
|
|
187
187
|
expect(row.worktreeFallbackReason).toMatch(/not a git repository/);
|
|
188
188
|
// Ran IN PLACE — the marker landed directly in projectCwd, not a worktree.
|
|
189
189
|
expect(fs.existsSync(path.join(projectCwd, 'ran-here.marker'))).toBe(true);
|
|
190
|
+
|
|
191
|
+
// A job losing worktree isolation must never be a silent downgrade
|
|
192
|
+
// discoverable only by reading queue.json afterwards — it must also land
|
|
193
|
+
// as a warn line in the durable ops error log, naming the slug, cwd, and
|
|
194
|
+
// reason.
|
|
195
|
+
const { todayFile } = require('../lib/opsErrorLog.cjs');
|
|
196
|
+
const errorLogPath = todayFile(projectCwd);
|
|
197
|
+
expect(fs.existsSync(errorLogPath)).toBe(true);
|
|
198
|
+
const logLines = fs.readFileSync(errorLogPath, 'utf8').trim().split('\n').map((l) => JSON.parse(l));
|
|
199
|
+
const fallbackLine = logLines.find((l) => l.scope === 'scheduler' && l.level === 'warn' && l.message.includes(slug));
|
|
200
|
+
expect(fallbackLine).toBeTruthy();
|
|
201
|
+
expect(fallbackLine.message).toMatch(/SHARED working tree/);
|
|
202
|
+
expect(fallbackLine.message).toMatch(/not a git repository/);
|
|
203
|
+
expect(fallbackLine.meta).toMatchObject({ slug, cwd: projectCwd, reason: expect.stringMatching(/not a git repository/) });
|
|
190
204
|
} finally {
|
|
191
205
|
fs.rmSync(projectCwd, { recursive: true, force: true });
|
|
192
206
|
fs.rmSync(runDir, { recursive: true, force: true });
|
package/src/main/chatRunner.cjs
CHANGED
|
@@ -530,11 +530,14 @@ function executeRun({ tabId, sessionId, prompt, cwd, resume, silent, onSilentRes
|
|
|
530
530
|
const fullPrompt = STOP_SIGNAL_INSTRUCTION + CHAT_MODE_TRUTH_INSTRUCTION + prompt;
|
|
531
531
|
|
|
532
532
|
// sessionId is the Epic's claudeSessionId for an Epic-backed tab (see
|
|
533
|
-
// comment above childEnv) — resolve that Epic's agentType persona model
|
|
534
|
-
//
|
|
535
|
-
//
|
|
536
|
-
//
|
|
537
|
-
//
|
|
533
|
+
// comment above childEnv) — resolve that Epic's agentType persona model,
|
|
534
|
+
// project-overlay-then-global (agentModelResolve.cjs's
|
|
535
|
+
// readOverlayAwarePersonaModel). Terminal (EpicTerminalPane.tsx) calls
|
|
536
|
+
// this SAME function via the agents:resolve-epic-model IPC, so the two
|
|
537
|
+
// views of one Epic agree by construction, not by convention. Never
|
|
538
|
+
// throws; falls back to the hardcoded 'sonnet' literal when there's no
|
|
539
|
+
// Epic, no agentType, or the persona has no model/'inherit' — --model
|
|
540
|
+
// must never be left unpinned (CLAUDE.md model-pinning rule).
|
|
538
541
|
const model = agentModelResolve.resolveEpicModel({ cwd, claudeSessionId: sessionId });
|
|
539
542
|
|
|
540
543
|
// Build argv as an array — no shell: true, no string interpolation
|
package/src/main/health.cjs
CHANGED
|
@@ -20,7 +20,7 @@ const { computeStallSummary, SCHEDULER_STATE_PATH, FAILURE_STREAK_WARN_THRESHOLD
|
|
|
20
20
|
const { findStarvedProjects } = require('./lib/schedulerBatch.cjs');
|
|
21
21
|
const { AUDIT_LOG_PATH } = require('./lib/auditLog.cjs');
|
|
22
22
|
const { DEFAULT_RUNS_DIR, computeReport, isRetentionEnabled, liveKeysFromJobs } = require('./lib/runLogRetention.cjs');
|
|
23
|
-
const { allProjectCwds } = require('
|
|
23
|
+
const { allProjectCwds } = require('./lib/activeSessions.cjs');
|
|
24
24
|
|
|
25
25
|
const MAX_LOG_AGE_MS = 5 * 60_000; // 5 min — warn if no logs this old
|
|
26
26
|
const PROJECT_ROOT = path.resolve(__dirname, '../..');
|
|
@@ -21,10 +21,14 @@ const CACHE_MAX = 50_000;
|
|
|
21
21
|
// tokens are priced far below input because they're served from
|
|
22
22
|
// Anthropic's prompt cache, not re-processed — this is what makes the
|
|
23
23
|
// cache-savings figure in the dashboard real money, not a vanity stat.
|
|
24
|
+
// fable: Claude Fable 5.1 (claude-fable-5-1) — $10/$50 per MTok input/output,
|
|
25
|
+
// cache reads at $0.25/MTok (the `claude-api` skill's model reference and its
|
|
26
|
+
// Claude Fable 5.1 migration notes, both cached 2026-06-24).
|
|
24
27
|
const MODEL_PRICING = {
|
|
25
28
|
opus: { i: 15, o: 75, c: 1.5 },
|
|
26
29
|
sonnet: { i: 3, o: 15, c: 0.3 },
|
|
27
30
|
haiku: { i: 0.8, o: 4, c: 0.08 },
|
|
31
|
+
fable: { i: 10, o: 50, c: 0.25 },
|
|
28
32
|
};
|
|
29
33
|
const DEFAULT_PRICING_KEY = 'sonnet'; // fallback for unrecognized model ids
|
|
30
34
|
|
|
@@ -40,6 +44,7 @@ function resolvePricingKey(modelId) {
|
|
|
40
44
|
if (id.includes('opus')) return { key: 'opus', estimated: false };
|
|
41
45
|
if (id.includes('sonnet')) return { key: 'sonnet', estimated: false };
|
|
42
46
|
if (id.includes('haiku')) return { key: 'haiku', estimated: false };
|
|
47
|
+
if (id.includes('fable')) return { key: 'fable', estimated: false };
|
|
43
48
|
return { key: DEFAULT_PRICING_KEY, estimated: true };
|
|
44
49
|
}
|
|
45
50
|
|
package/src/main/index.cjs
CHANGED
|
@@ -37,7 +37,11 @@ const epicWorktreeMint = require('./lib/epicWorktreeMint.cjs');
|
|
|
37
37
|
const epicWorktreeMerge = require('./lib/epicWorktreeMerge.cjs');
|
|
38
38
|
const epicWorktreeProjectConfig = require('./lib/epicWorktreeProjectConfig.cjs');
|
|
39
39
|
const agentLibrary = require('./agentLibrary.cjs');
|
|
40
|
-
const
|
|
40
|
+
const agentModelResolve = require('./lib/agentModelResolve.cjs');
|
|
41
|
+
const { resolveEffectiveModelInfo } = require('./lib/effectiveModelInfo.cjs');
|
|
42
|
+
const { checkDelegationReadiness, ensureGuardsInstalled, installPrdWriteGuard, installDestructiveGitGuard, installInlineImplementationGuard } = require('./lib/delegationReadiness.cjs');
|
|
43
|
+
const { resolveProjectContext } = require('./lib/projectRootResolve.cjs');
|
|
44
|
+
const { writeGuardShims } = require('./lib/guardShims.cjs');
|
|
41
45
|
const { MCP_TOOL_CATALOG, MCP_RECIPES } = require('./lib/mcpToolCatalog.cjs');
|
|
42
46
|
const { sendIfAlive } = require('./lib/sendToRenderer.cjs');
|
|
43
47
|
const { resolveBuildTarget } = require('./lib/buildTarget.cjs');
|
|
@@ -70,7 +74,7 @@ const telemetryConsent = require('./lib/telemetryConsent.cjs');
|
|
|
70
74
|
const { registerHistoryAggregatorHandlers, finalizeClosedDays, refreshIntradayToday } = require('./historyAggregator.cjs');
|
|
71
75
|
const runLogRetention = require('./lib/runLogRetention.cjs');
|
|
72
76
|
const { registerHistoryDashboardHandlers } = require('./historyDashboard.cjs');
|
|
73
|
-
const { tryAcquireLock, releaseLock, DEFAULT_LOCK_PATH } = require('
|
|
77
|
+
const { tryAcquireLock, releaseLock, DEFAULT_LOCK_PATH } = require('./lib/watchdogHelpers.cjs');
|
|
74
78
|
const schedulerConfig = require('./lib/schedulerConfig.cjs');
|
|
75
79
|
const memoryTool = require('./memoryTool.cjs');
|
|
76
80
|
const { registerMemoryAggregateIpc } = require('./memoryAggregate.cjs');
|
|
@@ -90,7 +94,8 @@ const { checkInsideHome, assertInsideHome } = require('./lib/insideHome.cjs');
|
|
|
90
94
|
const { openInEditor, openFileInEditor, openInFinder, openInTerminal } = require('./lib/openExternalApp.cjs');
|
|
91
95
|
const { rebuildActiveIndex } = require('./lib/activeIndexRebuild.cjs');
|
|
92
96
|
const { activeIndexPath: promptSessionsActiveIndexPath } = require('./lib/epicMint.cjs');
|
|
93
|
-
const { allProjectCwds } = require('
|
|
97
|
+
const { allProjectCwds } = require('./lib/activeSessions.cjs');
|
|
98
|
+
const { scheduleBootSelfHeal } = require('./lib/bootSelfHeal.cjs');
|
|
94
99
|
|
|
95
100
|
let mainWindow = null;
|
|
96
101
|
let rebooting = false;
|
|
@@ -520,12 +525,78 @@ ipcMain.handle('agents:remove-override', async (_e, payload) => {
|
|
|
520
525
|
ipcMain.handle('agents:get-persona-body', validated(schemas.agentsGetPersonaBody, (payload) =>
|
|
521
526
|
agentLibrary.getPersonaBody(payload)));
|
|
522
527
|
|
|
528
|
+
// "What will this Epic's launch actually use for --model?" — the SAME
|
|
529
|
+
// resolver chatRunner.cjs's Chat-view launch calls in-process
|
|
530
|
+
// (agentModelResolve.cjs's resolveEpicModel), reached here so Terminal's
|
|
531
|
+
// launch (EpicTerminalPane.tsx, a renderer module with no fs access) agrees
|
|
532
|
+
// with Chat by calling the identical function rather than a second one that
|
|
533
|
+
// is merely supposed to. Never throws (resolveEpicModel's own contract).
|
|
534
|
+
ipcMain.handle('agents:resolve-epic-model', validated(schemas.agentsResolveEpicModel, (payload) =>
|
|
535
|
+
agentModelResolve.resolveEpicModel(payload)));
|
|
536
|
+
|
|
537
|
+
// "What will this Epic actually run as?" (PRD: effective-model-resolver) —
|
|
538
|
+
// persona alias + evidence-based concrete model id + provenance, for a given
|
|
539
|
+
// (cwd, agentType). Read-only observer; never affects what a launch resolves.
|
|
540
|
+
ipcMain.handle('agents:resolve-model-info', validated(schemas.agentsResolveModelInfo, (payload) =>
|
|
541
|
+
resolveEffectiveModelInfo(payload)));
|
|
542
|
+
|
|
523
543
|
// "Can this project actually delegate?" probe (PRD: delegation-readiness).
|
|
524
544
|
// Structured data over the preconditions for scheduler_create_prd being in an
|
|
525
545
|
// agent's tool list at all — surfacing it in the UI is a sibling PRD.
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
546
|
+
//
|
|
547
|
+
// Self-heals BEFORE computing the checks (PRD: guards-auto-install) — Fix It
|
|
548
|
+
// used to be the only call site for the three guard installers, so every
|
|
549
|
+
// project the human opened started red and demanded three manual clicks.
|
|
550
|
+
// ensureGuardsInstalled never rejects on its own, but it is still awaited
|
|
551
|
+
// inside a try/catch here so a future change to it can never turn this into
|
|
552
|
+
// a rejected IPC handler.
|
|
553
|
+
//
|
|
554
|
+
// `cwd` is normalized to its real project root via the SAME resolver
|
|
555
|
+
// prdAdminRoutes.cjs's session_manager_help readiness route already uses
|
|
556
|
+
// (projectRootResolve.cjs's resolveProjectContext) before either the install
|
|
557
|
+
// or the read-back check runs, so a worktree/ops-internal cwd can't install
|
|
558
|
+
// into one path while the banner reads another.
|
|
559
|
+
ipcMain.handle('app:delegation-readiness', validated(schemas.delegationReadinessCwd, async (payload) => {
|
|
560
|
+
const resolved = resolveProjectContext({ cwd: payload.cwd });
|
|
561
|
+
const cwd = resolved.cwd || payload.cwd;
|
|
562
|
+
try {
|
|
563
|
+
await ensureGuardsInstalled(cwd);
|
|
564
|
+
} catch { /* auto-install must never block the readiness probe itself */ }
|
|
565
|
+
return checkDelegationReadiness({ ...payload, cwd });
|
|
566
|
+
}));
|
|
567
|
+
|
|
568
|
+
// Every guard-install attempt is logged from MAIN, not the renderer — a
|
|
569
|
+
// press that never reaches this process (e.g. a renderer talking to an
|
|
570
|
+
// older main with no handler for the channel, Electron's own "No handler
|
|
571
|
+
// registered for ..." rejection) leaves no line here, which is exactly what
|
|
572
|
+
// makes it distinguishable from a press that reached main and failed. Uses
|
|
573
|
+
// opsErrorLog.appendError (the sole writer of the `logs` ops namespace) so
|
|
574
|
+
// the record lands in the project's own
|
|
575
|
+
// session-manager-operations/logs/errors-<date>.jsonl, same shape as every
|
|
576
|
+
// other structured error line.
|
|
577
|
+
function logGuardInstallAttempt(guard, cwd, outcome) {
|
|
578
|
+
const opsErrorLog = require('./lib/opsErrorLog.cjs');
|
|
579
|
+
opsErrorLog.appendError({
|
|
580
|
+
cwd,
|
|
581
|
+
scope: 'delegationReadinessGuardInstall',
|
|
582
|
+
level: outcome.ok ? 'info' : 'error',
|
|
583
|
+
message: outcome.ok
|
|
584
|
+
? `${guard} install ${outcome.action}`
|
|
585
|
+
: `${guard} install failed: ${outcome.error || 'unknown error'}`,
|
|
586
|
+
meta: { guard, ...outcome },
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
async function handleGuardInstall(guard, installFn, payload) {
|
|
591
|
+
try {
|
|
592
|
+
const result = await installFn(payload);
|
|
593
|
+
logGuardInstallAttempt(guard, payload.cwd, result);
|
|
594
|
+
return result;
|
|
595
|
+
} catch (err) {
|
|
596
|
+
logGuardInstallAttempt(guard, payload.cwd, { ok: false, action: 'error', error: err?.message ?? String(err) });
|
|
597
|
+
throw err;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
529
600
|
|
|
530
601
|
// The three readiness checks with a sanctioned one-press install. Session
|
|
531
602
|
// Manager standardizes on the REFERENCE approach (absolute path to this
|
|
@@ -536,13 +607,13 @@ ipcMain.handle('app:delegation-readiness', validated(schemas.delegationReadiness
|
|
|
536
607
|
// header. inline-implementation-guard shares prd-write-guard's matcher, so
|
|
537
608
|
// its installer appends into that SAME matcher's `hooks` array instead.
|
|
538
609
|
ipcMain.handle('app:install-prd-write-guard', validated(schemas.delegationReadinessCwd, (payload) =>
|
|
539
|
-
installPrdWriteGuard
|
|
610
|
+
handleGuardInstall('prd-write-guard', installPrdWriteGuard, payload)
|
|
540
611
|
));
|
|
541
612
|
ipcMain.handle('app:install-destructive-git-guard', validated(schemas.delegationReadinessCwd, (payload) =>
|
|
542
|
-
installDestructiveGitGuard
|
|
613
|
+
handleGuardInstall('destructive-git-guard', installDestructiveGitGuard, payload)
|
|
543
614
|
));
|
|
544
615
|
ipcMain.handle('app:install-inline-implementation-guard', validated(schemas.delegationReadinessCwd, (payload) =>
|
|
545
|
-
installInlineImplementationGuard
|
|
616
|
+
handleGuardInstall('inline-implementation-guard', installInlineImplementationGuard, payload)
|
|
546
617
|
));
|
|
547
618
|
|
|
548
619
|
ipcMain.handle('app:engage-rules-path', () => process.env.SESSION_MANAGER_ENGAGE_RULES || null);
|
|
@@ -1274,42 +1345,22 @@ app.whenReady().then(async () => {
|
|
|
1274
1345
|
seedSchedulerMcp({ logger: console, writeLog: logs.writeLine }).catch((e) => {
|
|
1275
1346
|
logs.writeLine({ scope: 'seed-scheduler-mcp', level: 'error', message: 'seed failed', meta: { error: e?.message } });
|
|
1276
1347
|
});
|
|
1348
|
+
// Rewrite the stable guard-hook shims (~/.claude/session-manager/hooks/) to
|
|
1349
|
+
// point at THIS running app's root, on every boot. An already-installed
|
|
1350
|
+
// project hook resolves through the shim at invocation time, so an app
|
|
1351
|
+
// upgrade (a new npx hash dir) just follows along with no re-install — see
|
|
1352
|
+
// guardShims.cjs's header.
|
|
1353
|
+
writeGuardShims().catch((e) => {
|
|
1354
|
+
logs.writeLine({ scope: 'guard-shims', level: 'error', message: 'writeGuardShims failed', meta: { error: e?.message } });
|
|
1355
|
+
});
|
|
1277
1356
|
// Epic index self-heal: a project whose active-index.json is MISSING or
|
|
1278
1357
|
// fails to parse has lost every open Epic's status unless the per-Epic
|
|
1279
|
-
// status mirror (epicStatusMirror.cjs) can rebuild it. Runs once per boot
|
|
1280
|
-
//
|
|
1281
|
-
//
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
let unclean = false;
|
|
1286
|
-
let parseError = null;
|
|
1287
|
-
if (!fs.existsSync(indexPath)) {
|
|
1288
|
-
unclean = true;
|
|
1289
|
-
} else {
|
|
1290
|
-
try {
|
|
1291
|
-
JSON.parse(fs.readFileSync(indexPath, 'utf8'));
|
|
1292
|
-
} catch (e) {
|
|
1293
|
-
unclean = true;
|
|
1294
|
-
parseError = e.message;
|
|
1295
|
-
}
|
|
1296
|
-
}
|
|
1297
|
-
if (!unclean) continue;
|
|
1298
|
-
try {
|
|
1299
|
-
const { rows, skipped } = rebuildActiveIndex(projectCwd);
|
|
1300
|
-
logs.writeLine({
|
|
1301
|
-
scope: 'active-index-rebuild',
|
|
1302
|
-
level: 'info',
|
|
1303
|
-
message: 'rebuilt active-index.json from status mirrors',
|
|
1304
|
-
meta: { cwd: projectCwd, parseError, restored: rows.length, skipped: skipped.length },
|
|
1305
|
-
});
|
|
1306
|
-
} catch (e) {
|
|
1307
|
-
logs.writeLine({ scope: 'active-index-rebuild', level: 'error', message: 'rebuild failed', meta: { cwd: projectCwd, error: e?.message } });
|
|
1308
|
-
}
|
|
1309
|
-
}
|
|
1310
|
-
} catch (e) {
|
|
1311
|
-
logs.writeLine({ scope: 'active-index-rebuild', level: 'error', message: 'boot sweep failed', meta: { error: e?.message } });
|
|
1312
|
-
}
|
|
1358
|
+
// status mirror (epicStatusMirror.cjs) can rebuild it. Runs once per boot
|
|
1359
|
+
// and only touches a project whose index reads unclean — a clean index is
|
|
1360
|
+
// never rewritten. Deferred until the window paints (did-finish-load) so
|
|
1361
|
+
// the ~270ms allProjectCwds() scan doesn't compete with the renderer's
|
|
1362
|
+
// first IPC round trips for the event loop — see bootSelfHeal.cjs.
|
|
1363
|
+
scheduleBootSelfHeal(mainWindow, { fs, allProjectCwds, promptSessionsActiveIndexPath, rebuildActiveIndex, logs });
|
|
1313
1364
|
// History rollup finalize pass: deferred 30s past boot so it never competes
|
|
1314
1365
|
// with first-paint, fire-and-forget (cron/offline refresh is PRD 651 — this
|
|
1315
1366
|
// is just the one-shot in-app pass).
|
|
@@ -1340,7 +1391,7 @@ app.whenReady().then(async () => {
|
|
|
1340
1391
|
}, 30_000);
|
|
1341
1392
|
// Keep TODAY's rollup line current so the History dashboard never needs to
|
|
1342
1393
|
// fall back to a live transcript scan. Shares the same O_EXCL lock file as
|
|
1343
|
-
// the external watchdog's finalize pass (
|
|
1394
|
+
// the external watchdog's finalize pass (src/main/lib/watchdogHelpers.cjs)
|
|
1344
1395
|
// so the two never interleave writes to the rollup; a contended lock just
|
|
1345
1396
|
// means this tick's refresh is skipped (harmless — the next tick retries).
|
|
1346
1397
|
const intradayTimer = setInterval(runIntradayRefresh, schedulerConfig.HISTORY_INTRADAY_REFRESH_MS);
|
package/src/main/ipcSchemas.cjs
CHANGED
|
@@ -320,6 +320,11 @@ const PrdAgentTypeSchema = z.string().regex(PERSONA_NAME_RE, 'agent name must be
|
|
|
320
320
|
// call site since update also allows an explicit empty array to CLEAR the
|
|
321
321
|
// dependency, which create has no reason to accept.
|
|
322
322
|
const DepSlugSchema = z.string().min(1).max(160).regex(/^[A-Za-z0-9][\w.-]*$/);
|
|
323
|
+
// Wave-authoring decision (scheduler wave-disposition PRD), shared between
|
|
324
|
+
// schedulerCreatePrd (authoring time) and adminPrdFrontmatterPatch (the
|
|
325
|
+
// after-the-fact change path) so both can never disagree on what a valid
|
|
326
|
+
// disposition value is.
|
|
327
|
+
const PrdDispositionSchema = z.enum(['append', 'new-head']);
|
|
323
328
|
const schedulerCreatePrd = z.object({
|
|
324
329
|
title: z.string().min(1).max(200).regex(NO_NEWLINE_RE, 'must not contain newlines'),
|
|
325
330
|
// Optional (PRD: worktree-cwd Epic-lookup hazard) — when omitted, prdCreate.cjs's
|
|
@@ -340,6 +345,12 @@ const schedulerCreatePrd = z.object({
|
|
|
340
345
|
// Explicit ordering (PRD 832): slugs that must complete before this PRD
|
|
341
346
|
// becomes eligible. Written to frontmatter as `dependsOn: [a, b]`.
|
|
342
347
|
dependsOn: z.array(DepSlugSchema).max(20).optional(),
|
|
348
|
+
// Explicit wave-authoring decision (scheduler wave-disposition PRD) — only
|
|
349
|
+
// meaningful (and only stamped) when this PRD joins an Epic that already
|
|
350
|
+
// has incomplete PRDs and lands as a wave root (no dependsOn of its own);
|
|
351
|
+
// createPrd() requires it in that situation for an interactive caller and
|
|
352
|
+
// defaults to 'append' (logged as a default) for a non-interactive one.
|
|
353
|
+
disposition: PrdDispositionSchema.optional(),
|
|
343
354
|
// An EXISTING Epic's promptSessionId (PRD 748) — NOT a PromptTicket.id.
|
|
344
355
|
// ensureEpic() (epicMint.cjs) joins the Epic whose active-index.json
|
|
345
356
|
// sessions key literally equals this value; the renderer only ever sends
|
|
@@ -387,6 +398,19 @@ const schedulerCreatePrd = z.object({
|
|
|
387
398
|
quietMachine: z.boolean().optional(),
|
|
388
399
|
});
|
|
389
400
|
|
|
401
|
+
// Renderer-facing counterpart to adminPrdFrontmatterPatch's dependsOn/
|
|
402
|
+
// disposition patch (scheduler wave-disposition PRD) — backs the Scheduler
|
|
403
|
+
// UI's "change disposition" action. `dependsOn` is the caller's already-
|
|
404
|
+
// computed target (from the SAME backlog tree the UI renders — see
|
|
405
|
+
// lib/backlogTree.ts); the main-process handler only validates the rewrite
|
|
406
|
+
// is safe (prdDisposition.cjs), never re-derives "the" terminal itself.
|
|
407
|
+
const scheduleSetPrdDisposition = z.object({
|
|
408
|
+
slug: z.string().regex(SCHEDULE_SLUG_RE),
|
|
409
|
+
cwd: z.string().min(1).max(4096).optional(),
|
|
410
|
+
disposition: PrdDispositionSchema,
|
|
411
|
+
dependsOn: z.array(DepSlugSchema).max(20).optional(),
|
|
412
|
+
});
|
|
413
|
+
|
|
390
414
|
// Bulk archive: slug list, capped to limit unbounded retag/archive payloads.
|
|
391
415
|
// Optional cwd narrows every slug's directory search to one project (same
|
|
392
416
|
// role as scheduler_get_prd/scheduler_update_prd's cwd hint) — omitted, the
|
|
@@ -469,6 +493,11 @@ const adminPrdFrontmatterPatch = z.object({
|
|
|
469
493
|
// (depSlugResolve.cjs) scheduler_create_prd uses, so update and create
|
|
470
494
|
// can never disagree about what a dependsOn entry resolves to.
|
|
471
495
|
dependsOn: z.array(DepSlugSchema).max(20).optional(),
|
|
496
|
+
// Patchable after creation too — the Scheduler UI's "change disposition"
|
|
497
|
+
// action (promote a wave to its own head, or attach it behind another
|
|
498
|
+
// chain) rewrites this alongside dependsOn via the same
|
|
499
|
+
// schedule:set-prd-disposition path (see prdDisposition.cjs).
|
|
500
|
+
disposition: PrdDispositionSchema.optional(),
|
|
472
501
|
});
|
|
473
502
|
|
|
474
503
|
const adminUpdatePrd = z.object({
|
|
@@ -623,6 +652,21 @@ const agentsGetPersonaBody = z.object({
|
|
|
623
652
|
name: z.string().min(1).max(200),
|
|
624
653
|
}).strict();
|
|
625
654
|
|
|
655
|
+
// ──────────────────────────────────────────── Effective model info (PRD: what-will-this-run-as)
|
|
656
|
+
const agentsResolveModelInfo = z.object({
|
|
657
|
+
cwd: z.string().min(1).max(4096),
|
|
658
|
+
agentType: z.string().min(1).max(200),
|
|
659
|
+
}).strict();
|
|
660
|
+
|
|
661
|
+
// ──────────────────────────────────────────── Epic launch model (PRD: unify-epic-model-resolution)
|
|
662
|
+
// Terminal's (EpicTerminalPane.tsx) call into agentModelResolve.cjs's
|
|
663
|
+
// resolveEpicModel — the same launch-model authority chatRunner.cjs's Chat
|
|
664
|
+
// path calls in-process, reached here over IPC since the renderer has no fs.
|
|
665
|
+
const agentsResolveEpicModel = z.object({
|
|
666
|
+
cwd: z.string().min(1).max(4096),
|
|
667
|
+
claudeSessionId: z.string().min(1).max(200),
|
|
668
|
+
}).strict();
|
|
669
|
+
|
|
626
670
|
// ──────────────────────────────────────────── Project Pages (PRD 932)
|
|
627
671
|
// Same validation split as projectBriefCwd above: real path validation is
|
|
628
672
|
// config.cjs's validatePath at first fs access in projectPages.cjs.
|
|
@@ -1037,6 +1081,7 @@ module.exports = {
|
|
|
1037
1081
|
scheduleReadLog,
|
|
1038
1082
|
scheduleWritePrd,
|
|
1039
1083
|
schedulerCreatePrd,
|
|
1084
|
+
scheduleSetPrdDisposition,
|
|
1040
1085
|
scheduleArchivePrd,
|
|
1041
1086
|
scheduleRetagPrd,
|
|
1042
1087
|
adminListPrdsQuery,
|
|
@@ -1108,6 +1153,8 @@ module.exports = {
|
|
|
1108
1153
|
exchangesList,
|
|
1109
1154
|
agentsSavePersona,
|
|
1110
1155
|
agentsGetPersonaBody,
|
|
1156
|
+
agentsResolveModelInfo,
|
|
1157
|
+
agentsResolveEpicModel,
|
|
1111
1158
|
delegationReadinessCwd,
|
|
1112
1159
|
telemetrySetConfig,
|
|
1113
1160
|
},
|