praxis-agent 0.14.0 → 0.16.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/application/session-service.d.ts +18 -0
- package/dist/application/session-service.js +180 -41
- package/dist/application/subagent-service.d.ts +7 -1
- package/dist/application/subagent-service.js +238 -53
- package/dist/cli/interactive.d.ts +7 -1
- package/dist/cli/interactive.js +77 -3
- package/dist/cli/protocol.d.ts +13 -0
- package/dist/cli/protocol.js +34 -0
- package/dist/cli/tui/claude-command-inventory.d.ts +1 -2
- package/dist/cli/tui/claude-command-inventory.js +1 -6
- package/dist/cli/tui/claude-style.d.ts +4 -1
- package/dist/cli/tui/claude-style.js +8 -3
- package/dist/cli/tui/slash-commands.d.ts +1 -0
- package/dist/cli/tui/slash-commands.js +11 -0
- package/dist/cli/tui/theme.d.ts +2 -0
- package/dist/cli/tui/theme.js +69 -0
- package/dist/cli.d.ts +6 -0
- package/dist/cli.js +95 -27
- package/dist/compatibility/claude/agent-color.d.ts +25 -0
- package/dist/compatibility/claude/agent-color.js +79 -0
- package/dist/compatibility/claude/fork.js +5 -1
- package/dist/compatibility/claude/schema.js +13 -1
- package/dist/extensions/claude-extensions.d.ts +19 -1
- package/dist/extensions/claude-extensions.js +126 -1
- package/dist/hooks/claude-hooks.d.ts +3 -1
- package/dist/hooks/claude-hooks.js +14 -0
- package/dist/mcp/claude-mcp-tools.d.ts +18 -0
- package/dist/mcp/claude-mcp-tools.js +46 -0
- package/dist/persistence/claude-transcript-store.js +1 -0
- package/dist/tools/claude-interactive-tools.d.ts +1 -0
- package/dist/tools/claude-interactive-tools.js +3 -0
- package/package.json +2 -1
package/dist/cli.js
CHANGED
|
@@ -7,6 +7,7 @@ import { dirname, join, resolve } from 'node:path';
|
|
|
7
7
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
8
8
|
import { randomUUID } from 'node:crypto';
|
|
9
9
|
import { ClaudeSessionService, } from './application/session-service.js';
|
|
10
|
+
import { agentColorMessage, parseAgentColorInput, } from './compatibility/claude/agent-color.js';
|
|
10
11
|
import { ClaudeConditionalRuleResolver, ClaudeContextAssembler, } from './compatibility/claude/context.js';
|
|
11
12
|
import { loadClaudeDynamicContext } from './compatibility/claude/dynamic-context.js';
|
|
12
13
|
import { createClaudePrSessionFilter, filterClaudePrLinkedSessions, } from './compatibility/claude/pr-links.js';
|
|
@@ -46,7 +47,7 @@ import { WorkspaceContext } from './application/session-worktree.js';
|
|
|
46
47
|
import { launchTmuxWorktree } from './platform/tmux-worktree.js';
|
|
47
48
|
import { claudeSandboxRuntime } from './sandbox/claude-sandbox-runtime.js';
|
|
48
49
|
import { claudeSandboxTempDirectory, loadClaudeSandboxSettings, } from './sandbox/claude-sandbox-settings.js';
|
|
49
|
-
import { createErrorResult, createSuccessResult, parseCliInvocation, readStreamJsonMessages, StreamJsonOutput, } from './cli/protocol.js';
|
|
50
|
+
import { createErrorResult, createSuccessResult, matchHeadlessColorCommand, parseCliInvocation, readStreamJsonMessages, StreamJsonOutput, } from './cli/protocol.js';
|
|
50
51
|
import { describeClaudePlugin, initClaudePlugin, installClaudePlugin, loadClaudePlugins, readPluginRegistry, setClaudePluginEnabled, uninstallClaudePlugin, updateClaudePlugin, validateClaudePlugin, } from './plugins/claude-plugin-runtime.js';
|
|
51
52
|
import { addClaudeMarketplace, disableAllNativePlugins, installClaudeMarketplacePlugin, listClaudeMarketplaceAvailablePlugins, listNativePluginRecords, readClaudeKnownMarketplaces, removeClaudeMarketplace, setNativePluginEnabled, saveClaudePluginConfig, uninstallNativePlugin, updateClaudeMarketplace, updateNativePlugin, validateClaudeMarketplace, } from './plugins/claude-plugin-marketplace.js';
|
|
52
53
|
import { executeClaudePluginEvalCommand, PLUGIN_EVAL_HELP, } from './plugins/claude-plugin-eval.js';
|
|
@@ -733,6 +734,7 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
733
734
|
const runtimeEventSink = debug?.eventSink ?? eventSink;
|
|
734
735
|
let provider;
|
|
735
736
|
let providerForModel;
|
|
737
|
+
let providerForMainModel;
|
|
736
738
|
const context = parseContextEnvironment(runtimeEnvironment);
|
|
737
739
|
const apiKey = runtimeEnvironment.PRAXIS_API_KEY;
|
|
738
740
|
const model = cli.model ?? runtimeEnvironment.PRAXIS_MODEL;
|
|
@@ -755,13 +757,18 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
755
757
|
throw new Error('Provider environment is unavailable');
|
|
756
758
|
}
|
|
757
759
|
providerForModel = createProviderForModel(apiKey, providerEnvironment, context, cli, controls);
|
|
758
|
-
const models = [model, ...(cli.fallbackModels ?? [])].filter((candidate, index, all) => all.indexOf(candidate) === index);
|
|
759
760
|
const createProvider = providerForModel;
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
providers.
|
|
761
|
+
providerForMainModel = (primaryModel) => {
|
|
762
|
+
const models = [primaryModel, ...(cli.fallbackModels ?? [])].filter((candidate, index, all) => all.indexOf(candidate) === index);
|
|
763
|
+
const providers = models.map((candidate) => createProvider(candidate));
|
|
764
|
+
const selected = providers[0];
|
|
765
|
+
if (!selected)
|
|
766
|
+
throw new Error('A primary model is required');
|
|
767
|
+
return providers.length > 1
|
|
763
768
|
? new FallbackModelProvider({ providers })
|
|
764
|
-
:
|
|
769
|
+
: selected;
|
|
770
|
+
};
|
|
771
|
+
provider = providerForMainModel(model);
|
|
765
772
|
}
|
|
766
773
|
const options = {
|
|
767
774
|
configRoot,
|
|
@@ -769,6 +776,10 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
769
776
|
claudeVersion,
|
|
770
777
|
eventSink: runtimeEventSink,
|
|
771
778
|
sessionPersistence: cli.sessionPersistence,
|
|
779
|
+
explicitModel: interactiveModel !== undefined || controls.model !== undefined,
|
|
780
|
+
explicitSystemPrompt: cli.systemPrompt !== undefined,
|
|
781
|
+
agentInitialPromptHandledExternally: interactive,
|
|
782
|
+
agentSystemPromptOverridesExplicit: interactive,
|
|
772
783
|
effort: cli.effort ?? 'high',
|
|
773
784
|
...(cli.maxTurns === undefined ? {} : { maxModelTurns: cli.maxTurns }),
|
|
774
785
|
...(cli.brief && !cli.disallowedTools.includes('SendUserMessage')
|
|
@@ -908,6 +919,15 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
908
919
|
}
|
|
909
920
|
}
|
|
910
921
|
settings.push(...pluginResources.settings);
|
|
922
|
+
const configuredAgent = [...settings]
|
|
923
|
+
.reverse()
|
|
924
|
+
.map((resource) => resource.value &&
|
|
925
|
+
typeof resource.value === 'object' &&
|
|
926
|
+
!Array.isArray(resource.value)
|
|
927
|
+
? resource.value.agent
|
|
928
|
+
: undefined)
|
|
929
|
+
.find((value) => typeof value === 'string' && value.length > 0);
|
|
930
|
+
const selectedMainAgent = agent ?? configuredAgent;
|
|
911
931
|
const resources = {
|
|
912
932
|
...loadedResources,
|
|
913
933
|
commands: [...loadedResources.commands, ...pluginResources.commands],
|
|
@@ -1196,10 +1216,14 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
1196
1216
|
...options,
|
|
1197
1217
|
provider: hostedToolProvider,
|
|
1198
1218
|
...(providerForModel ? { providerForModel } : {}),
|
|
1219
|
+
...(providerForMainModel ? { providerForMainModel } : {}),
|
|
1199
1220
|
tools: filteredTools,
|
|
1200
1221
|
mcp: mcpTools,
|
|
1201
1222
|
permissions,
|
|
1202
1223
|
permissionResolverForMode,
|
|
1224
|
+
permissionMode: cli.dangerouslySkipPermissions
|
|
1225
|
+
? 'bypassPermissions'
|
|
1226
|
+
: cli.permissionMode,
|
|
1203
1227
|
persistPermissionUpdates: (updates) => persistTuiPermissionUpdates({
|
|
1204
1228
|
cwd: workspace.cwd(),
|
|
1205
1229
|
configRoot,
|
|
@@ -1220,7 +1244,7 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
1220
1244
|
: {}),
|
|
1221
1245
|
...(interactiveTools ? { interactiveTools } : {}),
|
|
1222
1246
|
...(hooks ? { hooks } : {}),
|
|
1223
|
-
...(
|
|
1247
|
+
...(selectedMainAgent ? { agent: selectedMainAgent } : {}),
|
|
1224
1248
|
contextAssembler: new ClaudeContextAssembler({
|
|
1225
1249
|
loadResources: loadContextResources,
|
|
1226
1250
|
loadDynamicContext: (runtimeCwd = workspace.cwd()) => loadClaudeDynamicContext({
|
|
@@ -1320,6 +1344,8 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
1320
1344
|
retryRecentlyDenied: (sessionId, display, retrySignal) => service.retryRecentlyDenied(sessionId, display, retrySignal),
|
|
1321
1345
|
answerSideQuestion: (sessionId, question, sideSignal, onDelta, permissionMode) => service.answerSideQuestion(sessionId, question, sideSignal, onDelta, permissionMode),
|
|
1322
1346
|
recordBtwUsage: (sessionId, permissionMode) => service.recordBtwUsage(sessionId, permissionMode),
|
|
1347
|
+
recordColorUsage: (sessionId, selection, display, permissionMode, options) => service.recordColorUsage(sessionId, selection, display, permissionMode, options),
|
|
1348
|
+
agentColor: (sessionId) => service.readEffectiveAgentColor(sessionId),
|
|
1323
1349
|
recordBackgroundUsage: (sessionId, permissionMode) => service.recordBackgroundUsage(sessionId, permissionMode),
|
|
1324
1350
|
recordBackgroundLaunch: (sessionId) => service.recordBackgroundLaunch(sessionId),
|
|
1325
1351
|
forkSideQuestion: (sessionId, question, sideSignal) => service.forkSideQuestion(sessionId, question, sideSignal),
|
|
@@ -1373,6 +1399,9 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
1373
1399
|
taskSnapshots: (sessionId) => service.taskSnapshots(sessionId),
|
|
1374
1400
|
stopTask: (sessionId, taskId) => service.stopTask(sessionId, taskId),
|
|
1375
1401
|
agentDefinitions: () => extensions.agentDefinitions(),
|
|
1402
|
+
initialAgentPrompt: () => selectedMainAgent
|
|
1403
|
+
? extensions.agent(selectedMainAgent)?.initialPrompt
|
|
1404
|
+
: undefined,
|
|
1376
1405
|
inspect: (sessionId) => service.inspect(sessionId),
|
|
1377
1406
|
export: (sessionId) => service.export(sessionId),
|
|
1378
1407
|
transcript: (sessionId) => service.transcript(sessionId, pendingResumeSessionAt),
|
|
@@ -1407,7 +1436,10 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
1407
1436
|
runtimeInfo: () => ({
|
|
1408
1437
|
...runtimeInfo,
|
|
1409
1438
|
cwd: workspace.cwd(),
|
|
1410
|
-
model:
|
|
1439
|
+
model: service.model() ??
|
|
1440
|
+
provider?.model ??
|
|
1441
|
+
runtimeEnvironment.PRAXIS_MODEL ??
|
|
1442
|
+
'unknown',
|
|
1411
1443
|
}),
|
|
1412
1444
|
promptSuggestion: (sessionId, suggestionSignal) => service.promptSuggestion(sessionId, suggestionSignal),
|
|
1413
1445
|
};
|
|
@@ -3706,6 +3738,7 @@ async function execute(argv, io, dependencies, signal) {
|
|
|
3706
3738
|
const pendingEvents = [];
|
|
3707
3739
|
let streamIterator;
|
|
3708
3740
|
let firstStreamMessage;
|
|
3741
|
+
let streamInputExhausted = false;
|
|
3709
3742
|
const queuedStreamUsers = [];
|
|
3710
3743
|
const earlyControlResponses = new Map();
|
|
3711
3744
|
let currentTurnAbort;
|
|
@@ -3905,6 +3938,19 @@ async function execute(argv, io, dependencies, signal) {
|
|
|
3905
3938
|
}
|
|
3906
3939
|
return 0;
|
|
3907
3940
|
}
|
|
3941
|
+
const headlessTurnReached = invocation.rewindFiles === undefined &&
|
|
3942
|
+
!['sessions', 'fork', 'inspect', 'export'].includes(command ?? 'run');
|
|
3943
|
+
if (streamIterator && headlessTurnReached) {
|
|
3944
|
+
const first = await nextStreamUser();
|
|
3945
|
+
if (first)
|
|
3946
|
+
firstStreamMessage = first;
|
|
3947
|
+
else
|
|
3948
|
+
streamInputExhausted = true;
|
|
3949
|
+
}
|
|
3950
|
+
const firstHeadlessPrompt = firstStreamMessage?.prompt ?? headlessPrompt;
|
|
3951
|
+
const firstTurnIsLocalColor = !invocation.disableSlashCommands &&
|
|
3952
|
+
firstHeadlessPrompt !== undefined &&
|
|
3953
|
+
matchHeadlessColorCommand(firstHeadlessPrompt) !== undefined;
|
|
3908
3954
|
const service = await dependencies.createService({
|
|
3909
3955
|
eventSink: outputFormat === 'stream-json' && !invocation.legacyJson
|
|
3910
3956
|
? (event) => {
|
|
@@ -3929,8 +3975,7 @@ async function execute(argv, io, dependencies, signal) {
|
|
|
3929
3975
|
}
|
|
3930
3976
|
}
|
|
3931
3977
|
: eventSink(io, outputFormat, invocation.legacyJson),
|
|
3932
|
-
requireProvider:
|
|
3933
|
-
!['fork', 'sessions', 'inspect', 'export'].includes(command ?? 'run'),
|
|
3978
|
+
requireProvider: !streamInputExhausted && !firstTurnIsLocalColor && headlessTurnReached,
|
|
3934
3979
|
...(retryInterruptedTools ? { approveRecovery: () => true } : {}),
|
|
3935
3980
|
...(streamIterator ? { approveTool: approveStreamTool } : {}),
|
|
3936
3981
|
...(streamIterator ? { onElicitation: respondStreamElicitation } : {}),
|
|
@@ -4038,12 +4083,8 @@ async function execute(argv, io, dependencies, signal) {
|
|
|
4038
4083
|
if (outputFormat === 'stream-json' && !invocation.legacyJson) {
|
|
4039
4084
|
streamOutput = new StreamJsonOutput((value) => writeJson(io, value), runtimeInfo, activeSessionId, includePartialMessages, invocation.includeHookEvents);
|
|
4040
4085
|
}
|
|
4041
|
-
if (
|
|
4042
|
-
|
|
4043
|
-
if (!first)
|
|
4044
|
-
return 0;
|
|
4045
|
-
firstStreamMessage = first;
|
|
4046
|
-
}
|
|
4086
|
+
if (streamInputExhausted)
|
|
4087
|
+
return 0;
|
|
4047
4088
|
const initialPrompt = firstStreamMessage?.prompt ??
|
|
4048
4089
|
promptFrom(command === 'resume'
|
|
4049
4090
|
? args.slice(2)
|
|
@@ -4070,15 +4111,38 @@ async function execute(argv, io, dependencies, signal) {
|
|
|
4070
4111
|
streamOutput.replayUser(streamMessage.message);
|
|
4071
4112
|
}
|
|
4072
4113
|
let result;
|
|
4114
|
+
let colorArgs;
|
|
4073
4115
|
try {
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4116
|
+
colorArgs = invocation.disableSlashCommands
|
|
4117
|
+
? undefined
|
|
4118
|
+
: matchHeadlessColorCommand(prompt);
|
|
4119
|
+
if (colorArgs !== undefined) {
|
|
4120
|
+
if (!service.recordColorUsage) {
|
|
4121
|
+
throw new Error('Session color is unavailable.');
|
|
4122
|
+
}
|
|
4123
|
+
const selection = parseAgentColorInput(colorArgs);
|
|
4124
|
+
const localSessionId = await service.recordColorUsage(activeSessionId, selection, prompt, invocation.permissionMode, isFirstTurn && existingSessionId === undefined
|
|
4125
|
+
? { createSession: true }
|
|
4126
|
+
: undefined);
|
|
4127
|
+
result = {
|
|
4128
|
+
sessionId: localSessionId,
|
|
4129
|
+
text: agentColorMessage(selection),
|
|
4130
|
+
usage: { inputTokens: 0, outputTokens: 0 },
|
|
4131
|
+
durationApiMs: 0,
|
|
4132
|
+
costUsd: 0,
|
|
4133
|
+
modelUsage: {},
|
|
4134
|
+
};
|
|
4135
|
+
}
|
|
4136
|
+
else {
|
|
4137
|
+
result =
|
|
4138
|
+
existingSessionId !== undefined || !isFirstTurn
|
|
4139
|
+
? runSignal
|
|
4140
|
+
? await service.resume(activeSessionId, prompt, runSignal, undefined, streamMessage?.images, streamMessage?.documents)
|
|
4141
|
+
: await service.resume(activeSessionId, prompt, undefined, undefined, streamMessage?.images, streamMessage?.documents)
|
|
4142
|
+
: runSignal
|
|
4143
|
+
? await service.run(prompt, runSignal, activeSessionId, undefined, streamMessage?.images, streamMessage?.documents)
|
|
4144
|
+
: await service.run(prompt, undefined, activeSessionId, undefined, streamMessage?.images, streamMessage?.documents);
|
|
4145
|
+
}
|
|
4082
4146
|
}
|
|
4083
4147
|
catch (error) {
|
|
4084
4148
|
if (isCancellation(error, turnAbort.signal)) {
|
|
@@ -4105,16 +4169,20 @@ async function execute(argv, io, dependencies, signal) {
|
|
|
4105
4169
|
return false;
|
|
4106
4170
|
}
|
|
4107
4171
|
activeSessionId = result.sessionId;
|
|
4108
|
-
if (streamOutput)
|
|
4172
|
+
if (streamOutput) {
|
|
4173
|
+
if (colorArgs !== undefined) {
|
|
4174
|
+
streamOutput.syntheticAssistant(result.text);
|
|
4175
|
+
}
|
|
4109
4176
|
streamOutput.result(result, startedAt);
|
|
4177
|
+
}
|
|
4110
4178
|
else if (outputFormat === 'json') {
|
|
4111
4179
|
const resultRuntimeInfo = service.runtimeInfo?.() ?? runtimeInfo;
|
|
4112
|
-
writeJson(io, createSuccessResult(result, resultRuntimeInfo, startedAt, Math.max(1, jsonModelTurns)));
|
|
4180
|
+
writeJson(io, createSuccessResult(result, resultRuntimeInfo, startedAt, colorArgs !== undefined ? 0 : Math.max(1, jsonModelTurns)));
|
|
4113
4181
|
}
|
|
4114
4182
|
else if (outputFormat !== 'text')
|
|
4115
4183
|
writeJson(io, { type: 'result', ...result });
|
|
4116
4184
|
else
|
|
4117
|
-
io.stdout('\n');
|
|
4185
|
+
io.stdout(colorArgs !== undefined ? `${result.text}\n` : '\n');
|
|
4118
4186
|
if (streamOutput && invocation.promptSuggestions) {
|
|
4119
4187
|
try {
|
|
4120
4188
|
const suggestion = await service.promptSuggestion?.(activeSessionId, runSignal);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ClaudeTranscriptEntry } from './schema.js';
|
|
2
|
+
export declare const AGENT_COLORS: readonly ["red", "blue", "green", "yellow", "purple", "orange", "pink", "cyan"];
|
|
3
|
+
export type AgentColorName = (typeof AGENT_COLORS)[number];
|
|
4
|
+
export declare const AGENT_COLOR_DEFAULT: "default";
|
|
5
|
+
export type AgentColorValue = AgentColorName | typeof AGENT_COLOR_DEFAULT;
|
|
6
|
+
export declare const AGENT_COLOR_VALUES: readonly AgentColorValue[];
|
|
7
|
+
export declare const AGENT_COLOR_CHOICES: string;
|
|
8
|
+
export declare const RESET_AGENT_COLOR_ALIASES: readonly ["default", "reset", "none", "gray", "grey"];
|
|
9
|
+
export declare function isAgentColorName(value: unknown): value is AgentColorName;
|
|
10
|
+
export declare function isAgentColorValue(value: unknown): value is AgentColorValue;
|
|
11
|
+
export declare function normalizeAgentColorInput(input: string): string;
|
|
12
|
+
export type AgentColorSelection = {
|
|
13
|
+
kind: 'color';
|
|
14
|
+
color: AgentColorName;
|
|
15
|
+
} | {
|
|
16
|
+
kind: 'reset';
|
|
17
|
+
} | {
|
|
18
|
+
kind: 'invalid';
|
|
19
|
+
input: string;
|
|
20
|
+
};
|
|
21
|
+
export declare function parseAgentColorInput(input: string): AgentColorSelection;
|
|
22
|
+
export declare function randomAgentColor(): AgentColorName;
|
|
23
|
+
export declare function agentColorMessage(selection: AgentColorSelection): string;
|
|
24
|
+
export declare function getClaudeEffectiveAgentColor(entries: readonly ClaudeTranscriptEntry[], sessionId: string): AgentColorName | undefined;
|
|
25
|
+
//# sourceMappingURL=agent-color.d.ts.map
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
export const AGENT_COLORS = [
|
|
2
|
+
'red',
|
|
3
|
+
'blue',
|
|
4
|
+
'green',
|
|
5
|
+
'yellow',
|
|
6
|
+
'purple',
|
|
7
|
+
'orange',
|
|
8
|
+
'pink',
|
|
9
|
+
'cyan',
|
|
10
|
+
];
|
|
11
|
+
export const AGENT_COLOR_DEFAULT = 'default';
|
|
12
|
+
export const AGENT_COLOR_VALUES = [
|
|
13
|
+
...AGENT_COLORS,
|
|
14
|
+
AGENT_COLOR_DEFAULT,
|
|
15
|
+
];
|
|
16
|
+
export const AGENT_COLOR_CHOICES = `${AGENT_COLORS.join(', ')}, ${AGENT_COLOR_DEFAULT}`;
|
|
17
|
+
export const RESET_AGENT_COLOR_ALIASES = [
|
|
18
|
+
AGENT_COLOR_DEFAULT,
|
|
19
|
+
'reset',
|
|
20
|
+
'none',
|
|
21
|
+
'gray',
|
|
22
|
+
'grey',
|
|
23
|
+
];
|
|
24
|
+
export function isAgentColorName(value) {
|
|
25
|
+
return (typeof value === 'string' &&
|
|
26
|
+
AGENT_COLORS.includes(value));
|
|
27
|
+
}
|
|
28
|
+
export function isAgentColorValue(value) {
|
|
29
|
+
return (typeof value === 'string' &&
|
|
30
|
+
AGENT_COLOR_VALUES.includes(value));
|
|
31
|
+
}
|
|
32
|
+
export function normalizeAgentColorInput(input) {
|
|
33
|
+
return input.trim().toLowerCase();
|
|
34
|
+
}
|
|
35
|
+
export function parseAgentColorInput(input) {
|
|
36
|
+
const normalized = normalizeAgentColorInput(input);
|
|
37
|
+
if (normalized === '') {
|
|
38
|
+
return { kind: 'color', color: randomAgentColor() };
|
|
39
|
+
}
|
|
40
|
+
if (RESET_AGENT_COLOR_ALIASES.includes(normalized)) {
|
|
41
|
+
return { kind: 'reset' };
|
|
42
|
+
}
|
|
43
|
+
if (isAgentColorName(normalized)) {
|
|
44
|
+
return { kind: 'color', color: normalized };
|
|
45
|
+
}
|
|
46
|
+
return { kind: 'invalid', input: normalized };
|
|
47
|
+
}
|
|
48
|
+
export function randomAgentColor() {
|
|
49
|
+
const index = Math.floor(Math.random() * AGENT_COLORS.length);
|
|
50
|
+
const color = AGENT_COLORS[index];
|
|
51
|
+
if (color === undefined)
|
|
52
|
+
throw new Error('AGENT_COLORS must not be empty');
|
|
53
|
+
return color;
|
|
54
|
+
}
|
|
55
|
+
export function agentColorMessage(selection) {
|
|
56
|
+
switch (selection.kind) {
|
|
57
|
+
case 'color':
|
|
58
|
+
return `Session color set to: ${selection.color}`;
|
|
59
|
+
case 'reset':
|
|
60
|
+
return 'Session color reset to default';
|
|
61
|
+
case 'invalid':
|
|
62
|
+
return `Invalid color "${selection.input}". Available colors: ${AGENT_COLOR_CHOICES}`;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
export function getClaudeEffectiveAgentColor(entries, sessionId) {
|
|
66
|
+
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
67
|
+
const entry = entries[index];
|
|
68
|
+
if (entry?.type !== 'agent-color' || entry.sessionId !== sessionId) {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const color = entry.agentColor;
|
|
72
|
+
if (color === AGENT_COLOR_DEFAULT)
|
|
73
|
+
return undefined;
|
|
74
|
+
if (isAgentColorName(color))
|
|
75
|
+
return color;
|
|
76
|
+
}
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=agent-color.js.map
|
|
@@ -152,6 +152,7 @@ function validateNativeHistory(entries) {
|
|
|
152
152
|
return undefined;
|
|
153
153
|
}
|
|
154
154
|
export function createClaudeNativeFork({ source, sourceSessionId, sessionId, resumeSessionAt, }) {
|
|
155
|
+
const agentColors = [];
|
|
155
156
|
const titles = [];
|
|
156
157
|
const modes = [];
|
|
157
158
|
const permissionModes = [];
|
|
@@ -204,7 +205,9 @@ export function createClaudeNativeFork({ source, sourceSessionId, sessionId, res
|
|
|
204
205
|
throw new Error('Claude fork source entry has the wrong sessionId');
|
|
205
206
|
}
|
|
206
207
|
const copied = copyClaudeEntryWithSessionId(entry, sessionId);
|
|
207
|
-
if (entry.type === '
|
|
208
|
+
if (entry.type === 'agent-color')
|
|
209
|
+
agentColors.push(copied);
|
|
210
|
+
else if (entry.type === 'ai-title')
|
|
208
211
|
titles.push(copied);
|
|
209
212
|
else if (entry.type === 'mode')
|
|
210
213
|
modes.push(copied);
|
|
@@ -232,6 +235,7 @@ export function createClaudeNativeFork({ source, sourceSessionId, sessionId, res
|
|
|
232
235
|
lastPrompt = undefined;
|
|
233
236
|
}
|
|
234
237
|
return [
|
|
238
|
+
...agentColors.slice(-1),
|
|
235
239
|
...titles.slice(-1),
|
|
236
240
|
...modes.slice(-1),
|
|
237
241
|
...permissionModes.slice(-1),
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { isAgentColorValue } from './agent-color.js';
|
|
1
2
|
const SUPPORTED_VERSION = '2.1.208';
|
|
2
3
|
const APPENDABLE_ENTRY_TYPES = new Set([
|
|
4
|
+
'agent-color',
|
|
3
5
|
'agent-name',
|
|
4
6
|
'agent-setting',
|
|
5
7
|
'assistant',
|
|
@@ -17,6 +19,7 @@ const APPENDABLE_ENTRY_TYPES = new Set([
|
|
|
17
19
|
'worktree-state',
|
|
18
20
|
]);
|
|
19
21
|
const FORKABLE_ENTRY_TYPES = new Set([
|
|
22
|
+
'agent-color',
|
|
20
23
|
'agent-name',
|
|
21
24
|
'agent-setting',
|
|
22
25
|
'ai-title',
|
|
@@ -717,6 +720,13 @@ function validateAppendableEntry(entry) {
|
|
|
717
720
|
}
|
|
718
721
|
return;
|
|
719
722
|
}
|
|
723
|
+
if (entry.type === 'agent-color') {
|
|
724
|
+
if (!isAgentColorValue(entry.agentColor) ||
|
|
725
|
+
!isNonEmptyString(entry.sessionId)) {
|
|
726
|
+
throw new Error('Claude agent-color entry has invalid metadata');
|
|
727
|
+
}
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
720
730
|
if (entry.type === 'agent-name') {
|
|
721
731
|
if (!isNonEmptyString(entry.agentName) ||
|
|
722
732
|
!isNonEmptyString(entry.sessionId)) {
|
|
@@ -868,7 +878,9 @@ function validateSidechainEntry(entry) {
|
|
|
868
878
|
}
|
|
869
879
|
}
|
|
870
880
|
function validateForkableEntry(entry) {
|
|
871
|
-
if (entry.type === 'custom-title' ||
|
|
881
|
+
if (entry.type === 'custom-title' ||
|
|
882
|
+
entry.type === 'agent-name' ||
|
|
883
|
+
entry.type === 'agent-color') {
|
|
872
884
|
validateAppendableEntry(entry);
|
|
873
885
|
return;
|
|
874
886
|
}
|
|
@@ -23,6 +23,24 @@ export interface ClaudeAgentDefinition {
|
|
|
23
23
|
name: string;
|
|
24
24
|
description: string;
|
|
25
25
|
}
|
|
26
|
+
export type ClaudeAgentEffort = 'low' | 'medium' | 'high' | 'max' | number;
|
|
27
|
+
export type ClaudeAgentPermissionMode = 'acceptEdits' | 'auto' | 'bypassPermissions' | 'default' | 'dontAsk' | 'plan';
|
|
28
|
+
export interface ClaudeAgentRuntimeDefinition extends ClaudeExtensionDefinition {
|
|
29
|
+
kind: 'agent';
|
|
30
|
+
tools?: readonly string[];
|
|
31
|
+
disallowedTools?: readonly string[];
|
|
32
|
+
model?: string;
|
|
33
|
+
effort?: ClaudeAgentEffort;
|
|
34
|
+
permissionMode?: ClaudeAgentPermissionMode;
|
|
35
|
+
maxTurns?: number;
|
|
36
|
+
skills?: readonly string[];
|
|
37
|
+
initialPrompt?: string;
|
|
38
|
+
memory?: 'user' | 'project' | 'local';
|
|
39
|
+
background?: boolean;
|
|
40
|
+
isolation?: 'worktree';
|
|
41
|
+
mcpServers?: readonly unknown[];
|
|
42
|
+
hooks?: Readonly<Record<string, unknown>>;
|
|
43
|
+
}
|
|
26
44
|
export interface ClaudePromptExpansion {
|
|
27
45
|
userMessages: readonly string[];
|
|
28
46
|
messages?: readonly ClaudePromptExpansionMessage[];
|
|
@@ -50,7 +68,7 @@ export declare class ClaudeExtensionCatalog {
|
|
|
50
68
|
slashCommandDefinitions(): readonly ClaudeSlashCommandDefinition[];
|
|
51
69
|
skill(name: string): ClaudeExtensionDefinition | null;
|
|
52
70
|
modelInvocableSkills(): readonly ClaudeExtensionDefinition[];
|
|
53
|
-
agent(name: string):
|
|
71
|
+
agent(name: string): ClaudeAgentRuntimeDefinition | null;
|
|
54
72
|
agentNames(): readonly string[];
|
|
55
73
|
agentDefinitions(): readonly ClaudeAgentDefinition[];
|
|
56
74
|
agentMentionMessages(prompt: string): readonly string[];
|
|
@@ -122,6 +122,123 @@ function parseFrontmatter(resource) {
|
|
|
122
122
|
body: lines.slice(closingIndex + 1).join('\n'),
|
|
123
123
|
};
|
|
124
124
|
}
|
|
125
|
+
function parseAgentList(value) {
|
|
126
|
+
if (value === undefined)
|
|
127
|
+
return undefined;
|
|
128
|
+
const values = typeof value === 'string'
|
|
129
|
+
? [value]
|
|
130
|
+
: Array.isArray(value)
|
|
131
|
+
? value.filter((item) => typeof item === 'string')
|
|
132
|
+
: [];
|
|
133
|
+
const parsed = [];
|
|
134
|
+
for (const value of values) {
|
|
135
|
+
let current = '';
|
|
136
|
+
let depth = 0;
|
|
137
|
+
for (const character of value) {
|
|
138
|
+
if (character === '(')
|
|
139
|
+
depth += 1;
|
|
140
|
+
if (character === ')' && depth > 0)
|
|
141
|
+
depth -= 1;
|
|
142
|
+
if (depth === 0 && (character === ',' || /\s/u.test(character))) {
|
|
143
|
+
if (current.trim())
|
|
144
|
+
parsed.push(current.trim());
|
|
145
|
+
current = '';
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
current += character;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (current.trim())
|
|
152
|
+
parsed.push(current.trim());
|
|
153
|
+
}
|
|
154
|
+
return parsed;
|
|
155
|
+
}
|
|
156
|
+
function parseAgentEffort(value) {
|
|
157
|
+
if (typeof value === 'number') {
|
|
158
|
+
return Number.isInteger(value) ? value : undefined;
|
|
159
|
+
}
|
|
160
|
+
if (typeof value !== 'string' || !value.trim())
|
|
161
|
+
return undefined;
|
|
162
|
+
const normalized = value.trim().toLowerCase();
|
|
163
|
+
if (['low', 'medium', 'high', 'max'].includes(normalized)) {
|
|
164
|
+
return normalized;
|
|
165
|
+
}
|
|
166
|
+
if (!/^[+-]?\d+$/u.test(normalized))
|
|
167
|
+
return undefined;
|
|
168
|
+
const numeric = Number(normalized);
|
|
169
|
+
return Number.isInteger(numeric) ? numeric : undefined;
|
|
170
|
+
}
|
|
171
|
+
function parsePositiveInteger(value) {
|
|
172
|
+
if (typeof value === 'number') {
|
|
173
|
+
return Number.isSafeInteger(value) && value > 0 ? value : undefined;
|
|
174
|
+
}
|
|
175
|
+
if (typeof value !== 'string' || !/^[1-9]\d*$/u.test(value)) {
|
|
176
|
+
return undefined;
|
|
177
|
+
}
|
|
178
|
+
const numeric = Number(value);
|
|
179
|
+
return Number.isSafeInteger(numeric) ? numeric : undefined;
|
|
180
|
+
}
|
|
181
|
+
function parseAgentDefinition(resource) {
|
|
182
|
+
const definition = parseDefinition('agent', resource);
|
|
183
|
+
const { metadata } = parseFrontmatter(resource);
|
|
184
|
+
const parsedTools = parseAgentList(metadata.tools);
|
|
185
|
+
const tools = parsedTools?.includes('*') ? undefined : parsedTools;
|
|
186
|
+
const disallowedTools = parseAgentList(metadata.disallowedTools);
|
|
187
|
+
const skills = parseAgentList(metadata.skills);
|
|
188
|
+
const model = typeof metadata.model === 'string' && metadata.model.trim()
|
|
189
|
+
? metadata.model.trim().toLowerCase() === 'inherit'
|
|
190
|
+
? 'inherit'
|
|
191
|
+
: metadata.model.trim()
|
|
192
|
+
: undefined;
|
|
193
|
+
const effort = parseAgentEffort(metadata.effort);
|
|
194
|
+
const permissionMode = [
|
|
195
|
+
'acceptEdits',
|
|
196
|
+
'auto',
|
|
197
|
+
'bypassPermissions',
|
|
198
|
+
'default',
|
|
199
|
+
'dontAsk',
|
|
200
|
+
'plan',
|
|
201
|
+
].includes(String(metadata.permissionMode))
|
|
202
|
+
? metadata.permissionMode
|
|
203
|
+
: undefined;
|
|
204
|
+
const maxTurns = parsePositiveInteger(metadata.maxTurns);
|
|
205
|
+
const initialPrompt = typeof metadata.initialPrompt === 'string' && metadata.initialPrompt.trim()
|
|
206
|
+
? metadata.initialPrompt
|
|
207
|
+
: undefined;
|
|
208
|
+
const memory = ['user', 'project', 'local'].includes(String(metadata.memory))
|
|
209
|
+
? metadata.memory
|
|
210
|
+
: undefined;
|
|
211
|
+
const background = metadata.background === true || metadata.background === 'true'
|
|
212
|
+
? true
|
|
213
|
+
: undefined;
|
|
214
|
+
const isolation = metadata.isolation === 'worktree' ? 'worktree' : undefined;
|
|
215
|
+
const mcpServers = Array.isArray(metadata.mcpServers) && metadata.mcpServers.length > 0
|
|
216
|
+
? [...metadata.mcpServers]
|
|
217
|
+
: undefined;
|
|
218
|
+
const hooks = typeof metadata.hooks === 'object' &&
|
|
219
|
+
metadata.hooks !== null &&
|
|
220
|
+
!Array.isArray(metadata.hooks) &&
|
|
221
|
+
Object.keys(metadata.hooks).length > 0
|
|
222
|
+
? { ...metadata.hooks }
|
|
223
|
+
: undefined;
|
|
224
|
+
return {
|
|
225
|
+
...definition,
|
|
226
|
+
kind: 'agent',
|
|
227
|
+
...(tools === undefined ? {} : { tools }),
|
|
228
|
+
...(disallowedTools === undefined ? {} : { disallowedTools }),
|
|
229
|
+
...(model === undefined ? {} : { model }),
|
|
230
|
+
...(effort === undefined ? {} : { effort }),
|
|
231
|
+
...(permissionMode === undefined ? {} : { permissionMode }),
|
|
232
|
+
...(maxTurns === undefined ? {} : { maxTurns }),
|
|
233
|
+
...(skills?.length ? { skills } : {}),
|
|
234
|
+
...(initialPrompt === undefined ? {} : { initialPrompt }),
|
|
235
|
+
...(memory === undefined ? {} : { memory }),
|
|
236
|
+
...(background === undefined ? {} : { background }),
|
|
237
|
+
...(isolation === undefined ? {} : { isolation }),
|
|
238
|
+
...(mcpServers === undefined ? {} : { mcpServers }),
|
|
239
|
+
...(hooks === undefined ? {} : { hooks }),
|
|
240
|
+
};
|
|
241
|
+
}
|
|
125
242
|
export function validateClaudeExtensions(resources) {
|
|
126
243
|
for (const resource of resources) {
|
|
127
244
|
const lines = resource.content.split(/\r?\n/);
|
|
@@ -216,6 +333,14 @@ function indexed(kind, resources) {
|
|
|
216
333
|
}
|
|
217
334
|
return definitions;
|
|
218
335
|
}
|
|
336
|
+
function indexedAgents(resources) {
|
|
337
|
+
const definitions = new Map();
|
|
338
|
+
for (const resource of resources) {
|
|
339
|
+
const definition = parseAgentDefinition(resource);
|
|
340
|
+
definitions.set(definition.name, definition);
|
|
341
|
+
}
|
|
342
|
+
return definitions;
|
|
343
|
+
}
|
|
219
344
|
function substituteArguments(body, argumentsText) {
|
|
220
345
|
const values = argumentsText.length === 0 ? [] : argumentsText.split(/\s+/);
|
|
221
346
|
return body.replace(/\$(ARGUMENTS|\d+)/g, (_, key) => key === 'ARGUMENTS' ? argumentsText : (values[Number(key)] ?? ''));
|
|
@@ -251,7 +376,7 @@ export class ClaudeExtensionCatalog {
|
|
|
251
376
|
: indexed('skill', resources.skills);
|
|
252
377
|
this.agents = new Map([
|
|
253
378
|
['statusline-setup', BUILTIN_STATUSLINE_AGENT],
|
|
254
|
-
...
|
|
379
|
+
...indexedAgents(resources.agents),
|
|
255
380
|
]);
|
|
256
381
|
}
|
|
257
382
|
setMcpPrompts(prompts) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ClaudeJsonResource } from '../compatibility/claude/shared-resources.js';
|
|
2
2
|
import type { PermissionBehavior } from '../core/runtime.js';
|
|
3
|
-
export type ClaudeHookEventName = 'Setup' | 'SessionStart' | 'UserPromptSubmit' | 'PreToolUse' | 'PermissionRequest' | 'PostToolUse' | 'PostToolUseFailure' | 'Stop' | 'SessionEnd';
|
|
3
|
+
export type ClaudeHookEventName = 'Setup' | 'SessionStart' | 'SubagentStart' | 'UserPromptSubmit' | 'PreToolUse' | 'PermissionRequest' | 'PostToolUse' | 'PostToolUseFailure' | 'Stop' | 'SubagentStop' | 'SessionEnd';
|
|
4
4
|
export interface ClaudeHookInput extends Record<string, unknown> {
|
|
5
5
|
session_id: string;
|
|
6
6
|
transcript_path: string;
|
|
@@ -79,7 +79,9 @@ export declare class ClaudeHookRunner {
|
|
|
79
79
|
private readonly maxTimeoutMs;
|
|
80
80
|
private readonly executeCommand;
|
|
81
81
|
private readonly onEvent;
|
|
82
|
+
private readonly cwd;
|
|
82
83
|
constructor(options: ClaudeHookRunnerOptions);
|
|
84
|
+
withAdditionalSettings(settings: readonly ClaudeJsonResource[]): ClaudeHookRunner;
|
|
83
85
|
run(input: ClaudeHookInput, matcherValue?: string, signal?: AbortSignal): Promise<ClaudeHookOutcome>;
|
|
84
86
|
private runCommand;
|
|
85
87
|
}
|
|
@@ -7,12 +7,14 @@ const KILL_GRACE_MS = 250;
|
|
|
7
7
|
const HOOK_EVENTS = [
|
|
8
8
|
'Setup',
|
|
9
9
|
'SessionStart',
|
|
10
|
+
'SubagentStart',
|
|
10
11
|
'UserPromptSubmit',
|
|
11
12
|
'PreToolUse',
|
|
12
13
|
'PermissionRequest',
|
|
13
14
|
'PostToolUse',
|
|
14
15
|
'PostToolUseFailure',
|
|
15
16
|
'Stop',
|
|
17
|
+
'SubagentStop',
|
|
16
18
|
'SessionEnd',
|
|
17
19
|
];
|
|
18
20
|
function isRecord(value) {
|
|
@@ -137,12 +139,24 @@ export class ClaudeHookRunner {
|
|
|
137
139
|
maxTimeoutMs;
|
|
138
140
|
executeCommand;
|
|
139
141
|
onEvent;
|
|
142
|
+
cwd;
|
|
140
143
|
constructor(options) {
|
|
141
144
|
this.settings = options.settings;
|
|
142
145
|
this.maxOutputBytes = options.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES;
|
|
143
146
|
this.maxTimeoutMs = options.maxTimeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
144
147
|
this.executeCommand = options.executeCommand ?? this.runCommand.bind(this);
|
|
145
148
|
this.onEvent = options.onEvent;
|
|
149
|
+
this.cwd = options.cwd;
|
|
150
|
+
}
|
|
151
|
+
withAdditionalSettings(settings) {
|
|
152
|
+
return new ClaudeHookRunner({
|
|
153
|
+
settings: [...this.settings, ...settings],
|
|
154
|
+
maxOutputBytes: this.maxOutputBytes,
|
|
155
|
+
maxTimeoutMs: this.maxTimeoutMs,
|
|
156
|
+
executeCommand: this.executeCommand,
|
|
157
|
+
...(this.onEvent ? { onEvent: this.onEvent } : {}),
|
|
158
|
+
cwd: this.cwd,
|
|
159
|
+
});
|
|
146
160
|
}
|
|
147
161
|
async run(input, matcherValue, signal) {
|
|
148
162
|
if (signal?.aborted)
|