praxis-agent 0.26.0 → 0.28.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.
Files changed (44) hide show
  1. package/dist/application/session-memory.d.ts +43 -10
  2. package/dist/application/session-memory.js +263 -62
  3. package/dist/application/session-service.d.ts +13 -4
  4. package/dist/application/session-service.js +139 -36
  5. package/dist/application/subagent-service.d.ts +1 -0
  6. package/dist/application/subagent-service.js +23 -1
  7. package/dist/cli-runtime.d.ts +4 -0
  8. package/dist/cli-runtime.js +10 -0
  9. package/dist/core/runtime.d.ts +10 -0
  10. package/dist/core/runtime.js +111 -28
  11. package/dist/core/tool-execution-scheduler.d.ts +38 -0
  12. package/dist/core/tool-execution-scheduler.js +95 -0
  13. package/dist/core/tool-scheduling-policy.d.ts +4 -0
  14. package/dist/core/tool-scheduling-policy.js +24 -0
  15. package/dist/extensions/claude-extension-tools.d.ts +1 -0
  16. package/dist/extensions/claude-extension-tools.js +6 -0
  17. package/dist/hooks/claude-hook-tools.d.ts +4 -0
  18. package/dist/hooks/claude-hook-tools.js +6 -0
  19. package/dist/mcp/claude-mcp-tools.d.ts +1 -0
  20. package/dist/mcp/claude-mcp-tools.js +42 -0
  21. package/dist/permissions/claude-permission-resolver.js +3 -3
  22. package/dist/permissions/permission-updates.d.ts +1 -0
  23. package/dist/permissions/permission-updates.js +13 -1
  24. package/dist/tools/claude-capabilities.d.ts +1 -0
  25. package/dist/tools/claude-capabilities.js +5 -0
  26. package/dist/tools/claude-interactive-tools.js +7 -0
  27. package/dist/tools/claude-lsp-tool.js +7 -0
  28. package/dist/tools/claude-scheduled-tools.d.ts +1 -0
  29. package/dist/tools/claude-scheduled-tools.js +7 -0
  30. package/dist/tools/claude-task-tools.d.ts +1 -0
  31. package/dist/tools/claude-task-tools.js +11 -0
  32. package/dist/tools/claude-user-message.d.ts +1 -0
  33. package/dist/tools/claude-user-message.js +7 -0
  34. package/dist/tools/claude-workflow-tools.d.ts +1 -0
  35. package/dist/tools/claude-workflow-tools.js +7 -0
  36. package/dist/tools/claude-worktree-tools.d.ts +1 -0
  37. package/dist/tools/claude-worktree-tools.js +7 -0
  38. package/dist/tools/filtered-tool-registry.d.ts +1 -0
  39. package/dist/tools/filtered-tool-registry.js +5 -0
  40. package/dist/tools/local-tools.d.ts +17 -0
  41. package/dist/tools/local-tools.js +86 -1
  42. package/dist/tools/web.d.ts +1 -0
  43. package/dist/tools/web.js +27 -0
  44. package/package.json +1 -1
@@ -1,5 +1,6 @@
1
1
  import { mkdir, readFile, realpath } from 'node:fs/promises';
2
2
  import { basename, dirname, join, resolve } from 'node:path';
3
+ import { resolveToolSchedulingPolicy } from '../core/tool-scheduling-policy.js';
3
4
  const ASK_USER_QUESTION = {
4
5
  name: 'AskUserQuestion',
5
6
  description: "Use this tool only when you are blocked on a decision that is genuinely the user's to make: one you cannot resolve from the request, the code, or sensible defaults. Users can always provide custom text. Use multiSelect for non-exclusive choices. In plan mode, use this tool to clarify requirements before ExitPlanMode; do not use it to request plan approval.",
@@ -197,6 +198,12 @@ class ClaudeInteractiveToolRegistry {
197
198
  ...INTERACTIVE_DEFINITIONS.filter(({ name }) => this.manager.enabledNames.has(name)),
198
199
  ];
199
200
  }
201
+ schedulingPolicy(call) {
202
+ if (this.manager.enabledNames.has(call.name)) {
203
+ return { concurrency: 'exclusive', cancelOnInterrupt: true };
204
+ }
205
+ return resolveToolSchedulingPolicy(this.base, call);
206
+ }
200
207
  async prepare(call, context) {
201
208
  return this.manager.enabledNames.has(call.name)
202
209
  ? call
@@ -4,6 +4,7 @@ import { homedir } from 'node:os';
4
4
  import { extname, resolve, sep } from 'node:path';
5
5
  import { pathToFileURL } from 'node:url';
6
6
  import { promisify } from 'node:util';
7
+ import { resolveToolSchedulingPolicy } from '../core/tool-scheduling-policy.js';
7
8
  import { redactSensitiveText, sanitizeChildEnvironment, sensitiveEnvironmentValues, } from '../platform/sensitive-data.js';
8
9
  import { formatClaudeLspResult } from './claude-lsp-formatters.js';
9
10
  const MAX_MESSAGE_BYTES = 8 * 1024 * 1024;
@@ -730,6 +731,12 @@ class ClaudeLspToolRegistry {
730
731
  definitions() {
731
732
  return [...this.base.definitions(), LSP_DEFINITION];
732
733
  }
734
+ schedulingPolicy(call) {
735
+ if (call.name === 'LSP') {
736
+ return { concurrency: 'exclusive', cancelOnInterrupt: true };
737
+ }
738
+ return resolveToolSchedulingPolicy(this.base, call);
739
+ }
733
740
  prepare(call, context) {
734
741
  return call.name === 'LSP'
735
742
  ? Promise.resolve(call)
@@ -16,6 +16,7 @@ export declare class ClaudeScheduledToolRegistry implements ToolRegistry {
16
16
  private readonly now;
17
17
  constructor(options: ClaudeScheduledToolRegistryOptions);
18
18
  definitions(): readonly ModelToolDefinition[];
19
+ schedulingPolicy(call: ModelToolCall): import("../core/runtime.js").ToolSchedulingPolicy;
19
20
  prepare(call: ModelToolCall, context: ToolExecutionContext): Promise<ModelToolCall>;
20
21
  execute(call: ModelToolCall, context: ToolExecutionContext): Promise<ToolExecutionResult>;
21
22
  }
@@ -1,3 +1,4 @@
1
+ import { resolveToolSchedulingPolicy } from '../core/tool-scheduling-policy.js';
1
2
  const SCHEMA = 'https://json-schema.org/draft/2020-12/schema';
2
3
  const CRON_CREATE_DESCRIPTION = `Schedule a prompt to be enqueued at a future time. Use for both recurring schedules and one-shot reminders.
3
4
 
@@ -191,6 +192,12 @@ export class ClaudeScheduledToolRegistry {
191
192
  ...scheduledDefinitions.filter(({ name }) => (this.enabled?.has(name) ?? true) && !existing.has(name)),
192
193
  ];
193
194
  }
195
+ schedulingPolicy(call) {
196
+ if (DEFINITION_NAMES.has(call.name)) {
197
+ return { concurrency: 'exclusive', cancelOnInterrupt: true };
198
+ }
199
+ return resolveToolSchedulingPolicy(this.options.base, call);
200
+ }
194
201
  async prepare(call, context) {
195
202
  if (!DEFINITION_NAMES.has(call.name)) {
196
203
  return this.options.base.prepare(call, context);
@@ -22,6 +22,7 @@ export declare class ClaudeTaskToolRegistry implements ToolRegistry {
22
22
  stopBackgroundTask(taskId: string): Promise<BackgroundBashToolResult>;
23
23
  isEnabled(name: string): boolean;
24
24
  definitions(): readonly ModelToolDefinition[];
25
+ schedulingPolicy(call: ModelToolCall): import("../core/runtime.js").ToolSchedulingPolicy;
25
26
  prepare(call: ModelToolCall, context: ToolExecutionContext): Promise<ModelToolCall>;
26
27
  execute(call: ModelToolCall, context: ToolExecutionContext): Promise<ToolExecutionResult>;
27
28
  notifications(waitForRunning: boolean): Promise<string[]>;
@@ -1,4 +1,5 @@
1
1
  import { resolve } from 'node:path';
2
+ import { resolveToolSchedulingPolicy } from '../core/tool-scheduling-policy.js';
2
3
  import { BackgroundBashManager, } from '../application/background-bash-manager.js';
3
4
  import { isBackgroundBashTaskId } from '../application/background-task-id.js';
4
5
  import { ClaudeTaskStore, } from '../persistence/claude-task-store.js';
@@ -260,6 +261,16 @@ export class ClaudeTaskToolRegistry {
260
261
  ...TASK_DEFINITIONS.filter(({ name }) => this.isEnabled(name) && !existing.has(name)),
261
262
  ];
262
263
  }
264
+ schedulingPolicy(call) {
265
+ if (call.name !== 'Bash' &&
266
+ !TASK_DEFINITIONS.some(({ name }) => name === call.name)) {
267
+ return resolveToolSchedulingPolicy(this.options.base, call);
268
+ }
269
+ if (call.name === 'Bash' && call.input.run_in_background !== true) {
270
+ return resolveToolSchedulingPolicy(this.options.base, call);
271
+ }
272
+ return { concurrency: 'exclusive', cancelOnInterrupt: true };
273
+ }
263
274
  async prepare(call, context) {
264
275
  if (call.name === 'Bash') {
265
276
  return this.prepareBash(call, context);
@@ -10,6 +10,7 @@ export declare class ClaudeUserMessageToolRegistry implements ToolRegistry {
10
10
  private readonly onMessage;
11
11
  constructor(base: ToolRegistry, onMessage: (message: UserMessage) => void);
12
12
  definitions(): readonly ModelToolDefinition[];
13
+ schedulingPolicy(call: ModelToolCall): import("../core/runtime.js").ToolSchedulingPolicy;
13
14
  prepare(call: ModelToolCall, context: ToolExecutionContext): Promise<ModelToolCall>;
14
15
  execute(call: ModelToolCall, context: ToolExecutionContext): Promise<ToolExecutionResult>;
15
16
  }
@@ -1,5 +1,6 @@
1
1
  import { stat } from 'node:fs/promises';
2
2
  import { extname, resolve } from 'node:path';
3
+ import { resolveToolSchedulingPolicy } from '../core/tool-scheduling-policy.js';
3
4
  export const CLAUDE_USER_MESSAGE_PROMPT = 'When brief mode is enabled, SendUserMessage is the primary user-visible reply channel. Use it for the answer, progress checkpoints, and blockers. Set status to normal for a direct reply and proactive for an unsolicited update.';
4
5
  const DEFINITION = {
5
6
  name: 'SendUserMessage',
@@ -42,6 +43,12 @@ export class ClaudeUserMessageToolRegistry {
42
43
  definitions() {
43
44
  return [...this.base.definitions(), DEFINITION];
44
45
  }
46
+ schedulingPolicy(call) {
47
+ if (call.name === DEFINITION.name) {
48
+ return { concurrency: 'exclusive', cancelOnInterrupt: true };
49
+ }
50
+ return resolveToolSchedulingPolicy(this.base, call);
51
+ }
45
52
  prepare(call, context) {
46
53
  if (call.name !== DEFINITION.name)
47
54
  return this.base.prepare(call, context);
@@ -21,6 +21,7 @@ export declare class ClaudeWorkflowToolRegistry implements ToolRegistry {
21
21
  private readonly prepared;
22
22
  constructor(options: ClaudeWorkflowToolRegistryOptions);
23
23
  definitions(): readonly ModelToolDefinition[];
24
+ schedulingPolicy(call: ModelToolCall): import("../core/runtime.js").ToolSchedulingPolicy;
24
25
  prepare(call: ModelToolCall, context: ToolExecutionContext): Promise<ModelToolCall>;
25
26
  execute(call: ModelToolCall, context: ToolExecutionContext): Promise<ToolExecutionResult>;
26
27
  private resolveSource;
@@ -1,5 +1,6 @@
1
1
  import { readFile } from 'node:fs/promises';
2
2
  import { isAbsolute, resolve } from 'node:path';
3
+ import { resolveToolSchedulingPolicy } from '../core/tool-scheduling-policy.js';
3
4
  import { parseWorkflowScript, } from '../application/workflow-manager.js';
4
5
  const MAX_SCRIPT_BYTES = 524_288;
5
6
  function workflowDefinition(dataPlane) {
@@ -91,6 +92,12 @@ export class ClaudeWorkflowToolRegistry {
91
92
  }
92
93
  return [...base, workflowDefinition(this.options.dataPlane ?? 'claude')];
93
94
  }
95
+ schedulingPolicy(call) {
96
+ if (['Workflow', 'TaskOutput', 'TaskStop'].includes(call.name)) {
97
+ return { concurrency: 'exclusive', cancelOnInterrupt: true };
98
+ }
99
+ return resolveToolSchedulingPolicy(this.options.base, call);
100
+ }
94
101
  async prepare(call, context) {
95
102
  if (call.name === 'TaskOutput' || call.name === 'TaskStop') {
96
103
  const taskId = call.input.task_id ?? call.input.shell_id;
@@ -12,6 +12,7 @@ export declare class ClaudeWorktreeToolRegistry implements ToolRegistry {
12
12
  dataPlane?: DataPlane;
13
13
  });
14
14
  definitions(): readonly ModelToolDefinition[];
15
+ schedulingPolicy(call: ModelToolCall): import("../core/runtime.js").ToolSchedulingPolicy;
15
16
  prepare(call: ModelToolCall, context: ToolExecutionContext): Promise<ModelToolCall>;
16
17
  execute(call: ModelToolCall, context: ToolExecutionContext): Promise<ToolExecutionResult>;
17
18
  }
@@ -1,3 +1,4 @@
1
+ import { resolveToolSchedulingPolicy } from '../core/tool-scheduling-policy.js';
1
2
  const ENTER_DEFINITION = {
2
3
  name: 'EnterWorktree',
3
4
  description: 'Create or enter an isolated Git worktree and switch the current session into it.',
@@ -96,6 +97,12 @@ export class ClaudeWorktreeToolRegistry {
96
97
  ...definitions.filter((definition) => this.enabled.has(definition.name) && !existing.has(definition.name)),
97
98
  ];
98
99
  }
100
+ schedulingPolicy(call) {
101
+ if (['EnterWorktree', 'ExitWorktree'].includes(call.name)) {
102
+ return { concurrency: 'exclusive', cancelOnInterrupt: true };
103
+ }
104
+ return resolveToolSchedulingPolicy(this.options.base, call);
105
+ }
99
106
  async prepare(call, context) {
100
107
  if (call.name === 'EnterWorktree' && this.enabled.has(call.name)) {
101
108
  const input = objectInput(call);
@@ -9,6 +9,7 @@ export declare class FilteredToolRegistry implements ToolRegistry {
9
9
  private readonly enabledNames;
10
10
  constructor(base: ToolRegistry, options?: FilteredToolRegistryOptions);
11
11
  definitions(): readonly ModelToolDefinition[];
12
+ schedulingPolicy(call: ModelToolCall): import("../core/runtime.js").ToolSchedulingPolicy;
12
13
  prepare(call: ModelToolCall, context: ToolExecutionContext): Promise<ModelToolCall>;
13
14
  execute(call: ModelToolCall, context: ToolExecutionContext): Promise<ToolExecutionResult>;
14
15
  private assertEnabled;
@@ -1,3 +1,4 @@
1
+ import { resolveToolSchedulingPolicy } from '../core/tool-scheduling-policy.js';
1
2
  function exactToolNames(rules) {
2
3
  return new Set(rules.flatMap((rule) => {
3
4
  const match = /^([A-Za-z][\w-]*)$/.exec(rule);
@@ -34,6 +35,10 @@ export class FilteredToolRegistry {
34
35
  return definition;
35
36
  });
36
37
  }
38
+ schedulingPolicy(call) {
39
+ this.assertEnabled(call.name);
40
+ return resolveToolSchedulingPolicy(this.base, call);
41
+ }
37
42
  async prepare(call, context) {
38
43
  this.assertEnabled(call.name);
39
44
  return this.base.prepare(call, context);
@@ -53,6 +53,23 @@ export declare class LocalToolRegistry implements ToolRegistry {
53
53
  private assertProtectedBashCommand;
54
54
  private currentCwd;
55
55
  definitions(): readonly ModelToolDefinition[];
56
+ schedulingPolicy(call: ModelToolCall): {
57
+ concurrency: "concurrent";
58
+ cancelOnInterrupt: boolean;
59
+ abortGroupOnError?: never;
60
+ } | {
61
+ concurrency: "concurrent";
62
+ cancelOnInterrupt: boolean;
63
+ abortGroupOnError: boolean;
64
+ } | {
65
+ abortGroupOnError?: boolean;
66
+ concurrency: "exclusive";
67
+ cancelOnInterrupt: boolean;
68
+ } | {
69
+ concurrency: "exclusive";
70
+ cancelOnInterrupt?: never;
71
+ abortGroupOnError?: never;
72
+ };
56
73
  prepare(call: ModelToolCall, context: ToolExecutionContext): Promise<ModelToolCall>;
57
74
  execute(call: ModelToolCall, context: ToolExecutionContext): Promise<ToolExecutionResult>;
58
75
  private workspacePath;
@@ -11,7 +11,7 @@ import { editNotebook, formatNotebookForRead } from './notebook.js';
11
11
  import { openPdf } from './pdf.js';
12
12
  import { validateBashPathSafety } from '../permissions/bash-path-safety.js';
13
13
  import { protectedWritePathReason } from '../permissions/bypass-immune-paths.js';
14
- import { effectiveAdditionalDirectories } from '../permissions/permission-updates.js';
14
+ import { effectiveAdditionalDirectories, shellInputIsReadOnly, } from '../permissions/permission-updates.js';
15
15
  const REPORT_FINDINGS_DEFINITION = {
16
16
  name: 'ReportFindings',
17
17
  description: "Report code-review findings as a typed list so the host UI can render them. Use this only when the active code-review instructions tell you to report findings with this tool; otherwise follow whatever output format those instructions specify. When reporting a review's results, call it once with the verified findings ranked most-severe first (empty array if nothing survived verification) and do not also print the findings as text. When re-reporting after applying fixes (only if the apply instructions ask for it), set outcome on each finding to what actually happened.",
@@ -277,6 +277,64 @@ function optionalNonNegativeInteger(input, name) {
277
277
  function isRecord(value) {
278
278
  return typeof value === 'object' && value !== null && !Array.isArray(value);
279
279
  }
280
+ function hasOnlyInputKeys(input, allowed) {
281
+ const names = new Set(allowed);
282
+ return Object.keys(input).every((name) => names.has(name));
283
+ }
284
+ function localSchedulingInputIsValid(call) {
285
+ try {
286
+ if (call.name === 'Read') {
287
+ stringInput(call.input, 'file_path');
288
+ optionalNonNegativeInteger(call.input, 'offset');
289
+ optionalPositiveInteger(call.input, 'limit');
290
+ optionalString(call.input, 'pages');
291
+ return hasOnlyInputKeys(call.input, [
292
+ 'file_path',
293
+ 'offset',
294
+ 'limit',
295
+ 'pages',
296
+ ]);
297
+ }
298
+ if (call.name === 'Glob') {
299
+ stringInput(call.input, 'pattern', true);
300
+ optionalString(call.input, 'path');
301
+ return hasOnlyInputKeys(call.input, ['pattern', 'path']);
302
+ }
303
+ if (call.name === 'Grep') {
304
+ stringInput(call.input, 'pattern');
305
+ optionalString(call.input, 'path');
306
+ optionalString(call.input, 'glob');
307
+ return hasOnlyInputKeys(call.input, ['pattern', 'path', 'glob']);
308
+ }
309
+ if (call.name === 'Bash') {
310
+ stringInput(call.input, 'command');
311
+ optionalPositiveInteger(call.input, 'timeout');
312
+ const description = call.input.description;
313
+ const background = call.input.run_in_background;
314
+ const disableSandbox = call.input.dangerouslyDisableSandbox;
315
+ if (description !== undefined && typeof description !== 'string') {
316
+ return false;
317
+ }
318
+ if (background !== undefined && typeof background !== 'boolean') {
319
+ return false;
320
+ }
321
+ if (disableSandbox !== undefined && typeof disableSandbox !== 'boolean') {
322
+ return false;
323
+ }
324
+ return hasOnlyInputKeys(call.input, [
325
+ 'command',
326
+ 'timeout',
327
+ 'description',
328
+ 'run_in_background',
329
+ 'dangerouslyDisableSandbox',
330
+ ]);
331
+ }
332
+ return false;
333
+ }
334
+ catch {
335
+ return false;
336
+ }
337
+ }
280
338
  const REPORT_LEVELS = ['low', 'medium', 'high', 'xhigh', 'max'];
281
339
  const REPORT_VERDICTS = ['CONFIRMED', 'PLAUSIBLE'];
282
340
  const REPORT_OUTCOMES = ['fixed', 'skipped', 'no_change_needed'];
@@ -535,6 +593,33 @@ export class LocalToolRegistry {
535
593
  }
536
594
  : definition);
537
595
  }
596
+ schedulingPolicy(call) {
597
+ if (['Read', 'Glob', 'Grep'].includes(call.name) &&
598
+ localSchedulingInputIsValid(call)) {
599
+ return { concurrency: 'concurrent', cancelOnInterrupt: true };
600
+ }
601
+ if (call.name === 'Bash' &&
602
+ localSchedulingInputIsValid(call) &&
603
+ call.input.run_in_background !== true &&
604
+ typeof call.input.command === 'string' &&
605
+ shellInputIsReadOnly(call.input.command)) {
606
+ return {
607
+ concurrency: 'concurrent',
608
+ cancelOnInterrupt: true,
609
+ abortGroupOnError: true,
610
+ };
611
+ }
612
+ if (TOOL_DEFINITIONS.some(({ name }) => name === call.name)) {
613
+ return {
614
+ concurrency: 'exclusive',
615
+ cancelOnInterrupt: true,
616
+ ...(call.name === 'Bash' ? { abortGroupOnError: true } : {}),
617
+ };
618
+ }
619
+ return {
620
+ concurrency: 'exclusive',
621
+ };
622
+ }
538
623
  async prepare(call, context) {
539
624
  if (context.signal?.aborted)
540
625
  throw abortError();
@@ -36,6 +36,7 @@ export declare class WebToolRegistry implements ToolRegistry {
36
36
  private cacheBytes;
37
37
  constructor(options: WebToolRegistryOptions);
38
38
  definitions(): readonly ModelToolDefinition[];
39
+ schedulingPolicy(call: ModelToolCall): import("../core/runtime.js").ToolSchedulingPolicy;
39
40
  prepare(call: ModelToolCall, context: ToolExecutionContext): Promise<ModelToolCall>;
40
41
  execute(call: ModelToolCall, context: ToolExecutionContext): Promise<ToolExecutionResult>;
41
42
  private webFetch;
package/dist/tools/web.js CHANGED
@@ -6,6 +6,7 @@ import { isIP } from 'node:net';
6
6
  import { join } from 'node:path';
7
7
  import ipaddr from 'ipaddr.js';
8
8
  import TurndownService from 'turndown';
9
+ import { resolveToolSchedulingPolicy } from '../core/tool-scheduling-policy.js';
9
10
  const MAX_URL_LENGTH = 2_000;
10
11
  const MAX_MARKDOWN_LENGTH = 100_000;
11
12
  const DEFAULT_MAX_RESPONSE_BYTES = 10 * 1024 * 1024;
@@ -273,6 +274,24 @@ function parseWebSearchInput(input) {
273
274
  ...(blockedDomains ? { blockedDomains } : {}),
274
275
  };
275
276
  }
277
+ function webSchedulingInputIsValid(call) {
278
+ try {
279
+ if (call.name === 'WebFetch') {
280
+ parseWebFetchInput(call.input);
281
+ return Object.keys(call.input).every((name) => name === 'url' || name === 'prompt');
282
+ }
283
+ if (call.name === 'WebSearch') {
284
+ parseWebSearchInput(call.input);
285
+ return Object.keys(call.input).every((name) => name === 'query' ||
286
+ name === 'allowed_domains' ||
287
+ name === 'blocked_domains');
288
+ }
289
+ return false;
290
+ }
291
+ catch {
292
+ return false;
293
+ }
294
+ }
276
295
  function normalizedAddress(value) {
277
296
  const parsed = ipaddr.parse(value);
278
297
  return parsed instanceof ipaddr.IPv6 && parsed.isIPv4MappedAddress()
@@ -475,6 +494,14 @@ export class WebToolRegistry {
475
494
  : []),
476
495
  ];
477
496
  }
497
+ schedulingPolicy(call) {
498
+ if (call.name === 'WebFetch' || call.name === 'WebSearch') {
499
+ return webSchedulingInputIsValid(call)
500
+ ? { concurrency: 'concurrent', cancelOnInterrupt: true }
501
+ : { concurrency: 'exclusive', cancelOnInterrupt: true };
502
+ }
503
+ return resolveToolSchedulingPolicy(this.options.base, call);
504
+ }
478
505
  async prepare(call, context) {
479
506
  if (context.signal?.aborted)
480
507
  throw abortError();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "praxis-agent",
3
- "version": "0.26.0",
3
+ "version": "0.28.0",
4
4
  "description": "Local-first, single-user general agent for the command line.",
5
5
  "license": "MIT",
6
6
  "author": "wuqisen",