claude-code-session-manager 0.104.2 → 0.106.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -3
- package/bin/cli.cjs +0 -5
- package/dist/assets/{AgentLibrary-QPw9gC_o.js → AgentLibrary-CKOZzc2W.js} +1 -1
- package/dist/assets/{Button-8RAbgh1d.js → Button-yuqFpQsK.js} +1 -1
- package/dist/assets/{DataModel-BEkuOZuS.js → DataModel-Bwdc_gaJ.js} +1 -1
- package/dist/assets/{History-BLrNSazM.js → History-D1Z0lydo.js} +1 -1
- package/dist/assets/{Hooks-C8P6wQq1.js → Hooks-CyD6l4Is.js} +1 -1
- package/dist/assets/{Library-CWFvBZw-.js → Library-fnod5cPe.js} +1 -1
- package/dist/assets/{ListDetail-B1jVMgzN.js → ListDetail-BKO68SJ4.js} +1 -1
- package/dist/assets/{MarkdownEditor-JrnUbWjW.js → MarkdownEditor-DrYMnMBy.js} +1 -1
- package/dist/assets/{McpServers-BGqTxDBm.js → McpServers-CgfItWhF.js} +1 -1
- package/dist/assets/{Memory-h8bhRNyI.js → Memory-4M3gO3_r.js} +1 -1
- package/dist/assets/{Panel-srf0FT0m.js → Panel-CKbXo-LT.js} +1 -1
- package/dist/assets/{Permissions-BA4oWydI.js → Permissions-Bc9EpjXL.js} +1 -1
- package/dist/assets/{Plugins-DTuTQqUL.js → Plugins-B_-PlEIx.js} +2 -2
- package/dist/assets/{ProvenanceBadge-BW18EquH.js → ProvenanceBadge-Cxev7XSA.js} +1 -1
- package/dist/assets/{SaveBar-Cc6UukFa.js → SaveBar-DiqscG6q.js} +1 -1
- package/dist/assets/{Scheduler-BctPlNL1.js → Scheduler-CCcJzuu8.js} +1 -1
- package/dist/assets/{ScopeSwitcher-DV1OnSuc.js → ScopeSwitcher-BsM4-jHQ.js} +1 -1
- package/dist/assets/{Settings-Bpyt1RGa.js → Settings-C3-VL-T8.js} +1 -1
- package/dist/assets/{SkillReferenceGraph-B28AYo12.js → SkillReferenceGraph-B9YlOuPo.js} +1 -1
- package/dist/assets/{Skills-zXLI5OB_.js → Skills-Dtf-z_zN.js} +1 -1
- package/dist/assets/{SystemPrompt-DKoN0UAV.js → SystemPrompt-HJDn0E8C.js} +1 -1
- package/dist/assets/{TagLibrary-BkDOa8IM.js → TagLibrary-DLGOfauU.js} +1 -1
- package/dist/assets/{TiptapBody-CIY1f6mI.js → TiptapBody-Cdmv1CuM.js} +1 -1
- package/dist/assets/{Toggle-C139wuAK.js → Toggle-CQFhOo1F.js} +1 -1
- package/dist/assets/{ViewTabs-CUEfsMOe.js → ViewTabs-CxjDCHjH.js} +1 -1
- package/dist/assets/index-Blo38vfX.js +3077 -0
- package/dist/assets/{index-D89EUizB.css → index-DkJv4l6L.css} +1 -1
- package/dist/assets/{settingsSchema-CAFckLz6.js → settingsSchema-B0uoL_1R.js} +1 -1
- package/dist/assets/useAgentsDirWatch-e3TJUFPP.js +1 -0
- package/dist/assets/{useConfigDirWatch-QKxfZx9X.js → useConfigDirWatch-Dxv7JbCU.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +10 -12
- package/plugins/session-manager-dev/skills/builder/3-publish/SKILL.md +12 -0
- package/plugins/session-manager-dev/skills/develop/SKILL.md +4 -2
- package/scripts/scheduler-mcp-server.cjs +51 -17
- package/src/main/build-info.json +4 -4
- package/src/main/chatRunner.cjs +8 -6
- package/src/main/crashDiagnostics.cjs +39 -3
- package/src/main/historyAggregator.cjs +7 -1
- package/src/main/index.cjs +73 -4
- package/src/main/ipcSchemas.cjs +27 -0
- package/src/main/lib/agentPersonaSchema.cjs +8 -1
- package/src/main/lib/appRuntime.cjs +57 -0
- package/src/main/lib/childWithLog.cjs +10 -10
- package/src/main/lib/claudeBin.cjs +58 -20
- package/src/main/lib/cleanEnv.cjs +33 -9
- package/src/main/lib/customerFeedbackAdminRoutes.cjs +94 -0
- package/src/main/lib/customerFeedbackClient.cjs +261 -0
- package/src/main/lib/customerFeedbackInbox.cjs +286 -0
- package/src/main/lib/customerFeedbackIpc.cjs +43 -0
- package/src/main/lib/defaultShell.cjs +36 -0
- package/src/main/lib/definitionOfDone.cjs +6 -2
- package/src/main/lib/delegationReadiness.cjs +13 -5
- package/src/main/lib/gitWorktree.cjs +6 -3
- package/src/main/lib/insideHome.cjs +15 -4
- package/src/main/lib/killTree.cjs +80 -0
- package/src/main/lib/loadGate.cjs +16 -0
- package/src/main/lib/loginShellPath.cjs +82 -0
- package/src/main/lib/machineProfile.cjs +4 -2
- package/src/main/lib/mcpStdioServer.cjs +127 -0
- package/src/main/lib/mcpToolCatalog.cjs +26 -1
- package/src/main/lib/openExternalApp.cjs +62 -11
- package/src/main/lib/otlpTraceExporter.cjs +147 -0
- package/src/main/lib/prereqs.cjs +145 -0
- package/src/main/lib/procIdentity.cjs +37 -2
- package/src/main/lib/projectFolder.cjs +89 -0
- package/src/main/lib/rcaReport.cjs +1 -0
- package/src/main/lib/reaperHelpers.cjs +5 -2
- package/src/main/lib/shippedPersonaSeeds.cjs +10 -0
- package/src/main/lib/timeoutShimScript.cjs +14 -2
- package/src/main/lib/updateCheck.cjs +41 -11
- package/src/main/lib/watchdogHelpers.cjs +23 -3
- package/src/main/lib/winSpawn.cjs +67 -0
- package/src/main/lib/winUserData.cjs +22 -0
- package/src/main/otel.cjs +18 -62
- package/src/main/pty.cjs +32 -11
- package/src/main/runVerify.cjs +42 -2
- package/src/main/scheduler.cjs +116 -29
- package/src/main/seedSchedulerMcp.cjs +11 -2
- package/src/preload/api.d.ts +82 -0
- package/src/preload/index.cjs +14 -0
- package/src/seed/agents/validator.md +2 -2
- package/dist/assets/index-DrvxIbmR.js +0 -3073
- package/dist/assets/useAgentsDirWatch-XhQEVlaK.js +0 -1
- package/scripts/postinstall.cjs +0 -52
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
*
|
|
17
17
|
* macro_list({ cwd? }) -> GET /admin/macros?cwd=<encoded>
|
|
18
18
|
* macro_save({ cwd?, id?, label, agentName, tag, prompt }) -> POST /admin/macros/save
|
|
19
|
+
* customer_feedback_list({ pull?, includeHidden? }) -> GET /admin/customer-feedback/inbox
|
|
20
|
+
* customer_feedback_set_status({ id, status, note? }) -> POST /admin/customer-feedback/status
|
|
19
21
|
*
|
|
20
22
|
* This is a separate process from the Electron app — it only ever reaches
|
|
21
23
|
* it over the token-authed loopback HTTP API in admin-api.json, never by
|
|
@@ -30,12 +32,7 @@ const { PROC_NAMES, setProcessTitle } = require('../src/main/lib/smProcNames.cjs
|
|
|
30
32
|
setProcessTitle(PROC_NAMES.mcpServer);
|
|
31
33
|
const path = require('node:path');
|
|
32
34
|
const fsp = require('node:fs/promises');
|
|
33
|
-
const {
|
|
34
|
-
const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');
|
|
35
|
-
const {
|
|
36
|
-
ListToolsRequestSchema,
|
|
37
|
-
CallToolRequestSchema,
|
|
38
|
-
} = require('@modelcontextprotocol/sdk/types.js');
|
|
35
|
+
const { createStdioServer } = require('../src/main/lib/mcpStdioServer.cjs');
|
|
39
36
|
const { PRD_WORK_TYPES } = require('../src/main/lib/workTypeLibrary.cjs');
|
|
40
37
|
const { MCP_TOOL_CATALOG, MCP_RECIPES, composeDescription } = require('../src/main/lib/mcpToolCatalog.cjs');
|
|
41
38
|
|
|
@@ -424,6 +421,30 @@ const TOOLS = [
|
|
|
424
421
|
required: ['label', 'agentName', 'tag', 'prompt'],
|
|
425
422
|
},
|
|
426
423
|
},
|
|
424
|
+
{
|
|
425
|
+
name: 'customer_feedback_list',
|
|
426
|
+
description: descriptionFor('customer_feedback_list'),
|
|
427
|
+
inputSchema: {
|
|
428
|
+
type: 'object',
|
|
429
|
+
properties: {
|
|
430
|
+
pull: { type: 'boolean', description: 'Fetch new items from the feedback service before listing. Default true.' },
|
|
431
|
+
includeHidden: { type: 'boolean', description: 'Include moderated-away items. Default false.' },
|
|
432
|
+
},
|
|
433
|
+
},
|
|
434
|
+
},
|
|
435
|
+
{
|
|
436
|
+
name: 'customer_feedback_set_status',
|
|
437
|
+
description: descriptionFor('customer_feedback_set_status'),
|
|
438
|
+
inputSchema: {
|
|
439
|
+
type: 'object',
|
|
440
|
+
properties: {
|
|
441
|
+
id: { type: 'string', description: 'Feedback item id (from customer_feedback_list).' },
|
|
442
|
+
status: { type: 'string', enum: ['open', 'in_progress', 'resolved', 'wontfix'], description: 'New status.' },
|
|
443
|
+
note: { type: 'string', description: 'Optional short note shown to the customer.' },
|
|
444
|
+
},
|
|
445
|
+
required: ['id', 'status'],
|
|
446
|
+
},
|
|
447
|
+
},
|
|
427
448
|
{
|
|
428
449
|
name: 'session_manager_help',
|
|
429
450
|
description: descriptionFor('session_manager_help'),
|
|
@@ -437,13 +458,6 @@ const TOOLS = [
|
|
|
437
458
|
},
|
|
438
459
|
];
|
|
439
460
|
|
|
440
|
-
const server = new Server(
|
|
441
|
-
{ name: 'session-manager-scheduler', version: '1.0.0' },
|
|
442
|
-
{ capabilities: { tools: {} } }
|
|
443
|
-
);
|
|
444
|
-
|
|
445
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
|
|
446
|
-
|
|
447
461
|
// Named + exported (see module.exports below) so mcpToolCatalog.test.cjs's
|
|
448
462
|
// sibling can exercise session_manager_help's argument handling directly —
|
|
449
463
|
// mocking node:fs/promises + global.fetch to drive adminRequest — without
|
|
@@ -674,6 +688,24 @@ async function handleCallTool(request) {
|
|
|
674
688
|
const result = await adminRequest('POST', '/admin/macros/save', payload);
|
|
675
689
|
return { content: [{ type: 'text', text: JSON.stringify(result) }] };
|
|
676
690
|
}
|
|
691
|
+
if (name === 'customer_feedback_list') {
|
|
692
|
+
const qs = new URLSearchParams();
|
|
693
|
+
if (!args || args.pull !== false) qs.set('pull', '1');
|
|
694
|
+
if (args && args.includeHidden === true) qs.set('includeHidden', '1');
|
|
695
|
+
const result = await adminRequest('GET', `/admin/customer-feedback/inbox?${qs.toString()}`);
|
|
696
|
+
if (result?.ok === false) return errorResult(result.error ?? 'customer_feedback_list failed');
|
|
697
|
+
return { content: [{ type: 'text', text: JSON.stringify(result) }] };
|
|
698
|
+
}
|
|
699
|
+
if (name === 'customer_feedback_set_status') {
|
|
700
|
+
if (!args || typeof args.id !== 'string' || !args.id || typeof args.status !== 'string') {
|
|
701
|
+
return errorResult('missing or invalid required argument(s): id, status');
|
|
702
|
+
}
|
|
703
|
+
const payload = { id: args.id, status: args.status };
|
|
704
|
+
if (typeof args.note === 'string' && args.note) payload.note = args.note;
|
|
705
|
+
const result = await adminRequest('POST', '/admin/customer-feedback/status', payload);
|
|
706
|
+
if (result?.ok === false) return errorResult(result.error ?? 'customer_feedback_set_status failed');
|
|
707
|
+
return { content: [{ type: 'text', text: JSON.stringify(result) }] };
|
|
708
|
+
}
|
|
677
709
|
if (name === 'session_manager_help') {
|
|
678
710
|
const toolName = args && typeof args.tool === 'string' ? args.tool : null;
|
|
679
711
|
const topic = args && typeof args.topic === 'string' ? args.topic : null;
|
|
@@ -713,11 +745,13 @@ async function handleCallTool(request) {
|
|
|
713
745
|
}
|
|
714
746
|
}
|
|
715
747
|
|
|
716
|
-
server.setRequestHandler(CallToolRequestSchema, handleCallTool);
|
|
717
|
-
|
|
718
748
|
async function main() {
|
|
719
|
-
|
|
720
|
-
|
|
749
|
+
createStdioServer({
|
|
750
|
+
name: 'session-manager-scheduler',
|
|
751
|
+
version: '1.0.0',
|
|
752
|
+
listTools: () => TOOLS,
|
|
753
|
+
callTool: (params) => handleCallTool({ params }),
|
|
754
|
+
}).start();
|
|
721
755
|
}
|
|
722
756
|
|
|
723
757
|
// Guarded so mcpToolCatalog.test.cjs can `require()` this file to read TOOLS
|
package/src/main/build-info.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.
|
|
3
|
-
"gitSha": "
|
|
4
|
-
"gitShortSha": "
|
|
2
|
+
"version": "0.106.0",
|
|
3
|
+
"gitSha": "6b6a18cb35bd060a6fa46a6645068a8768c9ff34",
|
|
4
|
+
"gitShortSha": "6b6a18cb",
|
|
5
5
|
"gitBranch": "HEAD",
|
|
6
|
-
"builtAt": "2026-10-
|
|
6
|
+
"builtAt": "2026-10-09T03:16:51.117Z",
|
|
7
7
|
"dirty": false
|
|
8
8
|
}
|
package/src/main/chatRunner.cjs
CHANGED
|
@@ -52,6 +52,7 @@ const { extractJson } = require('./lib/extractJson.cjs');
|
|
|
52
52
|
const { classifyPromptTicket } = require('./lib/classifyPromptTicket.cjs');
|
|
53
53
|
const sessionSlots = require('./lib/sessionSlots.cjs');
|
|
54
54
|
const opsErrorLog = require('./lib/opsErrorLog.cjs');
|
|
55
|
+
const { killTree, detachedSpawnOpts } = require('./lib/killTree.cjs');
|
|
55
56
|
const agentModelResolve = require('./lib/agentModelResolve.cjs');
|
|
56
57
|
const { resolveEpicEffort, effortArgs } = require('./lib/agentEffortResolve.cjs');
|
|
57
58
|
const { planEpicSpawn } = require('./lib/epicSpawnPlan.cjs');
|
|
@@ -629,9 +630,8 @@ function executeRun({ tabId, sessionId, prompt, cwd, resume, silent, onSilentRes
|
|
|
629
630
|
if (killed) return;
|
|
630
631
|
killed = true;
|
|
631
632
|
if (!child) return;
|
|
632
|
-
//
|
|
633
|
-
|
|
634
|
-
catch { try { child.kill(sig); } catch { /* already dead */ } }
|
|
633
|
+
// Group kill on POSIX / taskkill /T on win32 (requires detachedSpawnOpts at spawn)
|
|
634
|
+
if (!killTree(child.pid, sig)) { try { child.kill(sig); } catch { /* already dead */ } }
|
|
635
635
|
};
|
|
636
636
|
|
|
637
637
|
const cancelFn = () => {
|
|
@@ -690,13 +690,15 @@ function executeRun({ tabId, sessionId, prompt, cwd, resume, silent, onSilentRes
|
|
|
690
690
|
// real-time NDJSON streaming; stderr piped for error-message capture.
|
|
691
691
|
let thisChild;
|
|
692
692
|
try {
|
|
693
|
-
const
|
|
694
|
-
|
|
693
|
+
const chatArgs = buildArgs(resumeFlag);
|
|
694
|
+
const target = claudeSpawnTarget('chat', sessionId, claudeBin, chatArgs);
|
|
695
|
+
thisChild = spawn(target.command, target.args || chatArgs, {
|
|
695
696
|
...(target.argv0 ? { argv0: target.argv0 } : {}),
|
|
697
|
+
...(target.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {}),
|
|
696
698
|
cwd: execCwd,
|
|
697
699
|
env: childEnv,
|
|
698
700
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
699
|
-
|
|
701
|
+
...detachedSpawnOpts(), // own process group so killTree can SIGTERM descendants
|
|
700
702
|
});
|
|
701
703
|
} catch (err) {
|
|
702
704
|
clearTimeout(killTimer);
|
|
@@ -56,15 +56,51 @@ function sentinelFile() {
|
|
|
56
56
|
// crashes (NOT OOM — a SIGKILL leaves no dump). Local-only, never uploaded.
|
|
57
57
|
// Must run before the app is ready, so call this at module require time from
|
|
58
58
|
// index.cjs's top level.
|
|
59
|
-
|
|
59
|
+
//
|
|
60
|
+
// On Windows Electron relaunches this exe as the crashpad handler with
|
|
61
|
+
// --database=<crashDumps path>; an empty path makes the handler exit with
|
|
62
|
+
// "--database is required" and the browser process self-terminates before any
|
|
63
|
+
// window opens. So we resolve a non-empty dir first, and skip start() entirely
|
|
64
|
+
// if we cannot — crash reporting must never be able to stop boot.
|
|
65
|
+
// `deps` is injectable for tests; the runtime call signature is unchanged.
|
|
66
|
+
function startCrashReporter(deps = {}) {
|
|
67
|
+
const a = deps.app || app;
|
|
68
|
+
const reporter = deps.crashReporter || crashReporter;
|
|
69
|
+
const fsImpl = deps.fs || fs;
|
|
70
|
+
const log = deps.log || ((msg) => console.warn(msg));
|
|
60
71
|
try {
|
|
61
|
-
|
|
72
|
+
const reasons = [];
|
|
73
|
+
const getDir = (name) => {
|
|
74
|
+
try {
|
|
75
|
+
const d = a.getPath(name);
|
|
76
|
+
if (typeof d === 'string') return d.trim();
|
|
77
|
+
reasons.push(`${name}: non-string ${typeof d}`);
|
|
78
|
+
} catch (err) {
|
|
79
|
+
reasons.push(`${name}: ${err && err.message}`);
|
|
80
|
+
}
|
|
81
|
+
return '';
|
|
82
|
+
};
|
|
83
|
+
let dir = getDir('crashDumps');
|
|
84
|
+
let fellBack = false;
|
|
85
|
+
if (!dir) {
|
|
86
|
+
const userData = getDir('userData');
|
|
87
|
+
if (userData) { dir = path.join(userData, 'Crashpad'); fellBack = true; }
|
|
88
|
+
}
|
|
89
|
+
if (!dir) {
|
|
90
|
+
log('[crash-diag] no crashDumps/userData path; crash reporter skipped' + (reasons.length ? ` (${reasons.join('; ')})` : ''));
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
fsImpl.mkdirSync(dir, { recursive: true });
|
|
94
|
+
if (fellBack) a.setPath('crashDumps', dir);
|
|
95
|
+
reporter.start({
|
|
62
96
|
productName: 'claude-code-session-manager',
|
|
63
97
|
companyName: 'session-manager',
|
|
64
98
|
uploadToServer: false,
|
|
65
99
|
compress: true,
|
|
66
100
|
});
|
|
67
|
-
} catch {
|
|
101
|
+
} catch (err) {
|
|
102
|
+
try { (deps.log || ((m) => console.warn(m)))(`[crash-diag] crash reporter skipped: ${err && err.message}`); } catch { /* ignore */ }
|
|
103
|
+
}
|
|
68
104
|
}
|
|
69
105
|
|
|
70
106
|
// Snapshot per-process memory. app.getAppMetrics() returns one entry per
|
|
@@ -92,7 +92,12 @@ async function mapWithConcurrency(items, limit, fn) {
|
|
|
92
92
|
|
|
93
93
|
// ── date helpers ──────────────────────────────────────────────────────────────
|
|
94
94
|
|
|
95
|
-
function decodeCwd(encoded) {
|
|
95
|
+
function decodeCwd(encoded, platform = process.platform) {
|
|
96
|
+
// encodeCwd turns `C:\\Users\\Me` into `C--Users-Me`: drive letter, then `--`.
|
|
97
|
+
if (platform === 'win32') {
|
|
98
|
+
const m = /^([A-Za-z])--(.*)$/.exec(encoded);
|
|
99
|
+
if (m) return m[1] + ':\\' + m[2].replace(/-+/g, '\\');
|
|
100
|
+
}
|
|
96
101
|
return '/' + encoded.replace(/-+/g, '/');
|
|
97
102
|
}
|
|
98
103
|
|
|
@@ -944,6 +949,7 @@ module.exports = {
|
|
|
944
949
|
remote,
|
|
945
950
|
MODEL_PRICING,
|
|
946
951
|
finalizeClosedDays,
|
|
952
|
+
decodeCwd,
|
|
947
953
|
refreshIntradayToday,
|
|
948
954
|
computeIntradayBuckets,
|
|
949
955
|
// exported for tests
|
package/src/main/index.cjs
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
// Boot diagnostic (SM_BOOT_DIAG=1, set by the packaged smoke): one stderr line that
|
|
2
|
+
// shows whether `electron` resolved to the built-in module or ran in node mode.
|
|
3
|
+
if (process.env.SM_BOOT_DIAG === '1') {
|
|
4
|
+
try {
|
|
5
|
+
const e = require('electron');
|
|
6
|
+
let userData;
|
|
7
|
+
try { userData = String(e.app.getPath('userData')); } catch (err) { userData = `error: ${err && err.message}`; }
|
|
8
|
+
process.stderr.write(`[boot-diag] platform=${process.platform} electron=${process.versions.electron} typeof electron=${typeof e} typeof electron.app=${typeof (e && e.app)} ELECTRON_RUN_AS_NODE=${process.env.ELECTRON_RUN_AS_NODE} execPath=${process.execPath} __dirname=${__dirname} userData=${userData}\n`);
|
|
9
|
+
} catch (err) {
|
|
10
|
+
process.stderr.write(`[boot-diag] failed: ${err && err.message}\n`);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
1
13
|
const { PROC_NAMES, setProcessTitle, inhibitHolderShell } = require('./lib/smProcNames.cjs');
|
|
2
14
|
// Self-describing `comm` in System Monitor (pn-03). process.argv is a JS copy
|
|
3
15
|
// made at startup, so the argv-memory overwrite below never affects the
|
|
@@ -10,7 +22,9 @@ const fs = require('node:fs');
|
|
|
10
22
|
const fsp = require('node:fs/promises');
|
|
11
23
|
const os = require('node:os');
|
|
12
24
|
const { schemas, validated } = require('./ipcSchemas.cjs');
|
|
25
|
+
const { createProjectFolder } = require('./lib/projectFolder.cjs');
|
|
13
26
|
const { cleanChildEnv } = require('./lib/cleanEnv.cjs');
|
|
27
|
+
const { applyLoginShellPath } = require('./lib/loginShellPath.cjs');
|
|
14
28
|
const { terminateLosingInstance } = require('./lib/singleInstanceGuard.cjs');
|
|
15
29
|
const { acquireSchedulerOwnership, releaseSchedulerOwnership } = require('./lib/instanceLock.cjs');
|
|
16
30
|
const { manager: ptyManager, registerPtyHandlers } = require('./pty.cjs');
|
|
@@ -25,6 +39,20 @@ const crashDiagnostics = require('./crashDiagnostics.cjs');
|
|
|
25
39
|
// Start the local minidump collector before app-ready (required by Electron).
|
|
26
40
|
// Catches renderer/GPU SIGSEGV/SIGABRT; OOM-kills leave no dump but are caught
|
|
27
41
|
// by the sentinel + render-process-gone hooks in crashDiagnostics.init().
|
|
42
|
+
// ELECTRON_RUN_AS_NODE audit: every src/main site (appRuntime, watchdogHelpers,
|
|
43
|
+
// seedSchedulerMcp, timeoutShim) sets it only in a child's env / launcher text,
|
|
44
|
+
// or deletes it from a copied env — none writes the main process.env, so it
|
|
45
|
+
// cannot leak into Electron's own children (crashpad handler included).
|
|
46
|
+
// Chromium can't resolve known folders under a stripped Windows profile; pin an explicit
|
|
47
|
+
// userData path first (never allowed to stop boot).
|
|
48
|
+
try {
|
|
49
|
+
require('./lib/winUserData.cjs').ensureWinUserDataPath({
|
|
50
|
+
app: require('electron').app, env: process.env, platform: process.platform, fs: require('node:fs'), os: require('node:os'),
|
|
51
|
+
log: (m) => process.stderr.write(`${m}\n`),
|
|
52
|
+
});
|
|
53
|
+
} catch (err) {
|
|
54
|
+
process.stderr.write(`[win-userdata] fallback failed: ${err && err.message}\n`);
|
|
55
|
+
}
|
|
28
56
|
crashDiagnostics.startCrashReporter();
|
|
29
57
|
const voiceHotkey = require('./voiceHotkey.cjs');
|
|
30
58
|
const voiceWizard = require('./voiceWizard.cjs');
|
|
@@ -40,6 +68,7 @@ const chatRunner = require('./chatRunner.cjs');
|
|
|
40
68
|
const promptSessionEvents = require('./promptSessionEvents.cjs');
|
|
41
69
|
const activeIndexMerge = require('./lib/activeIndexMerge.cjs');
|
|
42
70
|
const promptSessionsCreateEpic = require('./lib/promptSessionsCreateEpic.cjs');
|
|
71
|
+
const customerFeedbackIpc = require('./lib/customerFeedbackIpc.cjs');
|
|
43
72
|
const epicWorktreeMint = require('./lib/epicWorktreeMint.cjs');
|
|
44
73
|
const epicWorktreeMerge = require('./lib/epicWorktreeMerge.cjs');
|
|
45
74
|
const epicWorktreeProjectConfig = require('./lib/epicWorktreeProjectConfig.cjs');
|
|
@@ -67,6 +96,7 @@ chatRunner.registerAdminRoute(adminHttp);
|
|
|
67
96
|
// several projects' operations roots open, so it performs the cross-folder
|
|
68
97
|
// write (lib/crossProjectFeedback.cjs).
|
|
69
98
|
crossProjectFeedback.registerAdminRoute(adminHttp);
|
|
99
|
+
require('./lib/customerFeedbackAdminRoutes.cjs').registerAdminRoute(adminHttp);
|
|
70
100
|
const supervisor = require('./supervisor.cjs');
|
|
71
101
|
const watchers = require('./watchers.cjs');
|
|
72
102
|
const queueOps = require('./queueOps.cjs');
|
|
@@ -101,7 +131,8 @@ const { registerDocEditHandlers, attachWindow: attachDocEditWindow } = require('
|
|
|
101
131
|
const { listExchanges } = require('./exchanges.cjs');
|
|
102
132
|
const { resolveClaudeBin } = require('./lib/claudeBin.cjs');
|
|
103
133
|
const { checkInsideHome, assertInsideHome } = require('./lib/insideHome.cjs');
|
|
104
|
-
const { openInEditor, openFileInEditor, openInFinder, openInTerminal } = require('./lib/openExternalApp.cjs');
|
|
134
|
+
const { openInEditor, openFileInEditor, openInFinder, openInTerminal, runInTerminal } = require('./lib/openExternalApp.cjs');
|
|
135
|
+
const { checkPrereqs } = require('./lib/prereqs.cjs');
|
|
105
136
|
const { rebuildActiveIndex } = require('./lib/activeIndexRebuild.cjs');
|
|
106
137
|
const { activeIndexPath: promptSessionsActiveIndexPath } = require('./lib/epicMint.cjs');
|
|
107
138
|
const { allProjectCwds } = require('./lib/activeSessions.cjs');
|
|
@@ -266,6 +297,8 @@ function writeFirstPaintFailureLog() {
|
|
|
266
297
|
}
|
|
267
298
|
}
|
|
268
299
|
|
|
300
|
+
const { isPackagedApp } = require('./lib/appRuntime.cjs');
|
|
301
|
+
|
|
269
302
|
function resolveNpx() {
|
|
270
303
|
const isWin = process.platform === 'win32';
|
|
271
304
|
try {
|
|
@@ -373,6 +406,14 @@ async function performReboot() {
|
|
|
373
406
|
// Bounded-lifetime marker: the watchdog must not relaunch a second instance
|
|
374
407
|
// during this deliberate exit window (single-instance-lock race).
|
|
375
408
|
upgradeDrain.markRestarting();
|
|
409
|
+
if (isPackagedApp()) {
|
|
410
|
+
// Installer build: no npx — restart the app binary in place.
|
|
411
|
+
logReboot('packaged build: app.relaunch()');
|
|
412
|
+
app.relaunch();
|
|
413
|
+
runShutdownCleanup(); // app.exit bypasses will-quit
|
|
414
|
+
app.exit(0);
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
376
417
|
let childOk = false;
|
|
377
418
|
try {
|
|
378
419
|
const child = relaunchViaNpx();
|
|
@@ -715,6 +756,15 @@ ipcMain.handle('app:engage-rules-path', () => process.env.SESSION_MANAGER_ENGAGE
|
|
|
715
756
|
// on disk or the home self-check failed (e.g. macOS /Users symlink mismatch).
|
|
716
757
|
ipcMain.handle('app:claude-bin-status', () => bootClaudeBin);
|
|
717
758
|
ipcMain.handle('app:home-self-check', () => bootHomeSelfCheck);
|
|
759
|
+
ipcMain.handle('app:prereqs', async () => await checkPrereqs());
|
|
760
|
+
// Renderer sends only an id; the command always comes from a fresh main-side probe.
|
|
761
|
+
ipcMain.handle('app:prereqs-run-fix', async (_e, id) => {
|
|
762
|
+
const items = await checkPrereqs();
|
|
763
|
+
const item = typeof id === 'string' ? items.find((i) => i.id === id) : undefined;
|
|
764
|
+
if (!item) throw new Error(`unknown prereq id: ${String(id)}`);
|
|
765
|
+
if (!item.fix || !item.fix.command) throw new Error(`prereq ${id} has no fix command`);
|
|
766
|
+
return runInTerminal({ command: item.fix.command, shell: item.fix.shell });
|
|
767
|
+
});
|
|
718
768
|
// First-boot seeder outcomes ('done' | 'pending' | 'exhausted' per seeder),
|
|
719
769
|
// read live from each seeder's own marker file — see seedStatus.cjs.
|
|
720
770
|
ipcMain.handle('app:seed-status', () => getSeedStatus());
|
|
@@ -722,9 +772,9 @@ ipcMain.handle('app:seed-status', () => getSeedStatus());
|
|
|
722
772
|
ipcMain.handle('app:pick-directory', async () => {
|
|
723
773
|
console.log('[main] pick-directory invoked');
|
|
724
774
|
const result = await dialog.showOpenDialog(mainWindow, {
|
|
725
|
-
// createDirectory
|
|
726
|
-
//
|
|
727
|
-
//
|
|
775
|
+
// createDirectory is macOS-only (it adds the picker's "New Folder"
|
|
776
|
+
// button there). Linux/Windows users create folders via the in-app
|
|
777
|
+
// Start new project form (app:create-project-folder, below).
|
|
728
778
|
properties: ['openDirectory', 'createDirectory'],
|
|
729
779
|
defaultPath: os.homedir(),
|
|
730
780
|
title: 'Open a project folder — or create a new one to start a project',
|
|
@@ -734,6 +784,12 @@ ipcMain.handle('app:pick-directory', async () => {
|
|
|
734
784
|
return result.filePaths[0];
|
|
735
785
|
});
|
|
736
786
|
|
|
787
|
+
ipcMain.handle('app:create-project-folder', validated(schemas.appCreateProjectFolder, async (payload) => {
|
|
788
|
+
const result = await createProjectFolder(payload);
|
|
789
|
+
console.log('[main] create-project-folder', result.ok ? 'ok' : result.code, JSON.stringify({ parentDir: payload.parentDir, name: payload.name }));
|
|
790
|
+
return result;
|
|
791
|
+
}));
|
|
792
|
+
|
|
737
793
|
ipcMain.on('app:reboot-app', (_e, opts) => { rebootApp({ force: opts?.force === true }).catch((e) => logReboot(`reboot failed: ${e?.message}`)); });
|
|
738
794
|
|
|
739
795
|
// Image paste — Ctrl+V in the Terminal pane. Reads the OS clipboard via
|
|
@@ -1009,6 +1065,7 @@ pluginInstall.registerPluginInstallHandlers();
|
|
|
1009
1065
|
memoryTool.registerMemoryHandlers();
|
|
1010
1066
|
activeIndexMerge.registerActiveIndexMergeHandlers();
|
|
1011
1067
|
promptSessionsCreateEpic.registerPromptSessionsCreateEpicHandlers();
|
|
1068
|
+
customerFeedbackIpc.registerCustomerFeedbackHandlers();
|
|
1012
1069
|
epicWorktreeMint.registerEpicWorktreeMintHandlers();
|
|
1013
1070
|
epicWorktreeMerge.registerEpicWorktreeMergeHandlers();
|
|
1014
1071
|
epicWorktreeProjectConfig.registerEpicWorktreeProjectConfigHandlers();
|
|
@@ -1190,6 +1247,18 @@ app.whenReady().then(async () => {
|
|
|
1190
1247
|
onSuspendWhileAlive: () => { stopSystemdInhibit(); startSystemdInhibit(); },
|
|
1191
1248
|
});
|
|
1192
1249
|
|
|
1250
|
+
// A Finder/Dock-launched packaged app inherits a minimal PATH; adopt the
|
|
1251
|
+
// login shell's so node/git/claude resolve like in the user's terminal.
|
|
1252
|
+
// Must run before resolveClaudeBin() and before any pty/scheduler/MCP spawn.
|
|
1253
|
+
if (process.platform !== 'win32' && process.env.SM_SKIP_LOGIN_SHELL_PATH !== '1') {
|
|
1254
|
+
try {
|
|
1255
|
+
const loginPath = await applyLoginShellPath();
|
|
1256
|
+
logs.writeLine({ scope: 'main', level: 'info', message: 'login-shell PATH', meta: loginPath });
|
|
1257
|
+
} catch (err) {
|
|
1258
|
+
logs.writeLine({ scope: 'main', level: 'warn', message: 'login-shell PATH failed', meta: { error: String(err && err.message || err) } });
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1193
1262
|
// Boot-time detection 1: surface `claude` binary resolution so a missing
|
|
1194
1263
|
// install becomes visible to the renderer instead of failing silently on
|
|
1195
1264
|
// first spawn attempt.
|
package/src/main/ipcSchemas.cjs
CHANGED
|
@@ -687,6 +687,13 @@ const delegationReadinessCwd = z.object({
|
|
|
687
687
|
cwd: z.string().min(1).max(4096),
|
|
688
688
|
}).strict();
|
|
689
689
|
|
|
690
|
+
// app:create-project-folder — name semantics (separators, '..') are checked in
|
|
691
|
+
// lib/projectFolder.cjs, not here.
|
|
692
|
+
const appCreateProjectFolder = z.object({
|
|
693
|
+
parentDir: z.string().min(1).max(4096),
|
|
694
|
+
name: z.string().min(1).max(255),
|
|
695
|
+
}).strict();
|
|
696
|
+
|
|
690
697
|
// ──────────────────────────────────────────── Agent persona body (New Epic AIM)
|
|
691
698
|
const agentsGetPersonaBody = z.object({
|
|
692
699
|
cwd: z.string().min(1).max(4096),
|
|
@@ -1051,6 +1058,20 @@ const telemetrySetConfig = z.object({
|
|
|
1051
1058
|
schemaVersion: z.literal(1),
|
|
1052
1059
|
});
|
|
1053
1060
|
|
|
1061
|
+
const customerFeedbackSubmit = z.object({
|
|
1062
|
+
title: z.string().max(120),
|
|
1063
|
+
body: z.string().max(4000),
|
|
1064
|
+
tag: z.enum(['bug', 'feature', 'discussion']),
|
|
1065
|
+
});
|
|
1066
|
+
const customerFeedbackMarkSeen = z.object({ ids: z.array(z.string().max(200)).max(500).optional() });
|
|
1067
|
+
const customerFeedbackInboxList = z.object({ includeHidden: z.boolean().optional() });
|
|
1068
|
+
const customerFeedbackInboxSetStatus = z.object({
|
|
1069
|
+
id: z.string().max(200),
|
|
1070
|
+
status: z.enum(['open', 'in_progress', 'resolved', 'wontfix']),
|
|
1071
|
+
note: z.string().max(500).optional(),
|
|
1072
|
+
});
|
|
1073
|
+
const customerFeedbackInboxLinkEpic = z.object({ id: z.string().max(200), epicId: z.string().max(200) });
|
|
1074
|
+
|
|
1054
1075
|
/**
|
|
1055
1076
|
* Wrap an IPC handler with schema validation. Returns a new handler that
|
|
1056
1077
|
* parses the payload before calling the original. On invalid payload throws
|
|
@@ -1179,7 +1200,13 @@ module.exports = {
|
|
|
1179
1200
|
agentsResolveEpicModel,
|
|
1180
1201
|
agentsResolveEpicEffort,
|
|
1181
1202
|
delegationReadinessCwd,
|
|
1203
|
+
appCreateProjectFolder,
|
|
1182
1204
|
telemetrySetConfig,
|
|
1205
|
+
customerFeedbackSubmit,
|
|
1206
|
+
customerFeedbackMarkSeen,
|
|
1207
|
+
customerFeedbackInboxList,
|
|
1208
|
+
customerFeedbackInboxSetStatus,
|
|
1209
|
+
customerFeedbackInboxLinkEpic,
|
|
1183
1210
|
},
|
|
1184
1211
|
validated,
|
|
1185
1212
|
};
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
*/
|
|
23
23
|
'use strict';
|
|
24
24
|
|
|
25
|
+
const path = require('node:path');
|
|
25
26
|
const { z } = require('zod');
|
|
26
27
|
const { WorkTypeSchema, LEGACY_WORK_TYPES } = require('./workTypeLibrary.cjs');
|
|
27
28
|
const { PERSONA_NAME_RE, ALL_PROJECTS } = require('../agentLibrary.cjs');
|
|
@@ -35,8 +36,13 @@ const BoundedString = z.string().max(2000);
|
|
|
35
36
|
// `projects` entries are either the `'*'` sentinel (every project) or an
|
|
36
37
|
// absolute path — agentLibrary.cjs's openProjects always deals in absolute
|
|
37
38
|
// cwds, and a relative entry could never match one.
|
|
39
|
+
/** @param {string} v @param {NodeJS.Platform} [platform] */
|
|
40
|
+
function isAbsoluteForPlatform(v, platform = process.platform) {
|
|
41
|
+
return (platform === 'win32' ? path.win32 : path.posix).isAbsolute(v);
|
|
42
|
+
}
|
|
43
|
+
|
|
38
44
|
const ProjectEntrySchema = z.string().refine(
|
|
39
|
-
(v) => v === ALL_PROJECTS || v
|
|
45
|
+
(/** @type {string} */ v) => v === ALL_PROJECTS || isAbsoluteForPlatform(v),
|
|
40
46
|
`must be "${ALL_PROJECTS}" or an absolute path`,
|
|
41
47
|
);
|
|
42
48
|
|
|
@@ -95,4 +101,5 @@ function assertValidAgentPersonaSave(persona) {
|
|
|
95
101
|
module.exports = {
|
|
96
102
|
AgentPersonaSaveSchema,
|
|
97
103
|
assertValidAgentPersonaSave,
|
|
104
|
+
isAbsoluteForPlatform,
|
|
98
105
|
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* appRuntime.cjs — how to run a Node script from an EXTERNAL process.
|
|
3
|
+
*
|
|
4
|
+
* Packaged installer builds may have no system `node`, so scripts handed to
|
|
5
|
+
* Claude Code hooks / MCP registrations run through the app's own binary with
|
|
6
|
+
* ELECTRON_RUN_AS_NODE=1. npx/dev builds keep using `node`.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param {{ packaged?: boolean }} [opts]
|
|
13
|
+
* @returns {boolean}
|
|
14
|
+
*/
|
|
15
|
+
function isPackagedApp(opts) {
|
|
16
|
+
if (opts && typeof opts.packaged === 'boolean') return opts.packaged;
|
|
17
|
+
if (process.env.SM_FORCE_PACKAGED === '1') return true;
|
|
18
|
+
try {
|
|
19
|
+
return require('electron').app?.isPackaged === true;
|
|
20
|
+
} catch {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {string} scriptPath
|
|
27
|
+
* @param {{ packaged?: boolean, execPath?: string }} [opts]
|
|
28
|
+
* @returns {{ command: string, args: string[], env: Record<string, string> }}
|
|
29
|
+
*/
|
|
30
|
+
function nodeSpawnSpec(scriptPath, opts) {
|
|
31
|
+
if (!isPackagedApp(opts)) return { command: 'node', args: [scriptPath], env: {} };
|
|
32
|
+
return {
|
|
33
|
+
command: (opts && opts.execPath) || process.execPath,
|
|
34
|
+
args: [scriptPath],
|
|
35
|
+
env: { ELECTRON_RUN_AS_NODE: '1' },
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** @param {string} p */
|
|
40
|
+
function quote(p) {
|
|
41
|
+
if (p.includes('"')) throw new Error(`appRuntime: path contains a double quote: ${p}`);
|
|
42
|
+
return `"${p}"`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Shell command string (hook `command` fields). Paths are double-quoted.
|
|
47
|
+
* @param {string} scriptPath
|
|
48
|
+
* @param {{ packaged?: boolean, execPath?: string }} [opts]
|
|
49
|
+
* @returns {string}
|
|
50
|
+
*/
|
|
51
|
+
function nodeShellCommand(scriptPath, opts) {
|
|
52
|
+
if (!isPackagedApp(opts)) return `node ${quote(scriptPath)}`;
|
|
53
|
+
const execPath = (opts && opts.execPath) || process.execPath;
|
|
54
|
+
return `ELECTRON_RUN_AS_NODE=1 ${quote(execPath)} ${quote(scriptPath)}`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = { isPackagedApp, nodeSpawnSpec, nodeShellCommand };
|
|
@@ -52,6 +52,7 @@
|
|
|
52
52
|
|
|
53
53
|
const fs = require('node:fs');
|
|
54
54
|
const { spawn, execFile } = require('node:child_process');
|
|
55
|
+
const { killTree } = require('./killTree.cjs');
|
|
55
56
|
|
|
56
57
|
// Injectable for tests (enumeration-failure coverage).
|
|
57
58
|
const deps = { execFile };
|
|
@@ -150,6 +151,7 @@ function openLog(logPath) {
|
|
|
150
151
|
// Async: resolves to the survivors list, rejects on ps failure/timeout. O(P) in
|
|
151
152
|
// the process table, off the main event loop.
|
|
152
153
|
function enumerateProcessGroupSurvivors(pgid) {
|
|
154
|
+
// ps -eo is Linux-only; on win32 (and macOS) report the empty/unknown result without spawning ps.
|
|
153
155
|
if (process.platform !== 'linux') return Promise.resolve([]);
|
|
154
156
|
if (!pgid || pgid <= 1) return Promise.resolve([]);
|
|
155
157
|
return new Promise((resolve, reject) => {
|
|
@@ -182,12 +184,14 @@ function sweepChildProcessGroup(child, detached, safeLog) {
|
|
|
182
184
|
if (!child || !child.pid || child.pid <= 1) return null;
|
|
183
185
|
// Defensive: never target the Session Manager process's own group.
|
|
184
186
|
if (child.pid === process.pid) return null;
|
|
187
|
+
// win32 has no process groups; taskkill on an already-exited pid risks hitting a reused pid.
|
|
188
|
+
if (process.platform === 'win32') return null;
|
|
185
189
|
|
|
186
190
|
const pid = child.pid;
|
|
187
191
|
const kill = () => {
|
|
188
|
-
|
|
192
|
+
killTree(pid, 'SIGTERM'); // ESRCH on an empty group is the normal case
|
|
189
193
|
const t = setTimeout(() => {
|
|
190
|
-
|
|
194
|
+
killTree(pid, 'SIGKILL');
|
|
191
195
|
}, POST_EXIT_GROUP_SWEEP_GRACE_MS);
|
|
192
196
|
if (t.unref) t.unref();
|
|
193
197
|
};
|
|
@@ -219,13 +223,9 @@ function withChildAndLog({ fd, logPath, safeLog, closeFd, spawn: spawnSpec, watc
|
|
|
219
223
|
killTree(signal) {
|
|
220
224
|
const c = ctx.child;
|
|
221
225
|
if (!c) return false;
|
|
222
|
-
//
|
|
223
|
-
//
|
|
224
|
-
|
|
225
|
-
catch {
|
|
226
|
-
try { process.kill(c.pid, signal); return true; }
|
|
227
|
-
catch { return false; /* already dead */ }
|
|
228
|
-
}
|
|
226
|
+
// Whole process group on POSIX / taskkill /T on win32 (requires detachedSpawnOpts at spawn);
|
|
227
|
+
// killTree falls back to the bare pid if the process is not a group leader.
|
|
228
|
+
return killTree(c.pid, signal, { log: safeLog });
|
|
229
229
|
},
|
|
230
230
|
addTimer(t) { extraTimers.push(t); },
|
|
231
231
|
};
|
|
@@ -315,7 +315,7 @@ function withChildAndLog({ fd, logPath, safeLog, closeFd, spawn: spawnSpec, watc
|
|
|
315
315
|
const cancel = () => {
|
|
316
316
|
clearAllTimers();
|
|
317
317
|
// Best-effort kill; ignore errors (process may already be gone).
|
|
318
|
-
|
|
318
|
+
if (!killTree(child.pid, 'SIGKILL', { log: safeLog })) {
|
|
319
319
|
try { child.kill('SIGKILL'); } catch { /* */ }
|
|
320
320
|
}
|
|
321
321
|
};
|