claude-code-session-manager 0.104.2 → 0.105.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-BDtr4oI-.js} +1 -1
- package/dist/assets/{Button-8RAbgh1d.js → Button-CuXBCQDp.js} +1 -1
- package/dist/assets/{DataModel-BEkuOZuS.js → DataModel-DmvRipT5.js} +1 -1
- package/dist/assets/{History-BLrNSazM.js → History-B-v9pwwr.js} +1 -1
- package/dist/assets/{Hooks-C8P6wQq1.js → Hooks-S_0oHHlr.js} +1 -1
- package/dist/assets/{Library-CWFvBZw-.js → Library-BcbGJxQ3.js} +1 -1
- package/dist/assets/{ListDetail-B1jVMgzN.js → ListDetail-DtqHqJHt.js} +1 -1
- package/dist/assets/{MarkdownEditor-JrnUbWjW.js → MarkdownEditor-B56sPp_l.js} +1 -1
- package/dist/assets/{McpServers-BGqTxDBm.js → McpServers-D5VnmxAn.js} +1 -1
- package/dist/assets/{Memory-h8bhRNyI.js → Memory-0yGZLyRP.js} +1 -1
- package/dist/assets/{Panel-srf0FT0m.js → Panel-NoAADJwx.js} +1 -1
- package/dist/assets/{Permissions-BA4oWydI.js → Permissions-D2Dh-ee8.js} +1 -1
- package/dist/assets/{Plugins-DTuTQqUL.js → Plugins-kGoUgS9b.js} +2 -2
- package/dist/assets/{ProvenanceBadge-BW18EquH.js → ProvenanceBadge-CHwDFTX7.js} +1 -1
- package/dist/assets/{SaveBar-Cc6UukFa.js → SaveBar-BtKNMIYK.js} +1 -1
- package/dist/assets/{Scheduler-BctPlNL1.js → Scheduler-B0_kMMDs.js} +1 -1
- package/dist/assets/{ScopeSwitcher-DV1OnSuc.js → ScopeSwitcher-BusohWZ5.js} +1 -1
- package/dist/assets/{Settings-Bpyt1RGa.js → Settings-DuRJntaE.js} +1 -1
- package/dist/assets/{SkillReferenceGraph-B28AYo12.js → SkillReferenceGraph-BHOdIpsy.js} +1 -1
- package/dist/assets/{Skills-zXLI5OB_.js → Skills-DSTLZimI.js} +1 -1
- package/dist/assets/{SystemPrompt-DKoN0UAV.js → SystemPrompt-BG7wFWdb.js} +1 -1
- package/dist/assets/{TagLibrary-BkDOa8IM.js → TagLibrary-Bx27aE3_.js} +1 -1
- package/dist/assets/{TiptapBody-CIY1f6mI.js → TiptapBody-j0O8J7YI.js} +1 -1
- package/dist/assets/{Toggle-C139wuAK.js → Toggle-DzVZ6rC7.js} +1 -1
- package/dist/assets/{ViewTabs-CUEfsMOe.js → ViewTabs-BizjuynT.js} +1 -1
- package/dist/assets/{index-D89EUizB.css → index-BLVzrc4k.css} +1 -1
- package/dist/assets/{index-DrvxIbmR.js → index-CLIaYljt.js} +416 -416
- package/dist/assets/{settingsSchema-CAFckLz6.js → settingsSchema-7MRAxaJk.js} +1 -1
- package/dist/assets/useAgentsDirWatch-BUwfU2Kq.js +1 -0
- package/dist/assets/{useConfigDirWatch-QKxfZx9X.js → useConfigDirWatch-ot0VYwQC.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 +7 -17
- package/src/main/build-info.json +4 -4
- package/src/main/chatRunner.cjs +4 -4
- package/src/main/historyAggregator.cjs +7 -1
- package/src/main/index.cjs +34 -1
- 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 +40 -16
- package/src/main/lib/cleanEnv.cjs +29 -6
- 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/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/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/otel.cjs +18 -62
- package/src/main/pty.cjs +32 -11
- package/src/main/runVerify.cjs +42 -2
- package/src/main/scheduler.cjs +105 -20
- package/src/main/seedSchedulerMcp.cjs +11 -2
- package/src/preload/api.d.ts +14 -0
- package/src/preload/index.cjs +2 -0
- package/src/seed/agents/validator.md +2 -2
- package/dist/assets/useAgentsDirWatch-XhQEVlaK.js +0 -1
- package/scripts/postinstall.cjs +0 -52
|
@@ -9,6 +9,7 @@ const schedulerPaths = require('./schedulerPaths.cjs');
|
|
|
9
9
|
const { isRestartingMarkerActive } = require('./upgradeDrain.cjs');
|
|
10
10
|
const { pidAlive: pidAliveCore } = require('./pidAlive.cjs');
|
|
11
11
|
const atomicFs = require('./atomicFs.cjs');
|
|
12
|
+
const { isPackagedApp } = require('./appRuntime.cjs');
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
|
|
@@ -230,14 +231,32 @@ function logRelaunchLine(logPath, message) {
|
|
|
230
231
|
* (short-lived) process doesn't block on the launched app and the app
|
|
231
232
|
* survives the watchdog exiting.
|
|
232
233
|
*/
|
|
233
|
-
|
|
234
|
-
|
|
234
|
+
/**
|
|
235
|
+
* relaunchSpec(opts?) — what the watchdog spawns to bring the app back.
|
|
236
|
+
* Packaged installer builds have no npx: relaunch the app binary itself.
|
|
237
|
+
* @param {{ packaged?: boolean, execPath?: string }} [opts]
|
|
238
|
+
* @returns {{ command: string, args: string[], packaged: boolean }}
|
|
239
|
+
*/
|
|
240
|
+
function relaunchSpec(opts) {
|
|
241
|
+
if (isPackagedApp(opts)) {
|
|
242
|
+
return { command: (opts && opts.execPath) || process.execPath, args: [], packaged: true };
|
|
243
|
+
}
|
|
244
|
+
return { command: 'npx', args: ['claude-code-session-manager@latest'], packaged: false };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function defaultSpawnRelaunch({ logPath = schedulerPaths.watchdogRelaunchLogPath(), packaged, execPath, spawnFn } = {}) {
|
|
248
|
+
const spawn = spawnFn || require('node:child_process').spawn;
|
|
249
|
+
const spec = relaunchSpec({ packaged, execPath });
|
|
235
250
|
fs.mkdirSync(path.dirname(logPath), { recursive: true });
|
|
236
251
|
const fd = fs.openSync(logPath, 'a');
|
|
237
252
|
try {
|
|
238
|
-
const
|
|
253
|
+
const env = { ...process.env };
|
|
254
|
+
// The watchdog may itself run as node via the app binary; the relaunched app must not.
|
|
255
|
+
if (spec.packaged) delete env.ELECTRON_RUN_AS_NODE;
|
|
256
|
+
const child = spawn(spec.command, spec.args, {
|
|
239
257
|
detached: true,
|
|
240
258
|
stdio: ['ignore', fd, fd],
|
|
259
|
+
env,
|
|
241
260
|
});
|
|
242
261
|
child.on('error', (e) => {
|
|
243
262
|
logRelaunchLine(logPath, `spawn error: ${e?.message}`);
|
|
@@ -460,6 +479,7 @@ module.exports = {
|
|
|
460
479
|
readRelaunchState,
|
|
461
480
|
writeRelaunchState,
|
|
462
481
|
defaultSpawnRelaunch,
|
|
482
|
+
relaunchSpec,
|
|
463
483
|
maybeRelaunchApp,
|
|
464
484
|
DEFAULT_MAX_AGE_MS,
|
|
465
485
|
DEFAULT_DISPATCH_DEAD_MS,
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* winSpawn.cjs — make spawn() work for Windows .cmd/.bat shims (npx, npm) without
|
|
3
|
+
* shell:true. Node refuses to spawn those directly (EINVAL), so they are wrapped in
|
|
4
|
+
* an explicit `cmd.exe /d /s /c "<line>"` argv with every cmd metacharacter escaped.
|
|
5
|
+
* Non-win32 input passes through untouched.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
const fs = require('node:fs');
|
|
11
|
+
const path = require('node:path');
|
|
12
|
+
|
|
13
|
+
const DEFAULT_PATHEXT = '.COM;.EXE;.BAT;.CMD';
|
|
14
|
+
const META = /([()\][%!^"`<>&|;, *?])/g;
|
|
15
|
+
|
|
16
|
+
function envGet(env, name) {
|
|
17
|
+
if (env[name] !== undefined) return env[name];
|
|
18
|
+
const key = Object.keys(env).find((k) => k.toLowerCase() === name.toLowerCase());
|
|
19
|
+
return key ? env[key] : undefined;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function findOnPath(command, env, exists) {
|
|
23
|
+
const w = path.win32;
|
|
24
|
+
const exts = (envGet(env, 'PATHEXT') || DEFAULT_PATHEXT)
|
|
25
|
+
.split(';').filter(Boolean).map((e) => e.toLowerCase());
|
|
26
|
+
const hasDir = /[\\/]/.test(command);
|
|
27
|
+
const dirs = hasDir ? [''] : (envGet(env, 'PATH') || '').split(';').filter(Boolean);
|
|
28
|
+
const hasExt = exts.includes(w.extname(command).toLowerCase());
|
|
29
|
+
for (const dir of dirs) {
|
|
30
|
+
const base = dir ? w.join(dir, command) : command;
|
|
31
|
+
const candidates = hasExt ? [base] : [];
|
|
32
|
+
for (const e of exts) candidates.push(base + e);
|
|
33
|
+
// PATHEXT order is by extension; keep the casing the filesystem probe accepts.
|
|
34
|
+
for (const cand of candidates) {
|
|
35
|
+
if (exists(cand)) return cand;
|
|
36
|
+
const upper = cand.slice(0, cand.length - w.extname(cand).length) + w.extname(cand).toUpperCase();
|
|
37
|
+
if (upper !== cand && exists(upper)) return upper;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Quote for CreateProcess argv parsing, then ^-escape every cmd metacharacter.
|
|
44
|
+
function quoteArg(arg) {
|
|
45
|
+
let s = String(arg);
|
|
46
|
+
s = s.replace(/(\\*)"/g, '$1$1\\"').replace(/(\\*)$/, '$1$1');
|
|
47
|
+
s = `"${s}"`;
|
|
48
|
+
return s.replace(META, '^$1');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function resolveSpawn(command, args = [], { platform = process.platform, env = process.env, exists = fs.existsSync } = {}) {
|
|
52
|
+
if (platform !== 'win32') return { command, args };
|
|
53
|
+
const resolved = findOnPath(command, env, exists);
|
|
54
|
+
if (!resolved) return { command, args };
|
|
55
|
+
const ext = path.win32.extname(resolved).toLowerCase();
|
|
56
|
+
if (ext === '.cmd' || ext === '.bat') {
|
|
57
|
+
const line = [resolved, ...args].map(quoteArg).join(' ');
|
|
58
|
+
return {
|
|
59
|
+
command: envGet(env, 'ComSpec') || 'cmd.exe',
|
|
60
|
+
args: ['/d', '/s', '/c', `"${line}"`],
|
|
61
|
+
windowsVerbatimArguments: true,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
return { command: resolved, args };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
module.exports = { resolveSpawn };
|
package/src/main/otel.cjs
CHANGED
|
@@ -8,58 +8,36 @@
|
|
|
8
8
|
* plan text, and agent prompts are excluded unless `includeContent` is on
|
|
9
9
|
* (matches upstream OTEL_LOG_USER_PROMPTS opt-in).
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* Threading: the BatchSpanProcessor batches/sends spans on its own timer; we
|
|
16
|
-
* never block the transcript flush.
|
|
11
|
+
* Spans are POSTed as OTLP/HTTP JSON by lib/otlpTraceExporter.cjs (zero
|
|
12
|
+
* dependencies). recordSpan() only queues; the exporter batches/sends on its
|
|
13
|
+
* own unref()'d timer, so we never block the transcript flush. The app must
|
|
14
|
+
* keep working without telemetry: a failed export is surfaced, never thrown.
|
|
17
15
|
*/
|
|
18
16
|
|
|
19
17
|
const otelSettings = require('./otelSettings.cjs');
|
|
18
|
+
const { createOtlpTraceExporter } = require('./lib/otlpTraceExporter.cjs');
|
|
20
19
|
|
|
21
|
-
let
|
|
22
|
-
let tracer = null;
|
|
20
|
+
let exporter = null;
|
|
23
21
|
let enabled = false;
|
|
24
22
|
let includeContent = false;
|
|
25
23
|
let lastError = null;
|
|
26
24
|
let initialized = false;
|
|
27
25
|
|
|
28
26
|
/**
|
|
29
|
-
*
|
|
30
|
-
* We catch the per-require so a partial install doesn't half-init.
|
|
31
|
-
*/
|
|
32
|
-
function loadDeps() {
|
|
33
|
-
try {
|
|
34
|
-
const api = require('@opentelemetry/api');
|
|
35
|
-
const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node');
|
|
36
|
-
const { BatchSpanProcessor } = require('@opentelemetry/sdk-trace-base');
|
|
37
|
-
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http');
|
|
38
|
-
const { resourceFromAttributes } = require('@opentelemetry/resources');
|
|
39
|
-
const { ATTR_SERVICE_NAME } = require('@opentelemetry/semantic-conventions');
|
|
40
|
-
return { api, NodeTracerProvider, BatchSpanProcessor, OTLPTraceExporter, resourceFromAttributes, ATTR_SERVICE_NAME };
|
|
41
|
-
} catch (err) {
|
|
42
|
-
return { error: err };
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Tear down any existing provider. Best-effort — flushes pending spans on a
|
|
27
|
+
* Tear down any existing exporter. Best-effort — flushes pending spans on a
|
|
48
28
|
* 2s ceiling so a wedged exporter doesn't block app shutdown / config reload.
|
|
49
29
|
*/
|
|
50
30
|
async function shutdown() {
|
|
51
|
-
if (!
|
|
31
|
+
if (!exporter) {
|
|
52
32
|
enabled = false;
|
|
53
|
-
tracer = null;
|
|
54
33
|
return;
|
|
55
34
|
}
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
tracer = null;
|
|
35
|
+
const e = exporter;
|
|
36
|
+
exporter = null;
|
|
59
37
|
enabled = false;
|
|
60
38
|
try {
|
|
61
39
|
await Promise.race([
|
|
62
|
-
|
|
40
|
+
e.shutdown(),
|
|
63
41
|
new Promise((resolve) => setTimeout(resolve, 2000)),
|
|
64
42
|
]);
|
|
65
43
|
} catch (err) {
|
|
@@ -68,7 +46,7 @@ async function shutdown() {
|
|
|
68
46
|
}
|
|
69
47
|
|
|
70
48
|
/**
|
|
71
|
-
* Configure the
|
|
49
|
+
* Configure the exporter. Tears down any prior exporter first so a
|
|
72
50
|
* settings change reliably re-points at the new endpoint.
|
|
73
51
|
*
|
|
74
52
|
* Returns { ok, error?: string } — the renderer surfaces `error` in the UI.
|
|
@@ -77,31 +55,13 @@ async function init({ endpoint, headers, serviceName, includeContent: ic } = {})
|
|
|
77
55
|
await shutdown();
|
|
78
56
|
initialized = true;
|
|
79
57
|
|
|
80
|
-
const deps = loadDeps();
|
|
81
|
-
if (deps.error) {
|
|
82
|
-
lastError = `OTEL packages unavailable: ${deps.error.message}`;
|
|
83
|
-
return { ok: false, error: lastError };
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const { NodeTracerProvider, BatchSpanProcessor, OTLPTraceExporter, resourceFromAttributes, ATTR_SERVICE_NAME } = deps;
|
|
87
|
-
|
|
88
58
|
try {
|
|
89
|
-
|
|
59
|
+
exporter = createOtlpTraceExporter({
|
|
90
60
|
url: String(endpoint || 'http://localhost:4318/v1/traces'),
|
|
91
61
|
headers: typeof headers === 'object' && headers ? headers : {},
|
|
62
|
+
serviceName: String(serviceName || 'session-manager'),
|
|
63
|
+
scopeName: 'session-manager-transcripts',
|
|
92
64
|
});
|
|
93
|
-
const tp = new NodeTracerProvider({
|
|
94
|
-
resource: resourceFromAttributes({
|
|
95
|
-
[ATTR_SERVICE_NAME]: String(serviceName || 'session-manager'),
|
|
96
|
-
}),
|
|
97
|
-
spanProcessors: [new BatchSpanProcessor(exporter)],
|
|
98
|
-
});
|
|
99
|
-
// Note: we deliberately do NOT call tp.register() — that installs a global
|
|
100
|
-
// tracer provider, which would intercept any other OTEL user in the
|
|
101
|
-
// process. We only want to emit our own spans, so we use the provider
|
|
102
|
-
// directly via getTracer().
|
|
103
|
-
provider = tp;
|
|
104
|
-
tracer = tp.getTracer('session-manager-transcripts');
|
|
105
65
|
includeContent = !!ic;
|
|
106
66
|
enabled = true;
|
|
107
67
|
lastError = null;
|
|
@@ -205,11 +165,11 @@ function pruneAttrs(obj) {
|
|
|
205
165
|
|
|
206
166
|
/**
|
|
207
167
|
* Emit a 0-duration span for one classified transcript event.
|
|
208
|
-
* Synchronous + non-blocking: the
|
|
168
|
+
* Synchronous + non-blocking: the exporter handles network I/O
|
|
209
169
|
* on its own schedule.
|
|
210
170
|
*/
|
|
211
171
|
function recordTranscriptEvent({ tabId, tabCwd, kind, data, ts }) {
|
|
212
|
-
if (!enabled || !
|
|
172
|
+
if (!enabled || !exporter) return;
|
|
213
173
|
try {
|
|
214
174
|
const startTime = typeof ts === 'number' ? ts : Date.now();
|
|
215
175
|
const attrs = pruneAttrs({
|
|
@@ -218,11 +178,7 @@ function recordTranscriptEvent({ tabId, tabCwd, kind, data, ts }) {
|
|
|
218
178
|
kind,
|
|
219
179
|
...attrsFor(kind, data),
|
|
220
180
|
});
|
|
221
|
-
|
|
222
|
-
startTime,
|
|
223
|
-
attributes: attrs,
|
|
224
|
-
});
|
|
225
|
-
span.end(startTime);
|
|
181
|
+
exporter.recordSpan({ name: `transcript.${kind}`, timeMs: startTime, attributes: attrs });
|
|
226
182
|
} catch (err) {
|
|
227
183
|
// A failure here must never break transcript ingestion. Log once-ish.
|
|
228
184
|
if (!recordTranscriptEvent._warned) {
|
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
|
}
|
|
@@ -6302,10 +6348,10 @@ async function executeJob(job, runDir, defaultCwd, onPid, execCwd, resumeTarget
|
|
|
6302
6348
|
...(jobSpawn.argv0 ? { argv0: jobSpawn.argv0 } : {}),
|
|
6303
6349
|
// detached:true puts the child in its own process group so we can kill
|
|
6304
6350
|
// the entire descendant tree (including any stray background bashes the
|
|
6305
|
-
// agent spawned) with
|
|
6351
|
+
// agent spawned) with killTree(pid). Without this, child.kill()
|
|
6306
6352
|
// only kills the immediate `claude` process, leaving orphaned subprocs
|
|
6307
6353
|
// that keep the parent alive (the 2026-05-10 cellar-publish hang).
|
|
6308
|
-
|
|
6354
|
+
...detachedSpawnOpts(),
|
|
6309
6355
|
},
|
|
6310
6356
|
},
|
|
6311
6357
|
watchdogs: [resultTailWatchdog, deadmanWatchdog, idleTailWatchdog, budgetWatchdog],
|
|
@@ -7249,6 +7295,7 @@ async function finalizeJobWorktree({ job, runDir, worktree, guardCwd, carriedPat
|
|
|
7249
7295
|
let worktreeIntegrationDetail = null;
|
|
7250
7296
|
let mergeAutoResolved = null;
|
|
7251
7297
|
let mergeAutoResolvedPaths = null;
|
|
7298
|
+
let integratedSha = null;
|
|
7252
7299
|
try {
|
|
7253
7300
|
worktreeLeftoverDirty = (await uncommitted(worktree.dir)) || [];
|
|
7254
7301
|
// Salvage the worktree's full diff (tracked + untracked) to the run
|
|
@@ -7273,6 +7320,7 @@ async function finalizeJobWorktree({ job, runDir, worktree, guardCwd, carriedPat
|
|
|
7273
7320
|
worktreeIntegrationDetail = integration;
|
|
7274
7321
|
console.error(`[scheduler] ${job.slug}: worktree branch integration FAILED (${integration.reason}) — branch ${worktree.branch} preserved in ${guardCwd} for manual recovery`);
|
|
7275
7322
|
} else if (integration.integrated) {
|
|
7323
|
+
integratedSha = integration.sha || null;
|
|
7276
7324
|
console.log(`[scheduler] ${job.slug}: worktree branch ${worktree.branch} integrated into ${guardCwd}${integration.mergeCommit ? ' (merge commit)' : ' (fast-forward)'}`);
|
|
7277
7325
|
if (integration.autoResolved) {
|
|
7278
7326
|
mergeAutoResolved = integration.autoResolved;
|
|
@@ -7294,7 +7342,7 @@ async function finalizeJobWorktree({ job, runDir, worktree, guardCwd, carriedPat
|
|
|
7294
7342
|
await jw.cleanupJobWorktree({ cwd: guardCwd, dir: worktree.dir, branch: worktree.branch, keepBranch: true });
|
|
7295
7343
|
} catch { /* already reported above */ }
|
|
7296
7344
|
}
|
|
7297
|
-
return { worktreeLeftoverDirty, salvagePatch, worktreeIntegrationFailure, worktreeIntegrationDetail, mergeAutoResolved, mergeAutoResolvedPaths };
|
|
7345
|
+
return { worktreeLeftoverDirty, salvagePatch, worktreeIntegrationFailure, worktreeIntegrationDetail, mergeAutoResolved, mergeAutoResolvedPaths, integratedSha };
|
|
7298
7346
|
}
|
|
7299
7347
|
|
|
7300
7348
|
/**
|
|
@@ -7686,6 +7734,7 @@ async function spawnJob(job, runId, runDir, defaultCwd, resumeTarget = null, sib
|
|
|
7686
7734
|
// self-healed rather than silently looking like an ordinary merge.
|
|
7687
7735
|
let mergeAutoResolved = null;
|
|
7688
7736
|
let mergeAutoResolvedPaths = null;
|
|
7737
|
+
let integratedSha = null;
|
|
7689
7738
|
// A job's uncommitted-work patch, whichever isolation mode produced it —
|
|
7690
7739
|
// set by EITHER branch below, never both (worktree.ok picks exactly one
|
|
7691
7740
|
// shape for the whole run). Named generically (not "worktree...") because
|
|
@@ -7721,7 +7770,7 @@ async function spawnJob(job, runId, runDir, defaultCwd, resumeTarget = null, sib
|
|
|
7721
7770
|
}, reviewInRun);
|
|
7722
7771
|
} finally {
|
|
7723
7772
|
if (worktree.ok) {
|
|
7724
|
-
({ worktreeLeftoverDirty, salvagePatch, worktreeIntegrationFailure, worktreeIntegrationDetail, mergeAutoResolved, mergeAutoResolvedPaths } =
|
|
7773
|
+
({ worktreeLeftoverDirty, salvagePatch, worktreeIntegrationFailure, worktreeIntegrationDetail, mergeAutoResolved, mergeAutoResolvedPaths, integratedSha } =
|
|
7725
7774
|
await finalizeJobWorktree({ job, runDir, worktree, guardCwd, carriedPaths }));
|
|
7726
7775
|
} else {
|
|
7727
7776
|
// In-place run (non-git cwd, cap reached, env-disabled, or a carry-over
|
|
@@ -7873,15 +7922,50 @@ async function spawnJob(job, runId, runDir, defaultCwd, resumeTarget = null, sib
|
|
|
7873
7922
|
// Used by the sentinel override: SCHEDULER_VERDICT: PASS + a landed
|
|
7874
7923
|
// commit together override incidental transcript noise verdicts.
|
|
7875
7924
|
const headAtExit = await gitHead(guardCwd);
|
|
7876
|
-
|
|
7877
|
-
|
|
7878
|
-
|
|
7925
|
+
// Worktree runs: attribution comes only from this run's own integration
|
|
7926
|
+
// (a concurrent job's commit moving shared HEAD must not count).
|
|
7927
|
+
const committedInWindow = worktree.ok
|
|
7928
|
+
? false
|
|
7929
|
+
: await computeCommittedDuringRun(
|
|
7930
|
+
guardCwd,
|
|
7931
|
+
guardHeadBefore,
|
|
7932
|
+
headAtExit,
|
|
7933
|
+
job.startedAt,
|
|
7934
|
+
new Date().toISOString(),
|
|
7935
|
+
);
|
|
7936
|
+
let rangeCommits = [];
|
|
7937
|
+
let transcriptCommitShas = [];
|
|
7938
|
+
let siblingOverlap = false;
|
|
7939
|
+
if (!worktree.ok) {
|
|
7940
|
+
rangeCommits = await gitRangeCommits(guardCwd, guardHeadBefore, headAtExit);
|
|
7941
|
+
transcriptCommitShas = parseLog(path.join(runDir, `${job.slug}.log`)).transcriptCommitShas || [];
|
|
7942
|
+
const overlapState = await readQueue().catch(() => ({ jobs: [] }));
|
|
7943
|
+
const myStart = Date.parse(job.startedAt) || 0;
|
|
7944
|
+
const myEnd = Date.now();
|
|
7945
|
+
siblingOverlap = (overlapState.jobs || []).some((j) => {
|
|
7946
|
+
if (j.slug === job.slug || (j.cwd || defaultCwd) !== guardCwd) return false;
|
|
7947
|
+
if (j.status === 'running') return true;
|
|
7948
|
+
const jStart = Date.parse(j.startedAt);
|
|
7949
|
+
const jEnd = Date.parse(j.finishedAt);
|
|
7950
|
+
if (!Number.isFinite(jStart) && !Number.isFinite(jEnd)) return false;
|
|
7951
|
+
const s0 = Number.isFinite(jStart) ? jStart : jEnd;
|
|
7952
|
+
const e0 = Number.isFinite(jEnd) ? jEnd : jStart;
|
|
7953
|
+
return s0 <= myEnd && e0 >= myStart;
|
|
7954
|
+
});
|
|
7955
|
+
}
|
|
7956
|
+
const attribution = resolveRunCommitAttribution({
|
|
7957
|
+
ranInWorktree: Boolean(worktree.ok),
|
|
7958
|
+
integratedSha,
|
|
7959
|
+
headBefore: guardHeadBefore,
|
|
7879
7960
|
headAtExit,
|
|
7880
|
-
|
|
7881
|
-
|
|
7882
|
-
|
|
7883
|
-
|
|
7884
|
-
|
|
7961
|
+
committedInWindow,
|
|
7962
|
+
rangeCommits,
|
|
7963
|
+
transcriptCommitShas,
|
|
7964
|
+
siblingOverlap,
|
|
7965
|
+
});
|
|
7966
|
+
const committedDuringRun = attribution.committedDuringRun;
|
|
7967
|
+
if (attribution.landedCommit) {
|
|
7968
|
+
jobLandedCommitThisRun = attribution.landedCommit;
|
|
7885
7969
|
}
|
|
7886
7970
|
|
|
7887
7971
|
const prdPath = (await resolveVerifyPrdPath(job)) ?? archivedPrdPathForJob(job);
|
|
@@ -7955,7 +8039,9 @@ async function spawnJob(job, runId, runDir, defaultCwd, resumeTarget = null, sib
|
|
|
7955
8039
|
(j) => j.slug !== job.slug && j.status === 'running' && (j.cwd || defaultCwd) === guardCwd,
|
|
7956
8040
|
);
|
|
7957
8041
|
const guardHeadAfter = await gitHead(guardCwd);
|
|
7958
|
-
const jobSelfCommitted =
|
|
8042
|
+
const jobSelfCommitted = worktree.ok
|
|
8043
|
+
? Boolean(jobLandedCommitThisRun)
|
|
8044
|
+
: guardHeadBefore && guardHeadAfter && guardHeadAfter !== guardHeadBefore;
|
|
7959
8045
|
const guardVerdict = commitGuardVerdict({
|
|
7960
8046
|
newlyDirty,
|
|
7961
8047
|
siblingRunning,
|
|
@@ -9703,9 +9789,7 @@ function bootRowPid(j, logPathOf) {
|
|
|
9703
9789
|
}
|
|
9704
9790
|
|
|
9705
9791
|
function signalAdoptedGroup(pgid, signal, pid) {
|
|
9706
|
-
|
|
9707
|
-
try { process.kill(pid, signal); } catch { /* already dead */ }
|
|
9708
|
-
}
|
|
9792
|
+
if (!killTree(pgid, signal)) killTree(pid, signal);
|
|
9709
9793
|
}
|
|
9710
9794
|
|
|
9711
9795
|
async function superviseAdoptedRunsPass(jobs) {
|
|
@@ -13595,6 +13679,7 @@ function registerAdminRoutes(adminHttp, remoteObj = remote) {
|
|
|
13595
13679
|
module.exports = {
|
|
13596
13680
|
reportSchedulerError,
|
|
13597
13681
|
finalizeJobWorktree,
|
|
13682
|
+
resolveRunCommitAttribution,
|
|
13598
13683
|
worktreeIntegrationVerdict,
|
|
13599
13684
|
guardedTick,
|
|
13600
13685
|
rescheduleIntervalTick,
|