praxis-agent 0.21.6 → 0.22.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 +6 -6
- package/dist/application/claude-project-purge.d.ts +4 -1
- package/dist/application/claude-project-purge.js +85 -18
- package/dist/application/session-memory.d.ts +1 -0
- package/dist/application/session-memory.js +1 -1
- package/dist/application/session-service.d.ts +4 -0
- package/dist/application/session-service.js +50 -19
- package/dist/application/session-worktree.d.ts +6 -0
- package/dist/application/session-worktree.js +11 -5
- package/dist/application/subagent-service.d.ts +4 -1
- package/dist/application/subagent-service.js +38 -28
- package/dist/application/top-level-agent-manager.d.ts +4 -0
- package/dist/application/top-level-agent-manager.js +34 -14
- package/dist/application/workflow-manager.d.ts +3 -1
- package/dist/application/workflow-manager.js +16 -6
- package/dist/cli/controls.js +8 -0
- package/dist/cli/interactive.d.ts +8 -1
- package/dist/cli/interactive.js +79 -46
- package/dist/cli/protocol.d.ts +9 -0
- package/dist/cli/protocol.js +87 -1
- package/dist/cli/tui/claude-style.d.ts +5 -2
- package/dist/cli/tui/claude-style.js +6 -4
- package/dist/cli/tui/config-settings.js +9 -8
- package/dist/cli/tui/custom-themes.js +2 -2
- package/dist/cli/tui/mcp-panel.d.ts +3 -1
- package/dist/cli/tui/mcp-panel.js +8 -6
- package/dist/cli/tui/memory-files.d.ts +3 -1
- package/dist/cli/tui/memory-files.js +37 -13
- package/dist/cli/tui/permission-settings.d.ts +6 -3
- package/dist/cli/tui/permission-settings.js +19 -11
- package/dist/cli/tui/sandbox-settings.d.ts +3 -1
- package/dist/cli/tui/sandbox-settings.js +7 -3
- package/dist/cli/tui/status-line.d.ts +5 -1
- package/dist/cli/tui/status-line.js +27 -11
- package/dist/cli/tui/terminal-setup.js +9 -3
- package/dist/cli/tui/theme-settings.js +2 -2
- package/dist/cli/tui/tool-permission.d.ts +2 -1
- package/dist/cli/tui/tool-permission.js +23 -17
- package/dist/cli-runtime.d.ts +10 -0
- package/dist/cli-runtime.js +541 -143
- package/dist/compatibility/claude/context.js +7 -1
- package/dist/compatibility/claude/fork.js +1 -0
- package/dist/extensions/claude-extensions.d.ts +2 -0
- package/dist/extensions/claude-extensions.js +27 -7
- package/dist/extensions/claude-init-command.d.ts +3 -2
- package/dist/extensions/claude-init-command.js +58 -2
- package/dist/maintenance/doctor.d.ts +2 -0
- package/dist/maintenance/doctor.js +48 -15
- package/dist/mcp/claude-mcp-management.d.ts +5 -0
- package/dist/mcp/claude-mcp-management.js +27 -9
- package/dist/mcp/praxis-mcp-server.js +1 -1
- package/dist/permissions/bypass-immune-paths.d.ts +2 -0
- package/dist/permissions/bypass-immune-paths.js +25 -3
- package/dist/persistence/claude-job-store.d.ts +2 -1
- package/dist/persistence/claude-job-store.js +4 -2
- package/dist/persistence/claude-migration.d.ts +5 -0
- package/dist/persistence/claude-migration.js +225 -0
- package/dist/persistence/data-plane.d.ts +34 -0
- package/dist/persistence/data-plane.js +66 -0
- package/dist/persistence/native-resources.d.ts +9 -0
- package/dist/persistence/native-resources.js +102 -0
- package/dist/plugins/claude-plugin-eval-discovery.d.ts +1 -0
- package/dist/plugins/claude-plugin-eval-discovery.js +4 -0
- package/dist/plugins/claude-plugin-eval-runner.d.ts +3 -0
- package/dist/plugins/claude-plugin-eval-runner.js +1 -0
- package/dist/plugins/claude-plugin-eval.d.ts +11 -3
- package/dist/plugins/claude-plugin-eval.js +54 -12
- package/dist/plugins/claude-plugin-marketplace.d.ts +15 -14
- package/dist/plugins/claude-plugin-marketplace.js +48 -46
- package/dist/plugins/claude-plugin-mcpb.d.ts +3 -0
- package/dist/plugins/claude-plugin-mcpb.js +4 -2
- package/dist/plugins/claude-plugin-runtime.d.ts +2 -0
- package/dist/plugins/claude-plugin-runtime.js +8 -6
- package/dist/sandbox/claude-sandbox-settings.d.ts +3 -1
- package/dist/sandbox/claude-sandbox-settings.js +9 -3
- package/dist/tools/claude-scheduled-tools.d.ts +2 -0
- package/dist/tools/claude-scheduled-tools.js +89 -74
- package/dist/tools/claude-workflow-tools.d.ts +2 -0
- package/dist/tools/claude-workflow-tools.js +26 -22
- package/dist/tools/claude-worktree-tools.d.ts +2 -0
- package/dist/tools/claude-worktree-tools.js +27 -1
- package/dist/tools/local-tools.d.ts +2 -0
- package/dist/tools/local-tools.js +15 -0
- package/dist/tools/web.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -161,16 +161,16 @@ not in this entry-point README.
|
|
|
161
161
|
|
|
162
162
|
## Claude Code interoperability
|
|
163
163
|
|
|
164
|
-
Praxis
|
|
164
|
+
Praxis uses an independent local data plane by default:
|
|
165
165
|
|
|
166
166
|
```text
|
|
167
|
-
|
|
168
|
-
├── ~/.claude (or CLAUDE_CONFIG_DIR)
|
|
169
|
-
Praxis ──────┘
|
|
167
|
+
Praxis ─── ~/.praxis (or PRAXIS_HOME)
|
|
170
168
|
```
|
|
171
169
|
|
|
172
|
-
|
|
173
|
-
|
|
170
|
+
Use `praxis --data-plane claude` when you need the legacy shared Claude Code
|
|
171
|
+
layout (`~/.claude` or `CLAUDE_CONFIG_DIR`). Praxis can resume Claude Code
|
|
172
|
+
sessions in that mode, and Claude Code can resume compatible sessions written
|
|
173
|
+
there. Every semver-like Claude Code producer version is
|
|
174
174
|
structurally validated and is read/write compatible when its entry shape is
|
|
175
175
|
supported; schema adapters are selected from transcript entry structure rather
|
|
176
176
|
than an installed or fixed producer version, and malformed or unsupported
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
import type { DataPlane } from '../persistence/data-plane.js';
|
|
2
|
+
export type ClaudeProjectPurgeItemKind = 'project' | 'memory' | 'scheduled' | 'tasks' | 'debug' | 'file-history' | 'prompt-history' | 'config-key';
|
|
2
3
|
type ClaudeProjectPurgeOperation = 'remove-path' | 'filter-history' | 'remove-project-config' | 'clear-project-config';
|
|
3
4
|
export interface ClaudeProjectPurgeItem {
|
|
4
5
|
id: string;
|
|
@@ -14,6 +15,7 @@ export interface ClaudeProjectPurgePlan {
|
|
|
14
15
|
mode: 'project' | 'all';
|
|
15
16
|
configRoot: string;
|
|
16
17
|
statePath: string;
|
|
18
|
+
allowStateOutsideConfigRoot?: boolean;
|
|
17
19
|
targetPath?: string;
|
|
18
20
|
projectIdentity?: string;
|
|
19
21
|
projectRootPaths: string[];
|
|
@@ -21,6 +23,7 @@ export interface ClaudeProjectPurgePlan {
|
|
|
21
23
|
items: ClaudeProjectPurgeItem[];
|
|
22
24
|
}
|
|
23
25
|
export interface PlanClaudeProjectPurgeOptions {
|
|
26
|
+
dataPlane?: DataPlane;
|
|
24
27
|
cwd: string;
|
|
25
28
|
path?: string;
|
|
26
29
|
all?: boolean;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { lstat, readFile, readdir, realpath, rm } from 'node:fs/promises';
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
|
-
import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
3
|
+
import { basename, dirname, isAbsolute, join, relative, resolve, sep, } from 'node:path';
|
|
4
4
|
import { isClaudeSessionId, sanitizeClaudeProjectPath, } from '../compatibility/claude/paths.js';
|
|
5
5
|
import { resolveClaudeProjectIdentity } from '../compatibility/claude/shared-resources.js';
|
|
6
6
|
import { writeFileAtomically } from '../platform/atomic-write.js';
|
|
@@ -74,7 +74,8 @@ async function assertSafePurgePath(configRoot, candidate) {
|
|
|
74
74
|
const canonical = await realpath(absolute);
|
|
75
75
|
assertLexicallyContained(configRoot, canonical);
|
|
76
76
|
}
|
|
77
|
-
async function assertMutableRegularFile(path) {
|
|
77
|
+
async function assertMutableRegularFile(path, boundary) {
|
|
78
|
+
const canonicalBoundary = await canonicalPath(boundary);
|
|
78
79
|
let current = resolve(path);
|
|
79
80
|
while (true) {
|
|
80
81
|
try {
|
|
@@ -87,6 +88,14 @@ async function assertMutableRegularFile(path) {
|
|
|
87
88
|
if (errorCode(error) !== 'ENOENT')
|
|
88
89
|
throw error;
|
|
89
90
|
}
|
|
91
|
+
try {
|
|
92
|
+
if ((await realpath(current)) === canonicalBoundary)
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
if (errorCode(error) !== 'ENOENT')
|
|
97
|
+
throw error;
|
|
98
|
+
}
|
|
90
99
|
const parent = dirname(current);
|
|
91
100
|
if (parent === current)
|
|
92
101
|
break;
|
|
@@ -182,8 +191,8 @@ async function discoverSessionIds(projectRoot) {
|
|
|
182
191
|
}
|
|
183
192
|
return [...sessionIds].sort();
|
|
184
193
|
}
|
|
185
|
-
async function discoverProjectRoots(configRoot, targetPaths) {
|
|
186
|
-
const projectsRoot = join(configRoot, 'projects');
|
|
194
|
+
async function discoverProjectRoots(configRoot, targetPaths, dataPlane) {
|
|
195
|
+
const projectsRoot = join(configRoot, dataPlane === 'native' ? 'sessions' : 'projects');
|
|
187
196
|
if (!(await pathExists(projectsRoot)))
|
|
188
197
|
return [];
|
|
189
198
|
await assertSafePurgePath(configRoot, projectsRoot);
|
|
@@ -198,9 +207,10 @@ async function discoverProjectRoots(configRoot, targetPaths) {
|
|
|
198
207
|
throw error;
|
|
199
208
|
}
|
|
200
209
|
const targetKeys = targetPaths.map(sanitizeClaudeProjectPath);
|
|
210
|
+
const worktreeSegment = dataPlane === 'native' ? '--praxis-worktrees-' : '--claude-worktrees-';
|
|
201
211
|
return entries
|
|
202
212
|
.filter((entry) => targetKeys.some((targetKey) => entry.name === targetKey ||
|
|
203
|
-
entry.name.startsWith(`${targetKey}
|
|
213
|
+
entry.name.startsWith(`${targetKey}${worktreeSegment}`)))
|
|
204
214
|
.map((entry) => join(projectsRoot, entry.name))
|
|
205
215
|
.sort();
|
|
206
216
|
}
|
|
@@ -227,11 +237,26 @@ export async function planClaudeProjectPurge(options) {
|
|
|
227
237
|
const configuredRoot = options.configRoot ??
|
|
228
238
|
(process.env.CLAUDE_CONFIG_DIR || join(homedir(), '.claude'));
|
|
229
239
|
const configRoot = await canonicalPath(configuredRoot);
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
const
|
|
234
|
-
|
|
240
|
+
const dataPlane = options.dataPlane ?? 'claude';
|
|
241
|
+
const homeDirectory = options.homeDirectory ?? homedir();
|
|
242
|
+
const legacyClaudeStatePath = resolve(homeDirectory, '.claude.json');
|
|
243
|
+
const legacyClaudeConfigRoot = await canonicalPath(resolve(homeDirectory, '.claude'));
|
|
244
|
+
const defaultStatePath = dataPlane === 'native'
|
|
245
|
+
? join(configRoot, 'state.json')
|
|
246
|
+
: options.configRoot !== undefined ||
|
|
247
|
+
Boolean(process.env.CLAUDE_CONFIG_DIR)
|
|
248
|
+
? join(configRoot, '.claude.json')
|
|
249
|
+
: legacyClaudeStatePath;
|
|
250
|
+
const statePath = resolve(options.statePath ?? defaultStatePath);
|
|
251
|
+
const usesLegacyDefaultClaudeState = dataPlane === 'claude' &&
|
|
252
|
+
!process.env.CLAUDE_CONFIG_DIR &&
|
|
253
|
+
configRoot === legacyClaudeConfigRoot &&
|
|
254
|
+
statePath === legacyClaudeStatePath;
|
|
255
|
+
const canonicalStateParent = await canonicalPath(dirname(statePath));
|
|
256
|
+
if (!usesLegacyDefaultClaudeState) {
|
|
257
|
+
assertLexicallyContained(configRoot, join(canonicalStateParent, basename(statePath)));
|
|
258
|
+
}
|
|
259
|
+
await assertMutableRegularFile(statePath, usesLegacyDefaultClaudeState ? canonicalStateParent : configRoot);
|
|
235
260
|
const state = parseState(await readOptionalFile(statePath), statePath);
|
|
236
261
|
const projects = stateProjects(state);
|
|
237
262
|
const items = [];
|
|
@@ -239,9 +264,25 @@ export async function planClaudeProjectPurge(options) {
|
|
|
239
264
|
const roots = [
|
|
240
265
|
{
|
|
241
266
|
kind: 'project',
|
|
242
|
-
name: 'projects',
|
|
243
|
-
description:
|
|
267
|
+
name: dataPlane === 'native' ? 'sessions' : 'projects',
|
|
268
|
+
description: dataPlane === 'native'
|
|
269
|
+
? 'all project transcripts'
|
|
270
|
+
: 'all project transcripts and memory',
|
|
244
271
|
},
|
|
272
|
+
...(dataPlane === 'native'
|
|
273
|
+
? [
|
|
274
|
+
{
|
|
275
|
+
kind: 'memory',
|
|
276
|
+
name: 'memory',
|
|
277
|
+
description: 'all project memory',
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
kind: 'scheduled',
|
|
281
|
+
name: 'scheduled',
|
|
282
|
+
description: 'all project scheduled prompts',
|
|
283
|
+
},
|
|
284
|
+
]
|
|
285
|
+
: []),
|
|
245
286
|
{
|
|
246
287
|
kind: 'tasks',
|
|
247
288
|
name: 'tasks',
|
|
@@ -282,6 +323,9 @@ export async function planClaudeProjectPurge(options) {
|
|
|
282
323
|
mode: 'all',
|
|
283
324
|
configRoot,
|
|
284
325
|
statePath,
|
|
326
|
+
...(usesLegacyDefaultClaudeState
|
|
327
|
+
? { allowStateOutsideConfigRoot: true }
|
|
328
|
+
: {}),
|
|
285
329
|
projectRootPaths: [],
|
|
286
330
|
sessionIds: [],
|
|
287
331
|
items,
|
|
@@ -294,7 +338,7 @@ export async function planClaudeProjectPurge(options) {
|
|
|
294
338
|
cwd: targetPath,
|
|
295
339
|
homeDirectory: options.homeDirectory ?? homedir(),
|
|
296
340
|
});
|
|
297
|
-
const projectRootPaths = await discoverProjectRoots(configRoot, targetPaths);
|
|
341
|
+
const projectRootPaths = await discoverProjectRoots(configRoot, targetPaths, dataPlane);
|
|
298
342
|
const sessionIdSet = new Set();
|
|
299
343
|
for (const projectRoot of projectRootPaths) {
|
|
300
344
|
await assertSafePurgePath(configRoot, projectRoot);
|
|
@@ -308,8 +352,20 @@ export async function planClaudeProjectPurge(options) {
|
|
|
308
352
|
await addExistingPath(items, configRoot, removePathItem('debug', join(configRoot, 'debug', `${sessionId}.txt`), `debug log for session ${sessionId}`));
|
|
309
353
|
await addExistingPath(items, configRoot, removePathItem('file-history', join(configRoot, 'file-history', sessionId), `file edit history for session ${sessionId}`));
|
|
310
354
|
}
|
|
355
|
+
if (dataPlane === 'native') {
|
|
356
|
+
const projectKeys = new Set([
|
|
357
|
+
...targetPaths.map(sanitizeClaudeProjectPath),
|
|
358
|
+
...projectRootPaths.map((path) => basename(path)),
|
|
359
|
+
]);
|
|
360
|
+
for (const projectKey of projectKeys) {
|
|
361
|
+
await addExistingPath(items, configRoot, removePathItem('memory', join(configRoot, 'memory', projectKey), `memory for project ${projectKey}`));
|
|
362
|
+
await addExistingPath(items, configRoot, removePathItem('scheduled', join(configRoot, 'scheduled', `${projectKey}.json`), `scheduled prompts for project ${projectKey}`));
|
|
363
|
+
}
|
|
364
|
+
}
|
|
311
365
|
for (const projectRoot of projectRootPaths) {
|
|
312
|
-
await addExistingPath(items, configRoot, removePathItem('project', projectRoot,
|
|
366
|
+
await addExistingPath(items, configRoot, removePathItem('project', projectRoot, dataPlane === 'native'
|
|
367
|
+
? 'project transcripts'
|
|
368
|
+
: 'project transcripts and memory'));
|
|
313
369
|
}
|
|
314
370
|
const historyPath = join(configRoot, 'history.jsonl');
|
|
315
371
|
const historyExists = await pathExists(historyPath);
|
|
@@ -351,6 +407,9 @@ export async function planClaudeProjectPurge(options) {
|
|
|
351
407
|
mode: 'project',
|
|
352
408
|
configRoot,
|
|
353
409
|
statePath,
|
|
410
|
+
...(usesLegacyDefaultClaudeState
|
|
411
|
+
? { allowStateOutsideConfigRoot: true }
|
|
412
|
+
: {}),
|
|
354
413
|
targetPath,
|
|
355
414
|
projectIdentity,
|
|
356
415
|
projectRootPaths,
|
|
@@ -358,8 +417,8 @@ export async function planClaudeProjectPurge(options) {
|
|
|
358
417
|
items,
|
|
359
418
|
};
|
|
360
419
|
}
|
|
361
|
-
async function atomicallyUpdate(path, update) {
|
|
362
|
-
await assertMutableRegularFile(path);
|
|
420
|
+
async function atomicallyUpdate(path, update, boundary) {
|
|
421
|
+
await assertMutableRegularFile(path, boundary);
|
|
363
422
|
for (let attempt = 0; attempt < MAX_ATOMIC_UPDATE_RETRIES; attempt += 1) {
|
|
364
423
|
const source = await readOptionalFile(path);
|
|
365
424
|
const content = update(source);
|
|
@@ -389,9 +448,17 @@ async function executeItem(plan, item) {
|
|
|
389
448
|
if (source === null)
|
|
390
449
|
return null;
|
|
391
450
|
return filterProjectHistory(source, projectPaths).content;
|
|
392
|
-
});
|
|
451
|
+
}, plan.configRoot);
|
|
393
452
|
return;
|
|
394
453
|
}
|
|
454
|
+
const stateParent = await canonicalPath(dirname(item.path));
|
|
455
|
+
let stateBoundary;
|
|
456
|
+
if (plan.allowStateOutsideConfigRoot)
|
|
457
|
+
stateBoundary = stateParent;
|
|
458
|
+
else {
|
|
459
|
+
assertLexicallyContained(plan.configRoot, join(stateParent, basename(item.path)));
|
|
460
|
+
stateBoundary = plan.configRoot;
|
|
461
|
+
}
|
|
395
462
|
await atomicallyUpdate(item.path, (source) => {
|
|
396
463
|
if (source === null)
|
|
397
464
|
return null;
|
|
@@ -412,7 +479,7 @@ async function executeItem(plan, item) {
|
|
|
412
479
|
}
|
|
413
480
|
state.projects = projects;
|
|
414
481
|
return `${JSON.stringify(state, null, 2)}\n`;
|
|
415
|
-
});
|
|
482
|
+
}, stateBoundary);
|
|
416
483
|
}
|
|
417
484
|
export async function executeClaudeProjectPurge(plan, options = {}) {
|
|
418
485
|
if (options.dryRun) {
|
|
@@ -22,6 +22,7 @@ export declare function createFreshSessionMemoryState(): SessionMemoryState;
|
|
|
22
22
|
export interface SessionMemoryStoreOptions {
|
|
23
23
|
configRoot: string;
|
|
24
24
|
sessionId: string;
|
|
25
|
+
sidecarRoot?: string;
|
|
25
26
|
}
|
|
26
27
|
/**
|
|
27
28
|
* Durable sidecar for one session's extracted memory under
|
|
@@ -94,7 +94,7 @@ export class SessionMemoryStore {
|
|
|
94
94
|
if (!isClaudeSessionId(options.sessionId)) {
|
|
95
95
|
throw new SessionMemoryStateError(`Invalid session memory session ID: ${options.sessionId}`);
|
|
96
96
|
}
|
|
97
|
-
this.directory = resolve(options.configRoot, 'praxis', 'session-memory', options.sessionId);
|
|
97
|
+
this.directory = resolve(options.sidecarRoot ?? resolve(options.configRoot, 'praxis'), 'session-memory', options.sessionId);
|
|
98
98
|
this.stateFile = join(this.directory, 'state.json');
|
|
99
99
|
this.summaryFile = join(this.directory, 'summary.md');
|
|
100
100
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type AgentColorName, type AgentColorSelection } from '../compatibility/claude/agent-color.js';
|
|
2
2
|
import type { ClaudeConditionalRuleResolver } from '../compatibility/claude/context.js';
|
|
3
|
+
import { type DataPlane } from '../persistence/data-plane.js';
|
|
3
4
|
import { type ClaudeFileResource, type ClaudeFileResourceConfig } from '../compatibility/claude/file-resources.js';
|
|
4
5
|
import { type ClaudeDisplayTranscriptItem } from '../compatibility/claude/projection.js';
|
|
5
6
|
import { type ModelDocument, type ModelImage, type ModelToolCall, type ModelProvider, type ModelUsage, type PermissionApproval, type PermissionDecision, type PermissionResolver, type PermissionUpdate, type RuntimeEventSink, type ToolRegistry } from '../core/runtime.js';
|
|
@@ -23,6 +24,8 @@ import type { ClaudePermissionMode } from '../permissions/claude-permission-reso
|
|
|
23
24
|
import type { ClaudeMcpRuntime, ClaudeMcpServerStatus, ClaudeMcpToolInspection } from '../mcp/claude-mcp-tools.js';
|
|
24
25
|
export interface ClaudeSessionServiceOptions {
|
|
25
26
|
configRoot: string;
|
|
27
|
+
/** `claude` preserves the legacy shared layout; the CLI selects `native` by default. */
|
|
28
|
+
dataPlane?: DataPlane;
|
|
26
29
|
cwd: string;
|
|
27
30
|
claudeVersion: string;
|
|
28
31
|
provider?: ModelProvider;
|
|
@@ -246,6 +249,7 @@ export declare class ClaudeSessionService {
|
|
|
246
249
|
private restoreWorktree;
|
|
247
250
|
private paths;
|
|
248
251
|
private discoverProjectRoot;
|
|
252
|
+
private pathsForCwd;
|
|
249
253
|
private appendCdCommand;
|
|
250
254
|
private appendAgentColorUsage;
|
|
251
255
|
private appendSystemLocalCommand;
|
|
@@ -5,6 +5,7 @@ import { basename, extname, isAbsolute, join, relative, resolve, } from 'node:pa
|
|
|
5
5
|
import { AGENT_COLOR_DEFAULT, agentColorMessage, getClaudeEffectiveAgentColor, } from '../compatibility/claude/agent-color.js';
|
|
6
6
|
import { createClaudeCompactEntries, formatClaudeCompactSummary, getCumulativeDroppedTokens, } from '../compatibility/claude/compaction.js';
|
|
7
7
|
import { discoverClaudeProjectRoot, isClaudeSessionId, resolveClaudePaths, resolveClaudeScheduledTaskFile, } from '../compatibility/claude/paths.js';
|
|
8
|
+
import { resolveDataPlanePaths, resolveScheduledTaskFile, } from '../persistence/data-plane.js';
|
|
8
9
|
import { downloadClaudeFileResources, } from '../compatibility/claude/file-resources.js';
|
|
9
10
|
import { createClaudeNativeFork } from '../compatibility/claude/fork.js';
|
|
10
11
|
import { selectClaudeActiveTranscript, selectClaudeTranscriptAtMessage, } from '../compatibility/claude/history.js';
|
|
@@ -347,15 +348,28 @@ export class ClaudeSessionService {
|
|
|
347
348
|
this.scheduledPrompts =
|
|
348
349
|
options.tools && (options.scheduledToolNames?.length ?? 0) > 0
|
|
349
350
|
? new ScheduledPromptManager({
|
|
350
|
-
filePath:
|
|
351
|
-
|
|
351
|
+
filePath: options.dataPlane === 'native'
|
|
352
|
+
? resolveScheduledTaskFile({
|
|
353
|
+
dataPlane: 'native',
|
|
354
|
+
cwd: options.cwd,
|
|
355
|
+
root: options.configRoot,
|
|
356
|
+
})
|
|
357
|
+
: resolveClaudeScheduledTaskFile(options.cwd),
|
|
358
|
+
lockFile: join(options.dataPlane === 'native'
|
|
359
|
+
? resolveDataPlanePaths({
|
|
360
|
+
dataPlane: 'native',
|
|
361
|
+
root: options.configRoot,
|
|
362
|
+
cwd: options.cwd,
|
|
363
|
+
sessionId: '00000000-0000-4000-8000-000000000000',
|
|
364
|
+
}).praxisRoot
|
|
365
|
+
: resolve(options.configRoot, 'praxis'), 'locks', 'scheduled-tasks.lock'),
|
|
352
366
|
...(options.enableDynamicWakeups === undefined
|
|
353
367
|
? {}
|
|
354
368
|
: { dynamicWakeupsEnabled: options.enableDynamicWakeups }),
|
|
355
369
|
})
|
|
356
370
|
: null;
|
|
357
371
|
this.workflowManager = options.enableWorkflows
|
|
358
|
-
? new WorkflowManager(options.configRoot, options.cwd, () => this.activeCwd())
|
|
372
|
+
? new WorkflowManager(options.configRoot, options.cwd, () => this.activeCwd(), options.dataPlane)
|
|
359
373
|
: null;
|
|
360
374
|
this.backgroundTasks = new BackgroundTaskRuntime(this.workflowManager);
|
|
361
375
|
this.worktreeManager =
|
|
@@ -363,6 +377,7 @@ export class ClaudeSessionService {
|
|
|
363
377
|
? new SessionWorktreeManager({
|
|
364
378
|
workspace: options.workspace,
|
|
365
379
|
sessionId: '',
|
|
380
|
+
dataPlane: options.dataPlane ?? 'claude',
|
|
366
381
|
...(options.worktreeBaseRef
|
|
367
382
|
? { baseRef: options.worktreeBaseRef }
|
|
368
383
|
: {}),
|
|
@@ -509,6 +524,7 @@ export class ClaudeSessionService {
|
|
|
509
524
|
manager: this.scheduledPrompts,
|
|
510
525
|
sessionId,
|
|
511
526
|
enabledTools: scheduledToolNames,
|
|
527
|
+
dataPlane: this.options.dataPlane ?? 'claude',
|
|
512
528
|
})
|
|
513
529
|
: null;
|
|
514
530
|
const wrappedBase = scheduledTools ?? taskTools ?? baseTools;
|
|
@@ -516,6 +532,7 @@ export class ClaudeSessionService {
|
|
|
516
532
|
this.options.permissions
|
|
517
533
|
? new ClaudeSubagentExecutor({
|
|
518
534
|
configRoot: this.options.configRoot,
|
|
535
|
+
dataPlane: this.options.dataPlane ?? 'claude',
|
|
519
536
|
cwd: this.activeCwd(),
|
|
520
537
|
cwdProvider: () => this.activeCwd(),
|
|
521
538
|
claudeVersion: this.options.claudeVersion,
|
|
@@ -592,6 +609,7 @@ export class ClaudeSessionService {
|
|
|
592
609
|
defaultModel: this.options.provider?.model ?? 'praxis/provider',
|
|
593
610
|
tokenBudget: null,
|
|
594
611
|
enabled: capabilities.has('Workflow'),
|
|
612
|
+
dataPlane: this.options.dataPlane ?? 'claude',
|
|
595
613
|
})
|
|
596
614
|
: agentTools;
|
|
597
615
|
if (this.worktreeManager)
|
|
@@ -601,6 +619,7 @@ export class ClaudeSessionService {
|
|
|
601
619
|
base: workflowTools,
|
|
602
620
|
manager: this.worktreeManager,
|
|
603
621
|
workspace: this.options.workspace,
|
|
622
|
+
dataPlane: this.options.dataPlane ?? 'claude',
|
|
604
623
|
...(this.options.worktreeToolNames
|
|
605
624
|
? { enabledTools: this.options.worktreeToolNames }
|
|
606
625
|
: {}),
|
|
@@ -1051,16 +1070,8 @@ export class ClaudeSessionService {
|
|
|
1051
1070
|
throw new Error(`Not a directory: ${requestedCwd}`);
|
|
1052
1071
|
}
|
|
1053
1072
|
if (sessionId && this.options.sessionPersistence !== false) {
|
|
1054
|
-
const sourcePaths =
|
|
1055
|
-
|
|
1056
|
-
cwd: this.sessionCwds.get(sessionId) ?? previousCwd,
|
|
1057
|
-
sessionId,
|
|
1058
|
-
});
|
|
1059
|
-
const targetPaths = resolveClaudePaths({
|
|
1060
|
-
configDir: this.options.configRoot,
|
|
1061
|
-
cwd,
|
|
1062
|
-
sessionId,
|
|
1063
|
-
});
|
|
1073
|
+
const sourcePaths = this.pathsForCwd(sessionId, this.sessionCwds.get(sessionId) ?? previousCwd);
|
|
1074
|
+
const targetPaths = this.pathsForCwd(sessionId, cwd);
|
|
1064
1075
|
if (sourcePaths.sessionFile !== targetPaths.sessionFile) {
|
|
1065
1076
|
try {
|
|
1066
1077
|
await lstat(targetPaths.sessionFile);
|
|
@@ -1909,6 +1920,7 @@ export class ClaudeSessionService {
|
|
|
1909
1920
|
manager: this.scheduledPrompts,
|
|
1910
1921
|
sessionId,
|
|
1911
1922
|
enabledTools: scheduledToolNames,
|
|
1923
|
+
dataPlane: this.options.dataPlane ?? 'claude',
|
|
1912
1924
|
})
|
|
1913
1925
|
: null;
|
|
1914
1926
|
const baseTools = scheduledTools ?? taskTools ?? this.options.tools;
|
|
@@ -1919,6 +1931,7 @@ export class ClaudeSessionService {
|
|
|
1919
1931
|
turnPermissions
|
|
1920
1932
|
? new ClaudeSubagentExecutor({
|
|
1921
1933
|
configRoot: this.options.configRoot,
|
|
1934
|
+
dataPlane: this.options.dataPlane ?? 'claude',
|
|
1922
1935
|
cwd: this.activeCwd(),
|
|
1923
1936
|
cwdProvider: () => this.activeCwd(),
|
|
1924
1937
|
claudeVersion: this.options.claudeVersion,
|
|
@@ -1996,6 +2009,7 @@ export class ClaudeSessionService {
|
|
|
1996
2009
|
defaultModel: provider.model ?? 'praxis/provider',
|
|
1997
2010
|
tokenBudget: workflowTokenTarget(effectivePrompt),
|
|
1998
2011
|
enabled: capabilities.has('Workflow'),
|
|
2012
|
+
dataPlane: this.options.dataPlane ?? 'claude',
|
|
1999
2013
|
})
|
|
2000
2014
|
: agentTools;
|
|
2001
2015
|
const workspaceTools = this.worktreeManager &&
|
|
@@ -2007,6 +2021,7 @@ export class ClaudeSessionService {
|
|
|
2007
2021
|
manager: this.worktreeManager,
|
|
2008
2022
|
workspace: this.options.workspace,
|
|
2009
2023
|
enabledTools: this.options.worktreeToolNames ?? [],
|
|
2024
|
+
dataPlane: this.options.dataPlane ?? 'claude',
|
|
2010
2025
|
})
|
|
2011
2026
|
: turnTools;
|
|
2012
2027
|
const messageTools = this.options.brief && workspaceTools
|
|
@@ -3277,11 +3292,9 @@ export class ClaudeSessionService {
|
|
|
3277
3292
|
this.worktreeManager.restore(state);
|
|
3278
3293
|
}
|
|
3279
3294
|
paths(sessionId) {
|
|
3280
|
-
const exact =
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
sessionId,
|
|
3284
|
-
});
|
|
3295
|
+
const exact = this.pathsForCwd(sessionId, this.sessionCwds.get(sessionId) ?? this.activeCwd());
|
|
3296
|
+
if (this.options.dataPlane === 'native')
|
|
3297
|
+
return exact;
|
|
3285
3298
|
const discovered = this.discoveredProjectRoots.get(sessionId);
|
|
3286
3299
|
if (discovered === undefined)
|
|
3287
3300
|
return exact;
|
|
@@ -3292,6 +3305,8 @@ export class ClaudeSessionService {
|
|
|
3292
3305
|
};
|
|
3293
3306
|
}
|
|
3294
3307
|
async discoverProjectRoot(sessionId) {
|
|
3308
|
+
if (this.options.dataPlane === 'native')
|
|
3309
|
+
return;
|
|
3295
3310
|
if (this.discoveredProjectRoots.has(sessionId))
|
|
3296
3311
|
return;
|
|
3297
3312
|
const discovered = await discoverClaudeProjectRoot({
|
|
@@ -3303,6 +3318,21 @@ export class ClaudeSessionService {
|
|
|
3303
3318
|
this.discoveredProjectRoots.set(sessionId, discovered);
|
|
3304
3319
|
}
|
|
3305
3320
|
}
|
|
3321
|
+
pathsForCwd(sessionId, cwd) {
|
|
3322
|
+
if (this.options.dataPlane === 'native') {
|
|
3323
|
+
return resolveDataPlanePaths({
|
|
3324
|
+
dataPlane: 'native',
|
|
3325
|
+
root: this.options.configRoot,
|
|
3326
|
+
cwd,
|
|
3327
|
+
sessionId,
|
|
3328
|
+
});
|
|
3329
|
+
}
|
|
3330
|
+
return resolveClaudePaths({
|
|
3331
|
+
configDir: this.options.configRoot,
|
|
3332
|
+
cwd,
|
|
3333
|
+
sessionId,
|
|
3334
|
+
});
|
|
3335
|
+
}
|
|
3306
3336
|
async appendCdCommand(sessionId, cwd) {
|
|
3307
3337
|
const result = await this.store(sessionId).withLease(async (lease) => {
|
|
3308
3338
|
const snapshot = await lease.load();
|
|
@@ -3724,7 +3754,7 @@ export class ClaudeSessionService {
|
|
|
3724
3754
|
!this.options.agentSystemPromptOverridesExplicit)) {
|
|
3725
3755
|
return null;
|
|
3726
3756
|
}
|
|
3727
|
-
const memory = await agentMemoryPrompt(this.options.configRoot, this.activeCwd(), agent);
|
|
3757
|
+
const memory = await agentMemoryPrompt(this.options.configRoot, this.activeCwd(), agent, this.options.dataPlane ?? 'claude');
|
|
3728
3758
|
const system = memory ? `${agent.body}\n\n${memory}` : agent.body;
|
|
3729
3759
|
return system.trim() ? system : null;
|
|
3730
3760
|
}
|
|
@@ -3766,6 +3796,7 @@ export class ClaudeSessionService {
|
|
|
3766
3796
|
store: new SessionMemoryStore({
|
|
3767
3797
|
configRoot: this.options.configRoot,
|
|
3768
3798
|
sessionId,
|
|
3799
|
+
sidecarRoot: this.paths(sessionId).praxisRoot,
|
|
3769
3800
|
}),
|
|
3770
3801
|
extractor: (input) => this.extractSessionMemory(sessionId, input),
|
|
3771
3802
|
});
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import type { DataPlane } from '../persistence/data-plane.js';
|
|
1
2
|
export interface WorktreeSessionState {
|
|
2
3
|
originalCwd: string;
|
|
4
|
+
/** Repository root used to validate the persisted worktree path on restore. */
|
|
5
|
+
repositoryRoot?: string;
|
|
3
6
|
preEnterOriginalCwd: string;
|
|
4
7
|
worktreePath: string;
|
|
5
8
|
worktreeName: string;
|
|
@@ -29,7 +32,10 @@ export declare class SessionWorktreeManager {
|
|
|
29
32
|
workspace: WorkspaceContext;
|
|
30
33
|
sessionId: string;
|
|
31
34
|
baseRef?: 'fresh' | 'head';
|
|
35
|
+
/** Claude compatibility retains its established project worktree path. */
|
|
36
|
+
dataPlane?: DataPlane;
|
|
32
37
|
});
|
|
38
|
+
private worktreeRoot;
|
|
33
39
|
bindSession(sessionId: string): void;
|
|
34
40
|
current(): WorktreeSessionState | null;
|
|
35
41
|
ensureInitial(name?: string): Promise<void>;
|
|
@@ -64,6 +64,9 @@ export class SessionWorktreeManager {
|
|
|
64
64
|
constructor(options) {
|
|
65
65
|
this.options = options;
|
|
66
66
|
}
|
|
67
|
+
worktreeRoot(repositoryRoot) {
|
|
68
|
+
return join(repositoryRoot, this.options.dataPlane === 'native' ? '.praxis' : '.claude', 'worktrees');
|
|
69
|
+
}
|
|
67
70
|
bindSession(sessionId) {
|
|
68
71
|
if (this.active && this.active.sessionId !== sessionId) {
|
|
69
72
|
throw new Error(`Cannot switch to session ${sessionId} while worktree session ${this.active.sessionId} is active`);
|
|
@@ -86,7 +89,7 @@ export class SessionWorktreeManager {
|
|
|
86
89
|
if (state.sessionId !== this.options.sessionId)
|
|
87
90
|
return;
|
|
88
91
|
const worktreePath = resolve(state.worktreePath);
|
|
89
|
-
const allowedRoot =
|
|
92
|
+
const allowedRoot = this.worktreeRoot(state.repositoryRoot ?? state.originalCwd);
|
|
90
93
|
if (!isWithin(allowedRoot, worktreePath))
|
|
91
94
|
return;
|
|
92
95
|
// A crashed session can leave the directory behind after Git has
|
|
@@ -141,13 +144,14 @@ export class SessionWorktreeManager {
|
|
|
141
144
|
if (invalidName(name)) {
|
|
142
145
|
throw new Error('Worktree name must use slash-separated letters, digits, dots, underscores, or dashes and be at most 64 characters');
|
|
143
146
|
}
|
|
144
|
-
const
|
|
147
|
+
const worktreeRoot = this.worktreeRoot(root);
|
|
148
|
+
const worktreePath = join(worktreeRoot, name);
|
|
145
149
|
const branch = `worktree-${nameFromPath(name)}`;
|
|
146
150
|
const baseCommit = this.options.baseRef === 'head'
|
|
147
151
|
? originalHeadCommit
|
|
148
152
|
: ((await gitOptional(root, ['merge-base', 'HEAD', '@{upstream}'])) ??
|
|
149
153
|
originalHeadCommit);
|
|
150
|
-
await mkdir(
|
|
154
|
+
await mkdir(worktreeRoot, { recursive: true });
|
|
151
155
|
try {
|
|
152
156
|
await git(root, [
|
|
153
157
|
'worktree',
|
|
@@ -174,6 +178,7 @@ export class SessionWorktreeManager {
|
|
|
174
178
|
}
|
|
175
179
|
const state = {
|
|
176
180
|
originalCwd,
|
|
181
|
+
repositoryRoot: root,
|
|
177
182
|
preEnterOriginalCwd: originalCwd,
|
|
178
183
|
worktreePath,
|
|
179
184
|
worktreeName: name,
|
|
@@ -283,9 +288,9 @@ export class SessionWorktreeManager {
|
|
|
283
288
|
}
|
|
284
289
|
async enterExisting(root, originalCwd, originalBranch, originalHeadCommit, requestedPath) {
|
|
285
290
|
const worktreePath = await realpath(resolve(originalCwd, requestedPath));
|
|
286
|
-
const allowedRoot =
|
|
291
|
+
const allowedRoot = this.worktreeRoot(root);
|
|
287
292
|
if (!isWithin(allowedRoot, worktreePath)) {
|
|
288
|
-
throw new Error(
|
|
293
|
+
throw new Error(`Worktree path must be inside ${this.options.dataPlane === 'native' ? '.praxis' : '.claude'}/worktrees`);
|
|
289
294
|
}
|
|
290
295
|
const worktrees = parseWorktrees(await git(root, ['worktree', 'list', '--porcelain']));
|
|
291
296
|
const entry = worktrees.find((candidate) => candidate.path === worktreePath);
|
|
@@ -296,6 +301,7 @@ export class SessionWorktreeManager {
|
|
|
296
301
|
throw new Error('Worktree path has an invalid name');
|
|
297
302
|
return {
|
|
298
303
|
originalCwd,
|
|
304
|
+
repositoryRoot: root,
|
|
299
305
|
preEnterOriginalCwd: originalCwd,
|
|
300
306
|
worktreePath,
|
|
301
307
|
worktreeName: name,
|
|
@@ -4,6 +4,7 @@ import { type ModelProvider, type ModelToolCall, type ModelToolDefinition, type
|
|
|
4
4
|
import { type ClaudeAgentRuntimeDefinition, type ClaudeExtensionCatalog } from '../extensions/claude-extensions.js';
|
|
5
5
|
import type { ClaudeHookRunner } from '../hooks/claude-hooks.js';
|
|
6
6
|
import type { ClaudeMcpRuntime } from '../mcp/claude-mcp-tools.js';
|
|
7
|
+
import { type DataPlane } from '../persistence/data-plane.js';
|
|
7
8
|
import { type BackgroundAgentSnapshot } from './background-agent-manager.js';
|
|
8
9
|
export interface WorkflowAgentRunOptions {
|
|
9
10
|
sessionId: string;
|
|
@@ -47,9 +48,10 @@ export declare class StructuredOutputRegistry implements ToolRegistry {
|
|
|
47
48
|
prepare(call: ModelToolCall, context: ToolExecutionContext): Promise<ModelToolCall>;
|
|
48
49
|
execute(call: ModelToolCall, context: ToolExecutionContext): Promise<ToolExecutionResult>;
|
|
49
50
|
}
|
|
50
|
-
export declare function agentMemoryPrompt(configRoot: string, cwd: string, definition: ClaudeAgentRuntimeDefinition | null): Promise<string | null>;
|
|
51
|
+
export declare function agentMemoryPrompt(configRoot: string, cwd: string, definition: ClaudeAgentRuntimeDefinition | null, dataPlane: DataPlane): Promise<string | null>;
|
|
51
52
|
export interface ClaudeSubagentExecutorOptions {
|
|
52
53
|
configRoot: string;
|
|
54
|
+
dataPlane: DataPlane;
|
|
53
55
|
cwd: string;
|
|
54
56
|
cwdProvider?: () => string;
|
|
55
57
|
claudeVersion: string;
|
|
@@ -99,6 +101,7 @@ export declare class ClaudeSubagentExecutor {
|
|
|
99
101
|
prepare(call: ModelToolCall, depth: number): ModelToolCall;
|
|
100
102
|
execute(call: ModelToolCall, sessionId: string, depth: number, promptId: string, context: ToolExecutionContext): Promise<ToolExecutionResult>;
|
|
101
103
|
private sidechainStore;
|
|
104
|
+
private sessionPaths;
|
|
102
105
|
private createAgentWorktree;
|
|
103
106
|
private createBackgroundAgentRun;
|
|
104
107
|
runWorkflowAgent(options: WorkflowAgentRunOptions): Promise<WorkflowAgentRunResult>;
|