praxis-agent 0.40.0 → 0.42.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.
@@ -1,3 +1,5 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { createReadStream } from 'node:fs';
1
3
  import { cp, mkdir, readFile, readdir, realpath, rename, rm, stat, writeFile, } from 'node:fs/promises';
2
4
  import { execFile } from 'node:child_process';
3
5
  import { basename, dirname, extname, join, relative, resolve } from 'node:path';
@@ -5,8 +7,34 @@ import { promisify } from 'node:util';
5
7
  import { parse as parseYaml } from 'yaml';
6
8
  import { countTokens } from '@anthropic-ai/tokenizer';
7
9
  import { claudePluginDataPath, materializeClaudePluginSource, readClaudePluginOptions, readClaudePluginMcpServerOptions, readClaudeSkillsDirectoryPlugins, replaceClaudePluginDirectory, readClaudeInstalledPlugins, validateClaudePluginUserConfig, } from './claude-plugin-marketplace.js';
8
- import { loadClaudePluginMcpb } from './claude-plugin-mcpb.js';
10
+ import { CLAUDE_PLUGIN_MCPB_ARCHIVE_BYTES, loadClaudePluginMcpb, } from './claude-plugin-mcpb.js';
9
11
  const execFileAsync = promisify(execFile);
12
+ async function boundedFileSha256(path) {
13
+ const metadata = await stat(path);
14
+ if (!metadata.isFile()) {
15
+ throw new Error(`MCPB archive must be a regular file: ${path}`);
16
+ }
17
+ if (metadata.size > CLAUDE_PLUGIN_MCPB_ARCHIVE_BYTES) {
18
+ throw new Error(`MCPB archive exceeds ${CLAUDE_PLUGIN_MCPB_ARCHIVE_BYTES} bytes`);
19
+ }
20
+ const source = createReadStream(path);
21
+ const hash = createHash('sha256');
22
+ let bytes = 0;
23
+ try {
24
+ for await (const chunk of source) {
25
+ const value = chunk;
26
+ bytes += value.byteLength;
27
+ if (bytes > CLAUDE_PLUGIN_MCPB_ARCHIVE_BYTES) {
28
+ throw new Error(`MCPB archive exceeds ${CLAUDE_PLUGIN_MCPB_ARCHIVE_BYTES} bytes`);
29
+ }
30
+ hash.update(value);
31
+ }
32
+ return hash.digest('hex');
33
+ }
34
+ finally {
35
+ source.destroy();
36
+ }
37
+ }
10
38
  const PLUGIN_MANIFEST = join('.claude-plugin', 'plugin.json');
11
39
  const LEGACY_MANIFEST = 'plugin.json';
12
40
  const MAX_PLUGIN_FILES = 2_000;
@@ -139,6 +167,16 @@ function safePluginPath(pluginPath, candidate) {
139
167
  }
140
168
  return resolved;
141
169
  }
170
+ async function canonicalPluginPath(pluginPath, candidate) {
171
+ const [root, resolved] = await Promise.all([
172
+ realpath(pluginPath),
173
+ realpath(candidate),
174
+ ]);
175
+ if (resolved !== root && !resolved.startsWith(`${root}/`)) {
176
+ throw new Error(`Plugin path escapes plugin root: ${candidate}`);
177
+ }
178
+ return resolved;
179
+ }
142
180
  async function assertPluginPath(pluginPath, candidate) {
143
181
  let resolved;
144
182
  try {
@@ -462,7 +500,7 @@ async function loadLspServers(root, manifest, source, configRoot, environment, u
462
500
  }
463
501
  return [...definitions.values()];
464
502
  }
465
- async function loadPlugin(pluginPath, source, enabled, cwd, requireManifest = false, resourceScope, configRoot, environment = process.env, configId, readOnlyHooks = false, dataPlane) {
503
+ async function loadPlugin(pluginPath, source, enabled, cwd, requireManifest = false, resourceScope, configRoot, environment = process.env, configId, readOnlyHooks = false, readOnlyExecutables = false, allowWorkspaceMcpb = true, dataPlane) {
466
504
  const canonical = await realpath(pluginPath);
467
505
  if (!(await isDirectory(canonical)))
468
506
  throw new Error(`Plugin path is not a directory: ${pluginPath}`);
@@ -480,7 +518,8 @@ async function loadPlugin(pluginPath, source, enabled, cwd, requireManifest = fa
480
518
  : [])
481
519
  .filter((value) => value.length > 0);
482
520
  const pluginData = claudePluginDataPath(configRoot ?? canonical, source);
483
- if (configRoot !== undefined && !readOnlyHooks) {
521
+ const readOnlyComponents = readOnlyHooks || readOnlyExecutables;
522
+ if (configRoot !== undefined && !readOnlyComponents) {
484
523
  await mkdir(pluginData, { recursive: true });
485
524
  }
486
525
  const pluginEnvironment = pluginOptionEnvironment(canonical, pluginData, userConfig);
@@ -491,7 +530,7 @@ async function loadPlugin(pluginPath, source, enabled, cwd, requireManifest = fa
491
530
  environment: pluginEnvironment,
492
531
  ...(sensitiveValues.length === 0 ? {} : { sensitiveValues }),
493
532
  };
494
- const scope = resourceScope ?? scopeForPath(canonical, cwd);
533
+ const scope = resourceScope ?? scopeForPath(canonical, await realpath(resolve(cwd)));
495
534
  const commandDefinitions = isRecord(manifest.commands)
496
535
  ? manifest.commands
497
536
  : undefined;
@@ -506,7 +545,7 @@ async function loadPlugin(pluginPath, source, enabled, cwd, requireManifest = fa
506
545
  ? [join(canonical, 'skills'), canonical]
507
546
  : pathList(manifest.skills, 'skills').map((path) => safePluginPath(canonical, path));
508
547
  const agentsRoots = pathList(manifest.agents, 'agents').map((path) => safePluginPath(canonical, path));
509
- const [commands, skills, agents, lsp] = readOnlyHooks
548
+ const [commands, skills, agents, lsp] = readOnlyComponents
510
549
  ? [[], [], [], []]
511
550
  : await (async () => {
512
551
  const [commandFiles, skillFiles, agentFiles, loadedLsp] = await Promise.all([
@@ -524,7 +563,7 @@ async function loadPlugin(pluginPath, source, enabled, cwd, requireManifest = fa
524
563
  ]);
525
564
  return [...loadedText, loadedLsp];
526
565
  })();
527
- if (commandDefinitions && !readOnlyHooks) {
566
+ if (commandDefinitions && !readOnlyComponents) {
528
567
  for (const [commandName, definition] of Object.entries(commandDefinitions)) {
529
568
  if (definition.content !== undefined) {
530
569
  commands.push({
@@ -649,17 +688,61 @@ async function loadPlugin(pluginPath, source, enabled, cwd, requireManifest = fa
649
688
  if (typeof spec === 'string') {
650
689
  const urlReference = isUrlReference(spec);
651
690
  if ((urlReference &&
652
- (!/^https?:\/\//u.test(spec) || !isMcpbReference(spec))) ||
691
+ (!/^https?:\/\//u.test(spec) ||
692
+ !isMcpbReference(spec) ||
693
+ (scope !== 'user' && !/^https:\/\//u.test(spec)))) ||
653
694
  (!urlReference && isMcpbReference(spec) && !spec.startsWith('./'))) {
654
695
  mcpErrors.push(`Invalid plugin MCPB reference at index ${index}: expected ./file.mcpb, ./file.dxt, or an exact-suffix HTTP(S) URL`);
655
696
  continue;
656
697
  }
657
698
  if (isMcpbReference(spec)) {
699
+ let mcpbPath;
700
+ let sourceFingerprint;
701
+ try {
702
+ mcpbPath = urlReference
703
+ ? join(canonical, '.claude-plugin', `plugin-mcpb-${index}.json`)
704
+ : await canonicalPluginPath(canonical, safePluginPath(canonical, spec));
705
+ sourceFingerprint = createHash('sha256')
706
+ .update(urlReference ? spec : await boundedFileSha256(mcpbPath))
707
+ .digest('hex');
708
+ }
709
+ catch (error) {
710
+ mcpErrors.push(`Invalid plugin MCPB reference at index ${index}: ${error instanceof Error ? error.message : String(error)}`);
711
+ continue;
712
+ }
713
+ const pluginExecutableSource = {
714
+ kind: 'mcpb',
715
+ source: spec,
716
+ fingerprint: sourceFingerprint,
717
+ };
718
+ if (readOnlyExecutables) {
719
+ mcp.push({
720
+ path: mcpbPath,
721
+ scope,
722
+ plugin: true,
723
+ value: {
724
+ mcpServers: {
725
+ [`plugin:${manifest.name}:mcpb-${index}`]: {
726
+ type: 'mcpb-reference',
727
+ source: spec,
728
+ fingerprint: sourceFingerprint,
729
+ },
730
+ },
731
+ },
732
+ environment: pluginEnvironment,
733
+ pluginExecutableSource,
734
+ sensitiveValues,
735
+ });
736
+ continue;
737
+ }
738
+ if (scope !== 'user' && !allowWorkspaceMcpb)
739
+ continue;
658
740
  try {
659
741
  const loaded = await loadClaudePluginMcpb({
660
742
  pluginRoot: canonical,
661
743
  pluginData,
662
744
  source: spec,
745
+ requireHttps: scope !== 'user',
663
746
  ...(configRoot === undefined ? {} : { configRoot }),
664
747
  ...(dataPlane === undefined ? {} : { dataPlane }),
665
748
  environment,
@@ -667,8 +750,14 @@ async function loadPlugin(pluginPath, source, enabled, cwd, requireManifest = fa
667
750
  ? readClaudePluginMcpServerOptions(configRoot, cwd, configId, bundleManifest.name, bundleManifest.user_config, dataPlane)
668
751
  : {},
669
752
  });
753
+ if (!urlReference &&
754
+ createHash('sha256')
755
+ .update(await boundedFileSha256(mcpbPath))
756
+ .digest('hex') !== sourceFingerprint) {
757
+ throw new Error(`Plugin MCPB source changed while loading: ${mcpbPath}`);
758
+ }
670
759
  mcp.push({
671
- path: join(canonical, '.claude-plugin', `plugin-mcpb-${index}.json`),
760
+ path: mcpbPath,
672
761
  scope,
673
762
  plugin: true,
674
763
  value: {
@@ -677,6 +766,7 @@ async function loadPlugin(pluginPath, source, enabled, cwd, requireManifest = fa
677
766
  },
678
767
  },
679
768
  environment: pluginEnvironment,
769
+ pluginExecutableSource,
680
770
  sensitiveValues: [...sensitiveValues, ...loaded.sensitiveValues],
681
771
  });
682
772
  }
@@ -840,7 +930,7 @@ export async function loadClaudePlugins(options) {
840
930
  if (seen.has(canonical))
841
931
  return null;
842
932
  seen.add(canonical);
843
- return await loadPlugin(canonical, candidate.source, candidate.enabled, options.cwd, false, candidate.resourceScope, options.configRoot, options.environment ?? process.env, candidate.configId, options.readOnlyHooks, options.dataPlane);
933
+ return await loadPlugin(canonical, candidate.source, candidate.enabled, options.cwd, false, candidate.resourceScope, options.configRoot, options.environment ?? process.env, candidate.configId, options.readOnlyHooks, options.readOnlyExecutables, options.allowWorkspaceMcpb, options.dataPlane);
844
934
  }
845
935
  catch (error) {
846
936
  if (options.strictPluginDirectories &&
@@ -0,0 +1,16 @@
1
+ import { type ModelProvider, type ModelRequest, type ModelStreamEvent } from '../core/runtime.js';
2
+ export declare const DEFAULT_PROVIDER_DEADLINE_MS = 90000;
3
+ export interface DeadlineModelProviderOptions {
4
+ provider: ModelProvider;
5
+ deadlineMs?: number;
6
+ }
7
+ /** Bounds each streamed completion while preserving the wrapped provider's API. */
8
+ export declare class DeadlineModelProvider implements ModelProvider {
9
+ private readonly provider;
10
+ private readonly deadlineMs;
11
+ readonly model?: string;
12
+ constructor(options: DeadlineModelProviderOptions);
13
+ get capabilities(): ModelProvider['capabilities'];
14
+ complete(request: ModelRequest): AsyncIterable<ModelStreamEvent>;
15
+ }
16
+ //# sourceMappingURL=deadline-provider.d.ts.map
@@ -0,0 +1,226 @@
1
+ import { ModelProviderError, } from '../core/runtime.js';
2
+ export const DEFAULT_PROVIDER_DEADLINE_MS = 90_000;
3
+ const MAX_TIMER_DELAY_MS = 2_147_483_647;
4
+ function deferred() {
5
+ let resolve;
6
+ const promise = new Promise((promiseResolve) => {
7
+ resolve = promiseResolve;
8
+ });
9
+ return { promise, resolve };
10
+ }
11
+ /** Bounds each streamed completion while preserving the wrapped provider's API. */
12
+ export class DeadlineModelProvider {
13
+ provider;
14
+ deadlineMs;
15
+ constructor(options) {
16
+ const deadlineMs = options.deadlineMs === undefined
17
+ ? DEFAULT_PROVIDER_DEADLINE_MS
18
+ : options.deadlineMs;
19
+ if (!Number.isFinite(deadlineMs) ||
20
+ !Number.isInteger(deadlineMs) ||
21
+ deadlineMs <= 0) {
22
+ throw new Error('Provider deadline must be a positive integer');
23
+ }
24
+ this.provider = options.provider;
25
+ this.deadlineMs = deadlineMs;
26
+ Object.defineProperty(this, 'model', {
27
+ configurable: false,
28
+ enumerable: true,
29
+ get: () => this.provider.model,
30
+ });
31
+ }
32
+ get capabilities() {
33
+ return this.provider.capabilities;
34
+ }
35
+ complete(request) {
36
+ let started = false;
37
+ let closed = false;
38
+ let interruption;
39
+ let controller;
40
+ let callerSignal;
41
+ let timer;
42
+ let deadlineStartedAt;
43
+ let underlying;
44
+ let cleanupStarted = false;
45
+ const interruptionDeferred = deferred();
46
+ const interruptionError = () => {
47
+ if (interruption?.kind === 'timeout') {
48
+ return new ModelProviderError('Provider request timed out', {
49
+ kind: 'timeout',
50
+ retryable: true,
51
+ cause: interruption.cause,
52
+ });
53
+ }
54
+ return new ModelProviderError('Provider request cancelled', {
55
+ kind: 'cancelled',
56
+ retryable: false,
57
+ cause: interruption?.cause,
58
+ });
59
+ };
60
+ const bestEffortUnderlyingReturn = () => {
61
+ const iterator = underlying;
62
+ if (!iterator || cleanupStarted)
63
+ return;
64
+ cleanupStarted = true;
65
+ let returnMethod;
66
+ try {
67
+ returnMethod = iterator.return;
68
+ }
69
+ catch {
70
+ return;
71
+ }
72
+ if (!returnMethod)
73
+ return;
74
+ try {
75
+ const result = returnMethod.call(iterator);
76
+ if (result && typeof result.then === 'function') {
77
+ void Promise.resolve(result).catch(() => undefined);
78
+ }
79
+ }
80
+ catch {
81
+ // Cleanup is best effort; preserve the original completion outcome.
82
+ }
83
+ };
84
+ const clearResources = () => {
85
+ if (timer !== undefined)
86
+ clearTimeout(timer);
87
+ timer = undefined;
88
+ callerSignal?.removeEventListener('abort', onCallerAbort);
89
+ };
90
+ const finish = (outcome) => {
91
+ if (interruption !== undefined)
92
+ return;
93
+ interruption = outcome;
94
+ closed = true;
95
+ interruptionDeferred.resolve(outcome);
96
+ controller?.abort(outcome.cause);
97
+ clearResources();
98
+ if (outcome.kind !== 'return')
99
+ bestEffortUnderlyingReturn();
100
+ };
101
+ const onCallerAbort = () => finish({ kind: 'cancelled', cause: callerSignal?.reason });
102
+ const timeoutCause = new Error('Provider request timed out');
103
+ timeoutCause.name = 'TimeoutError';
104
+ const scheduleDeadline = () => {
105
+ if (deadlineStartedAt === undefined || closed)
106
+ return;
107
+ const elapsedMs = Math.max(0, performance.now() - deadlineStartedAt);
108
+ const remainingMs = this.deadlineMs - elapsedMs;
109
+ if (remainingMs <= 0) {
110
+ finish({ kind: 'timeout', cause: timeoutCause });
111
+ return;
112
+ }
113
+ timer = setTimeout(scheduleDeadline, Math.min(remainingMs, MAX_TIMER_DELAY_MS));
114
+ };
115
+ const start = () => {
116
+ if (started || closed)
117
+ return;
118
+ started = true;
119
+ controller = new AbortController();
120
+ callerSignal = request.signal;
121
+ if (callerSignal?.aborted)
122
+ onCallerAbort();
123
+ else
124
+ callerSignal?.addEventListener('abort', onCallerAbort, { once: true });
125
+ if (interruption !== undefined)
126
+ return;
127
+ deadlineStartedAt = performance.now();
128
+ scheduleDeadline();
129
+ const iterable = this.provider.complete({
130
+ ...request,
131
+ signal: controller.signal,
132
+ });
133
+ underlying = iterable[Symbol.asyncIterator]();
134
+ if (interruption !== undefined)
135
+ bestEffortUnderlyingReturn();
136
+ };
137
+ const next = async () => {
138
+ try {
139
+ start();
140
+ }
141
+ catch (error) {
142
+ if (interruption !== undefined)
143
+ throw interruptionError();
144
+ closed = true;
145
+ clearResources();
146
+ bestEffortUnderlyingReturn();
147
+ throw error;
148
+ }
149
+ if (interruption?.kind === 'return')
150
+ return { done: true, value: undefined };
151
+ if (interruption !== undefined)
152
+ throw interruptionError();
153
+ if (closed || !underlying)
154
+ return { done: true, value: undefined };
155
+ let nextPromise;
156
+ try {
157
+ const result = underlying.next();
158
+ nextPromise = Promise.resolve(result);
159
+ }
160
+ catch (error) {
161
+ if (interruption !== undefined)
162
+ throw interruptionError();
163
+ closed = true;
164
+ clearResources();
165
+ bestEffortUnderlyingReturn();
166
+ throw error;
167
+ }
168
+ if (interruption !== undefined) {
169
+ void nextPromise.catch(() => undefined);
170
+ throw interruptionError();
171
+ }
172
+ const result = await Promise.race([
173
+ nextPromise.then((value) => ({ type: 'result', value }), (error) => ({ type: 'error', error })),
174
+ interruptionDeferred.promise.then((outcome) => ({
175
+ type: 'interruption',
176
+ outcome,
177
+ })),
178
+ ]);
179
+ const observedInterruption = interruption ??
180
+ (result.type === 'interruption' ? result.outcome : undefined);
181
+ if (observedInterruption !== undefined) {
182
+ if (result.type === 'result')
183
+ void nextPromise.catch(() => undefined);
184
+ if (observedInterruption.kind === 'return')
185
+ return { done: true, value: undefined };
186
+ throw interruptionError();
187
+ }
188
+ if (result.type === 'error') {
189
+ closed = true;
190
+ clearResources();
191
+ bestEffortUnderlyingReturn();
192
+ throw result.error;
193
+ }
194
+ if (result.type === 'interruption') {
195
+ if (result.outcome.kind === 'return')
196
+ return { done: true, value: undefined };
197
+ throw interruptionError();
198
+ }
199
+ if (result.value.done) {
200
+ closed = true;
201
+ clearResources();
202
+ }
203
+ return result.value;
204
+ };
205
+ const returnFromConsumer = async () => {
206
+ if (!started) {
207
+ closed = true;
208
+ return { done: true, value: undefined };
209
+ }
210
+ if (!closed) {
211
+ finish({ kind: 'return', cause: undefined });
212
+ bestEffortUnderlyingReturn();
213
+ }
214
+ return { done: true, value: undefined };
215
+ };
216
+ const result = {
217
+ [Symbol.asyncIterator]() {
218
+ return result;
219
+ },
220
+ next,
221
+ return: returnFromConsumer,
222
+ };
223
+ return result;
224
+ }
225
+ }
226
+ //# sourceMappingURL=deadline-provider.js.map
@@ -1,6 +1,7 @@
1
1
  export interface ProviderEnvironment {
2
2
  provider: 'openai' | 'anthropic';
3
3
  baseUrl: string;
4
+ deadlineMs: number;
4
5
  maxOutputTokens?: number;
5
6
  anthropicVersion?: string;
6
7
  webSearch?: boolean;
@@ -1,9 +1,20 @@
1
1
  import { createAnthropicPromptCachePolicyResolver } from './anthropic-prompt-cache.js';
2
+ import { DEFAULT_PROVIDER_DEADLINE_MS } from './deadline-provider.js';
2
3
  export function parseProviderEnvironment(environment) {
3
4
  const provider = environment.PRAXIS_PROVIDER ?? 'openai';
4
5
  if (provider !== 'openai' && provider !== 'anthropic') {
5
6
  throw new Error('PRAXIS_PROVIDER must be openai or anthropic');
6
7
  }
8
+ const rawDeadlineMs = environment.PRAXIS_PROVIDER_DEADLINE_MS;
9
+ if (rawDeadlineMs !== undefined &&
10
+ (!/^\d+$/.test(rawDeadlineMs) ||
11
+ Number(rawDeadlineMs) <= 0 ||
12
+ !Number.isSafeInteger(Number(rawDeadlineMs)))) {
13
+ throw new Error('PRAXIS_PROVIDER_DEADLINE_MS must be a positive safe integer');
14
+ }
15
+ const deadlineMs = rawDeadlineMs === undefined
16
+ ? DEFAULT_PROVIDER_DEADLINE_MS
17
+ : Number(rawDeadlineMs);
7
18
  const maxOutputTokens = environment.PRAXIS_MAX_OUTPUT_TOKENS;
8
19
  if (maxOutputTokens !== undefined &&
9
20
  (!/^\d+$/.test(maxOutputTokens) ||
@@ -47,6 +58,7 @@ export function parseProviderEnvironment(environment) {
47
58
  (provider === 'anthropic'
48
59
  ? 'https://api.anthropic.com/v1'
49
60
  : 'https://api.openai.com/v1'),
61
+ deadlineMs,
50
62
  ...(maxOutputTokens === undefined
51
63
  ? {}
52
64
  : { maxOutputTokens: Number(maxOutputTokens) }),
@@ -6,6 +6,38 @@ function retryable(error) {
6
6
  error.retryable &&
7
7
  modelProviderErrorKind(error) !== 'prompt_too_long');
8
8
  }
9
+ function cancellationError(signal) {
10
+ return new ModelProviderError('Provider request cancelled', {
11
+ kind: 'cancelled',
12
+ retryable: false,
13
+ cause: signal.reason,
14
+ });
15
+ }
16
+ function throwIfAborted(signal) {
17
+ if (signal?.aborted)
18
+ throw cancellationError(signal);
19
+ }
20
+ async function waitForRetry(delayMs, signal) {
21
+ throwIfAborted(signal);
22
+ if (delayMs <= 0)
23
+ return;
24
+ await new Promise((resolve, reject) => {
25
+ let settled = false;
26
+ const finish = (result) => {
27
+ if (settled)
28
+ return;
29
+ settled = true;
30
+ clearTimeout(timer);
31
+ signal?.removeEventListener('abort', onAbort);
32
+ result();
33
+ };
34
+ const onAbort = () => finish(() => reject(cancellationError(signal)));
35
+ const timer = setTimeout(() => finish(resolve), delayMs);
36
+ signal?.addEventListener('abort', onAbort, { once: true });
37
+ if (signal?.aborted)
38
+ onAbort();
39
+ });
40
+ }
9
41
  /** Routes each complete request through Claude's retry-then-fallback policy. */
10
42
  export class FallbackModelProvider {
11
43
  active;
@@ -30,6 +62,7 @@ export class FallbackModelProvider {
30
62
  let lastError;
31
63
  for (const provider of this.providers()) {
32
64
  for (let attempt = 0; attempt < MAX_ATTEMPTS_PER_MODEL; attempt += 1) {
65
+ throwIfAborted(request.signal);
33
66
  this.active = provider;
34
67
  const attemptStartedAt = performance.now();
35
68
  try {
@@ -73,6 +106,7 @@ export class FallbackModelProvider {
73
106
  return;
74
107
  }
75
108
  catch (error) {
109
+ throwIfAborted(request.signal);
76
110
  lastError = error;
77
111
  if (!retryable(error))
78
112
  throw error;
@@ -89,8 +123,7 @@ export class FallbackModelProvider {
89
123
  error: modelProviderErrorKind(error),
90
124
  };
91
125
  }
92
- if (delay > 0)
93
- await new Promise((resolve) => setTimeout(resolve, delay));
126
+ await waitForRetry(delay, request.signal);
94
127
  }
95
128
  }
96
129
  }
@@ -0,0 +1,30 @@
1
+ import type { JsonResource } from '../core/resources.js';
2
+ export interface WorkspaceExecutableOrigin {
3
+ readonly kind: 'hook' | 'mcp';
4
+ readonly scope: 'project' | 'local';
5
+ readonly path: string;
6
+ readonly label: string;
7
+ }
8
+ export interface WorkspaceTrustInventory {
9
+ readonly canonicalPath: string;
10
+ readonly fingerprint: string;
11
+ readonly origins: readonly WorkspaceExecutableOrigin[];
12
+ }
13
+ export type WorkspaceTrustStatus = 'not-required' | 'trusted' | 'untrusted';
14
+ export interface WorkspaceTrustAssessment extends WorkspaceTrustInventory {
15
+ readonly status: WorkspaceTrustStatus;
16
+ }
17
+ export declare function canonicalizeWorkspaceTrust(value: unknown, ancestors?: Set<object>): string;
18
+ export declare function hasWorkspaceHooks(resource: JsonResource): boolean;
19
+ export declare function hasWorkspaceMcpServers(resource: JsonResource): boolean;
20
+ export declare function allowedWorkspaceHookSettings(resources: readonly JsonResource[], trusted: boolean): JsonResource[];
21
+ export declare function allowedWorkspaceMcpResources(resources: readonly JsonResource[], trusted: boolean): JsonResource[];
22
+ export declare function workspaceTrustInventory(options: {
23
+ cwd: string;
24
+ settings?: readonly JsonResource[];
25
+ mcp?: readonly JsonResource[];
26
+ }): Promise<WorkspaceTrustInventory>;
27
+ export declare function workspaceTrustDecisionKey(inventory: Pick<WorkspaceTrustInventory, 'canonicalPath' | 'fingerprint'>): string;
28
+ export declare function assessWorkspaceTrust(inventory: WorkspaceTrustInventory, statePath: string): Promise<WorkspaceTrustAssessment>;
29
+ export declare function persistWorkspaceTrust(assessment: WorkspaceTrustAssessment, statePath: string): Promise<void>;
30
+ //# sourceMappingURL=workspace-trust.d.ts.map