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.
- package/README.md +5 -2
- package/dist/cli/controls.js +2 -0
- package/dist/cli/protocol.d.ts +1 -0
- package/dist/cli/protocol.js +6 -0
- package/dist/cli/workspace-trust-prompt.d.ts +10 -0
- package/dist/cli/workspace-trust-prompt.js +53 -0
- package/dist/cli-runtime.d.ts +2 -0
- package/dist/cli-runtime.js +168 -27
- package/dist/core/resources.d.ts +5 -0
- package/dist/plugins/claude-plugin-mcpb.d.ts +2 -0
- package/dist/plugins/claude-plugin-mcpb.js +11 -2
- package/dist/plugins/claude-plugin-runtime.d.ts +2 -0
- package/dist/plugins/claude-plugin-runtime.js +99 -9
- package/dist/providers/deadline-provider.d.ts +16 -0
- package/dist/providers/deadline-provider.js +226 -0
- package/dist/providers/environment.d.ts +1 -0
- package/dist/providers/environment.js +12 -0
- package/dist/providers/fallback-provider.js +35 -2
- package/dist/security/workspace-trust.d.ts +30 -0
- package/dist/security/workspace-trust.js +278 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -170,7 +170,9 @@ troubleshooting. Run `praxis --help` for the authoritative command surface.
|
|
|
170
170
|
exclusions, violation reporting, and bare-repository control-file cleanup,
|
|
171
171
|
safe-property Skill auto-allow, interactive
|
|
172
172
|
workspace-directory add/remove controls, path confinement, credential
|
|
173
|
-
redaction,
|
|
173
|
+
redaction, sanitized child processes, and exact-fingerprint workspace trust
|
|
174
|
+
that blocks automatically discovered project/local hooks and MCP servers
|
|
175
|
+
until the canonical workspace configuration is explicitly accepted.
|
|
174
176
|
- **Durable local work** — resumable sessions, full-history forks, file
|
|
175
177
|
checkpoints, tasks, foreground/background subagents, top-level agents, and
|
|
176
178
|
Claude-compatible main-thread agent definitions with native prompt, model,
|
|
@@ -198,7 +200,8 @@ troubleshooting. Run `praxis --help` for the authoritative command surface.
|
|
|
198
200
|
imports, memory, skills, commands, agents, hooks, settings, MCP servers,
|
|
199
201
|
plugins, and append-only `praxis.transcript` JSONL sessions under `~/.praxis`.
|
|
200
202
|
- **Provider-neutral models** — native Anthropic Messages and OpenAI-compatible
|
|
201
|
-
streaming adapters with explicit capability checks
|
|
203
|
+
streaming adapters with explicit capability checks, metering controls, and
|
|
204
|
+
a bounded absolute deadline for every provider attempt.
|
|
202
205
|
|
|
203
206
|
Detailed feature status and executable evidence live in the
|
|
204
207
|
[parity matrix](https://github.com/Forest-Isle/Praxis/blob/main/docs/PARITY_MATRIX.md),
|
package/dist/cli/controls.js
CHANGED
|
@@ -7,6 +7,7 @@ export const DEFAULT_CLI_CONTROLS = {
|
|
|
7
7
|
settingSources: undefined,
|
|
8
8
|
safeMode: false,
|
|
9
9
|
bare: false,
|
|
10
|
+
trustProject: false,
|
|
10
11
|
systemPrompt: undefined,
|
|
11
12
|
systemPromptFile: undefined,
|
|
12
13
|
appendSystemPrompt: undefined,
|
|
@@ -180,6 +181,7 @@ export async function resolveCliControls(controls, cwd) {
|
|
|
180
181
|
settingSources: controls.settingSources,
|
|
181
182
|
safeMode: controls.safeMode,
|
|
182
183
|
bare: controls.bare,
|
|
184
|
+
trustProject: controls.trustProject,
|
|
183
185
|
systemPrompt,
|
|
184
186
|
appendSystemPrompt,
|
|
185
187
|
excludeDynamicSystemPromptSections: controls.excludeDynamicSystemPromptSections,
|
package/dist/cli/protocol.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export interface CliControls {
|
|
|
14
14
|
settingSources: readonly ('user' | 'project' | 'local')[] | undefined;
|
|
15
15
|
safeMode: boolean;
|
|
16
16
|
bare: boolean;
|
|
17
|
+
trustProject: boolean;
|
|
17
18
|
systemPrompt: string | undefined;
|
|
18
19
|
systemPromptFile: string | undefined;
|
|
19
20
|
appendSystemPrompt: string | undefined;
|
package/dist/cli/protocol.js
CHANGED
|
@@ -359,6 +359,7 @@ export function parseCliInvocation(argv) {
|
|
|
359
359
|
let autocompact;
|
|
360
360
|
let safeMode = false;
|
|
361
361
|
let bare = false;
|
|
362
|
+
let trustProject = false;
|
|
362
363
|
let systemPrompt;
|
|
363
364
|
let systemPromptFile;
|
|
364
365
|
let appendSystemPrompt;
|
|
@@ -1082,6 +1083,10 @@ export function parseCliInvocation(argv) {
|
|
|
1082
1083
|
bare = true;
|
|
1083
1084
|
continue;
|
|
1084
1085
|
}
|
|
1086
|
+
if (value === '--trust-project') {
|
|
1087
|
+
trustProject = true;
|
|
1088
|
+
continue;
|
|
1089
|
+
}
|
|
1085
1090
|
if (value === '--init') {
|
|
1086
1091
|
init = true;
|
|
1087
1092
|
continue;
|
|
@@ -1343,6 +1348,7 @@ export function parseCliInvocation(argv) {
|
|
|
1343
1348
|
settingSources,
|
|
1344
1349
|
safeMode,
|
|
1345
1350
|
bare,
|
|
1351
|
+
trustProject,
|
|
1346
1352
|
systemPrompt,
|
|
1347
1353
|
systemPromptFile,
|
|
1348
1354
|
appendSystemPrompt,
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type WorkspaceTrustInventory } from '../security/workspace-trust.js';
|
|
2
|
+
export interface WorkspaceTrustPromptIO {
|
|
3
|
+
input?: AsyncIterable<string>;
|
|
4
|
+
output?: (text: string) => void;
|
|
5
|
+
signal?: AbortSignal;
|
|
6
|
+
}
|
|
7
|
+
export declare function createWorkspaceTrustDecisionCache(decide: (inventory: WorkspaceTrustInventory) => boolean | Promise<boolean>): (inventory: WorkspaceTrustInventory) => Promise<boolean>;
|
|
8
|
+
export declare function safeWorkspaceTrustDisplayField(value: string): string;
|
|
9
|
+
export declare function promptWorkspaceTrust(inventory: WorkspaceTrustInventory, io?: WorkspaceTrustPromptIO): Promise<boolean>;
|
|
10
|
+
//# sourceMappingURL=workspace-trust-prompt.d.ts.map
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { workspaceTrustDecisionKey, } from '../security/workspace-trust.js';
|
|
2
|
+
export function createWorkspaceTrustDecisionCache(decide) {
|
|
3
|
+
const decisions = new Map();
|
|
4
|
+
return async (inventory) => {
|
|
5
|
+
const key = workspaceTrustDecisionKey(inventory);
|
|
6
|
+
const cached = decisions.get(key);
|
|
7
|
+
if (cached !== undefined)
|
|
8
|
+
return cached;
|
|
9
|
+
const decision = await decide(inventory);
|
|
10
|
+
decisions.set(key, decision);
|
|
11
|
+
return decision;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export function safeWorkspaceTrustDisplayField(value) {
|
|
15
|
+
return [...value]
|
|
16
|
+
.map((character) => {
|
|
17
|
+
return /[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/u.test(character) ? '?' : character;
|
|
18
|
+
})
|
|
19
|
+
.join('')
|
|
20
|
+
.slice(0, 512);
|
|
21
|
+
}
|
|
22
|
+
export async function promptWorkspaceTrust(inventory, io = {}) {
|
|
23
|
+
const output = io.output ?? ((text) => process.stderr.write(text));
|
|
24
|
+
if (io.signal?.aborted)
|
|
25
|
+
return false;
|
|
26
|
+
output(`Workspace executable resources found for ${safeWorkspaceTrustDisplayField(inventory.canonicalPath)}\n`);
|
|
27
|
+
for (const origin of inventory.origins)
|
|
28
|
+
output(` ${origin.kind} (${origin.scope}) ${safeWorkspaceTrustDisplayField(origin.path)}: ${safeWorkspaceTrustDisplayField(origin.label)}\n`);
|
|
29
|
+
output('Trust these workspace executables? [y/N] ');
|
|
30
|
+
const input = io.input ?? process.stdin;
|
|
31
|
+
const iterator = input[Symbol.asyncIterator]();
|
|
32
|
+
const abortedResult = Symbol('workspace-trust-prompt-aborted');
|
|
33
|
+
let abort;
|
|
34
|
+
try {
|
|
35
|
+
const aborted = new Promise((resolve) => {
|
|
36
|
+
abort = () => resolve(abortedResult);
|
|
37
|
+
io.signal?.addEventListener('abort', abort, { once: true });
|
|
38
|
+
if (io.signal?.aborted)
|
|
39
|
+
abort();
|
|
40
|
+
});
|
|
41
|
+
const result = await Promise.race([iterator.next(), aborted]);
|
|
42
|
+
if (result === abortedResult)
|
|
43
|
+
return false;
|
|
44
|
+
if (result.done)
|
|
45
|
+
return false;
|
|
46
|
+
return /^(?:y|yes)$/iu.test(String(result.value).trim());
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
if (abort)
|
|
50
|
+
io.signal?.removeEventListener('abort', abort);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=workspace-trust-prompt.js.map
|
package/dist/cli-runtime.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ import type { TuiSlashCommand } from './cli/tui/slash-commands.js';
|
|
|
11
11
|
import { type TuiHookConfiguration } from './cli/tui/hook-settings.js';
|
|
12
12
|
import { type PraxisRuntimeSettings } from './cli/tui/runtime-settings.js';
|
|
13
13
|
import { type ClaudePermissionMode } from './permissions/claude-permission-resolver.js';
|
|
14
|
+
import { type WorkspaceTrustInventory } from './security/workspace-trust.js';
|
|
14
15
|
import { type ClaudeMcpServerStatus, type ClaudeMcpToolInspection } from './mcp/claude-mcp-tools.js';
|
|
15
16
|
import { authenticateMcpServer } from './mcp/claude-mcp-oauth.js';
|
|
16
17
|
import { servePraxisMcpStdio } from './mcp/praxis-mcp-server.js';
|
|
@@ -138,6 +139,7 @@ export interface CliDependencies extends InteractiveServiceFactory {
|
|
|
138
139
|
onElicitation?: (request: CliElicitationRequest) => Promise<CliElicitationResult>;
|
|
139
140
|
askUser?: ClaudeInteractiveToolCallbacks['askUser'];
|
|
140
141
|
approvePlan?: ClaudeInteractiveToolCallbacks['approvePlan'];
|
|
142
|
+
approveWorkspaceTrust?: (request: WorkspaceTrustInventory) => boolean | Promise<boolean>;
|
|
141
143
|
}): Promise<SessionCommands>;
|
|
142
144
|
createAutoModeCritic?(options: {
|
|
143
145
|
model?: string;
|
package/dist/cli-runtime.js
CHANGED
|
@@ -33,6 +33,8 @@ import { createClaudeModelAutoClassifier, defaultClaudeAutoModeConfig, loadClaud
|
|
|
33
33
|
import { ClaudeExtensionPermissionResolver, ClaudeExtensionToolRegistry, } from './extensions/claude-extension-tools.js';
|
|
34
34
|
import { ClaudeExtensionCatalog } from './extensions/claude-extensions.js';
|
|
35
35
|
import { ClaudeHookRunner } from './hooks/claude-hooks.js';
|
|
36
|
+
import { allowedWorkspaceHookSettings, allowedWorkspaceMcpResources, assessWorkspaceTrust, persistWorkspaceTrust, workspaceTrustDecisionKey, workspaceTrustInventory, } from './security/workspace-trust.js';
|
|
37
|
+
import { createWorkspaceTrustDecisionCache, promptWorkspaceTrust, safeWorkspaceTrustDisplayField, } from './cli/workspace-trust-prompt.js';
|
|
36
38
|
import { ClaudeSessionEnvironment } from './hooks/claude-session-environment.js';
|
|
37
39
|
import { ClaudeMcpToolRegistry, } from './mcp/claude-mcp-tools.js';
|
|
38
40
|
import { ClaudeMcpManagement, filterDisabledMcpResources, mcpScope, } from './mcp/claude-mcp-management.js';
|
|
@@ -45,6 +47,7 @@ import { redactSensitiveText, sensitiveEnvironmentValues, } from './platform/sen
|
|
|
45
47
|
import { AnthropicCompatibleProvider } from './providers/anthropic-compatible.js';
|
|
46
48
|
import { createAnthropicPromptCachePolicyResolver } from './providers/anthropic-prompt-cache.js';
|
|
47
49
|
import { FallbackModelProvider } from './providers/fallback-provider.js';
|
|
50
|
+
import { DeadlineModelProvider } from './providers/deadline-provider.js';
|
|
48
51
|
import { OpenAICompatibleProvider } from './providers/openai-compatible.js';
|
|
49
52
|
import { parseContextEnvironment, parseProviderEnvironment, } from './providers/environment.js';
|
|
50
53
|
import { ModelPricingRegistry, usageCostUsd } from './core/usage.js';
|
|
@@ -105,7 +108,7 @@ function createProviderForModel({ apiKey, environment, provider, context, contro
|
|
|
105
108
|
? { contextWindowTokens: context.contextWindowTokens }
|
|
106
109
|
: {}),
|
|
107
110
|
};
|
|
108
|
-
|
|
111
|
+
const concrete = provider.provider === 'anthropic'
|
|
109
112
|
? new AnthropicCompatibleProvider({
|
|
110
113
|
...providerOptions,
|
|
111
114
|
promptCaching: resolvePromptCachePolicy({
|
|
@@ -124,7 +127,9 @@ function createProviderForModel({ apiKey, environment, provider, context, contro
|
|
|
124
127
|
...('anthropicVersion' in provider
|
|
125
128
|
? { anthropicVersion: provider.anthropicVersion }
|
|
126
129
|
: {}),
|
|
127
|
-
...('webSearch' in provider
|
|
130
|
+
...('webSearch' in provider
|
|
131
|
+
? { webSearch: provider.webSearch }
|
|
132
|
+
: {}),
|
|
128
133
|
})
|
|
129
134
|
: new OpenAICompatibleProvider({
|
|
130
135
|
...providerOptions,
|
|
@@ -140,6 +145,10 @@ function createProviderForModel({ apiKey, environment, provider, context, contro
|
|
|
140
145
|
},
|
|
141
146
|
}),
|
|
142
147
|
});
|
|
148
|
+
return new DeadlineModelProvider({
|
|
149
|
+
provider: concrete,
|
|
150
|
+
deadlineMs: provider.deadlineMs,
|
|
151
|
+
});
|
|
143
152
|
};
|
|
144
153
|
}
|
|
145
154
|
const HELP = `Praxis — local-first general agent
|
|
@@ -216,6 +225,7 @@ Options:
|
|
|
216
225
|
--setting-sources <sources> user, project, local, or an empty list
|
|
217
226
|
--safe-mode Disable shared customizations
|
|
218
227
|
--bare Use only explicitly supplied context
|
|
228
|
+
--trust-project Trust current workspace executables
|
|
219
229
|
--system-prompt <prompt> Set system prompt
|
|
220
230
|
--append-system-prompt <prompt> Append system prompt
|
|
221
231
|
--exclude-dynamic-system-prompt-sections
|
|
@@ -755,6 +765,12 @@ const consoleIO = {
|
|
|
755
765
|
isTTY: Boolean(process.stdin.isTTY && process.stdout.isTTY),
|
|
756
766
|
readStdinLines: () => process.stdin,
|
|
757
767
|
};
|
|
768
|
+
function warningEventSink(io) {
|
|
769
|
+
return (event) => {
|
|
770
|
+
if (event.type === 'warning')
|
|
771
|
+
io.stderr(`${event.message}\n`);
|
|
772
|
+
};
|
|
773
|
+
}
|
|
758
774
|
/**
|
|
759
775
|
* Shared runtime model precedence used by every consumer (provider
|
|
760
776
|
* construction, status/doctor output, and the interactive display):
|
|
@@ -789,7 +805,7 @@ export function resolveUnknownCostSidecarPath(dataPlane, configRoot) {
|
|
|
789
805
|
assertNativeDataPlane(dataPlane);
|
|
790
806
|
return join(configRoot, 'state', 'unknown-cost-sidecar.json');
|
|
791
807
|
}
|
|
792
|
-
const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = false, approveRecovery, approveTool, agent, model: interactiveModel, effort: interactiveEffort, permissionMode: interactivePermissionMode, isSessionActionApproved, controls = DEFAULT_CLI_CONTROLS, interactive = false, sessionKind, signal, exposeToolRegistry = false, onElicitation, askUser, approvePlan, emitToolUseSummaries = false, cwd: requestedCwd, sandboxOriginalCwd, configRoot: requestedConfigRoot, environment, providerEnvironment: requestedProviderEnvironment, }) => {
|
|
808
|
+
const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = false, approveRecovery, approveTool, agent, model: interactiveModel, effort: interactiveEffort, permissionMode: interactivePermissionMode, isSessionActionApproved, controls = DEFAULT_CLI_CONTROLS, interactive = false, sessionKind, signal, exposeToolRegistry = false, onElicitation, askUser, approvePlan, approveWorkspaceTrust, emitToolUseSummaries = false, cwd: requestedCwd, sandboxOriginalCwd, configRoot: requestedConfigRoot, environment, providerEnvironment: requestedProviderEnvironment, }) => {
|
|
793
809
|
const runtimeEnvironment = requestedProviderEnvironment ?? process.env;
|
|
794
810
|
const sandboxEnvironment = { ...runtimeEnvironment, ...environment };
|
|
795
811
|
const claudeVersion = VERIFIED_CLAUDE_SCHEMA_VERSION;
|
|
@@ -971,11 +987,58 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
971
987
|
}
|
|
972
988
|
: {}),
|
|
973
989
|
};
|
|
990
|
+
const warnedWorkspaceFingerprints = new Set();
|
|
991
|
+
let trustProjectRequestAvailable = cli.trustProject;
|
|
992
|
+
const authorizeWorkspaceExecutables = async (automaticSettings, automaticMcp, runtimeCwd) => {
|
|
993
|
+
if (cli.safeMode || simpleMode)
|
|
994
|
+
return true;
|
|
995
|
+
const trustRequested = trustProjectRequestAvailable;
|
|
996
|
+
trustProjectRequestAvailable = false;
|
|
997
|
+
const inventory = await workspaceTrustInventory({
|
|
998
|
+
cwd: runtimeCwd,
|
|
999
|
+
settings: automaticSettings,
|
|
1000
|
+
mcp: automaticMcp,
|
|
1001
|
+
});
|
|
1002
|
+
const assessment = await assessWorkspaceTrust(inventory, claudeStatePath);
|
|
1003
|
+
if (assessment.status !== 'untrusted')
|
|
1004
|
+
return true;
|
|
1005
|
+
let approved = trustRequested;
|
|
1006
|
+
if (!approved && approveWorkspaceTrust) {
|
|
1007
|
+
try {
|
|
1008
|
+
approved = await approveWorkspaceTrust(assessment);
|
|
1009
|
+
}
|
|
1010
|
+
catch (error) {
|
|
1011
|
+
if (!(error instanceof Error &&
|
|
1012
|
+
(error.name === 'AbortError' || error.name === 'CancellationError'))) {
|
|
1013
|
+
throw error;
|
|
1014
|
+
}
|
|
1015
|
+
approved = false;
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
if (approved) {
|
|
1019
|
+
await persistWorkspaceTrust(assessment, claudeStatePath);
|
|
1020
|
+
return true;
|
|
1021
|
+
}
|
|
1022
|
+
const warningKey = workspaceTrustDecisionKey(assessment);
|
|
1023
|
+
if (!warnedWorkspaceFingerprints.has(warningKey)) {
|
|
1024
|
+
warnedWorkspaceFingerprints.add(warningKey);
|
|
1025
|
+
runtimeEventSink({
|
|
1026
|
+
type: 'warning',
|
|
1027
|
+
message: `Workspace executable resources blocked for ${safeWorkspaceTrustDisplayField(assessment.canonicalPath)}; restart to review them interactively, or rerun with --trust-project to approve the current fingerprint.`,
|
|
1028
|
+
});
|
|
1029
|
+
}
|
|
1030
|
+
return false;
|
|
1031
|
+
};
|
|
974
1032
|
const hookConfiguration = async () => {
|
|
975
|
-
const [
|
|
1033
|
+
const [sharedResources, pluginResources] = await Promise.all([
|
|
976
1034
|
cli.safeMode || simpleMode
|
|
977
|
-
? []
|
|
978
|
-
:
|
|
1035
|
+
? Promise.resolve({ settings: [], mcp: [] })
|
|
1036
|
+
: loadNativeSharedResources({
|
|
1037
|
+
root: configRoot,
|
|
1038
|
+
cwd,
|
|
1039
|
+
environment: runtimeEnvironment,
|
|
1040
|
+
includeProjectMemory: false,
|
|
1041
|
+
}),
|
|
979
1042
|
loadClaudePlugins({
|
|
980
1043
|
configRoot,
|
|
981
1044
|
cwd,
|
|
@@ -983,13 +1046,20 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
983
1046
|
pluginUrls: cli.pluginUrls,
|
|
984
1047
|
strictPluginDirectories: cli.pluginDirectories.length + cli.pluginUrls.length > 0,
|
|
985
1048
|
loadInstalled: !cli.safeMode && !simpleMode,
|
|
986
|
-
|
|
1049
|
+
readOnlyExecutables: true,
|
|
987
1050
|
environment: runtimeEnvironment,
|
|
988
1051
|
}),
|
|
989
1052
|
]);
|
|
990
|
-
|
|
991
|
-
...settings,
|
|
1053
|
+
const automaticSettings = [
|
|
1054
|
+
...sharedResources.settings,
|
|
992
1055
|
...pluginResources.settings,
|
|
1056
|
+
];
|
|
1057
|
+
const automaticMcp = cli.strictMcpConfig
|
|
1058
|
+
? []
|
|
1059
|
+
: [...sharedResources.mcp, ...pluginResources.mcp];
|
|
1060
|
+
const trusted = await authorizeWorkspaceExecutables(automaticSettings, automaticMcp, cwd);
|
|
1061
|
+
return projectTuiHooks([
|
|
1062
|
+
...allowedWorkspaceHookSettings(automaticSettings, trusted),
|
|
993
1063
|
...(cli.additionalSettings ? [cli.additionalSettings] : []),
|
|
994
1064
|
]);
|
|
995
1065
|
};
|
|
@@ -1001,7 +1071,10 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
1001
1071
|
const service = new ClaudeSessionService(options);
|
|
1002
1072
|
if (!interactive)
|
|
1003
1073
|
return service;
|
|
1004
|
-
|
|
1074
|
+
const initialHookConfiguration = await hookConfiguration();
|
|
1075
|
+
return Object.assign(service, {
|
|
1076
|
+
hookConfiguration: () => Promise.resolve(initialHookConfiguration),
|
|
1077
|
+
});
|
|
1005
1078
|
}
|
|
1006
1079
|
const toolProvider = provider ?? {
|
|
1007
1080
|
model: 'praxis/provider',
|
|
@@ -1034,7 +1107,7 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
1034
1107
|
const baseSettings = nativeSharedResourcesEnabled
|
|
1035
1108
|
? await loadNativeSettings({ root: configRoot, cwd })
|
|
1036
1109
|
: [];
|
|
1037
|
-
const
|
|
1110
|
+
const pluginLoadOptions = {
|
|
1038
1111
|
configRoot,
|
|
1039
1112
|
cwd,
|
|
1040
1113
|
pluginDirectories: cli.pluginDirectories,
|
|
@@ -1042,6 +1115,10 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
1042
1115
|
strictPluginDirectories: cli.pluginDirectories.length + cli.pluginUrls.length > 0,
|
|
1043
1116
|
loadInstalled: !cli.safeMode && !simpleMode,
|
|
1044
1117
|
environment: runtimeEnvironment,
|
|
1118
|
+
};
|
|
1119
|
+
const executablePluginResources = await loadClaudePlugins({
|
|
1120
|
+
...pluginLoadOptions,
|
|
1121
|
+
readOnlyExecutables: true,
|
|
1045
1122
|
});
|
|
1046
1123
|
const projectMemoryPolicy = cli.safeMode || simpleMode
|
|
1047
1124
|
? { enabled: false, extraction: false, recall: false }
|
|
@@ -1049,7 +1126,7 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
1049
1126
|
dataPlane,
|
|
1050
1127
|
settings: [
|
|
1051
1128
|
...baseSettings,
|
|
1052
|
-
...
|
|
1129
|
+
...executablePluginResources.settings,
|
|
1053
1130
|
...(cli.additionalSettings ? [cli.additionalSettings] : []),
|
|
1054
1131
|
],
|
|
1055
1132
|
environment: runtimeEnvironment,
|
|
@@ -1071,7 +1148,42 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
1071
1148
|
settings: [],
|
|
1072
1149
|
mcp: [],
|
|
1073
1150
|
};
|
|
1074
|
-
const
|
|
1151
|
+
const trustSettings = [
|
|
1152
|
+
...loadedResources.settings,
|
|
1153
|
+
...executablePluginResources.settings,
|
|
1154
|
+
];
|
|
1155
|
+
const trustMcp = cli.strictMcpConfig
|
|
1156
|
+
? []
|
|
1157
|
+
: [...loadedResources.mcp, ...executablePluginResources.mcp];
|
|
1158
|
+
const workspaceExecutablesTrusted = await authorizeWorkspaceExecutables(trustSettings, trustMcp, cwd);
|
|
1159
|
+
const loadedPluginResources = await loadClaudePlugins({
|
|
1160
|
+
...pluginLoadOptions,
|
|
1161
|
+
allowWorkspaceMcpb: workspaceExecutablesTrusted,
|
|
1162
|
+
});
|
|
1163
|
+
const materializedPluginMcp = new Map(loadedPluginResources.mcp.map((resource) => [resource.path, resource]));
|
|
1164
|
+
const pluginResources = {
|
|
1165
|
+
...loadedPluginResources,
|
|
1166
|
+
settings: executablePluginResources.settings,
|
|
1167
|
+
mcp: executablePluginResources.mcp.flatMap((resource) => {
|
|
1168
|
+
if (resource.pluginExecutableSource?.kind !== 'mcpb')
|
|
1169
|
+
return [resource];
|
|
1170
|
+
const materialized = materializedPluginMcp.get(resource.path);
|
|
1171
|
+
if (!materialized)
|
|
1172
|
+
return [];
|
|
1173
|
+
if (materialized.pluginExecutableSource?.source !==
|
|
1174
|
+
resource.pluginExecutableSource.source ||
|
|
1175
|
+
materialized.pluginExecutableSource.fingerprint !==
|
|
1176
|
+
resource.pluginExecutableSource.fingerprint) {
|
|
1177
|
+
runtimeEventSink({
|
|
1178
|
+
type: 'warning',
|
|
1179
|
+
message: `Workspace MCPB source changed during trust preflight and was blocked: ${safeWorkspaceTrustDisplayField(resource.path)}. Restart to review the new fingerprint.`,
|
|
1180
|
+
});
|
|
1181
|
+
return [];
|
|
1182
|
+
}
|
|
1183
|
+
return [materialized];
|
|
1184
|
+
}),
|
|
1185
|
+
};
|
|
1186
|
+
const allSettings = [
|
|
1075
1187
|
...loadedResources.settings,
|
|
1076
1188
|
...pluginResources.settings,
|
|
1077
1189
|
...(cli.additionalSettings ? [cli.additionalSettings] : []),
|
|
@@ -1084,7 +1196,7 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
1084
1196
|
});
|
|
1085
1197
|
}
|
|
1086
1198
|
}
|
|
1087
|
-
const configuredAgent = [...
|
|
1199
|
+
const configuredAgent = [...allSettings]
|
|
1088
1200
|
.reverse()
|
|
1089
1201
|
.map((resource) => resource.value &&
|
|
1090
1202
|
typeof resource.value === 'object' &&
|
|
@@ -1093,6 +1205,21 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
1093
1205
|
: undefined)
|
|
1094
1206
|
.find((value) => typeof value === 'string' && value.length > 0);
|
|
1095
1207
|
const selectedMainAgent = agent ?? configuredAgent;
|
|
1208
|
+
const automaticSettings = [
|
|
1209
|
+
...loadedResources.settings,
|
|
1210
|
+
...pluginResources.settings,
|
|
1211
|
+
];
|
|
1212
|
+
const automaticMcp = cli.strictMcpConfig
|
|
1213
|
+
? []
|
|
1214
|
+
: [...loadedResources.mcp, ...pluginResources.mcp];
|
|
1215
|
+
const hookSettings = [
|
|
1216
|
+
...allowedWorkspaceHookSettings(automaticSettings, workspaceExecutablesTrusted),
|
|
1217
|
+
...(cli.additionalSettings ? [cli.additionalSettings] : []),
|
|
1218
|
+
];
|
|
1219
|
+
const executableMcp = [
|
|
1220
|
+
...allowedWorkspaceMcpResources(automaticMcp, workspaceExecutablesTrusted),
|
|
1221
|
+
...cli.mcpResources,
|
|
1222
|
+
];
|
|
1096
1223
|
const resources = {
|
|
1097
1224
|
...loadedResources,
|
|
1098
1225
|
commands: [...loadedResources.commands, ...pluginResources.commands],
|
|
@@ -1102,10 +1229,8 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
1102
1229
|
...pluginResources.agents,
|
|
1103
1230
|
...cli.inlineAgents,
|
|
1104
1231
|
],
|
|
1105
|
-
settings,
|
|
1106
|
-
mcp:
|
|
1107
|
-
? cli.mcpResources
|
|
1108
|
-
: [...loadedResources.mcp, ...pluginResources.mcp, ...cli.mcpResources],
|
|
1232
|
+
settings: allSettings,
|
|
1233
|
+
mcp: executableMcp,
|
|
1109
1234
|
};
|
|
1110
1235
|
const extensions = new ClaudeExtensionCatalog(resources, {
|
|
1111
1236
|
disableSlashCommands: cli.disableSlashCommands,
|
|
@@ -1174,7 +1299,7 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
1174
1299
|
...(exposePlanDirectory ? [resolve(configRoot, 'plans')] : []),
|
|
1175
1300
|
];
|
|
1176
1301
|
const sandboxSettings = loadClaudeSandboxSettings({
|
|
1177
|
-
resources:
|
|
1302
|
+
resources: allSettings.filter((resource) => resource.plugin !== true),
|
|
1178
1303
|
cwd: workspace.cwd(),
|
|
1179
1304
|
originalCwd: sandboxOriginalCwd ?? workspace.cwd(),
|
|
1180
1305
|
configRoot,
|
|
@@ -1200,7 +1325,7 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
1200
1325
|
cwd,
|
|
1201
1326
|
cwdProvider: () => workspace.cwd(),
|
|
1202
1327
|
configRoot,
|
|
1203
|
-
settings,
|
|
1328
|
+
settings: allSettings,
|
|
1204
1329
|
allowedTools: cli.allowedTools,
|
|
1205
1330
|
disallowedTools: cli.disallowedTools,
|
|
1206
1331
|
additionalDirectories: permissionAdditionalDirectories,
|
|
@@ -1275,9 +1400,14 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
1275
1400
|
settings: [],
|
|
1276
1401
|
mcp: [],
|
|
1277
1402
|
};
|
|
1403
|
+
const refreshedSettings = [
|
|
1404
|
+
...refreshed.settings,
|
|
1405
|
+
...pluginResources.settings,
|
|
1406
|
+
];
|
|
1407
|
+
const refreshedMcp = [...refreshed.mcp, ...pluginResources.mcp];
|
|
1408
|
+
const trusted = await authorizeWorkspaceExecutables(refreshedSettings, refreshedMcp, workspace.cwd());
|
|
1278
1409
|
return runtimeMcpResources([
|
|
1279
|
-
...
|
|
1280
|
-
...pluginResources.mcp,
|
|
1410
|
+
...allowedWorkspaceMcpResources(refreshedMcp, trusted),
|
|
1281
1411
|
...cli.mcpResources,
|
|
1282
1412
|
]);
|
|
1283
1413
|
},
|
|
@@ -1439,7 +1569,7 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
1439
1569
|
const hooks = cli.safeMode || simpleMode
|
|
1440
1570
|
? undefined
|
|
1441
1571
|
: new ClaudeHookRunner({
|
|
1442
|
-
settings,
|
|
1572
|
+
settings: hookSettings,
|
|
1443
1573
|
cwd,
|
|
1444
1574
|
onEvent: (event) => runtimeEventSink({ type: 'hook', event }),
|
|
1445
1575
|
sessionEnvironment: hookSessionEnvironment,
|
|
@@ -1771,7 +1901,7 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
1771
1901
|
? {}
|
|
1772
1902
|
: { progressMessage: definition.progressMessage }),
|
|
1773
1903
|
})),
|
|
1774
|
-
hookConfiguration: async () => projectTuiHooks(
|
|
1904
|
+
hookConfiguration: async () => projectTuiHooks(hookSettings),
|
|
1775
1905
|
mcpInspect: () => service.mcpInspect(),
|
|
1776
1906
|
mcpReconnect: (name) => service.mcpReconnect(name),
|
|
1777
1907
|
mcpAuthenticate: (name) => service.mcpAuthenticate(name),
|
|
@@ -2051,6 +2181,14 @@ export function createDefaultDependencies(entrypoint = fileURLToPath(import.meta
|
|
|
2051
2181
|
runInteractive: async ({ agent, controls, initialPrompt, resume, signal, }) => {
|
|
2052
2182
|
const { runInteractive } = await import('./cli/interactive.js');
|
|
2053
2183
|
const interactiveControls = controls ?? DEFAULT_CLI_CONTROLS;
|
|
2184
|
+
let workspaceTrustPreflightOpen = true;
|
|
2185
|
+
const cachedWorkspaceTrustDecision = createWorkspaceTrustDecisionCache((request) => promptWorkspaceTrust(request, {
|
|
2186
|
+
output: (text) => process.stderr.write(text),
|
|
2187
|
+
...(signal ? { signal } : {}),
|
|
2188
|
+
}));
|
|
2189
|
+
const approveInteractiveWorkspaceTrust = (request) => workspaceTrustPreflightOpen
|
|
2190
|
+
? cachedWorkspaceTrustDecision(request)
|
|
2191
|
+
: Promise.resolve(false);
|
|
2054
2192
|
const initialAdditionalDirectories = interactiveControls.addDirectories.map((directory) => realpathSync(resolve(process.cwd(), directory)));
|
|
2055
2193
|
const interactiveDataPlane = interactiveControls.dataPlane ?? resolveDataPlane();
|
|
2056
2194
|
const { configRoot: interactiveConfigRoot, statePath: interactiveStatePath, } = resolveInteractiveRuntimeSettingsLocation(interactiveDataPlane);
|
|
@@ -2079,11 +2217,14 @@ export function createDefaultDependencies(entrypoint = fileURLToPath(import.meta
|
|
|
2079
2217
|
...(agent === undefined ? {} : { agent }),
|
|
2080
2218
|
controls: {
|
|
2081
2219
|
...interactiveControls,
|
|
2220
|
+
trustProject: workspaceTrustPreflightOpen && interactiveControls.trustProject,
|
|
2082
2221
|
addDirectories: options.additionalDirectories ??
|
|
2083
2222
|
interactiveControls.addDirectories,
|
|
2084
2223
|
},
|
|
2085
2224
|
interactive: true,
|
|
2225
|
+
approveWorkspaceTrust: approveInteractiveWorkspaceTrust,
|
|
2086
2226
|
});
|
|
2227
|
+
workspaceTrustPreflightOpen = false;
|
|
2087
2228
|
if (resumePath !== undefined) {
|
|
2088
2229
|
await commands.registerResumePath?.(resumePath);
|
|
2089
2230
|
}
|
|
@@ -3491,7 +3632,7 @@ async function executeMcpCommand(args, invocation, io, dependencies, signal) {
|
|
|
3491
3632
|
writeError: (message) => io.stderr(message),
|
|
3492
3633
|
createToolRegistry: async () => {
|
|
3493
3634
|
const service = await dependencies.createService({
|
|
3494
|
-
eventSink: ()
|
|
3635
|
+
eventSink: warningEventSink(io),
|
|
3495
3636
|
requireProvider: false,
|
|
3496
3637
|
exposeToolRegistry: true,
|
|
3497
3638
|
approveTool: async () => true,
|
|
@@ -4273,7 +4414,7 @@ async function executeTeamCommand(args, io, dependencies, signal) {
|
|
|
4273
4414
|
dataPlane: 'native',
|
|
4274
4415
|
};
|
|
4275
4416
|
const service = await dependencies.createService({
|
|
4276
|
-
eventSink: ()
|
|
4417
|
+
eventSink: warningEventSink(io),
|
|
4277
4418
|
requireProvider: !['list', 'status', 'logs', 'attach'].includes(parsed.command),
|
|
4278
4419
|
exposeToolRegistry: true,
|
|
4279
4420
|
controls: nativeControls,
|
|
@@ -4594,7 +4735,7 @@ async function execute(argv, io, dependencies, signal) {
|
|
|
4594
4735
|
}
|
|
4595
4736
|
if (invocation.initOnly) {
|
|
4596
4737
|
const lifecycleService = await dependencies.createService({
|
|
4597
|
-
eventSink: ()
|
|
4738
|
+
eventSink: warningEventSink(io),
|
|
4598
4739
|
requireProvider: false,
|
|
4599
4740
|
exposeToolRegistry: true,
|
|
4600
4741
|
...(signal ? { signal } : {}),
|
package/dist/core/resources.d.ts
CHANGED
|
@@ -13,6 +13,11 @@ export interface JsonResource {
|
|
|
13
13
|
plugin?: true;
|
|
14
14
|
pluginName?: string;
|
|
15
15
|
pluginSource?: string;
|
|
16
|
+
pluginExecutableSource?: {
|
|
17
|
+
kind: 'mcpb';
|
|
18
|
+
source: string;
|
|
19
|
+
fingerprint: string;
|
|
20
|
+
};
|
|
16
21
|
environment?: Readonly<Record<string, string>>;
|
|
17
22
|
sensitiveValues?: readonly string[];
|
|
18
23
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type DataPlane } from '../persistence/data-plane.js';
|
|
2
|
+
export declare const CLAUDE_PLUGIN_MCPB_ARCHIVE_BYTES: number;
|
|
2
3
|
export type ClaudePluginMcpbUserValue = string | number | boolean | readonly string[];
|
|
3
4
|
export interface ClaudePluginMcpbManifest {
|
|
4
5
|
manifest_version: string;
|
|
@@ -60,6 +61,7 @@ export interface LoadClaudePluginMcpbOptions {
|
|
|
60
61
|
signal?: AbortSignal;
|
|
61
62
|
timeoutMs?: number;
|
|
62
63
|
refresh?: boolean;
|
|
64
|
+
requireHttps?: boolean;
|
|
63
65
|
limits?: ClaudePluginMcpbLimits;
|
|
64
66
|
fetch?: typeof fetch;
|
|
65
67
|
}
|
|
@@ -13,6 +13,7 @@ import manifestSchemaV04 from './mcpb-schemas/mcpb-manifest-v0.4.schema.json' wi
|
|
|
13
13
|
import { ExclusiveFileLease, } from '../platform/exclusive-file-lease.js';
|
|
14
14
|
import { resolveDataPlaneRoot, } from '../persistence/data-plane.js';
|
|
15
15
|
const DEFAULT_ARCHIVE_BYTES = 512 * 1024 * 1024;
|
|
16
|
+
export const CLAUDE_PLUGIN_MCPB_ARCHIVE_BYTES = DEFAULT_ARCHIVE_BYTES;
|
|
16
17
|
const DEFAULT_EXTRACTED_BYTES = 1024 * 1024 * 1024;
|
|
17
18
|
const DEFAULT_FILE_BYTES = 512 * 1024 * 1024;
|
|
18
19
|
const DEFAULT_FILES = 100_000;
|
|
@@ -540,9 +541,14 @@ async function fetchArchive(source, cached, options, signal, archiveLimit) {
|
|
|
540
541
|
throw new Error('MCPB download redirect is missing Location');
|
|
541
542
|
}
|
|
542
543
|
const redirected = new URL(location, requestUrl);
|
|
543
|
-
if (
|
|
544
|
+
if ((options.requireHttps && redirected.protocol !== 'https:') ||
|
|
545
|
+
(!options.requireHttps &&
|
|
546
|
+
redirected.protocol !== 'http:' &&
|
|
547
|
+
redirected.protocol !== 'https:')) {
|
|
544
548
|
await response.body?.cancel();
|
|
545
|
-
throw new Error(
|
|
549
|
+
throw new Error(options.requireHttps
|
|
550
|
+
? 'MCPB download redirect must use HTTPS'
|
|
551
|
+
: 'MCPB download redirect must use HTTP or HTTPS');
|
|
546
552
|
}
|
|
547
553
|
await response.body?.cancel();
|
|
548
554
|
if (++redirects > 5) {
|
|
@@ -627,6 +633,9 @@ async function resolveMcpbSource(options, signal) {
|
|
|
627
633
|
const pluginRoot = await realpath(resolve(options.pluginRoot));
|
|
628
634
|
signal.throwIfAborted();
|
|
629
635
|
const remote = /^https?:\/\//u.test(options.source);
|
|
636
|
+
if (options.requireHttps && remote && !/^https:\/\//u.test(options.source)) {
|
|
637
|
+
throw new Error('MCPB download source must use HTTPS');
|
|
638
|
+
}
|
|
630
639
|
if (!remote && /^[a-z][a-z0-9+.-]*:\/\//iu.test(options.source))
|
|
631
640
|
throw new Error('MCPB source URL must use HTTP or HTTPS');
|
|
632
641
|
if (!['.mcpb', '.dxt'].some((extension) => options.source.endsWith(extension))) {
|
|
@@ -108,6 +108,8 @@ export declare function loadClaudePlugins(options: {
|
|
|
108
108
|
pluginUrls?: readonly string[];
|
|
109
109
|
loadInstalled?: boolean;
|
|
110
110
|
readOnlyHooks?: boolean;
|
|
111
|
+
readOnlyExecutables?: boolean;
|
|
112
|
+
allowWorkspaceMcpb?: boolean;
|
|
111
113
|
environment?: Readonly<Record<string, string | undefined>>;
|
|
112
114
|
}): Promise<ClaudePluginResources>;
|
|
113
115
|
export declare function validateClaudePlugin(path: string, options?: {
|