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 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),
@@ -174,6 +174,7 @@ export interface CliDependencies extends InteractiveServiceFactory {
174
174
  operation: 'install' | 'update';
175
175
  target?: string;
176
176
  force?: boolean;
177
+ signal?: AbortSignal;
177
178
  }) => Promise<SelfUpdateResult>;
178
179
  }
179
180
  /**
@@ -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
- const result = await execFileAsync(executable, [...args], {
9
- cwd: options.cwd,
10
- env: options.env,
11
- timeout: options.timeout,
12
- maxBuffer: options.maxBuffer,
13
- });
14
- return {
15
- stdout: String(result.stdout ?? ''),
16
- stderr: String(result.stderr ?? ''),
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
- result = await runner(npmExecutable, args, {
60
- env: process.env,
61
- timeout: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
62
- maxBuffer: MAX_OUTPUT_BYTES,
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: commandOutput(result.stdout, result.stderr),
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 clients;
99
- private readonly serverClients;
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;