praxis-agent 0.42.0 → 0.43.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 +7 -1
- package/dist/cli-runtime.d.ts +1 -0
- package/dist/cli-runtime.js +5 -2
- package/dist/maintenance/self-update-transaction.d.ts +39 -0
- package/dist/maintenance/self-update-transaction.js +356 -0
- package/dist/maintenance/self-update.d.ts +4 -0
- package/dist/maintenance/self-update.js +29 -20
- package/dist/mcp/claude-mcp-tools.d.ts +10 -7
- package/dist/mcp/claude-mcp-tools.js +223 -331
- package/dist/mcp/mcp-server-session.d.ts +81 -0
- package/dist/mcp/mcp-server-session.js +590 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -45,6 +45,7 @@ praxis --version
|
|
|
45
45
|
|
|
46
46
|
Release tarballs, SBOMs, SHA-256 checksums, and build attestations are attached
|
|
47
47
|
to every [GitHub release](https://github.com/Forest-Isle/Praxis/releases).
|
|
48
|
+
Use `praxis update` for transactional self-updates; see [Getting Started](https://github.com/Forest-Isle/Praxis/blob/main/docs/GETTING_STARTED.md) for recovery and verification details.
|
|
48
49
|
|
|
49
50
|
## Quick start
|
|
50
51
|
|
|
@@ -198,10 +199,15 @@ troubleshooting. Run `praxis --help` for the authoritative command surface.
|
|
|
198
199
|
validation for unsupported payloads.
|
|
199
200
|
- **Native resource ecosystem** — shared Praxis instructions with recursive `@`
|
|
200
201
|
imports, memory, skills, commands, agents, hooks, settings, MCP servers,
|
|
201
|
-
plugins, and append-only `praxis.transcript` JSONL sessions under `~/.praxis
|
|
202
|
+
plugins, and append-only `praxis.transcript` JSONL sessions under `~/.praxis`,
|
|
203
|
+
with bounded MCP connection, discovery, and tool operations plus safe
|
|
204
|
+
disconnect recovery that never replays an already-dispatched call.
|
|
202
205
|
- **Provider-neutral models** — native Anthropic Messages and OpenAI-compatible
|
|
203
206
|
streaming adapters with explicit capability checks, metering controls, and
|
|
204
207
|
a bounded absolute deadline for every provider attempt.
|
|
208
|
+
- **Transactional self-update** — `praxis update` verifies the package before
|
|
209
|
+
installing it, rejects concurrent updates, and can roll back after an
|
|
210
|
+
interruption or crash.
|
|
205
211
|
|
|
206
212
|
Detailed feature status and executable evidence live in the
|
|
207
213
|
[parity matrix](https://github.com/Forest-Isle/Praxis/blob/main/docs/PARITY_MATRIX.md),
|
package/dist/cli-runtime.d.ts
CHANGED
package/dist/cli-runtime.js
CHANGED
|
@@ -1381,6 +1381,7 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
1381
1381
|
provider: hostedToolProvider,
|
|
1382
1382
|
}),
|
|
1383
1383
|
resources: await runtimeMcpResources(resources.mcp),
|
|
1384
|
+
environment: runtimeEnvironment,
|
|
1384
1385
|
reloadResources: async () => {
|
|
1385
1386
|
if (cli.strictMcpConfig)
|
|
1386
1387
|
return runtimeMcpResources(cli.mcpResources);
|
|
@@ -2787,7 +2788,7 @@ async function executeDoctorCommand(args, invocation, io) {
|
|
|
2787
2788
|
}
|
|
2788
2789
|
return report.ok ? 0 : 1;
|
|
2789
2790
|
}
|
|
2790
|
-
async function executeSelfUpdateCommand(argv, io, dependencies) {
|
|
2791
|
+
async function executeSelfUpdateCommand(argv, io, dependencies, signal) {
|
|
2791
2792
|
const command = argv[0];
|
|
2792
2793
|
const values = [];
|
|
2793
2794
|
for (let index = 1; index < argv.length; index += 1) {
|
|
@@ -2811,6 +2812,7 @@ async function executeSelfUpdateCommand(argv, io, dependencies) {
|
|
|
2811
2812
|
const runtimeSettings = await loadRuntimeSettings({ configRoot, statePath });
|
|
2812
2813
|
const result = await dependencies.selfUpdate?.({
|
|
2813
2814
|
operation: 'update',
|
|
2815
|
+
...(signal ? { signal } : {}),
|
|
2814
2816
|
...(runtimeSettings.autoUpdatesChannel === 'latest'
|
|
2815
2817
|
? {}
|
|
2816
2818
|
: { target: runtimeSettings.autoUpdatesChannel }),
|
|
@@ -2835,6 +2837,7 @@ async function executeSelfUpdateCommand(argv, io, dependencies) {
|
|
|
2835
2837
|
throw new Error('install accepts at most one target');
|
|
2836
2838
|
const result = await dependencies.selfUpdate?.({
|
|
2837
2839
|
operation: 'install',
|
|
2840
|
+
...(signal ? { signal } : {}),
|
|
2838
2841
|
force,
|
|
2839
2842
|
...(targets[0] === undefined ? {} : { target: targets[0] }),
|
|
2840
2843
|
});
|
|
@@ -4599,7 +4602,7 @@ async function execute(argv, io, dependencies, signal) {
|
|
|
4599
4602
|
return executeProjectPurgeCommand(['project', 'purge', ...prefixedPurgeOptions, ...special.args.slice(2)], io);
|
|
4600
4603
|
}
|
|
4601
4604
|
if (['install', 'update', 'upgrade'].includes(special.args[0] ?? '')) {
|
|
4602
|
-
return executeSelfUpdateCommand([special.args[0], ...special.args.slice(1)], io, dependencies);
|
|
4605
|
+
return executeSelfUpdateCommand([special.args[0], ...special.args.slice(1)], io, dependencies, signal);
|
|
4603
4606
|
}
|
|
4604
4607
|
if (printCommandHelp(argv, io))
|
|
4605
4608
|
return 0;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export interface SelfUpdateLayout {
|
|
2
|
+
packageRoot: string;
|
|
3
|
+
globalNodeModulesRoot: string;
|
|
4
|
+
globalPrefix: string;
|
|
5
|
+
binPath: string;
|
|
6
|
+
}
|
|
7
|
+
export interface TransactionRunnerOptions {
|
|
8
|
+
cwd?: string;
|
|
9
|
+
env?: NodeJS.ProcessEnv;
|
|
10
|
+
timeout?: number;
|
|
11
|
+
maxBuffer?: number;
|
|
12
|
+
signal?: AbortSignal;
|
|
13
|
+
}
|
|
14
|
+
export type TransactionRunner = (executable: string, args: readonly string[], options: TransactionRunnerOptions) => Promise<{
|
|
15
|
+
stdout: string;
|
|
16
|
+
stderr: string;
|
|
17
|
+
}>;
|
|
18
|
+
export interface SelfUpdateTransactionOptions {
|
|
19
|
+
packageName: string;
|
|
20
|
+
target: string;
|
|
21
|
+
force: boolean;
|
|
22
|
+
npmExecutable: string;
|
|
23
|
+
timeoutMs: number;
|
|
24
|
+
signal?: AbortSignal;
|
|
25
|
+
run: TransactionRunner;
|
|
26
|
+
layout?: SelfUpdateLayout;
|
|
27
|
+
}
|
|
28
|
+
export interface SelfUpdateTransactionResult {
|
|
29
|
+
output: string;
|
|
30
|
+
}
|
|
31
|
+
/** Transactional updater implementation. */
|
|
32
|
+
export declare function runSelfUpdateTransaction(options: SelfUpdateTransactionOptions): Promise<SelfUpdateTransactionResult>;
|
|
33
|
+
export declare function generateLauncherSource(layout: SelfUpdateLayout, lockPath: string, journal: string): string;
|
|
34
|
+
export declare function validateSelfUpdateLayout(layout: SelfUpdateLayout): SelfUpdateLayout;
|
|
35
|
+
export declare function checksum(bytes: Uint8Array): {
|
|
36
|
+
sha1: string;
|
|
37
|
+
sha512: string;
|
|
38
|
+
};
|
|
39
|
+
//# sourceMappingURL=self-update-transaction.d.ts.map
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { access, chmod, mkdir, mkdtemp, open, readFile, rename, rm, stat, symlink, } from 'node:fs/promises';
|
|
3
|
+
import { dirname, basename, join, resolve } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { ExclusiveFileLease } from '../platform/exclusive-file-lease.js';
|
|
6
|
+
/** Transactional updater implementation. */
|
|
7
|
+
export async function runSelfUpdateTransaction(options) {
|
|
8
|
+
if (process.platform === 'win32')
|
|
9
|
+
throw new Error('self-update is unsupported on Windows');
|
|
10
|
+
options.signal?.throwIfAborted();
|
|
11
|
+
const layout = validateSelfUpdateLayout(options.layout ?? deriveLayout());
|
|
12
|
+
if (layout.packageRoot !==
|
|
13
|
+
join(layout.globalNodeModulesRoot, options.packageName))
|
|
14
|
+
throw new Error('current package is not installed in the requested global layout');
|
|
15
|
+
const parent = dirname(layout.packageRoot);
|
|
16
|
+
const lockPath = `${layout.packageRoot}.update.lock`;
|
|
17
|
+
let owner;
|
|
18
|
+
try {
|
|
19
|
+
owner = await new ExclusiveFileLease(lockPath).tryAcquire();
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
throw normalizeTransactionError(error);
|
|
23
|
+
}
|
|
24
|
+
if (!owner)
|
|
25
|
+
throw new Error('Praxis update already in progress');
|
|
26
|
+
let backup = '';
|
|
27
|
+
let staging = '';
|
|
28
|
+
const journal = `${layout.packageRoot}.update.journal`;
|
|
29
|
+
try {
|
|
30
|
+
await validateCandidate(layout.packageRoot, options.packageName);
|
|
31
|
+
options.signal?.throwIfAborted();
|
|
32
|
+
const spec = `${options.packageName}@${options.target}`;
|
|
33
|
+
const view = await options.run(options.npmExecutable, ['view', spec, 'version', 'dist', '--json'], runnerOptions(options));
|
|
34
|
+
const metadata = parseView(view.stdout, options.packageName);
|
|
35
|
+
staging = await mkdtemp(join(parent, '.praxis-update-'));
|
|
36
|
+
await chmod(staging, 0o700);
|
|
37
|
+
const download = join(staging, 'download');
|
|
38
|
+
await mkdir(download, { recursive: true, mode: 0o700 });
|
|
39
|
+
await chmod(download, 0o700);
|
|
40
|
+
const packed = await options.run(options.npmExecutable, [
|
|
41
|
+
'pack',
|
|
42
|
+
spec,
|
|
43
|
+
'--ignore-scripts',
|
|
44
|
+
'--json',
|
|
45
|
+
'--pack-destination',
|
|
46
|
+
download,
|
|
47
|
+
], runnerOptions(options));
|
|
48
|
+
const pack = parsePack(packed.stdout, metadata, download, options.packageName);
|
|
49
|
+
const bytes = await readFile(pack.path);
|
|
50
|
+
const sums = checksum(bytes);
|
|
51
|
+
if (sums.sha512 !== metadata.integrity || sums.sha1 !== metadata.shasum)
|
|
52
|
+
throw new Error('package integrity/checksum mismatch');
|
|
53
|
+
const stagingPrefix = join(staging, 'prefix');
|
|
54
|
+
await mkdir(stagingPrefix, { recursive: true, mode: 0o700 });
|
|
55
|
+
await chmod(stagingPrefix, 0o700);
|
|
56
|
+
const installArgs = [
|
|
57
|
+
'install',
|
|
58
|
+
'--global',
|
|
59
|
+
'--prefix',
|
|
60
|
+
stagingPrefix,
|
|
61
|
+
'--no-fund',
|
|
62
|
+
'--no-audit',
|
|
63
|
+
'--ignore-scripts',
|
|
64
|
+
...(options.force ? ['--force'] : []),
|
|
65
|
+
pack.path,
|
|
66
|
+
];
|
|
67
|
+
await options.run(options.npmExecutable, installArgs, runnerOptions(options));
|
|
68
|
+
const candidate = join(stagingPrefix, 'lib', 'node_modules', options.packageName);
|
|
69
|
+
await validateCandidate(candidate, options.packageName, metadata.version);
|
|
70
|
+
await gate(options, join(candidate, 'dist', 'cli.js'), metadata.version);
|
|
71
|
+
options.signal?.throwIfAborted();
|
|
72
|
+
backup = `${layout.packageRoot}.update-backup-${randomUUID()}`;
|
|
73
|
+
await installLauncher(layout, lockPath, journal);
|
|
74
|
+
await writeJournal(journal, {
|
|
75
|
+
version: 1,
|
|
76
|
+
root: layout.packageRoot,
|
|
77
|
+
backup,
|
|
78
|
+
staging,
|
|
79
|
+
targetVersion: metadata.version,
|
|
80
|
+
phase: 'prepared',
|
|
81
|
+
});
|
|
82
|
+
await rename(layout.packageRoot, backup);
|
|
83
|
+
await rename(candidate, layout.packageRoot);
|
|
84
|
+
await syncDirectory(parent);
|
|
85
|
+
await writeJournal(journal, {
|
|
86
|
+
version: 1,
|
|
87
|
+
root: layout.packageRoot,
|
|
88
|
+
backup,
|
|
89
|
+
staging,
|
|
90
|
+
targetVersion: metadata.version,
|
|
91
|
+
phase: 'candidate',
|
|
92
|
+
});
|
|
93
|
+
await gate(options, join(layout.packageRoot, 'dist', 'cli.js'), metadata.version);
|
|
94
|
+
await cleanupCompletedTransaction(backup, staging, journal);
|
|
95
|
+
return { output: 'completed' };
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
const failure = normalizeTransactionError(error);
|
|
99
|
+
if (backup) {
|
|
100
|
+
try {
|
|
101
|
+
await rollback(layout.packageRoot, backup, staging, journal);
|
|
102
|
+
}
|
|
103
|
+
catch (recovery) {
|
|
104
|
+
const recoveryFailure = normalizeTransactionError(recovery);
|
|
105
|
+
throw new Error(`${failure instanceof Error ? failure.message : String(failure)}; recovery failed: ${recoveryFailure instanceof Error ? recoveryFailure.message : String(recoveryFailure)}`, { cause: failure });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
else if (staging)
|
|
109
|
+
await cleanupCompletedTransaction('', staging, journal);
|
|
110
|
+
throw failure;
|
|
111
|
+
}
|
|
112
|
+
finally {
|
|
113
|
+
await releaseLease(owner);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function deriveLayout() {
|
|
117
|
+
const packageRoot = resolve(process.env.PRAXIS_SELF_UPDATE_ROOT ??
|
|
118
|
+
dirname(fileURLToPath(import.meta.url)), process.env.PRAXIS_SELF_UPDATE_ROOT ? '.' : '../..');
|
|
119
|
+
const globalNodeModulesRoot = dirname(packageRoot);
|
|
120
|
+
const globalPrefix = dirname(dirname(globalNodeModulesRoot));
|
|
121
|
+
return {
|
|
122
|
+
packageRoot,
|
|
123
|
+
globalNodeModulesRoot,
|
|
124
|
+
globalPrefix,
|
|
125
|
+
binPath: join(globalPrefix, 'bin', 'praxis'),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
function runnerOptions(options) {
|
|
129
|
+
return {
|
|
130
|
+
env: process.env,
|
|
131
|
+
timeout: options.timeoutMs,
|
|
132
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
133
|
+
...(options.signal ? { signal: options.signal } : {}),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
function parseJson(text) {
|
|
137
|
+
try {
|
|
138
|
+
return JSON.parse(text);
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
throw new Error('invalid npm metadata');
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function semver(value) {
|
|
145
|
+
return (typeof value === 'string' &&
|
|
146
|
+
/^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-(?:0|[1-9]\d*|[A-Za-z-][0-9A-Za-z-]*)(?:\.(?:0|[1-9]\d*|[A-Za-z-][0-9A-Za-z-]*))*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/u.test(value));
|
|
147
|
+
}
|
|
148
|
+
function parseView(text, name) {
|
|
149
|
+
const value = parseJson(text);
|
|
150
|
+
const dist = value.dist;
|
|
151
|
+
if (!semver(value.version) ||
|
|
152
|
+
!dist ||
|
|
153
|
+
typeof dist.tarball !== 'string' ||
|
|
154
|
+
!/^https:\/\//u.test(dist.tarball) ||
|
|
155
|
+
typeof dist.integrity !== 'string' ||
|
|
156
|
+
!/^sha512-[A-Za-z0-9+/]+=*$/u.test(dist.integrity) ||
|
|
157
|
+
typeof dist.shasum !== 'string' ||
|
|
158
|
+
!/^[0-9a-f]{40}$/iu.test(dist.shasum))
|
|
159
|
+
throw new Error(`invalid metadata for ${name}`);
|
|
160
|
+
return {
|
|
161
|
+
version: value.version,
|
|
162
|
+
integrity: dist.integrity,
|
|
163
|
+
shasum: dist.shasum.toLowerCase(),
|
|
164
|
+
tarball: dist.tarball,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
function parsePack(text, metadata, dir, packageName) {
|
|
168
|
+
const value = parseJson(text);
|
|
169
|
+
if (!Array.isArray(value) || value.length !== 1)
|
|
170
|
+
throw new Error('invalid npm pack metadata');
|
|
171
|
+
const record = value[0];
|
|
172
|
+
if (record.version !== metadata.version ||
|
|
173
|
+
record.name !== packageName ||
|
|
174
|
+
record.integrity !== metadata.integrity ||
|
|
175
|
+
String(record.shasum).toLowerCase() !== metadata.shasum ||
|
|
176
|
+
typeof record.filename !== 'string' ||
|
|
177
|
+
basename(record.filename) !== record.filename ||
|
|
178
|
+
!record.filename.endsWith('.tgz'))
|
|
179
|
+
throw new Error('npm pack metadata mismatch');
|
|
180
|
+
return { path: join(dir, record.filename) };
|
|
181
|
+
}
|
|
182
|
+
async function validateCandidate(root, name, expectedVersion) {
|
|
183
|
+
const manifest = parseJson(await readFile(join(root, 'package.json'), 'utf8'));
|
|
184
|
+
const cli = await stat(join(root, 'dist', 'cli.js'));
|
|
185
|
+
if (manifest.name !== name ||
|
|
186
|
+
!semver(manifest.version) ||
|
|
187
|
+
(expectedVersion !== undefined && manifest.version !== expectedVersion) ||
|
|
188
|
+
!cli.isFile())
|
|
189
|
+
throw new Error('staged package validation failed');
|
|
190
|
+
return manifest.version;
|
|
191
|
+
}
|
|
192
|
+
async function gate(options, cli, version) {
|
|
193
|
+
const result = await options.run(process.execPath, [cli, '--version'], runnerOptions(options));
|
|
194
|
+
if (result.stdout.trim() !== version)
|
|
195
|
+
throw new Error('candidate version gate failed');
|
|
196
|
+
}
|
|
197
|
+
async function writeJournal(path, value) {
|
|
198
|
+
const tmp = `${path}.${randomUUID()}.tmp`;
|
|
199
|
+
try {
|
|
200
|
+
const handle = await open(tmp, 'wx', 0o600);
|
|
201
|
+
try {
|
|
202
|
+
await handle.writeFile(JSON.stringify(value));
|
|
203
|
+
await handle.chmod(0o600);
|
|
204
|
+
await handle.sync();
|
|
205
|
+
}
|
|
206
|
+
finally {
|
|
207
|
+
await handle.close();
|
|
208
|
+
}
|
|
209
|
+
await rename(tmp, path);
|
|
210
|
+
await syncDirectory(dirname(path));
|
|
211
|
+
}
|
|
212
|
+
finally {
|
|
213
|
+
await rm(tmp, { force: true });
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
async function rollback(root, backup, staging, journal) {
|
|
217
|
+
if (await exists(backup)) {
|
|
218
|
+
let failed = '';
|
|
219
|
+
if (await exists(root)) {
|
|
220
|
+
failed = join(staging, 'failed-candidate');
|
|
221
|
+
await rename(root, failed);
|
|
222
|
+
}
|
|
223
|
+
await rename(backup, root);
|
|
224
|
+
await syncDirectory(dirname(root));
|
|
225
|
+
if (failed)
|
|
226
|
+
await rm(failed, { recursive: true, force: true });
|
|
227
|
+
}
|
|
228
|
+
else if (!(await exists(root))) {
|
|
229
|
+
throw new Error('update backup is unavailable');
|
|
230
|
+
}
|
|
231
|
+
await cleanupCompletedTransaction('', staging, journal);
|
|
232
|
+
}
|
|
233
|
+
async function cleanupCompletedTransaction(backup, staging, journal) {
|
|
234
|
+
try {
|
|
235
|
+
if (backup)
|
|
236
|
+
await rm(backup, { recursive: true, force: true });
|
|
237
|
+
if (staging)
|
|
238
|
+
await rm(staging, { recursive: true, force: true });
|
|
239
|
+
await rm(journal, { force: true });
|
|
240
|
+
await syncDirectory(dirname(journal));
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
243
|
+
// The external launcher retries validated cleanup once the lease is gone.
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
async function syncDirectory(path) {
|
|
247
|
+
const handle = await open(path, 'r');
|
|
248
|
+
try {
|
|
249
|
+
await handle.sync();
|
|
250
|
+
}
|
|
251
|
+
finally {
|
|
252
|
+
await handle.close();
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
async function exists(path) {
|
|
256
|
+
try {
|
|
257
|
+
await access(path);
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
260
|
+
catch {
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
function normalizeTransactionError(error) {
|
|
265
|
+
if (typeof error !== 'object' || error === null)
|
|
266
|
+
return error;
|
|
267
|
+
const systemError = error;
|
|
268
|
+
if (typeof systemError.path === 'string' ||
|
|
269
|
+
typeof systemError.dest === 'string' ||
|
|
270
|
+
typeof systemError.syscall === 'string') {
|
|
271
|
+
return new Error('self-update filesystem transaction failed', {
|
|
272
|
+
cause: error,
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
return error;
|
|
276
|
+
}
|
|
277
|
+
async function releaseLease(owner) {
|
|
278
|
+
try {
|
|
279
|
+
await owner.release();
|
|
280
|
+
}
|
|
281
|
+
catch (error) {
|
|
282
|
+
throw normalizeTransactionError(error);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
async function installLauncher(layout, lockPath, journal) {
|
|
286
|
+
const launcher = `${layout.packageRoot}.launcher.mjs`;
|
|
287
|
+
const source = generateLauncherSource(layout, lockPath, journal);
|
|
288
|
+
const tmp = `${launcher}.${randomUUID()}.tmp`;
|
|
289
|
+
try {
|
|
290
|
+
const handle = await open(tmp, 'wx', 0o700);
|
|
291
|
+
try {
|
|
292
|
+
await handle.writeFile(source);
|
|
293
|
+
await handle.chmod(0o700);
|
|
294
|
+
await handle.sync();
|
|
295
|
+
}
|
|
296
|
+
finally {
|
|
297
|
+
await handle.close();
|
|
298
|
+
}
|
|
299
|
+
await rename(tmp, launcher);
|
|
300
|
+
await syncDirectory(dirname(launcher));
|
|
301
|
+
const link = `${layout.binPath}.${randomUUID()}.tmp`;
|
|
302
|
+
try {
|
|
303
|
+
await symlink(launcher, link);
|
|
304
|
+
await rename(link, layout.binPath);
|
|
305
|
+
await syncDirectory(dirname(layout.binPath));
|
|
306
|
+
}
|
|
307
|
+
finally {
|
|
308
|
+
await rm(link, { force: true });
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
finally {
|
|
312
|
+
await rm(tmp, { force: true });
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
export function generateLauncherSource(layout, lockPath, journal) {
|
|
316
|
+
return `#!/usr/bin/env node
|
|
317
|
+
import { existsSync, readFileSync, renameSync, rmSync } from 'node:fs';
|
|
318
|
+
import { dirname, join, resolve } from 'node:path';
|
|
319
|
+
const root=${JSON.stringify(layout.packageRoot)}, parent=${JSON.stringify(dirname(layout.packageRoot))}, backupPrefix=${JSON.stringify(`${layout.packageRoot}.update-backup-`)}, stagingPrefix=${JSON.stringify(`${dirname(layout.packageRoot)}/.praxis-update-`)}, lock=${JSON.stringify(lockPath)}, journal=${JSON.stringify(journal)};
|
|
320
|
+
const alive=(p)=>{try{process.kill(p,0);return true}catch(e){return e.code==='EPERM'}};
|
|
321
|
+
const owner=()=>{try{const j=JSON.parse(readFileSync(lock,'utf8'));return j&&j.version===1&&Number.isSafeInteger(j.pid)&&j.pid>0&&typeof j.token==='string'&&/^[A-Za-z0-9_-]{1,128}$/.test(j.token)&&typeof j.createdAt==='string'&&alive(j.pid)}catch{return false}};
|
|
322
|
+
const sibling=(value,prefix)=>typeof value==='string'&&value===resolve(value)&&dirname(value)===parent&&value.startsWith(prefix)&&value.length>prefix.length;
|
|
323
|
+
const valid=(j)=>j&&j.version===1&&j.root===root&&sibling(j.backup,backupPrefix)&&sibling(j.staging,stagingPrefix)&&['prepared','backup','candidate'].includes(j.phase)&&typeof j.targetVersion==='string';
|
|
324
|
+
const liveOwner=owner();
|
|
325
|
+
if(liveOwner&&!existsSync(root)){let n=0;while(n++<20&&!existsSync(root)){await new Promise(r=>setTimeout(r,25))}if(!existsSync(root))throw new Error('Praxis update already in progress');
|
|
326
|
+
} else if(!liveOwner&&existsSync(journal)){const j=JSON.parse(readFileSync(journal,'utf8'));if(!valid(j))throw new Error('invalid update journal');if(!existsSync(root)&&existsSync(j.backup))renameSync(j.backup,root);if(existsSync(root)){if(existsSync(j.backup))rmSync(j.backup,{recursive:true,force:true});rmSync(j.staging,{recursive:true,force:true});rmSync(journal,{force:true});}}
|
|
327
|
+
if(!existsSync(root)) throw new Error('Praxis update recovery failed');
|
|
328
|
+
process.env.PRAXIS_SELF_UPDATE_ROOT=root; const controller=new AbortController(); const cancel=()=>controller.abort(); process.on('SIGINT',cancel); process.on('SIGTERM',cancel); try { const mod=await import(join(root,'dist','cli.js')); if(typeof mod.run!=='function') throw new Error('CLI entrypoint unavailable'); process.exitCode=await mod.run(process.argv.slice(2),undefined,undefined,controller.signal); } finally { process.removeListener('SIGINT',cancel); process.removeListener('SIGTERM',cancel); }
|
|
329
|
+
`;
|
|
330
|
+
}
|
|
331
|
+
export function validateSelfUpdateLayout(layout) {
|
|
332
|
+
const packageRoot = resolve(layout.packageRoot);
|
|
333
|
+
const modules = resolve(layout.globalNodeModulesRoot);
|
|
334
|
+
const prefix = resolve(layout.globalPrefix);
|
|
335
|
+
const binPath = resolve(layout.binPath);
|
|
336
|
+
if (dirname(packageRoot) !== modules ||
|
|
337
|
+
dirname(modules) !== join(prefix, 'lib')) {
|
|
338
|
+
throw new Error('unsupported global npm installation layout');
|
|
339
|
+
}
|
|
340
|
+
if (binPath !== join(prefix, 'bin', 'praxis')) {
|
|
341
|
+
throw new Error('unsupported global npm bin layout');
|
|
342
|
+
}
|
|
343
|
+
return {
|
|
344
|
+
packageRoot,
|
|
345
|
+
globalNodeModulesRoot: modules,
|
|
346
|
+
globalPrefix: prefix,
|
|
347
|
+
binPath,
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
export function checksum(bytes) {
|
|
351
|
+
return {
|
|
352
|
+
sha1: createHash('sha1').update(bytes).digest('hex'),
|
|
353
|
+
sha512: `sha512-${createHash('sha512').update(bytes).digest('base64')}`,
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
//# sourceMappingURL=self-update-transaction.js.map
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type SelfUpdateLayout } from './self-update-transaction.js';
|
|
1
2
|
export type SelfUpdateOperation = 'install' | 'update';
|
|
2
3
|
export interface SelfUpdateOptions {
|
|
3
4
|
operation: SelfUpdateOperation;
|
|
@@ -6,6 +7,8 @@ export interface SelfUpdateOptions {
|
|
|
6
7
|
packageName?: string;
|
|
7
8
|
npmExecutable?: string;
|
|
8
9
|
timeoutMs?: number;
|
|
10
|
+
signal?: AbortSignal;
|
|
11
|
+
layout?: SelfUpdateLayout;
|
|
9
12
|
run?: SelfUpdateRunner;
|
|
10
13
|
}
|
|
11
14
|
export interface SelfUpdateRunnerOptions {
|
|
@@ -13,6 +16,7 @@ export interface SelfUpdateRunnerOptions {
|
|
|
13
16
|
env?: NodeJS.ProcessEnv;
|
|
14
17
|
timeout?: number;
|
|
15
18
|
maxBuffer?: number;
|
|
19
|
+
signal?: AbortSignal;
|
|
16
20
|
}
|
|
17
21
|
export type SelfUpdateRunner = (executable: string, args: readonly string[], options: SelfUpdateRunnerOptions) => Promise<{
|
|
18
22
|
stdout: string;
|
|
@@ -1,20 +1,27 @@
|
|
|
1
1
|
import { execFile } from 'node:child_process';
|
|
2
2
|
import { promisify } from 'node:util';
|
|
3
|
+
import { runSelfUpdateTransaction, } from './self-update-transaction.js';
|
|
3
4
|
const execFileAsync = promisify(execFile);
|
|
4
5
|
const DEFAULT_PACKAGE_NAME = 'praxis-agent';
|
|
5
6
|
const DEFAULT_TIMEOUT_MS = 120_000;
|
|
6
|
-
const MAX_OUTPUT_BYTES = 4 * 1024 * 1024;
|
|
7
7
|
const runCommand = async (executable, args, options) => {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
8
|
+
try {
|
|
9
|
+
const result = await execFileAsync(executable, [...args], {
|
|
10
|
+
cwd: options.cwd,
|
|
11
|
+
env: options.env,
|
|
12
|
+
timeout: options.timeout,
|
|
13
|
+
maxBuffer: options.maxBuffer,
|
|
14
|
+
signal: options.signal,
|
|
15
|
+
});
|
|
16
|
+
return {
|
|
17
|
+
stdout: String(result.stdout ?? ''),
|
|
18
|
+
stderr: String(result.stderr ?? ''),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
options.signal?.throwIfAborted();
|
|
23
|
+
throw new Error('self-update subprocess failed', { cause: error });
|
|
24
|
+
}
|
|
18
25
|
};
|
|
19
26
|
function validPackageName(value) {
|
|
20
27
|
return /^(?:@[^/\s]+\/)?[^/\s]+$/u.test(value);
|
|
@@ -30,10 +37,6 @@ function requireTarget(value) {
|
|
|
30
37
|
}
|
|
31
38
|
return target;
|
|
32
39
|
}
|
|
33
|
-
function commandOutput(stdout, stderr) {
|
|
34
|
-
const output = `${stdout.trim()}${stderr.trim() ? `\n${stderr.trim()}` : ''}`;
|
|
35
|
-
return output.length > 0 ? output : 'completed';
|
|
36
|
-
}
|
|
37
40
|
export async function runSelfUpdate(options) {
|
|
38
41
|
const packageName = options.packageName ?? DEFAULT_PACKAGE_NAME;
|
|
39
42
|
if (!validPackageName(packageName)) {
|
|
@@ -56,10 +59,16 @@ export async function runSelfUpdate(options) {
|
|
|
56
59
|
const runner = options.run ?? runCommand;
|
|
57
60
|
let result;
|
|
58
61
|
try {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
const transactionRunner = runner;
|
|
63
|
+
result = await runSelfUpdateTransaction({
|
|
64
|
+
packageName,
|
|
65
|
+
target,
|
|
66
|
+
force,
|
|
67
|
+
npmExecutable,
|
|
68
|
+
timeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
69
|
+
run: transactionRunner,
|
|
70
|
+
...(options.signal ? { signal: options.signal } : {}),
|
|
71
|
+
...(options.layout ? { layout: options.layout } : {}),
|
|
63
72
|
});
|
|
64
73
|
}
|
|
65
74
|
catch (error) {
|
|
@@ -75,7 +84,7 @@ export async function runSelfUpdate(options) {
|
|
|
75
84
|
target,
|
|
76
85
|
force,
|
|
77
86
|
command: [npmExecutable, ...args],
|
|
78
|
-
output:
|
|
87
|
+
output: result.output,
|
|
79
88
|
};
|
|
80
89
|
}
|
|
81
90
|
//# sourceMappingURL=self-update.js.map
|
|
@@ -70,6 +70,7 @@ export interface ClaudeMcpToolRegistryOptions {
|
|
|
70
70
|
configRoot?: string;
|
|
71
71
|
onWarning?: (message: string) => void;
|
|
72
72
|
signal?: AbortSignal;
|
|
73
|
+
environment?: NodeJS.ProcessEnv;
|
|
73
74
|
eventSink?: RuntimeEventSink;
|
|
74
75
|
onPromptsChanged?: (prompts: readonly ClaudeMcpPromptDefinition[]) => void;
|
|
75
76
|
onInstructionsChanged?: (instructions: readonly ClaudeMcpServerInstruction[]) => void;
|
|
@@ -91,21 +92,22 @@ export declare function validateClaudeMcpConfiguration(resources: readonly JsonR
|
|
|
91
92
|
export declare class ClaudeMcpToolRegistry implements ToolRegistry, ClaudeMcpRuntime {
|
|
92
93
|
private readonly options;
|
|
93
94
|
private readonly connectedTools;
|
|
95
|
+
private readonly toolRoutes;
|
|
94
96
|
private readonly reservedTools;
|
|
95
97
|
private readonly resourceServers;
|
|
98
|
+
private readonly resourceRoutes;
|
|
96
99
|
private readonly promptServers;
|
|
97
100
|
private readonly statuses;
|
|
98
|
-
private readonly
|
|
99
|
-
private readonly
|
|
100
|
-
private readonly reconnectableServers;
|
|
101
|
+
private readonly sessions;
|
|
102
|
+
private readonly serverSensitiveValues;
|
|
101
103
|
private readonly serverCapabilities;
|
|
102
104
|
private readonly serverInstructions;
|
|
103
|
-
private readonly reconnectingServers;
|
|
104
105
|
private readonly promptOperations;
|
|
105
106
|
private promptResultDirectoryPromise;
|
|
106
107
|
private closePromise;
|
|
107
108
|
private generation;
|
|
108
109
|
private closed;
|
|
110
|
+
private readonly timeouts;
|
|
109
111
|
private constructor();
|
|
110
112
|
static connect(options: ClaudeMcpToolRegistryOptions): Promise<ClaudeMcpToolRegistry>;
|
|
111
113
|
definitions(): readonly ModelToolDefinition[];
|
|
@@ -134,16 +136,17 @@ export declare class ClaudeMcpToolRegistry implements ToolRegistry, ClaudeMcpRun
|
|
|
134
136
|
execute(call: ModelToolCall, context: ToolExecutionContext): Promise<ToolExecutionResult>;
|
|
135
137
|
close(): Promise<void>;
|
|
136
138
|
private finishClose;
|
|
139
|
+
private awaitPromptOperationsBounded;
|
|
137
140
|
private listResources;
|
|
138
141
|
private readResource;
|
|
139
142
|
private resourceServer;
|
|
140
143
|
private connectServer;
|
|
141
|
-
private discoverTools;
|
|
142
|
-
private discoverResources;
|
|
143
|
-
private discoverPrompts;
|
|
144
144
|
private invokePrompt;
|
|
145
145
|
private ensurePromptConnected;
|
|
146
146
|
private reconnectServer;
|
|
147
|
+
private removeServerPublication;
|
|
148
|
+
private removeServerRoutes;
|
|
149
|
+
private publishCatalog;
|
|
147
150
|
private publishPrompts;
|
|
148
151
|
private publishInstructions;
|
|
149
152
|
private assertOpenGeneration;
|