praxis-agent 0.9.0 → 0.11.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 +18 -4
- package/dist/application/session-service.d.ts +5 -2
- package/dist/application/session-service.js +23 -0
- package/dist/application/subagent-service.d.ts +4 -2
- package/dist/application/subagent-service.js +4 -0
- package/dist/cli/interactive.d.ts +4 -3
- package/dist/cli/interactive.js +760 -195
- package/dist/cli/tui/claude-command-inventory.d.ts +368 -0
- package/dist/cli/tui/claude-command-inventory.js +220 -0
- package/dist/cli/tui/claude-style.d.ts +12 -14
- package/dist/cli/tui/claude-style.js +45 -67
- package/dist/cli/tui/config-dashboard.d.ts +4 -8
- package/dist/cli/tui/config-dashboard.js +4 -18
- package/dist/cli/tui/config-settings.js +1 -1
- package/dist/cli/tui/conversation-export.d.ts +1 -1
- package/dist/cli/tui/conversation-export.js +2 -11
- package/dist/cli/tui/mcp-elicitation.d.ts +75 -0
- package/dist/cli/tui/mcp-elicitation.js +497 -0
- package/dist/cli/tui/open-url.d.ts +2 -0
- package/dist/cli/tui/open-url.js +13 -0
- package/dist/cli/tui/permission-settings.d.ts +6 -0
- package/dist/cli/tui/permission-settings.js +84 -0
- package/dist/cli/tui/runtime-interactions.d.ts +2 -0
- package/dist/cli/tui/runtime-interactions.js +44 -17
- package/dist/cli/tui/slash-commands.js +3 -28
- package/dist/cli/tui/tool-permission.d.ts +36 -0
- package/dist/cli/tui/tool-permission.js +386 -0
- package/dist/cli.d.ts +2 -2
- package/dist/cli.js +28 -13
- package/dist/core/runtime.d.ts +50 -2
- package/dist/core/runtime.js +30 -3
- package/dist/extensions/claude-extension-tools.d.ts +3 -2
- package/dist/extensions/claude-extension-tools.js +15 -5
- package/dist/extensions/claude-extensions.d.ts +1 -0
- package/dist/extensions/claude-extensions.js +10 -0
- package/dist/hooks/claude-hook-tools.js +1 -1
- package/dist/mcp/claude-mcp-tools.js +12 -6
- package/dist/permissions/bash-ast.d.ts +39 -0
- package/dist/permissions/bash-ast.js +1046 -0
- package/dist/permissions/bash-normalization.d.ts +5 -0
- package/dist/permissions/bash-normalization.js +241 -0
- package/dist/permissions/bash-path-safety.d.ts +28 -0
- package/dist/permissions/bash-path-safety.js +575 -0
- package/dist/permissions/claude-permission-resolver.d.ts +7 -0
- package/dist/permissions/claude-permission-resolver.js +351 -40
- package/dist/permissions/claude-shell-permission.d.ts +4 -0
- package/dist/permissions/claude-shell-permission.js +108 -0
- package/dist/permissions/permission-updates.d.ts +16 -0
- package/dist/permissions/permission-updates.js +320 -0
- package/dist/permissions/sed-safety.d.ts +9 -0
- package/dist/permissions/sed-safety.js +172 -0
- package/dist/permissions/shell-rule-matching.d.ts +14 -0
- package/dist/permissions/shell-rule-matching.js +76 -0
- package/dist/tools/claude-interactive-tools.d.ts +10 -1
- package/dist/tools/claude-interactive-tools.js +10 -4
- package/dist/tools/local-tools.js +22 -2
- package/package.json +3 -1
package/dist/core/runtime.d.ts
CHANGED
|
@@ -293,6 +293,10 @@ export interface ToolExecutionContext {
|
|
|
293
293
|
messages?: readonly ModelMessage[];
|
|
294
294
|
signal?: AbortSignal;
|
|
295
295
|
toolResultDirectory?: string;
|
|
296
|
+
originalCall?: ModelToolCall;
|
|
297
|
+
permissionUpdates?: readonly PermissionUpdate[];
|
|
298
|
+
permissionPhase?: 'request' | 'execute';
|
|
299
|
+
permissionApproved?: boolean;
|
|
296
300
|
}
|
|
297
301
|
export interface ToolRegistry {
|
|
298
302
|
definitions(): readonly ModelToolDefinition[];
|
|
@@ -300,9 +304,46 @@ export interface ToolRegistry {
|
|
|
300
304
|
execute(call: ModelToolCall, context: ToolExecutionContext): Promise<ToolExecutionResult>;
|
|
301
305
|
}
|
|
302
306
|
export type PermissionBehavior = 'allow' | 'ask' | 'deny';
|
|
307
|
+
export type PermissionMode = 'acceptEdits' | 'auto' | 'bypassPermissions' | 'manual' | 'dontAsk' | 'plan' | 'default';
|
|
308
|
+
export type PermissionUpdateDestination = 'userSettings' | 'projectSettings' | 'localSettings' | 'session' | 'cliArg';
|
|
309
|
+
export type PermissionUpdateMode = Exclude<PermissionMode, 'auto' | 'manual'>;
|
|
310
|
+
export interface PermissionRuleValue {
|
|
311
|
+
toolName: string;
|
|
312
|
+
ruleContent?: string;
|
|
313
|
+
}
|
|
314
|
+
export type PermissionUpdate = {
|
|
315
|
+
type: 'addRules';
|
|
316
|
+
rules: readonly PermissionRuleValue[];
|
|
317
|
+
behavior: PermissionBehavior;
|
|
318
|
+
destination: PermissionUpdateDestination;
|
|
319
|
+
} | {
|
|
320
|
+
type: 'replaceRules';
|
|
321
|
+
rules: readonly PermissionRuleValue[];
|
|
322
|
+
behavior: PermissionBehavior;
|
|
323
|
+
destination: PermissionUpdateDestination;
|
|
324
|
+
} | {
|
|
325
|
+
type: 'removeRules';
|
|
326
|
+
rules: readonly PermissionRuleValue[];
|
|
327
|
+
behavior: PermissionBehavior;
|
|
328
|
+
destination: PermissionUpdateDestination;
|
|
329
|
+
} | {
|
|
330
|
+
type: 'setMode';
|
|
331
|
+
mode: PermissionUpdateMode;
|
|
332
|
+
destination: PermissionUpdateDestination;
|
|
333
|
+
} | {
|
|
334
|
+
type: 'addDirectories';
|
|
335
|
+
directories: readonly string[];
|
|
336
|
+
destination: PermissionUpdateDestination;
|
|
337
|
+
} | {
|
|
338
|
+
type: 'removeDirectories';
|
|
339
|
+
directories: readonly string[];
|
|
340
|
+
destination: PermissionUpdateDestination;
|
|
341
|
+
};
|
|
303
342
|
export type PermissionApproval = boolean | {
|
|
304
343
|
behavior: 'allow';
|
|
305
344
|
updatedInput?: Record<string, unknown>;
|
|
345
|
+
updatedPermissions?: readonly PermissionUpdate[];
|
|
346
|
+
feedback?: string;
|
|
306
347
|
} | {
|
|
307
348
|
behavior: 'deny';
|
|
308
349
|
message: string;
|
|
@@ -312,6 +353,9 @@ export interface PermissionResolutionContext {
|
|
|
312
353
|
cwd: string;
|
|
313
354
|
messages?: readonly ModelMessage[];
|
|
314
355
|
signal?: AbortSignal;
|
|
356
|
+
toolResultDirectory?: string;
|
|
357
|
+
originalCall?: ModelToolCall;
|
|
358
|
+
permissionUpdates?: readonly PermissionUpdate[];
|
|
315
359
|
}
|
|
316
360
|
export type PermissionDecisionSource = 'auto-classifier' | 'rule' | 'mode' | 'default';
|
|
317
361
|
export type AutoModePermissionOutcome = 'blocked' | 'unavailable';
|
|
@@ -321,6 +365,8 @@ export type PermissionDecision = {
|
|
|
321
365
|
} | {
|
|
322
366
|
behavior: 'ask';
|
|
323
367
|
reason?: string;
|
|
368
|
+
suggestions?: readonly PermissionUpdate[];
|
|
369
|
+
metadata?: Readonly<Record<string, unknown>>;
|
|
324
370
|
source?: PermissionDecisionSource;
|
|
325
371
|
} | {
|
|
326
372
|
behavior: 'deny';
|
|
@@ -369,7 +415,9 @@ export interface AgentRunRequest {
|
|
|
369
415
|
toolResultDirectory?: string;
|
|
370
416
|
observer?: AgentRunObserver;
|
|
371
417
|
reloadMessages?: () => Promise<readonly ModelMessage[]>;
|
|
372
|
-
approveTool?: (call: ModelToolCall, originalCall?: ModelToolCall) => PermissionApproval | Promise<PermissionApproval>;
|
|
418
|
+
approveTool?: (call: ModelToolCall, originalCall?: ModelToolCall, decision?: PermissionDecision) => PermissionApproval | Promise<PermissionApproval>;
|
|
419
|
+
permissionUpdates?: readonly PermissionUpdate[];
|
|
420
|
+
onPermissionUpdates?: (updates: readonly PermissionUpdate[]) => void | Promise<void>;
|
|
373
421
|
onStop?: (text: string) => Promise<readonly string[] | {
|
|
374
422
|
messages: readonly string[];
|
|
375
423
|
usage?: ModelUsage;
|
|
@@ -381,7 +429,7 @@ export interface AgentRunRequest {
|
|
|
381
429
|
maxModelTurns?: number;
|
|
382
430
|
betas?: readonly string[];
|
|
383
431
|
}
|
|
384
|
-
export interface AgentToolRecoveryRequest extends Pick<AgentRunRequest, 'approveTool' | 'cwd' | 'observer' | 'signal' | 'toolResultDirectory'> {
|
|
432
|
+
export interface AgentToolRecoveryRequest extends Pick<AgentRunRequest, 'approveTool' | 'cwd' | 'observer' | 'onPermissionUpdates' | 'permissionUpdates' | 'signal' | 'toolResultDirectory'> {
|
|
385
433
|
approveRecovery?: (call: ModelToolCall) => boolean | Promise<boolean>;
|
|
386
434
|
messages?: readonly ModelMessage[];
|
|
387
435
|
}
|
package/dist/core/runtime.js
CHANGED
|
@@ -444,7 +444,13 @@ export class AgentRuntime {
|
|
|
444
444
|
isError: true,
|
|
445
445
|
};
|
|
446
446
|
}
|
|
447
|
-
const context = {
|
|
447
|
+
const context = {
|
|
448
|
+
cwd: request.cwd ?? '',
|
|
449
|
+
messages,
|
|
450
|
+
originalCall: call,
|
|
451
|
+
permissionPhase: 'request',
|
|
452
|
+
permissionUpdates: request.permissionUpdates ?? [],
|
|
453
|
+
};
|
|
448
454
|
if (request.signal)
|
|
449
455
|
context.signal = request.signal;
|
|
450
456
|
if (request.toolResultDirectory) {
|
|
@@ -478,16 +484,26 @@ export class AgentRuntime {
|
|
|
478
484
|
let allowed = decision.behavior === 'allow';
|
|
479
485
|
let denialReason;
|
|
480
486
|
let interrupt = false;
|
|
487
|
+
let approvalFeedback;
|
|
481
488
|
if (decision.behavior === 'ask') {
|
|
482
489
|
this.emit({ type: 'state', state: 'awaiting-permission' });
|
|
483
490
|
const approval = request.approveTool
|
|
484
|
-
? await request.approveTool(prepared, call)
|
|
491
|
+
? await request.approveTool(prepared, call, decision)
|
|
485
492
|
: false;
|
|
486
493
|
if (typeof approval === 'boolean') {
|
|
487
494
|
allowed = approval;
|
|
488
495
|
}
|
|
489
496
|
else if (approval.behavior === 'allow') {
|
|
490
497
|
allowed = true;
|
|
498
|
+
approvalFeedback = approval.feedback?.trim() || undefined;
|
|
499
|
+
if (approval.updatedPermissions?.length) {
|
|
500
|
+
const updatedPermissions = [
|
|
501
|
+
...(context.permissionUpdates ?? []),
|
|
502
|
+
...approval.updatedPermissions,
|
|
503
|
+
];
|
|
504
|
+
await request.onPermissionUpdates?.(approval.updatedPermissions);
|
|
505
|
+
context.permissionUpdates = updatedPermissions;
|
|
506
|
+
}
|
|
491
507
|
if (approval.updatedInput) {
|
|
492
508
|
try {
|
|
493
509
|
prepared = await tools.prepare({ ...call, input: approval.updatedInput }, context);
|
|
@@ -521,8 +537,19 @@ export class AgentRuntime {
|
|
|
521
537
|
if (request.signal?.aborted)
|
|
522
538
|
return this.cancel();
|
|
523
539
|
this.emit({ type: 'state', state: 'executing-tools' });
|
|
540
|
+
context.permissionPhase = 'execute';
|
|
541
|
+
context.permissionApproved = true;
|
|
524
542
|
try {
|
|
525
|
-
|
|
543
|
+
const result = await tools.execute(prepared, context);
|
|
544
|
+
if (!approvalFeedback)
|
|
545
|
+
return result;
|
|
546
|
+
return {
|
|
547
|
+
...result,
|
|
548
|
+
followUpUserMessages: [
|
|
549
|
+
...(result.followUpUserMessages ?? []),
|
|
550
|
+
approvalFeedback,
|
|
551
|
+
],
|
|
552
|
+
};
|
|
526
553
|
}
|
|
527
554
|
catch (error) {
|
|
528
555
|
if (request.signal?.aborted)
|
|
@@ -10,7 +10,8 @@ export declare class ClaudeExtensionToolRegistry implements ToolRegistry {
|
|
|
10
10
|
}
|
|
11
11
|
export declare class ClaudeExtensionPermissionResolver implements PermissionResolver {
|
|
12
12
|
private readonly base;
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
private readonly catalog;
|
|
14
|
+
constructor(base: PermissionResolver, catalog: ClaudeExtensionCatalog);
|
|
15
|
+
resolve(call: ModelToolCall, context?: PermissionResolutionContext): Promise<PermissionDecision>;
|
|
15
16
|
}
|
|
16
17
|
//# sourceMappingURL=claude-extension-tools.d.ts.map
|
|
@@ -66,13 +66,23 @@ export class ClaudeExtensionToolRegistry {
|
|
|
66
66
|
}
|
|
67
67
|
export class ClaudeExtensionPermissionResolver {
|
|
68
68
|
base;
|
|
69
|
-
|
|
69
|
+
catalog;
|
|
70
|
+
constructor(base, catalog) {
|
|
70
71
|
this.base = base;
|
|
72
|
+
this.catalog = catalog;
|
|
71
73
|
}
|
|
72
|
-
resolve(call, context) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
async resolve(call, context) {
|
|
75
|
+
const decision = await this.base.resolve(call, context);
|
|
76
|
+
if (call.name !== 'Skill' || decision.behavior !== 'ask')
|
|
77
|
+
return decision;
|
|
78
|
+
const input = skillInput(call);
|
|
79
|
+
const definition = this.catalog.skill(input.skill.replace(/^\//u, ''));
|
|
80
|
+
if (definition?.modelInvocable && definition.permissionSafe) {
|
|
81
|
+
return { behavior: 'allow' };
|
|
82
|
+
}
|
|
83
|
+
return definition
|
|
84
|
+
? { ...decision, metadata: { command: definition } }
|
|
85
|
+
: decision;
|
|
76
86
|
}
|
|
77
87
|
}
|
|
78
88
|
//# sourceMappingURL=claude-extension-tools.js.map
|
|
@@ -8,6 +8,7 @@ const BUILTIN_LOOP = {
|
|
|
8
8
|
name: 'loop',
|
|
9
9
|
description: 'Run a prompt or slash command on a recurring interval; defaults to 10 minutes.',
|
|
10
10
|
modelInvocable: true,
|
|
11
|
+
permissionSafe: true,
|
|
11
12
|
body: `# /loop — schedule a recurring prompt
|
|
12
13
|
|
|
13
14
|
Parse the input into an optional interval followed by a prompt, then schedule it with CronCreate.
|
|
@@ -128,6 +129,14 @@ function parseDefinition(kind, resource) {
|
|
|
128
129
|
? metadata.name
|
|
129
130
|
: defaultName(kind, resource.path);
|
|
130
131
|
const description = typeof metadata.description === 'string' ? metadata.description : '';
|
|
132
|
+
const allowedTools = metadata['allowed-tools'];
|
|
133
|
+
const hasAllowedTools = Array.isArray(allowedTools)
|
|
134
|
+
? allowedTools.length > 0
|
|
135
|
+
: typeof allowedTools === 'string' && allowedTools.trim().length > 0;
|
|
136
|
+
const hooks = metadata.hooks;
|
|
137
|
+
const hasHooks = hooks !== undefined &&
|
|
138
|
+
hooks !== null &&
|
|
139
|
+
(typeof hooks !== 'object' || Object.keys(hooks).length > 0);
|
|
131
140
|
return {
|
|
132
141
|
...resource,
|
|
133
142
|
kind,
|
|
@@ -135,6 +144,7 @@ function parseDefinition(kind, resource) {
|
|
|
135
144
|
description,
|
|
136
145
|
body,
|
|
137
146
|
modelInvocable: metadata['disable-model-invocation'] !== true,
|
|
147
|
+
permissionSafe: !hasAllowedTools && !hasHooks,
|
|
138
148
|
};
|
|
139
149
|
}
|
|
140
150
|
function indexed(kind, resources) {
|
|
@@ -62,7 +62,7 @@ export class ClaudeHookToolCoordinator {
|
|
|
62
62
|
tool_name: call.name,
|
|
63
63
|
tool_input: prepared?.hookInput ?? call.input,
|
|
64
64
|
tool_use_id: call.id,
|
|
65
|
-
permission_suggestions: [],
|
|
65
|
+
permission_suggestions: decision.suggestions ?? [],
|
|
66
66
|
}, call.name, prepared?.signal);
|
|
67
67
|
await this.options.recordOutcome(outcome);
|
|
68
68
|
if (outcome.blockedReason || outcome.permissionDecision === 'deny') {
|
|
@@ -8,6 +8,7 @@ import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'
|
|
|
8
8
|
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
9
9
|
import { ElicitRequestSchema, ElicitationCompleteNotificationSchema, PromptListChangedNotificationSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
10
10
|
import { redactSensitiveError, redactSensitiveText, redactSensitiveValue, sanitizeChildEnvironment, sensitiveEnvironmentValues, } from '../platform/sensitive-data.js';
|
|
11
|
+
import { parsePermissionUpdates } from '../permissions/permission-updates.js';
|
|
11
12
|
import { loadMcpOAuthProvider, mcpOAuthServerIdentity, } from './claude-mcp-oauth.js';
|
|
12
13
|
const MAX_TOOL_PAGES = 100;
|
|
13
14
|
const MAX_TOOLS = 10_000;
|
|
@@ -270,20 +271,25 @@ function toolContent(result, sensitiveValues) {
|
|
|
270
271
|
}
|
|
271
272
|
return redactSensitiveText(parts.join('\n'), sensitiveValues);
|
|
272
273
|
}
|
|
273
|
-
const INVALID_PERMISSION_RESULT = "The permission prompt tool returned an invalid permission result. Expected {behavior: 'allow', updatedInput
|
|
274
|
+
const INVALID_PERMISSION_RESULT = "The permission prompt tool returned an invalid permission result. Expected {behavior: 'allow', updatedInput: object} or {behavior: 'deny', message: string}.";
|
|
274
275
|
function invalidPermissionResult() {
|
|
275
276
|
return { behavior: 'deny', message: INVALID_PERMISSION_RESULT };
|
|
276
277
|
}
|
|
277
|
-
function parsePermissionResult(source) {
|
|
278
|
+
function parsePermissionResult(source, currentInput) {
|
|
278
279
|
const value = JSON.parse(source);
|
|
279
280
|
if (!isRecord(value))
|
|
280
281
|
return invalidPermissionResult();
|
|
281
282
|
if (value.behavior === 'allow') {
|
|
282
|
-
if (value.updatedInput === undefined)
|
|
283
|
-
return { behavior: 'allow' };
|
|
284
283
|
if (!isRecord(value.updatedInput))
|
|
285
284
|
return invalidPermissionResult();
|
|
286
|
-
|
|
285
|
+
const updatedPermissions = parsePermissionUpdates(value.updatedPermissions);
|
|
286
|
+
return {
|
|
287
|
+
behavior: 'allow',
|
|
288
|
+
updatedInput: Object.keys(value.updatedInput).length === 0
|
|
289
|
+
? currentInput
|
|
290
|
+
: value.updatedInput,
|
|
291
|
+
...(updatedPermissions ? { updatedPermissions } : {}),
|
|
292
|
+
};
|
|
287
293
|
}
|
|
288
294
|
if (value.behavior === 'deny' && typeof value.message === 'string') {
|
|
289
295
|
return {
|
|
@@ -812,7 +818,7 @@ export class ClaudeMcpToolRegistry {
|
|
|
812
818
|
return invalidPermissionResult();
|
|
813
819
|
}
|
|
814
820
|
try {
|
|
815
|
-
return parsePermissionResult(toolContent(result, tool.sensitiveValues));
|
|
821
|
+
return parsePermissionResult(toolContent(result, tool.sensitiveValues), call.input);
|
|
816
822
|
}
|
|
817
823
|
catch {
|
|
818
824
|
return invalidPermissionResult();
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export declare const MAX_BASH_PERMISSION_COMMANDS = 50;
|
|
2
|
+
export type BashSemanticResult = {
|
|
3
|
+
safe: true;
|
|
4
|
+
} | {
|
|
5
|
+
safe: false;
|
|
6
|
+
reason: string;
|
|
7
|
+
};
|
|
8
|
+
export type BashWrapperResult = {
|
|
9
|
+
ok: true;
|
|
10
|
+
argv: string[];
|
|
11
|
+
} | {
|
|
12
|
+
ok: false;
|
|
13
|
+
reason: string;
|
|
14
|
+
};
|
|
15
|
+
export declare function normalizeBashWrapperArgv(input: readonly string[]): BashWrapperResult;
|
|
16
|
+
export interface BashStaticCommand {
|
|
17
|
+
text: string;
|
|
18
|
+
argv: readonly string[];
|
|
19
|
+
}
|
|
20
|
+
export interface BashStaticRedirect {
|
|
21
|
+
operator: '>' | '>>' | '<' | '>&' | '<&' | '>|' | '&>' | '&>>' | '<<<';
|
|
22
|
+
target: string;
|
|
23
|
+
}
|
|
24
|
+
export type BashStaticAnalysis = {
|
|
25
|
+
parsed: true;
|
|
26
|
+
commands: readonly BashStaticCommand[];
|
|
27
|
+
redirects: readonly BashStaticRedirect[];
|
|
28
|
+
} | {
|
|
29
|
+
parsed: false;
|
|
30
|
+
reason: string;
|
|
31
|
+
};
|
|
32
|
+
export declare function analyzeBashStructure(source: string): BashStaticAnalysis;
|
|
33
|
+
export declare function validateBashSemantics(source: string): BashSemanticResult;
|
|
34
|
+
export interface BashCommandAnalysis {
|
|
35
|
+
commands: readonly string[];
|
|
36
|
+
parsed: boolean;
|
|
37
|
+
}
|
|
38
|
+
export declare function analyzeBashCommands(source: string): BashCommandAnalysis;
|
|
39
|
+
//# sourceMappingURL=bash-ast.d.ts.map
|