mcp-pacemaker 2.0.2 → 2.1.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/CHANGELOG.md +75 -1
- package/CONTRIBUTING.md +39 -13
- package/README.md +255 -8
- package/THIRD_PARTY_NOTICES.txt +1 -28
- package/autostart/windows/account-task.ps1 +167 -0
- package/autostart/windows/inspect-task.ps1 +42 -0
- package/autostart/windows/inspect-worker-process-sessions.ps1 +155 -0
- package/autostart/windows/legacy-task.ps1 +301 -0
- package/autostart/windows/register-task.ps1 +11 -2
- package/bin/account-task-adapter.mjs +47 -0
- package/bin/account-upgrade-controller.mjs +198 -0
- package/bin/account-upgrade-worker.mjs +134 -0
- package/bin/account-worker-session.mjs +215 -0
- package/bin/cli-dispatch.mjs +154 -0
- package/bin/cli-main.mjs +1007 -0
- package/bin/cli-selection.mjs +43 -0
- package/bin/cli.mjs +7 -876
- package/bin/legacy-1.3.json +32 -0
- package/bin/legacy-installation.mjs +217 -0
- package/bin/legacy-task.mjs +52 -0
- package/bin/legacy-upgrade.mjs +471 -0
- package/bin/managed-runtime.mjs +170 -0
- package/bin/managed-state.mjs +257 -0
- package/bin/managed-upgrade.mjs +491 -0
- package/bin/mcp-bridge.mjs +132 -4
- package/bin/pooling-batches.mjs +13 -0
- package/bin/pooling-writer.mjs +4 -0
- package/bin/prewarm.mjs +33 -21
- package/bin/runtime-context.mjs +100 -0
- package/bin/service-control.mjs +27 -4
- package/bin/stable-launcher.mjs +54 -0
- package/bin/task-only-controller.mjs +226 -0
- package/bin/task-scope.mjs +65 -0
- package/bin/task-transaction-protocol.mjs +173 -0
- package/bin/top.mjs +22 -10
- package/bin/update-check.mjs +13 -2
- package/bin/upgrade-autostart.mjs +47 -0
- package/bin/upgrade-capability.json +8 -0
- package/bin/upgrade-dependencies.mjs +122 -0
- package/bin/upgrade-package.mjs +203 -0
- package/bin/windows-legacy/LegacyProcessBroker.build.json +42 -0
- package/bin/windows-legacy/LegacyProcessBroker.exe +0 -0
- package/bin/windows-legacy/src/AssemblyInfo.cs +8 -0
- package/bin/windows-legacy/src/LegacyNative.cs +783 -0
- package/bin/windows-legacy/src/LegacyProcessBroker.cs +841 -0
- package/bin/windows-legacy-process.mjs +241 -0
- package/bin/windows-lifetime/ProcessLifetimeHelper.build.json +38 -0
- package/bin/windows-lifetime/ProcessLifetimeHelper.exe +0 -0
- package/bin/windows-lifetime/src/AssemblyInfo.cs +8 -0
- package/bin/windows-lifetime/src/ProcessLifetimeHelper.cs +468 -0
- package/bin/windows-process-lifetime.mjs +218 -0
- package/bin/windows-task-channel/TaskChannelGuard.build.json +45 -0
- package/bin/windows-task-channel/TaskChannelGuard.exe +0 -0
- package/bin/windows-task-channel/src/AssemblyInfo.cs +8 -0
- package/bin/windows-task-channel/src/ChannelCliContext.cs +210 -0
- package/bin/windows-task-channel/src/ChannelFiles.cs +231 -0
- package/bin/windows-task-channel/src/ChannelNative.cs +477 -0
- package/bin/windows-task-channel/src/ChannelSession.cs +203 -0
- package/bin/windows-task-channel/src/TaskChannelGuard.cs +686 -0
- package/bin/windows-task-channel.mjs +335 -0
- package/docs/account-upgrades.md +205 -0
- package/docs/execution-context.md +113 -0
- package/package.json +12 -5
- package/supervisor/supervise.mjs +226 -40
- package/tools/account-upgrade/qualify-two-account.ps1 +66 -0
- package/tools/windows-legacy/README.md +225 -0
- package/tools/windows-legacy/build.ps1 +122 -0
- package/tools/windows-legacy/inventory.mjs +90 -0
- package/tools/windows-process-lifetime/README.md +119 -0
- package/tools/windows-process-lifetime/build.ps1 +122 -0
- package/tools/windows-process-lifetime/inventory.mjs +86 -0
- package/tools/windows-task-channel/README.md +329 -0
- package/tools/windows-task-channel/build.ps1 +107 -0
- package/tools/windows-task-channel/inventory.mjs +117 -0
- package/ui/dist/THIRD_PARTY_NOTICES.txt +1 -28
- package/ui/dist/assets/{index-CgPruWYi.js → index-CcwMo2sI.js} +1 -1
- package/ui/dist/assets/index-PVcuDNrN.css +1 -0
- package/ui/dist/index.html +2 -2
- package/ui/dist/third-party-manifest.json +37 -25
- package/ui/dist/assets/index-CaLPg0c-.css +0 -1
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { readFileSync, realpathSync, statSync } from 'node:fs';
|
|
4
|
+
import { isAbsolute } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
|
|
7
|
+
const HELPER = fileURLToPath(new URL('./windows-legacy/LegacyProcessBroker.exe', import.meta.url));
|
|
8
|
+
const METADATA = fileURLToPath(new URL('./windows-legacy/LegacyProcessBroker.build.json', import.meta.url));
|
|
9
|
+
|
|
10
|
+
class LegacyBroker {
|
|
11
|
+
constructor() {
|
|
12
|
+
this.pending = null;
|
|
13
|
+
this.buffer = '';
|
|
14
|
+
this.stderr = '';
|
|
15
|
+
this.closed = false;
|
|
16
|
+
this.committed = false;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
verifyHelper() {
|
|
20
|
+
const metadataBytes = readFileSync(METADATA);
|
|
21
|
+
if (metadataBytes.length > 65536) throw new Error('Invalid packaged legacy helper metadata');
|
|
22
|
+
const metadata = JSON.parse(metadataBytes);
|
|
23
|
+
const digest = metadata.binarySha256;
|
|
24
|
+
const valid = metadata.schemaVersion === 1 &&
|
|
25
|
+
metadata.binary === 'LegacyProcessBroker.exe' &&
|
|
26
|
+
typeof digest === 'string' &&
|
|
27
|
+
digest.length === 64 &&
|
|
28
|
+
[...digest].every((character) => '0123456789abcdef'.includes(character));
|
|
29
|
+
if (!valid) throw new Error('Invalid packaged legacy helper digest');
|
|
30
|
+
const size = statSync(HELPER).size;
|
|
31
|
+
if (size < 1 ||
|
|
32
|
+
size > 4 * 1024 * 1024 ||
|
|
33
|
+
createHash('sha256').update(readFileSync(HELPER)).digest('hex') !== digest) {
|
|
34
|
+
throw new Error('Packaged legacy helper identity mismatch');
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
start() {
|
|
39
|
+
if (process.platform !== 'win32') throw new Error('Legacy process handoff requires Windows');
|
|
40
|
+
this.verifyHelper();
|
|
41
|
+
this.child = spawn(HELPER, [], {
|
|
42
|
+
detached: true, windowsHide: true, stdio: ['pipe', 'pipe', 'pipe'],
|
|
43
|
+
});
|
|
44
|
+
this.child.stdout.setEncoding('utf8');
|
|
45
|
+
this.child.stderr.setEncoding('utf8');
|
|
46
|
+
this.exit = new Promise((resolve) => {
|
|
47
|
+
this.child.on('error', (error) => {
|
|
48
|
+
this.failure(error);
|
|
49
|
+
resolve({ code: null, signal: null, error: error.code });
|
|
50
|
+
});
|
|
51
|
+
this.child.on('close', (code, signal) => {
|
|
52
|
+
this.closed = true;
|
|
53
|
+
if (this.pending) this.failure(new Error(`Legacy process broker exited (code ${code}, signal ${signal})`));
|
|
54
|
+
resolve({ code, signal });
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
this.child.stdin.on('error', (error) => this.failure(error));
|
|
58
|
+
this.child.stdout.on('error', (error) => this.failure(error));
|
|
59
|
+
this.child.stderr.on('error', (error) => this.failure(error));
|
|
60
|
+
this.child.stderr.on('data', (chunk) => {
|
|
61
|
+
this.stderr = (this.stderr + chunk).slice(-2048);
|
|
62
|
+
});
|
|
63
|
+
this.child.stdout.on('data', (chunk) => {
|
|
64
|
+
this.buffer += chunk;
|
|
65
|
+
if (this.buffer.length > 262144) {
|
|
66
|
+
this.failure(new Error('Legacy broker response exceeded bounds'));
|
|
67
|
+
this.child.kill();
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const newline = this.buffer.indexOf('\n');
|
|
71
|
+
if (newline === -1) return;
|
|
72
|
+
const line = this.buffer.slice(0, newline);
|
|
73
|
+
this.buffer = this.buffer.slice(newline + 1);
|
|
74
|
+
if (!this.pending ||
|
|
75
|
+
this.buffer.trim() !== '') {
|
|
76
|
+
this.failure(new Error('Unexpected legacy broker response'));
|
|
77
|
+
this.child.kill();
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const pending = this.pending;
|
|
81
|
+
this.pending = null;
|
|
82
|
+
clearTimeout(pending.timer);
|
|
83
|
+
try {
|
|
84
|
+
const response = JSON.parse(line);
|
|
85
|
+
if (response.ok !== true) {
|
|
86
|
+
const error = new Error(`Legacy process handoff refused at ${response.stage || 'protocol'} (${response.type || 'invalid response'})`);
|
|
87
|
+
if (typeof response.reason === 'string' &&
|
|
88
|
+
response.reason.length <= 100) error.message += `: ${response.reason}`;
|
|
89
|
+
error.code = response.error || 'LEGACY_PROCESS_REFUSED';
|
|
90
|
+
error.detail = response;
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
pending.resolve(response);
|
|
94
|
+
} catch (error) {
|
|
95
|
+
this.error = error;
|
|
96
|
+
pending.reject(error);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
failure(error) {
|
|
102
|
+
this.error = error;
|
|
103
|
+
if (!this.pending) return;
|
|
104
|
+
const pending = this.pending;
|
|
105
|
+
this.pending = null;
|
|
106
|
+
clearTimeout(pending.timer);
|
|
107
|
+
pending.reject(error);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
request(value, milliseconds) {
|
|
111
|
+
if (this.closed ||
|
|
112
|
+
this.error) return Promise.reject(this.error || new Error('Legacy broker closed'));
|
|
113
|
+
if (this.pending) return Promise.reject(new Error('Concurrent legacy broker request refused'));
|
|
114
|
+
let bytes;
|
|
115
|
+
try { bytes = this.encode(value); }
|
|
116
|
+
catch (error) { return Promise.reject(error); }
|
|
117
|
+
return new Promise((resolve, reject) => {
|
|
118
|
+
const timer = setTimeout(() => {
|
|
119
|
+
this.failure(new Error('Legacy process broker deadline exceeded; outcome is unverified'));
|
|
120
|
+
this.child.kill();
|
|
121
|
+
}, milliseconds);
|
|
122
|
+
this.pending = { resolve, reject, timer };
|
|
123
|
+
this.child.stdin.write(bytes, (error) => {
|
|
124
|
+
if (error) this.failure(error);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
encode(value) {
|
|
130
|
+
const bytes = Buffer.from(`${JSON.stringify(value)}\n`, 'utf8');
|
|
131
|
+
if (bytes.length > 262144) throw new Error('Legacy broker request exceeded bounds');
|
|
132
|
+
return bytes;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async prepare(options) {
|
|
136
|
+
const { port, root, expected = null } = options;
|
|
137
|
+
if (!Number.isInteger(port) ||
|
|
138
|
+
port < 1 ||
|
|
139
|
+
port > 65535 ||
|
|
140
|
+
typeof root !== 'string' ||
|
|
141
|
+
!isAbsolute(root)) throw new Error('Exact absolute legacy root and port 1..65535 required');
|
|
142
|
+
const request = { action: 'prepare', port, root: realpathSync.native(root), expected };
|
|
143
|
+
this.encode(request);
|
|
144
|
+
this.start();
|
|
145
|
+
try {
|
|
146
|
+
const result = await this.request(request, 20000);
|
|
147
|
+
const plan = result.plan;
|
|
148
|
+
if (plan?.kind !== 'legacy-observed-process-set' ||
|
|
149
|
+
plan.protocol !== 1 ||
|
|
150
|
+
plan.treeCompleteness !== 'unproven' ||
|
|
151
|
+
typeof result.token !== 'string') throw new Error('Invalid legacy broker plan');
|
|
152
|
+
this.plan = structuredClone(plan);
|
|
153
|
+
this.token = result.token;
|
|
154
|
+
return {
|
|
155
|
+
plan: structuredClone(plan),
|
|
156
|
+
diagnostics: structuredClone(result.diagnostics),
|
|
157
|
+
stop: () => this.stop(),
|
|
158
|
+
status: () => this.status(),
|
|
159
|
+
close: () => this.close(),
|
|
160
|
+
};
|
|
161
|
+
} catch (error) {
|
|
162
|
+
await this.close();
|
|
163
|
+
throw error;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
receipt(value) {
|
|
168
|
+
if (value?.treeCompleteness !== 'unproven' ||
|
|
169
|
+
typeof value.legacyRootStopVerified !== 'boolean' ||
|
|
170
|
+
typeof value.observedDescendantsStopped !== 'boolean' ||
|
|
171
|
+
!Array.isArray(value.errors)) throw new Error('Invalid partial legacy process receipt');
|
|
172
|
+
return value;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async status() {
|
|
176
|
+
const response = await this.request({ action: 'status' }, 3000);
|
|
177
|
+
return this.receipt(response.receipt);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async stop() {
|
|
181
|
+
if (this.committed) throw new Error('Legacy stop approval already consumed');
|
|
182
|
+
this.committed = true;
|
|
183
|
+
try {
|
|
184
|
+
const response = await this.request({
|
|
185
|
+
action: 'stop', token: this.token, planSha256: this.plan.planSha256,
|
|
186
|
+
}, 30000);
|
|
187
|
+
const receipt = this.receipt(response.receipt);
|
|
188
|
+
const exit = await this.finish();
|
|
189
|
+
if (exit.code !== 0 ||
|
|
190
|
+
exit.signal !== null) throw new Error('Legacy broker exit was not verified after stop');
|
|
191
|
+
return { ...receipt, diagnostics: structuredClone(response.diagnostics) };
|
|
192
|
+
} finally { await this.close(); }
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async finish() {
|
|
196
|
+
let timer;
|
|
197
|
+
try {
|
|
198
|
+
return await Promise.race([this.exit, new Promise((_, reject) => {
|
|
199
|
+
timer = setTimeout(() => reject(new Error('Legacy broker did not exit')), 3000);
|
|
200
|
+
})]);
|
|
201
|
+
} finally { clearTimeout(timer); }
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async close() {
|
|
205
|
+
if (!this.child) return;
|
|
206
|
+
if (this.closed) return this.exit;
|
|
207
|
+
let closeError;
|
|
208
|
+
if (!this.pending &&
|
|
209
|
+
!this.error &&
|
|
210
|
+
!this.committed) {
|
|
211
|
+
try { await this.request({ action: 'close' }, 2000); }
|
|
212
|
+
catch (error) { closeError = error; }
|
|
213
|
+
}
|
|
214
|
+
if (!this.closed &&
|
|
215
|
+
this.child.exitCode == null &&
|
|
216
|
+
this.child.signalCode == null) this.child.kill();
|
|
217
|
+
const exit = await this.finish();
|
|
218
|
+
if (closeError) throw closeError;
|
|
219
|
+
return exit;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async verify(plan) {
|
|
223
|
+
this.encode({ action: 'verify', expected: plan });
|
|
224
|
+
this.start();
|
|
225
|
+
try {
|
|
226
|
+
const response = await this.request({ action: 'verify', expected: plan }, 20000);
|
|
227
|
+
const receipt = this.receipt(response.receipt);
|
|
228
|
+
const exit = await this.finish();
|
|
229
|
+
if (exit.code !== 0) throw new Error('Legacy verification broker exit failed');
|
|
230
|
+
return receipt;
|
|
231
|
+
} finally { await this.close(); }
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export async function prepareLegacyProcesses(options) {
|
|
236
|
+
return new LegacyBroker().prepare(options);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export async function verifyLegacyProcessesGone(plan) {
|
|
240
|
+
return new LegacyBroker().verify(plan);
|
|
241
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"binary": "ProcessLifetimeHelper.exe",
|
|
4
|
+
"binarySha256": "bd7bc6da95e425ce31a0a32947692cf1ef054ecfbf26f5cb2830b495351ea776",
|
|
5
|
+
"inputs": {
|
|
6
|
+
"targetFramework": ".NETFramework,Version=v4.6.2",
|
|
7
|
+
"platform": "AnyCPU",
|
|
8
|
+
"compilerVersion": "5.9.0-1.26357.3 (35d9211b841e7613c1d2f8f5af6d628ace696c4c)",
|
|
9
|
+
"compilerSha256": "3aafb7b9c54fa31a7092af35148971ee616965e7f9a0b80fb7fdc4bdd1d1a555",
|
|
10
|
+
"compilerArguments": [
|
|
11
|
+
"/nologo",
|
|
12
|
+
"/noconfig",
|
|
13
|
+
"/nostdlib+",
|
|
14
|
+
"/target:exe",
|
|
15
|
+
"/platform:anycpu",
|
|
16
|
+
"/langversion:7.3",
|
|
17
|
+
"/optimize+",
|
|
18
|
+
"/debug-",
|
|
19
|
+
"/deterministic+",
|
|
20
|
+
"/checked-",
|
|
21
|
+
"/unsafe-",
|
|
22
|
+
"/warn:4",
|
|
23
|
+
"/warnaserror+",
|
|
24
|
+
"/utf8output"
|
|
25
|
+
],
|
|
26
|
+
"pathMap": "/_/mcp-pacemaker/windows-process-lifetime",
|
|
27
|
+
"sourceEncoding": "UTF-8 without BOM, LF line endings",
|
|
28
|
+
"sourceSha256": {
|
|
29
|
+
"AssemblyInfo.cs": "147a81ea19a2765b6e8c273fbcc609ac6dac5bdd343d433ae320fe790893d3cc",
|
|
30
|
+
"ProcessLifetimeHelper.cs": "938b63e02d6c034e58312edf46224a4f3bc08c0c3f572158e69b56dcfc0341af"
|
|
31
|
+
},
|
|
32
|
+
"buildScriptSha256": "b99bc2c251823bccff96cf729bf892b3309423c9a585ac8fca6b4e4ef643d2ac",
|
|
33
|
+
"referenceSha256": {
|
|
34
|
+
"mscorlib.dll": "7118fe658567606fa0538f7b5a2071e4606756c07e9062e1e5b24fa49e97d1f6",
|
|
35
|
+
"System.dll": "b76822f9c08aff597120e3167b3e0fbda836d6bc6f14bc4bc2eeffab5b2c6125"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
using System.Reflection;
|
|
2
|
+
using System.Runtime.InteropServices;
|
|
3
|
+
|
|
4
|
+
[assembly: AssemblyTitle("MCP Process Lifetime Helper")]
|
|
5
|
+
[assembly: AssemblyDescription("Owns a bridge's Windows Job Object until the bridge exits")]
|
|
6
|
+
[assembly: AssemblyVersion("1.0.0.0")]
|
|
7
|
+
[assembly: AssemblyFileVersion("1.0.0.0")]
|
|
8
|
+
[assembly: ComVisible(false)]
|