praxis-agent 0.20.21 → 0.21.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 +11 -8
- package/dist/application/background-agent-manager.js +15 -10
- package/dist/application/session-memory.d.ts +96 -0
- package/dist/application/session-memory.js +383 -0
- package/dist/application/session-service.d.ts +18 -0
- package/dist/application/session-service.js +325 -65
- package/dist/application/subagent-service.d.ts +2 -1
- package/dist/application/subagent-service.js +147 -51
- package/dist/cli/interactive.js +17 -5
- package/dist/cli/tui/claude-style.d.ts +4 -0
- package/dist/cli/tui/claude-style.js +11 -0
- package/dist/cli-runtime.js +22 -18
- package/dist/compatibility/claude/schema.d.ts +7 -0
- package/dist/compatibility/claude/schema.js +22 -10
- package/dist/compatibility/claude/sidechain.d.ts +5 -1
- package/dist/compatibility/claude/sidechain.js +15 -2
- package/dist/core/context-budget.d.ts +53 -2
- package/dist/core/context-budget.js +117 -5
- package/dist/tools/claude-capabilities.d.ts +79 -0
- package/dist/tools/claude-capabilities.js +187 -0
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { randomBytes, randomUUID } from 'node:crypto';
|
|
2
|
-
import { mkdir, readFile, readdir
|
|
3
|
-
import { join } from 'node:path';
|
|
2
|
+
import { lstat, mkdir, readFile, readdir } from 'node:fs/promises';
|
|
3
|
+
import { join, relative } from 'node:path';
|
|
4
4
|
import { Ajv2020 } from 'ajv/dist/2020.js';
|
|
5
5
|
import { resolveClaudePaths } from '../compatibility/claude/paths.js';
|
|
6
6
|
import { workflowAgentFiles } from '../compatibility/claude/workflow.js';
|
|
@@ -22,11 +22,24 @@ import { createWorkflowWorktree } from './workflow-worktree.js';
|
|
|
22
22
|
const DEFAULT_MAX_DEPTH = 4;
|
|
23
23
|
const DEFAULT_MAX_CALLS = 16;
|
|
24
24
|
const DEFAULT_MAX_OUTPUT_BYTES = 1024 * 1024;
|
|
25
|
+
const SIDECHAIN_DISCOVERY_MAX_DEPTH = 4;
|
|
25
26
|
const structuredOnlyTools = {
|
|
26
27
|
definitions: () => [],
|
|
27
28
|
prepare: async (call) => call,
|
|
28
29
|
execute: async () => ({ content: '', isError: false }),
|
|
29
30
|
};
|
|
31
|
+
async function readSidechainMetadataName(metadataFile) {
|
|
32
|
+
try {
|
|
33
|
+
const value = JSON.parse(await readFile(metadataFile, 'utf8'));
|
|
34
|
+
if (!value || typeof value !== 'object')
|
|
35
|
+
return undefined;
|
|
36
|
+
const name = value.name;
|
|
37
|
+
return typeof name === 'string' ? name : undefined;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
30
43
|
export class StructuredOutputRegistry {
|
|
31
44
|
base;
|
|
32
45
|
schema;
|
|
@@ -134,7 +147,7 @@ function agentToolRuleName(rule) {
|
|
|
134
147
|
const opening = rule.indexOf('(');
|
|
135
148
|
return (opening < 0 ? rule : rule.slice(0, opening)).trim();
|
|
136
149
|
}
|
|
137
|
-
function enabledAgentToolNames(base, definition, background, additiveTools = new Set()) {
|
|
150
|
+
function enabledAgentToolNames(base, definition, background, permissionMode, additiveTools = new Set()) {
|
|
138
151
|
const requested = definition?.tools
|
|
139
152
|
? new Set(definition.tools.map(agentToolRuleName))
|
|
140
153
|
: null;
|
|
@@ -150,8 +163,10 @@ function enabledAgentToolNames(base, definition, background, additiveTools = new
|
|
|
150
163
|
.filter((name) => {
|
|
151
164
|
if (additiveTools.has(name))
|
|
152
165
|
return true;
|
|
153
|
-
if (AGENT_UNAVAILABLE_TOOLS.has(name)
|
|
166
|
+
if (AGENT_UNAVAILABLE_TOOLS.has(name) &&
|
|
167
|
+
!(name === 'ExitPlanMode' && permissionMode === 'plan')) {
|
|
154
168
|
return false;
|
|
169
|
+
}
|
|
155
170
|
if (background &&
|
|
156
171
|
!name.startsWith('mcp__') &&
|
|
157
172
|
!BACKGROUND_AGENT_TOOLS.has(name)) {
|
|
@@ -968,25 +983,28 @@ export class ClaudeSubagentExecutor {
|
|
|
968
983
|
cwd: this.cwd(),
|
|
969
984
|
sessionId,
|
|
970
985
|
});
|
|
971
|
-
const
|
|
972
|
-
if (!
|
|
986
|
+
const sidechainPaths = await this.resolvePersistedSidechain(paths.projectRoot, sessionId, identifier);
|
|
987
|
+
if (!sidechainPaths)
|
|
973
988
|
return;
|
|
974
|
-
const
|
|
989
|
+
const agentId = sidechainPaths.agentId;
|
|
975
990
|
const sidechain = new ClaudeSidechainStore(sidechainPaths, join(paths.praxisRoot, 'locks', `${sessionId}-${agentId}.lock`), this.schema);
|
|
976
|
-
let metadata;
|
|
977
991
|
let snapshot;
|
|
978
992
|
try {
|
|
979
|
-
;
|
|
980
|
-
[metadata, snapshot] = await Promise.all([
|
|
981
|
-
sidechain.metadata(),
|
|
982
|
-
sidechain.loadReadOnly(),
|
|
983
|
-
]);
|
|
993
|
+
snapshot = await sidechain.loadReadOnly();
|
|
984
994
|
}
|
|
985
995
|
catch (error) {
|
|
986
996
|
if (error.code === 'ENOENT')
|
|
987
997
|
return;
|
|
988
998
|
throw error;
|
|
989
999
|
}
|
|
1000
|
+
let metadata = null;
|
|
1001
|
+
try {
|
|
1002
|
+
metadata = await sidechain.metadata();
|
|
1003
|
+
}
|
|
1004
|
+
catch (error) {
|
|
1005
|
+
if (error.code !== 'ENOENT')
|
|
1006
|
+
throw error;
|
|
1007
|
+
}
|
|
990
1008
|
const root = snapshot.entries[0];
|
|
991
1009
|
if (!root || root.type !== 'user') {
|
|
992
1010
|
throw new Error(`Background agent ${agentId} has no sidechain root`);
|
|
@@ -1003,15 +1021,20 @@ export class ClaudeSubagentExecutor {
|
|
|
1003
1021
|
(lastAssistant.toolCalls?.length ?? 0) > 0) {
|
|
1004
1022
|
throw new Error(`Background agent ${agentId} is not completed`);
|
|
1005
1023
|
}
|
|
1024
|
+
const agentType = metadata?.agentType ?? 'general-purpose';
|
|
1025
|
+
const description = metadata?.description ?? 'Recovered Claude sidechain';
|
|
1026
|
+
const toolUseId = metadata?.toolUseId ?? `recovered:${agentId}`;
|
|
1027
|
+
const spawnDepth = metadata?.spawnDepth ?? 1;
|
|
1028
|
+
const name = metadata?.name;
|
|
1029
|
+
const permissionMode = metadata?.permissionMode;
|
|
1030
|
+
const isolation = metadata?.isolation;
|
|
1006
1031
|
const input = {
|
|
1007
|
-
description
|
|
1032
|
+
description,
|
|
1008
1033
|
prompt,
|
|
1009
|
-
subagentType:
|
|
1010
|
-
...(
|
|
1011
|
-
...(
|
|
1012
|
-
|
|
1013
|
-
: {}),
|
|
1014
|
-
...(metadata.isolation ? { isolation: metadata.isolation } : {}),
|
|
1034
|
+
subagentType: agentType,
|
|
1035
|
+
...(name ? { name } : {}),
|
|
1036
|
+
...(permissionMode ? { permissionMode } : {}),
|
|
1037
|
+
...(isolation ? { isolation } : {}),
|
|
1015
1038
|
runInBackground: true,
|
|
1016
1039
|
};
|
|
1017
1040
|
const provider = this.options.provider;
|
|
@@ -1024,9 +1047,9 @@ export class ClaudeSubagentExecutor {
|
|
|
1024
1047
|
input,
|
|
1025
1048
|
provider,
|
|
1026
1049
|
agentId,
|
|
1027
|
-
spawnDepth
|
|
1050
|
+
spawnDepth,
|
|
1028
1051
|
promptId: String(root.promptId ?? randomUUID()),
|
|
1029
|
-
toolUseId
|
|
1052
|
+
toolUseId,
|
|
1030
1053
|
transcriptPath: sidechainPaths.transcriptFile,
|
|
1031
1054
|
toolResultDirectory: join(paths.projectRoot, sessionId, 'tool-results'),
|
|
1032
1055
|
...(continuation ? { continuationMessage: message } : {}),
|
|
@@ -1036,11 +1059,11 @@ export class ClaudeSubagentExecutor {
|
|
|
1036
1059
|
});
|
|
1037
1060
|
this.background.registerCompleted({
|
|
1038
1061
|
agentId,
|
|
1039
|
-
...(
|
|
1040
|
-
agentType
|
|
1041
|
-
description
|
|
1062
|
+
...(name ? { name } : {}),
|
|
1063
|
+
agentType,
|
|
1064
|
+
description,
|
|
1042
1065
|
prompt,
|
|
1043
|
-
toolUseId
|
|
1066
|
+
toolUseId,
|
|
1044
1067
|
outputFile: sidechainPaths.transcriptFile,
|
|
1045
1068
|
resolvedModel: provider.model ?? 'praxis/provider',
|
|
1046
1069
|
run,
|
|
@@ -1051,37 +1074,110 @@ export class ClaudeSubagentExecutor {
|
|
|
1051
1074
|
durationMs: 0,
|
|
1052
1075
|
});
|
|
1053
1076
|
}
|
|
1054
|
-
async
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
if (
|
|
1077
|
+
async resolvePersistedSidechain(projectRoot, sessionId, identifier) {
|
|
1078
|
+
const isAgentId = /^a[0-9a-f]{16}$/u.test(identifier);
|
|
1079
|
+
const isName = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/u.test(identifier);
|
|
1080
|
+
if (!isAgentId && !isName)
|
|
1058
1081
|
return null;
|
|
1059
|
-
const
|
|
1060
|
-
|
|
1082
|
+
const candidates = await this.discoverSidechainCandidates(projectRoot, sessionId);
|
|
1083
|
+
const matches = candidates.filter((candidate) => isAgentId
|
|
1084
|
+
? candidate.agentId === identifier
|
|
1085
|
+
: candidate.name === identifier);
|
|
1086
|
+
matches.sort((left, right) => (right.metadataModifiedAt ?? -Infinity) -
|
|
1087
|
+
(left.metadataModifiedAt ?? -Infinity) ||
|
|
1088
|
+
left.agentId.localeCompare(right.agentId) ||
|
|
1089
|
+
left.paths.directory.localeCompare(right.paths.directory));
|
|
1090
|
+
return matches[0]?.paths ?? null;
|
|
1091
|
+
}
|
|
1092
|
+
async discoverSidechainCandidates(projectRoot, sessionId) {
|
|
1093
|
+
const rootDirectory = join(projectRoot, sessionId, 'subagents');
|
|
1094
|
+
let rootStat;
|
|
1061
1095
|
try {
|
|
1062
|
-
|
|
1096
|
+
rootStat = await lstat(rootDirectory);
|
|
1063
1097
|
}
|
|
1064
1098
|
catch (error) {
|
|
1065
1099
|
if (error.code === 'ENOENT')
|
|
1066
|
-
return
|
|
1100
|
+
return [];
|
|
1067
1101
|
throw error;
|
|
1068
1102
|
}
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1103
|
+
if (rootStat.isSymbolicLink() || !rootStat.isDirectory())
|
|
1104
|
+
return [];
|
|
1105
|
+
const candidates = [];
|
|
1106
|
+
const walk = async (directory, depth) => {
|
|
1107
|
+
if (depth > SIDECHAIN_DISCOVERY_MAX_DEPTH)
|
|
1108
|
+
return;
|
|
1109
|
+
let entries;
|
|
1110
|
+
try {
|
|
1111
|
+
entries = await readdir(directory, { withFileTypes: true });
|
|
1112
|
+
}
|
|
1113
|
+
catch (error) {
|
|
1114
|
+
if (error.code === 'ENOENT')
|
|
1115
|
+
return;
|
|
1116
|
+
throw error;
|
|
1117
|
+
}
|
|
1118
|
+
const byName = new Map(entries.map((entry) => [entry.name, entry]));
|
|
1119
|
+
const subdirectory = relative(rootDirectory, directory).replaceAll('\\', '/');
|
|
1120
|
+
await Promise.all(entries.map(async (entry) => {
|
|
1121
|
+
if (entry.isSymbolicLink())
|
|
1122
|
+
return;
|
|
1123
|
+
const entryPath = join(directory, entry.name);
|
|
1124
|
+
if (entry.isDirectory()) {
|
|
1125
|
+
await walk(entryPath, depth + 1);
|
|
1126
|
+
return;
|
|
1127
|
+
}
|
|
1128
|
+
if (!entry.isFile())
|
|
1129
|
+
return;
|
|
1130
|
+
const metadataMatch = /^agent-(a[0-9a-f]{16})\.meta\.json$/u.exec(entry.name);
|
|
1131
|
+
if (metadataMatch) {
|
|
1132
|
+
const agentId = metadataMatch[1];
|
|
1133
|
+
if (agentId === undefined)
|
|
1134
|
+
return;
|
|
1135
|
+
const transcriptEntry = byName.get(`agent-${agentId}.jsonl`);
|
|
1136
|
+
if (!transcriptEntry ||
|
|
1137
|
+
transcriptEntry.isSymbolicLink() ||
|
|
1138
|
+
!transcriptEntry.isFile()) {
|
|
1139
|
+
return;
|
|
1140
|
+
}
|
|
1141
|
+
const paths = resolveClaudeSidechainPaths(projectRoot, sessionId, agentId, subdirectory === '' ? {} : { subdirectory });
|
|
1142
|
+
let metadataStat;
|
|
1143
|
+
try {
|
|
1144
|
+
metadataStat = await lstat(paths.metadataFile);
|
|
1145
|
+
}
|
|
1146
|
+
catch (error) {
|
|
1147
|
+
if (error.code === 'ENOENT')
|
|
1148
|
+
return;
|
|
1149
|
+
throw error;
|
|
1150
|
+
}
|
|
1151
|
+
if (metadataStat.isSymbolicLink() || !metadataStat.isFile())
|
|
1152
|
+
return;
|
|
1153
|
+
const name = await readSidechainMetadataName(paths.metadataFile);
|
|
1154
|
+
candidates.push({
|
|
1155
|
+
agentId,
|
|
1156
|
+
paths,
|
|
1157
|
+
metadataModifiedAt: metadataStat.mtimeMs,
|
|
1158
|
+
...(name === undefined ? {} : { name }),
|
|
1159
|
+
});
|
|
1160
|
+
return;
|
|
1161
|
+
}
|
|
1162
|
+
const transcriptMatch = /^agent-(a[0-9a-f]{16})\.jsonl$/u.exec(entry.name);
|
|
1163
|
+
if (!transcriptMatch)
|
|
1164
|
+
return;
|
|
1165
|
+
const agentId = transcriptMatch[1];
|
|
1166
|
+
if (agentId === undefined)
|
|
1167
|
+
return;
|
|
1168
|
+
// A metadata companion (even an invalid one) is owned by the
|
|
1169
|
+
// metadata branch; only a bare transcript without metadata is a
|
|
1170
|
+
// legacy candidate.
|
|
1171
|
+
if (byName.has(`agent-${agentId}.meta.json`))
|
|
1172
|
+
return;
|
|
1173
|
+
candidates.push({
|
|
1174
|
+
agentId,
|
|
1175
|
+
paths: resolveClaudeSidechainPaths(projectRoot, sessionId, agentId, subdirectory === '' ? {} : { subdirectory }),
|
|
1176
|
+
});
|
|
1177
|
+
}));
|
|
1178
|
+
};
|
|
1179
|
+
await walk(rootDirectory, 0);
|
|
1180
|
+
return candidates;
|
|
1085
1181
|
}
|
|
1086
1182
|
asyncLaunchResult(options) {
|
|
1087
1183
|
return [
|
|
@@ -1181,7 +1277,7 @@ export class ClaudeSubagentExecutor {
|
|
|
1181
1277
|
.definitions()
|
|
1182
1278
|
.map(({ name }) => name)
|
|
1183
1279
|
.filter((name) => !inheritedToolNames.has(name)));
|
|
1184
|
-
const agentScopedTools = new RestrictedToolRegistry(agentToolBase, enabledAgentToolNames(agentToolBase, customAgent, options.input.runInBackground, additiveAgentToolNames));
|
|
1280
|
+
const agentScopedTools = new RestrictedToolRegistry(agentToolBase, enabledAgentToolNames(agentToolBase, customAgent, options.input.runInBackground, options.input.permissionMode, additiveAgentToolNames));
|
|
1185
1281
|
const builtInStatusLineAgent = customAgent?.path === BUILTIN_STATUSLINE_AGENT_PATH;
|
|
1186
1282
|
const scopedTools = builtInStatusLineAgent
|
|
1187
1283
|
? new RestrictedToolRegistry(agentScopedTools, ['Read', 'Edit'])
|
package/dist/cli/interactive.js
CHANGED
|
@@ -11,7 +11,7 @@ import { permissionRuleValueToString } from '../permissions/permission-updates.j
|
|
|
11
11
|
import { AgentRunCancelledError } from '../core/runtime.js';
|
|
12
12
|
import { claudePermissionActionKey, claudePermissionRuleMatches, } from '../permissions/claude-permission-resolver.js';
|
|
13
13
|
import { redactSensitiveText, sensitiveEnvironmentValues, } from '../platform/sensitive-data.js';
|
|
14
|
-
import { CommandPalette, BtwPanel, Composer, DiffDashboard, DialogFrame, ExternalEditorWait, HelpMenu, HookDashboard, ListDashboard, MemoryDashboard, MentionPicker, ModelMenu, PermissionDashboard, SelectionMenu, SessionPicker, ThemePicker, CustomThemeEditor, Transcript, WelcomePanel, useTerminalRows, useTerminalWidth, } from './tui/claude-style.js';
|
|
14
|
+
import { CommandPalette, BtwPanel, Composer, DiffDashboard, DialogFrame, ExternalEditorWait, HelpMenu, HookDashboard, ListDashboard, MemoryDashboard, MentionPicker, ModelMenu, PermissionDashboard, SelectionMenu, SessionPicker, ThemePicker, CustomThemeEditor, SessionIdentity, Transcript, WelcomePanel, useTerminalRows, useTerminalWidth, } from './tui/claude-style.js';
|
|
15
15
|
import { loadTuiMemoryFiles, openTuiMemoryFolder, } from './tui/memory-files.js';
|
|
16
16
|
import { loadClaudeReleaseNotes } from './tui/release-notes.js';
|
|
17
17
|
import { createClaudeStatusLineInput, StatusLine } from './tui/status-line.js';
|
|
@@ -417,9 +417,21 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
|
|
|
417
417
|
const [history, setHistory] = useState([...initialHistory]);
|
|
418
418
|
// Startup diagnostics are useful before the first prompt, but they are not
|
|
419
419
|
// conversation history and must not suppress the new-session welcome panel.
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
420
|
+
// Only real user/assistant transcript entries start a conversation; every
|
|
421
|
+
// other kind (thinking, context, tool, shell, notices, results, and so on)
|
|
422
|
+
// is operational bookkeeping that must not hide the fresh-session welcome.
|
|
423
|
+
const isRealConversation = (item) => item.kind === 'user' || item.kind === 'assistant';
|
|
424
|
+
// The original loaded transcript decides whether the session was resumed,
|
|
425
|
+
// separately from the live history that grows while the session runs.
|
|
426
|
+
const resumedWithTranscript = initialHistory.some(isRealConversation);
|
|
427
|
+
const hasConversationHistory = history.some(isRealConversation);
|
|
428
|
+
// A session is resumed only when it was opened through `resume` and the
|
|
429
|
+
// original transcript already contained real conversation content. Supplying
|
|
430
|
+
// a session ID alone with an empty transcript keeps the session fresh, so the
|
|
431
|
+
// full welcome panel renders and the compact identity stays hidden until real
|
|
432
|
+
// conversation content appears.
|
|
433
|
+
const resumed = resume !== undefined && resumedWithTranscript;
|
|
434
|
+
const freshSession = !resumed && !hasConversationHistory;
|
|
423
435
|
const sessionLoadRef = useRef(0);
|
|
424
436
|
const [turnDiffs, setTurnDiffs] = useState([]);
|
|
425
437
|
const turnNumberRef = useRef(0);
|
|
@@ -5635,7 +5647,7 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
|
|
|
5635
5647
|
});
|
|
5636
5648
|
return (_jsx(TuiThemeProvider, { settings: themeSettings, children: _jsx(Box, { flexDirection: "column", ...(!fixedViewport
|
|
5637
5649
|
? {}
|
|
5638
|
-
: { height: rows, overflowY: 'hidden' }), children: selectingSession ? (_jsx(SessionPicker, { sessions: filteredPickerChoices, selectedIndex: selectedIndex, screenReader: axScreenReader, query: sessionSearch })) : (_jsxs(_Fragment, { children: [!axScreenReader &&
|
|
5650
|
+
: { height: rows, overflowY: 'hidden' }), children: selectingSession ? (_jsx(SessionPicker, { sessions: filteredPickerChoices, selectedIndex: selectedIndex, screenReader: axScreenReader, query: sessionSearch })) : (_jsxs(_Fragment, { children: [!axScreenReader && freshSession ? (_jsx(WelcomePanel, { display: runtimeDisplay, width: width, showTips: runtimeSettings.tips })) : null, sessionId ? (_jsxs(Text, { dimColor: true, children: ["Session ", sessionId.slice(0, 8)] })) : null, !axScreenReader && !resumed && hasConversationHistory ? (_jsx(SessionIdentity, { display: runtimeDisplay, width: width })) : null, _jsx(Transcript, { items: history, activeText: activeText, activeThinking: activeThinking, thinkingExpanded: thinkingExpanded, detailedTranscript: thinkingExpanded || runtimeSettings.verbose, screenReader: axScreenReader }), externalEditorRequest !== null ||
|
|
5639
5651
|
keybindingsEditing ||
|
|
5640
5652
|
memoryEditorRequest !== null ? (_jsx(ExternalEditorWait, { screenReader: axScreenReader })) : permission ? (permission.kind === 'tool' && toolPermissionModel ? (_jsx(ToolPermissionDialog, { model: toolPermissionModel, selection: permissionSelection, feedbackMode: permissionFeedbackMode, feedback: input, ruleEditor: permissionRuleEditor, screenReader: axScreenReader })) : (_jsxs(DialogFrame, { title: `Retry interrupted ${permission.call.name}?`, screenReader: axScreenReader, children: [_jsx(Box, { flexDirection: "column", paddingX: 1, paddingY: 1, children: _jsx(Text, { bold: true, children: describeTool(permission.call, sensitiveValues) }) }), _jsx(Text, { children: "Do you want to proceed?" }), _jsxs(Text, { inverse: !axScreenReader && permissionSelection === 0, children: [selectionPrefix(permissionSelection === 0, axScreenReader), "1. Yes"] }), _jsxs(Text, { inverse: !axScreenReader && permissionSelection === 1, children: [selectionPrefix(permissionSelection === 1, axScreenReader), "2. No"] }), permissionFeedbackMode ? (_jsxs(Text, { children: ["\u203A", ' ', input ||
|
|
5641
5653
|
(permissionSelection === 0
|
|
@@ -77,6 +77,10 @@ export declare function WelcomePanel({ display, width, showTips, }: {
|
|
|
77
77
|
width: number;
|
|
78
78
|
showTips: boolean;
|
|
79
79
|
}): import("react").JSX.Element | null;
|
|
80
|
+
export declare function SessionIdentity({ display, width, }: {
|
|
81
|
+
display: TuiDisplayMetadata;
|
|
82
|
+
width: number;
|
|
83
|
+
}): import("react").JSX.Element;
|
|
80
84
|
export declare function MarkdownText({ text }: {
|
|
81
85
|
text: string;
|
|
82
86
|
}): ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
@@ -125,6 +125,17 @@ export function WelcomePanel({ display, width, showTips, }) {
|
|
|
125
125
|
const rightWidth = Math.max(12, panelWidth - leftWidth - 4);
|
|
126
126
|
return (_jsxs(Box, { flexDirection: "column", width: panelWidth, children: [_jsxs(Text, { color: palette.muted, children: ['╭───', _jsx(Text, { color: palette.brand, bold: true, children: brand }), ` Code v${display.version} `, _jsx(Text, { dimColor: true, children: '─'.repeat(fill) }), '╮'] }), _jsxs(Box, { borderStyle: "round", borderColor: palette.muted, borderTop: false, flexDirection: wide ? 'row' : 'column', width: panelWidth, paddingX: 1, children: [_jsxs(Box, { alignItems: wide ? 'center' : undefined, flexDirection: "column", width: wide ? leftWidth : '100%', children: [_jsx(Text, { children: " " }), _jsxs(Text, { bold: true, children: ["Welcome to ", brand] }), _jsx(Text, { children: " " }), _jsx(Text, { color: palette.brand, bold: true, children: "\u2590\u259B\u2588\u2588\u2588\u259C\u258C" }), _jsx(Text, { color: palette.brand, children: "\u259D\u259C\u2588\u2588\u2588\u2588\u2588\u259B\u2598" }), _jsx(Text, { color: palette.brand, children: " \u2598\u2598 \u259D\u259D" }), _jsx(Text, { children: " " }), _jsxs(Text, { wrap: "truncate-end", children: [model, effort] }), _jsx(Text, { dimColor: true, wrap: "truncate-end", children: cwd })] }), _jsxs(Box, { flexDirection: "column", width: wide ? rightWidth : '100%', marginTop: wide ? 0 : 1, children: [_jsx(Text, { bold: true, children: "Get started" }), _jsx(Text, { wrap: "truncate-end", children: "/init to create CLAUDE.md" }), _jsx(Text, { wrap: "truncate-end", children: "/config to open settings" }), _jsx(Text, { dimColor: true, children: " " }), _jsx(Text, { bold: true, children: "Shared with Claude Code" }), _jsx(Text, { dimColor: true, wrap: "truncate-end", children: "Sessions \u00B7 memory \u00B7 skills" })] })] })] }));
|
|
127
127
|
}
|
|
128
|
+
// Compact identity header shown above the transcript once a fresh session has
|
|
129
|
+
// conversation content. Keeps the same product/model/cwd facts as WelcomePanel
|
|
130
|
+
// in a small fixed footprint, truncating every line to the supplied width.
|
|
131
|
+
export function SessionIdentity({ display, width, }) {
|
|
132
|
+
const palette = useTuiPalette();
|
|
133
|
+
const identityWidth = Math.max(1, Math.floor(width));
|
|
134
|
+
const model = display.model ?? 'provider default';
|
|
135
|
+
const effort = display.effort ? ` · ${display.effort} effort` : '';
|
|
136
|
+
const cwd = compactPath(display.cwd);
|
|
137
|
+
return (_jsxs(Box, { flexDirection: "column", width: identityWidth, children: [_jsx(Text, { wrap: "truncate-end", children: _jsx(Text, { color: palette.brand, bold: true, children: `Praxis Code v${display.version}` }) }), _jsxs(Text, { wrap: "truncate-end", children: [model, effort] }), _jsx(Text, { dimColor: true, wrap: "truncate-end", children: cwd })] }));
|
|
138
|
+
}
|
|
128
139
|
const INLINE_SEGMENT_CACHE_MAX = 4096;
|
|
129
140
|
const inlineSegmentCache = new Map();
|
|
130
141
|
function cachedInlineSegments(text) {
|
package/dist/cli-runtime.js
CHANGED
|
@@ -720,7 +720,9 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
720
720
|
const claudeStatePath = requestedConfigRoot || configuredRoot
|
|
721
721
|
? join(configRoot, '.claude.json')
|
|
722
722
|
: resolve(homedir(), '.claude.json');
|
|
723
|
-
const
|
|
723
|
+
const simpleMode = controls.bare ||
|
|
724
|
+
/^(?:1|true|yes|on)$/iu.test((runtimeEnvironment.CLAUDE_CODE_SIMPLE ?? '').trim());
|
|
725
|
+
const runtimeSettings = controls.safeMode || simpleMode
|
|
724
726
|
? undefined
|
|
725
727
|
: await loadRuntimeSettings({ configRoot, statePath: claudeStatePath });
|
|
726
728
|
const runtimeSettingsPrompt = runtimeSettingsSystemPrompt(runtimeSettings);
|
|
@@ -799,6 +801,7 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
799
801
|
eventSink: runtimeEventSink,
|
|
800
802
|
sessionPersistence: cli.sessionPersistence,
|
|
801
803
|
costStateStore,
|
|
804
|
+
simpleMode,
|
|
802
805
|
explicitModel: interactiveModel !== undefined || controls.model !== undefined,
|
|
803
806
|
explicitSystemPrompt: cli.systemPrompt !== undefined,
|
|
804
807
|
agentInitialPromptHandledExternally: interactive,
|
|
@@ -843,7 +846,7 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
843
846
|
loadClaudeSettings({
|
|
844
847
|
configRoot,
|
|
845
848
|
cwd,
|
|
846
|
-
...(
|
|
849
|
+
...(simpleMode
|
|
847
850
|
? { settingSources: [] }
|
|
848
851
|
: cli.settingSources === undefined
|
|
849
852
|
? {}
|
|
@@ -855,7 +858,7 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
855
858
|
pluginDirectories: cli.pluginDirectories,
|
|
856
859
|
pluginUrls: cli.pluginUrls,
|
|
857
860
|
strictPluginDirectories: cli.pluginDirectories.length + cli.pluginUrls.length > 0,
|
|
858
|
-
loadInstalled: !cli.safeMode && !
|
|
861
|
+
loadInstalled: !cli.safeMode && !simpleMode,
|
|
859
862
|
readOnlyHooks: true,
|
|
860
863
|
environment: runtimeEnvironment,
|
|
861
864
|
}),
|
|
@@ -903,12 +906,12 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
903
906
|
complete: (request) => toolProvider.complete(request),
|
|
904
907
|
}
|
|
905
908
|
: toolProvider;
|
|
906
|
-
const automaticSettingSources = cli.safeMode ||
|
|
909
|
+
const automaticSettingSources = cli.safeMode || simpleMode ? [] : cli.settingSources;
|
|
907
910
|
const settings = [
|
|
908
911
|
...(await loadClaudeSettings({
|
|
909
912
|
configRoot,
|
|
910
913
|
cwd,
|
|
911
|
-
...(
|
|
914
|
+
...(simpleMode
|
|
912
915
|
? { settingSources: [] }
|
|
913
916
|
: cli.settingSources === undefined
|
|
914
917
|
? {}
|
|
@@ -930,7 +933,7 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
930
933
|
pluginDirectories: cli.pluginDirectories,
|
|
931
934
|
pluginUrls: cli.pluginUrls,
|
|
932
935
|
strictPluginDirectories: cli.pluginDirectories.length + cli.pluginUrls.length > 0,
|
|
933
|
-
loadInstalled: !cli.safeMode && !
|
|
936
|
+
loadInstalled: !cli.safeMode && !simpleMode,
|
|
934
937
|
environment: runtimeEnvironment,
|
|
935
938
|
});
|
|
936
939
|
for (const plugin of pluginResources.plugins) {
|
|
@@ -972,7 +975,7 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
972
975
|
const extensions = new ClaudeExtensionCatalog(resources, {
|
|
973
976
|
disableSlashCommands: cli.disableSlashCommands,
|
|
974
977
|
});
|
|
975
|
-
const memoryDirectory = cli.safeMode ||
|
|
978
|
+
const memoryDirectory = cli.safeMode || simpleMode
|
|
976
979
|
? undefined
|
|
977
980
|
: await resolveClaudeProjectMemoryDirectory({ configRoot, cwd });
|
|
978
981
|
if (memoryDirectory)
|
|
@@ -1056,7 +1059,7 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
1056
1059
|
return filterDisabledMcpResources(candidates, await management.disabled());
|
|
1057
1060
|
};
|
|
1058
1061
|
const mcpTools = await ClaudeMcpToolRegistry.connect({
|
|
1059
|
-
base:
|
|
1062
|
+
base: simpleMode
|
|
1060
1063
|
? localTools
|
|
1061
1064
|
: new WebToolRegistry({
|
|
1062
1065
|
base: localTools,
|
|
@@ -1115,7 +1118,7 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
1115
1118
|
const extensionTools = new ClaudeExtensionToolRegistry(mcpTools, extensions);
|
|
1116
1119
|
const lspEnabled = interactive &&
|
|
1117
1120
|
!cli.safeMode &&
|
|
1118
|
-
!
|
|
1121
|
+
!simpleMode &&
|
|
1119
1122
|
pluginResources.lsp.length > 0;
|
|
1120
1123
|
lspTools = lspEnabled
|
|
1121
1124
|
? new ClaudeLspToolManager({
|
|
@@ -1159,24 +1162,24 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
1159
1162
|
cli.tools.includes(name)) &&
|
|
1160
1163
|
!cli.disallowedTools.includes(name));
|
|
1161
1164
|
const selectedTaskTools = taskToolNames.filter((name) => cli.sessionPersistence &&
|
|
1162
|
-
!
|
|
1165
|
+
!simpleMode &&
|
|
1163
1166
|
(cli.tools === undefined ||
|
|
1164
1167
|
cli.tools.includes('default') ||
|
|
1165
1168
|
cli.tools.includes(name)) &&
|
|
1166
1169
|
!cli.disallowedTools.includes(name));
|
|
1167
|
-
const selectedScheduledTools = scheduledToolNames.filter((name) => !
|
|
1170
|
+
const selectedScheduledTools = scheduledToolNames.filter((name) => !simpleMode &&
|
|
1168
1171
|
(cli.tools === undefined ||
|
|
1169
1172
|
cli.tools.includes('default') ||
|
|
1170
1173
|
cli.tools.includes(name)) &&
|
|
1171
1174
|
!cli.disallowedTools.includes(name));
|
|
1172
1175
|
const selectedWorkflowTools = workflowToolNames.filter((name) => (runtimeSettings?.workflows ?? true) &&
|
|
1173
1176
|
cli.sessionPersistence &&
|
|
1174
|
-
!
|
|
1177
|
+
!simpleMode &&
|
|
1175
1178
|
(cli.tools === undefined ||
|
|
1176
1179
|
cli.tools.includes('default') ||
|
|
1177
1180
|
cli.tools.includes(name)) &&
|
|
1178
1181
|
!cli.disallowedTools.includes(name));
|
|
1179
|
-
const selectedWorktreeTools = worktreeToolNames.filter((name) => !
|
|
1182
|
+
const selectedWorktreeTools = worktreeToolNames.filter((name) => !simpleMode &&
|
|
1180
1183
|
(cli.tools === undefined ||
|
|
1181
1184
|
cli.tools.includes('default') ||
|
|
1182
1185
|
cli.tools.includes(name)) &&
|
|
@@ -1187,7 +1190,7 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
1187
1190
|
cli.tools.includes(name)) &&
|
|
1188
1191
|
!cli.disallowedTools.includes(name));
|
|
1189
1192
|
const enableBackgroundBash = cli.sessionPersistence &&
|
|
1190
|
-
!
|
|
1193
|
+
!simpleMode &&
|
|
1191
1194
|
(cli.tools === undefined ||
|
|
1192
1195
|
cli.tools.includes('default') ||
|
|
1193
1196
|
cli.tools.includes('Bash')) &&
|
|
@@ -1206,17 +1209,17 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
1206
1209
|
!workflowToolNames.includes(name) &&
|
|
1207
1210
|
!worktreeToolNames.includes(name) &&
|
|
1208
1211
|
!interactiveToolNames.includes(name) &&
|
|
1209
|
-
(!
|
|
1212
|
+
(!simpleMode || (name !== 'WebFetch' && name !== 'WebSearch')));
|
|
1210
1213
|
const filteredTools = new FilteredToolRegistry(extensionAndLspTools, {
|
|
1211
1214
|
...(cli.tools === undefined
|
|
1212
|
-
?
|
|
1215
|
+
? simpleMode
|
|
1213
1216
|
? { tools: ['Bash', 'Edit', 'Read'] }
|
|
1214
1217
|
: {}
|
|
1215
1218
|
: { tools: selectedBaseTools ?? [] }),
|
|
1216
1219
|
disallowedTools: cli.disallowedTools,
|
|
1217
1220
|
});
|
|
1218
|
-
const enableSubagents = !
|
|
1219
|
-
const hooks = cli.safeMode ||
|
|
1221
|
+
const enableSubagents = !simpleMode && selectedAgentTools.length > 0;
|
|
1222
|
+
const hooks = cli.safeMode || simpleMode
|
|
1220
1223
|
? undefined
|
|
1221
1224
|
: new ClaudeHookRunner({
|
|
1222
1225
|
settings,
|
|
@@ -1243,6 +1246,7 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
1243
1246
|
...(providerForModel ? { providerForModel } : {}),
|
|
1244
1247
|
...(providerForMainModel ? { providerForMainModel } : {}),
|
|
1245
1248
|
tools: filteredTools,
|
|
1249
|
+
toolCapabilityEnvironment: runtimeEnvironment,
|
|
1246
1250
|
mcp: mcpTools,
|
|
1247
1251
|
permissions,
|
|
1248
1252
|
permissionResolverForMode,
|
|
@@ -11,6 +11,13 @@ export interface ClaudeSchemaAdapter {
|
|
|
11
11
|
serializeForSidechainAppend(entry: ClaudeTranscriptEntry): string;
|
|
12
12
|
serializeForFork(entry: ClaudeTranscriptEntry): string;
|
|
13
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* Historical fixture/default provenance constant for the Claude Code
|
|
16
|
+
* transcript schema. It records the version Praxis fixtures and default
|
|
17
|
+
* translation paths were captured against; it is not an append gate. Any
|
|
18
|
+
* nonempty semver-like Claude Code version whose entry shape is structurally
|
|
19
|
+
* supported is writable through the append/sidechain/fork serializers.
|
|
20
|
+
*/
|
|
14
21
|
export declare const VERIFIED_CLAUDE_SCHEMA_VERSION = "2.1.208";
|
|
15
22
|
export declare function isClaudeForkableEntryType(type: string): boolean;
|
|
16
23
|
export declare function copyClaudeEntryWithSessionId(entry: ClaudeTranscriptEntry, sessionId: string): ClaudeTranscriptEntry;
|
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
import { isAgentColorValue } from './agent-color.js';
|
|
2
|
+
/**
|
|
3
|
+
* Historical fixture/default provenance constant for the Claude Code
|
|
4
|
+
* transcript schema. It records the version Praxis fixtures and default
|
|
5
|
+
* translation paths were captured against; it is not an append gate. Any
|
|
6
|
+
* nonempty semver-like Claude Code version whose entry shape is structurally
|
|
7
|
+
* supported is writable through the append/sidechain/fork serializers.
|
|
8
|
+
*/
|
|
2
9
|
export const VERIFIED_CLAUDE_SCHEMA_VERSION = '2.1.208';
|
|
10
|
+
const CLAUDE_CODE_VERSION_PATTERN = /^\d+\.\d+\.\d+$/;
|
|
11
|
+
function isClaudeCodeVersion(version) {
|
|
12
|
+
return CLAUDE_CODE_VERSION_PATTERN.test(version);
|
|
13
|
+
}
|
|
3
14
|
const APPENDABLE_ENTRY_TYPES = new Set([
|
|
4
15
|
'agent-color',
|
|
5
16
|
'agent-name',
|
|
@@ -776,9 +787,8 @@ function validateAppendableEntry(entry) {
|
|
|
776
787
|
throw new Error(`Claude transcript entry is missing ${field}`);
|
|
777
788
|
}
|
|
778
789
|
}
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
}
|
|
790
|
+
// version is producer provenance: it must be present and nonempty (enforced
|
|
791
|
+
// above) but is not compared against a single verified writer version.
|
|
782
792
|
if (!('parentUuid' in entry) ||
|
|
783
793
|
(entry.parentUuid !== null && !isNonEmptyString(entry.parentUuid))) {
|
|
784
794
|
throw new Error('Claude transcript entry has invalid parentUuid');
|
|
@@ -837,9 +847,8 @@ function validateSidechainEntry(entry) {
|
|
|
837
847
|
throw new Error(`Claude sidechain entry is missing ${field}`);
|
|
838
848
|
}
|
|
839
849
|
}
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
}
|
|
850
|
+
// version is producer provenance: it must be present and nonempty (enforced
|
|
851
|
+
// above) but is not compared against a single verified writer version.
|
|
843
852
|
if (!('parentUuid' in entry) ||
|
|
844
853
|
(entry.parentUuid !== null && !isNonEmptyString(entry.parentUuid))) {
|
|
845
854
|
throw new Error('Claude sidechain entry has invalid parentUuid');
|
|
@@ -971,9 +980,12 @@ function validateForkableEntry(entry) {
|
|
|
971
980
|
else
|
|
972
981
|
validateForkAssistantMessage(entry.message);
|
|
973
982
|
}
|
|
974
|
-
class
|
|
975
|
-
version
|
|
983
|
+
class ClaudeCodeWritableAdapter {
|
|
984
|
+
version;
|
|
976
985
|
writeMode = 'read-write';
|
|
986
|
+
constructor(version) {
|
|
987
|
+
this.version = version;
|
|
988
|
+
}
|
|
977
989
|
parse(line) {
|
|
978
990
|
return parseEntry(line);
|
|
979
991
|
}
|
|
@@ -1022,8 +1034,8 @@ class ReadOnlyClaudeAdapter {
|
|
|
1022
1034
|
}
|
|
1023
1035
|
}
|
|
1024
1036
|
export function selectClaudeSchemaAdapter(version) {
|
|
1025
|
-
if (version
|
|
1026
|
-
return new
|
|
1037
|
+
if (isClaudeCodeVersion(version)) {
|
|
1038
|
+
return new ClaudeCodeWritableAdapter(version);
|
|
1027
1039
|
}
|
|
1028
1040
|
return new ReadOnlyClaudeAdapter(version);
|
|
1029
1041
|
}
|
|
@@ -7,6 +7,10 @@ export interface ClaudeSidechainPaths {
|
|
|
7
7
|
transcriptFile: string;
|
|
8
8
|
metadataFile: string;
|
|
9
9
|
}
|
|
10
|
+
export interface ClaudeSidechainPathOptions {
|
|
11
|
+
/** Bounded relative subdirectory under `<sessionId>/subagents` (e.g. `workflows/<runId>`). */
|
|
12
|
+
subdirectory?: string;
|
|
13
|
+
}
|
|
10
14
|
export interface ClaudeSidechainMetadata {
|
|
11
15
|
agentType: string;
|
|
12
16
|
description: string;
|
|
@@ -17,7 +21,7 @@ export interface ClaudeSidechainMetadata {
|
|
|
17
21
|
isolation?: 'worktree';
|
|
18
22
|
}
|
|
19
23
|
export type ClaudeSidechainPermissionMode = 'acceptEdits' | 'auto' | 'bypassPermissions' | 'default' | 'dontAsk' | 'plan';
|
|
20
|
-
export declare function resolveClaudeSidechainPaths(projectRoot: string, sessionId: string, agentId: string): ClaudeSidechainPaths;
|
|
24
|
+
export declare function resolveClaudeSidechainPaths(projectRoot: string, sessionId: string, agentId: string, options?: ClaudeSidechainPathOptions): ClaudeSidechainPaths;
|
|
21
25
|
export declare function createClaudeSidechainRoot(options: {
|
|
22
26
|
sessionId: string;
|
|
23
27
|
promptId: string;
|