praxis-agent 0.3.0 → 0.5.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 +4 -2
- package/dist/application/session-service.d.ts +29 -0
- package/dist/application/session-service.d.ts.map +1 -1
- package/dist/application/session-service.js +431 -31
- package/dist/application/session-service.js.map +1 -1
- package/dist/application/top-level-agent-manager.d.ts +15 -1
- package/dist/application/top-level-agent-manager.d.ts.map +1 -1
- package/dist/application/top-level-agent-manager.js +101 -34
- package/dist/application/top-level-agent-manager.js.map +1 -1
- package/dist/cli/agents-dashboard.d.ts.map +1 -1
- package/dist/cli/agents-dashboard.js +22 -6
- package/dist/cli/agents-dashboard.js.map +1 -1
- package/dist/cli/interactive.d.ts +22 -2
- package/dist/cli/interactive.d.ts.map +1 -1
- package/dist/cli/interactive.js +311 -5
- package/dist/cli/interactive.js.map +1 -1
- package/dist/cli/tui/claude-style.d.ts +15 -0
- package/dist/cli/tui/claude-style.d.ts.map +1 -1
- package/dist/cli/tui/claude-style.js +26 -0
- package/dist/cli/tui/claude-style.js.map +1 -1
- package/dist/cli/tui/clipboard.d.ts +3 -0
- package/dist/cli/tui/clipboard.d.ts.map +1 -1
- package/dist/cli/tui/clipboard.js +12 -0
- package/dist/cli/tui/clipboard.js.map +1 -1
- package/dist/cli/tui/slash-commands.d.ts.map +1 -1
- package/dist/cli/tui/slash-commands.js +10 -0
- package/dist/cli/tui/slash-commands.js.map +1 -1
- package/dist/cli.d.ts +11 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +25 -1
- package/dist/cli.js.map +1 -1
- package/dist/compatibility/claude/schema.d.ts.map +1 -1
- package/dist/compatibility/claude/schema.js +11 -1
- package/dist/compatibility/claude/schema.js.map +1 -1
- package/dist/persistence/claude-job-store.d.ts +11 -1
- package/dist/persistence/claude-job-store.d.ts.map +1 -1
- package/dist/persistence/claude-job-store.js +23 -2
- package/dist/persistence/claude-job-store.js.map +1 -1
- package/dist/persistence/claude-transcript-store.d.ts.map +1 -1
- package/dist/persistence/claude-transcript-store.js +8 -2
- package/dist/persistence/claude-transcript-store.js.map +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -83,8 +83,10 @@ troubleshooting. Run `praxis --help` for the authoritative command surface.
|
|
|
83
83
|
restored active-branch conversation history, streaming and expandable
|
|
84
84
|
thinking, grouped multi-file reads, globally expandable tool results,
|
|
85
85
|
command-specific `/add-dir`, `/copy`, `/branch`, `/rename`, `/export`,
|
|
86
|
-
provider-backed `/compact`, native `/rewind`, runtime `/cd`,
|
|
87
|
-
`/
|
|
86
|
+
provider-backed `/compact`, native `/rewind`, runtime `/cd`, transcript-free
|
|
87
|
+
`/btw` side questions with background-Agent handoff, interactive
|
|
88
|
+
`/background` terminal handoff, `/config`, `/usage`, `/mcp`, and live
|
|
89
|
+
extension-reload controls,
|
|
88
90
|
cursor/history composer, per-session model/effort/permission controls,
|
|
89
91
|
context/status/skill/task dashboards, prompt stash and continuation shortcuts,
|
|
90
92
|
filterable `@` file and agent references, composer undo, `Ctrl+G` external
|
|
@@ -70,6 +70,16 @@ export interface SessionRunResult {
|
|
|
70
70
|
costUsd?: number;
|
|
71
71
|
modelUsage?: Readonly<Record<string, ModelUsage>>;
|
|
72
72
|
}
|
|
73
|
+
export interface SideQuestionResult {
|
|
74
|
+
sessionId: string;
|
|
75
|
+
text: string;
|
|
76
|
+
usage: ModelUsage;
|
|
77
|
+
costUsd?: number;
|
|
78
|
+
}
|
|
79
|
+
export interface SideQuestionForkResult {
|
|
80
|
+
agentId: string;
|
|
81
|
+
name: string;
|
|
82
|
+
}
|
|
73
83
|
export interface SessionSummary {
|
|
74
84
|
sessionId: string;
|
|
75
85
|
name?: string;
|
|
@@ -93,6 +103,10 @@ export interface ForkResult {
|
|
|
93
103
|
sessionId: string;
|
|
94
104
|
parentSessionId: string;
|
|
95
105
|
}
|
|
106
|
+
export interface SessionForkCheckpoint {
|
|
107
|
+
resumeSessionAt: string;
|
|
108
|
+
entryCount: number;
|
|
109
|
+
}
|
|
96
110
|
export interface ManualCompactResult {
|
|
97
111
|
summary: string;
|
|
98
112
|
usage: ModelUsage;
|
|
@@ -122,6 +136,8 @@ export declare class ClaudeSessionService {
|
|
|
122
136
|
private readonly worktreeManager;
|
|
123
137
|
private readonly sessionCwds;
|
|
124
138
|
private readonly hostedSubagents;
|
|
139
|
+
private readonly hostedSubagentsByRegistry;
|
|
140
|
+
private readonly backgroundNotificationWrites;
|
|
125
141
|
private readonly downloadedFileResourceSessions;
|
|
126
142
|
private runtimeCwd;
|
|
127
143
|
constructor(options: ClaudeSessionServiceOptions);
|
|
@@ -133,6 +149,8 @@ export declare class ClaudeSessionService {
|
|
|
133
149
|
runShell(command: string, signal?: AbortSignal, sessionId?: string, name?: string): Promise<SessionRunResult>;
|
|
134
150
|
resume(sessionId: string, prompt: string, signal?: AbortSignal, name?: string, images?: readonly ModelImage[], documents?: readonly ModelDocument[], resumeSessionAt?: string): Promise<SessionRunResult>;
|
|
135
151
|
resumeShell(sessionId: string, command: string, signal?: AbortSignal, name?: string, resumeSessionAt?: string): Promise<SessionRunResult>;
|
|
152
|
+
answerSideQuestion(sessionId: string | undefined, question: string, signal?: AbortSignal, onDelta?: (delta: string) => void, permissionMode?: ClaudePermissionMode): Promise<SideQuestionResult>;
|
|
153
|
+
forkSideQuestion(sessionId: string, question: string, signal?: AbortSignal): Promise<SideQuestionForkResult>;
|
|
136
154
|
promptSuggestion(sessionId: string, signal?: AbortSignal): Promise<string | null>;
|
|
137
155
|
sessionNameSuggestion(sessionId: string, signal?: AbortSignal): Promise<string | null>;
|
|
138
156
|
sessions(): Promise<SessionSummary[]>;
|
|
@@ -142,8 +160,13 @@ export declare class ClaudeSessionService {
|
|
|
142
160
|
rename(sessionId: string, name: string): Promise<void>;
|
|
143
161
|
changeCwd(sessionId: string | undefined, requestedCwd: string): Promise<string>;
|
|
144
162
|
recordCdUsage(sessionId: string): Promise<void>;
|
|
163
|
+
recordBtwUsage(sessionId: string | undefined, permissionMode?: ClaudePermissionMode): Promise<string>;
|
|
164
|
+
recordBackgroundUsage(sessionId: string | undefined, permissionMode?: ClaudePermissionMode): Promise<string>;
|
|
165
|
+
recordBackgroundLaunch(sessionId: string): Promise<SessionForkCheckpoint>;
|
|
145
166
|
compact(sessionId: string, signal?: AbortSignal, selection?: ManualCompactSelection): Promise<ManualCompactResult>;
|
|
146
167
|
fork(parentSessionId: string, sessionId?: string, resumeSessionAt?: string): Promise<ForkResult>;
|
|
168
|
+
ensureFork(parentSessionId: string, sessionId: string, checkpoint?: SessionForkCheckpoint): Promise<ForkResult>;
|
|
169
|
+
private nativeForkEntries;
|
|
147
170
|
rewindFiles(sessionId: string, userMessageId: string): Promise<void>;
|
|
148
171
|
rewindPoints(sessionId: string): Promise<RewindPoint[]>;
|
|
149
172
|
setPermissionMode(sessionId: string, permissionMode: ClaudePermissionMode): Promise<void>;
|
|
@@ -162,7 +185,13 @@ export declare class ClaudeSessionService {
|
|
|
162
185
|
private restoreWorktree;
|
|
163
186
|
private paths;
|
|
164
187
|
private appendCdCommand;
|
|
188
|
+
private appendSystemLocalCommand;
|
|
189
|
+
private ensureLocalSession;
|
|
190
|
+
private appendInputHistory;
|
|
191
|
+
private appendBackgroundNotification;
|
|
192
|
+
private enqueueBackgroundNotifications;
|
|
165
193
|
private cdCommandEntries;
|
|
194
|
+
private localCommandEntries;
|
|
166
195
|
private store;
|
|
167
196
|
private turnStore;
|
|
168
197
|
private assertSessionPersistence;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-service.d.ts","sourceRoot":"","sources":["../../src/application/session-service.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"session-service.d.ts","sourceRoot":"","sources":["../../src/application/session-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import { copyFile, link, lstat, mkdir, readdir, realpath, stat, unlink, } from 'node:fs/promises';
|
|
2
|
+
import { appendFile, copyFile, link, lstat, mkdir, readdir, realpath, stat, unlink, } from 'node:fs/promises';
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
4
|
import { basename, extname, isAbsolute, join, relative } from 'node:path';
|
|
5
5
|
import { createClaudeCompactEntries, formatClaudeCompactSummary, getCumulativeDroppedTokens, } from '../compatibility/claude/compaction.js';
|
|
@@ -91,6 +91,8 @@ export class ClaudeSessionService {
|
|
|
91
91
|
worktreeManager;
|
|
92
92
|
sessionCwds = new Map();
|
|
93
93
|
hostedSubagents = new Set();
|
|
94
|
+
hostedSubagentsByRegistry = new WeakMap();
|
|
95
|
+
backgroundNotificationWrites = new Map();
|
|
94
96
|
downloadedFileResourceSessions = new Set();
|
|
95
97
|
runtimeCwd;
|
|
96
98
|
constructor(options) {
|
|
@@ -127,6 +129,8 @@ export class ClaudeSessionService {
|
|
|
127
129
|
async close() {
|
|
128
130
|
this.scheduledPrompts?.close();
|
|
129
131
|
await Promise.all([...this.hostedSubagents].map((executor) => executor.close()));
|
|
132
|
+
await Promise.resolve();
|
|
133
|
+
await Promise.all([...this.backgroundNotificationWrites.values()]);
|
|
130
134
|
this.hostedSubagents.clear();
|
|
131
135
|
await this.workflowManager?.close();
|
|
132
136
|
}
|
|
@@ -293,7 +297,7 @@ export class ClaudeSessionService {
|
|
|
293
297
|
'Monitor',
|
|
294
298
|
'PushNotification',
|
|
295
299
|
];
|
|
296
|
-
|
|
300
|
+
const hostedRegistry = {
|
|
297
301
|
definitions: () => {
|
|
298
302
|
const definitions = interactiveRegistry.definitions();
|
|
299
303
|
return [...definitions].sort((left, right) => {
|
|
@@ -306,6 +310,10 @@ export class ClaudeSessionService {
|
|
|
306
310
|
prepare: (call, context) => interactiveRegistry.prepare(call, context),
|
|
307
311
|
execute: (call, context) => interactiveRegistry.execute(call, context),
|
|
308
312
|
};
|
|
313
|
+
if (subagentExecutor) {
|
|
314
|
+
this.hostedSubagentsByRegistry.set(hostedRegistry, subagentExecutor);
|
|
315
|
+
}
|
|
316
|
+
return hostedRegistry;
|
|
309
317
|
}
|
|
310
318
|
async run(prompt, signal, sessionId = randomUUID(), name, images, documents) {
|
|
311
319
|
this.worktreeManager?.bindSession(sessionId);
|
|
@@ -323,6 +331,119 @@ export class ClaudeSessionService {
|
|
|
323
331
|
this.worktreeManager?.bindSession(sessionId);
|
|
324
332
|
return this.executeTurn(sessionId, `! ${command}`, true, signal, name, [], [], resumeSessionAt, command);
|
|
325
333
|
}
|
|
334
|
+
async answerSideQuestion(sessionId, question, signal, onDelta, permissionMode = 'default') {
|
|
335
|
+
const prompt = question.trim();
|
|
336
|
+
if (!prompt)
|
|
337
|
+
throw new Error('Side question must not be empty');
|
|
338
|
+
const activeSessionId = await this.ensureLocalSession(sessionId, permissionMode);
|
|
339
|
+
await this.appendInputHistory(`/btw ${prompt}`, activeSessionId);
|
|
340
|
+
const provider = this.provider();
|
|
341
|
+
let entries = [];
|
|
342
|
+
if (sessionId) {
|
|
343
|
+
const loaded = this.options.sessionPersistence === false
|
|
344
|
+
? await this.turnStore(activeSessionId).withLease((lease) => lease.load())
|
|
345
|
+
: {
|
|
346
|
+
status: 'completed',
|
|
347
|
+
value: await this.store(activeSessionId).loadReadOnly(),
|
|
348
|
+
};
|
|
349
|
+
if (loaded.status === 'conflict') {
|
|
350
|
+
throw new Error(`Claude side question conflict: ${loaded.reason}`);
|
|
351
|
+
}
|
|
352
|
+
if (loaded.value.entries.length === 0) {
|
|
353
|
+
throw new Error(`Claude session not found: ${activeSessionId}`);
|
|
354
|
+
}
|
|
355
|
+
entries = loaded.value.entries;
|
|
356
|
+
this.restoreWorktree(entries);
|
|
357
|
+
}
|
|
358
|
+
const assembledContext = await this.options.contextAssembler?.assemble({
|
|
359
|
+
cwd: this.activeCwd(),
|
|
360
|
+
});
|
|
361
|
+
const messages = [
|
|
362
|
+
...(assembledContext?.systemMessages ?? []),
|
|
363
|
+
...injectFirstUserMessageContext([
|
|
364
|
+
...projectClaudeModelMessages(entries),
|
|
365
|
+
{ role: 'user', content: prompt },
|
|
366
|
+
], assembledContext?.firstUserMessageContext),
|
|
367
|
+
];
|
|
368
|
+
const budget = this.contextBudget(provider);
|
|
369
|
+
if (budget)
|
|
370
|
+
budget.assertFits(budget.evaluate(messages));
|
|
371
|
+
let text = '';
|
|
372
|
+
let usage = { inputTokens: 0, outputTokens: 0 };
|
|
373
|
+
for await (const event of provider.complete({
|
|
374
|
+
messages,
|
|
375
|
+
...(this.options.effort ? { effort: this.options.effort } : {}),
|
|
376
|
+
...(signal ? { signal } : {}),
|
|
377
|
+
})) {
|
|
378
|
+
if (event.type === 'text-delta') {
|
|
379
|
+
text += event.delta;
|
|
380
|
+
onDelta?.(event.delta);
|
|
381
|
+
}
|
|
382
|
+
else if (event.type === 'usage') {
|
|
383
|
+
usage = event.usage;
|
|
384
|
+
}
|
|
385
|
+
else if (event.type === 'tool-call') {
|
|
386
|
+
throw new Error('Side questions cannot call tools; press f to fork');
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
const pricing = this.options.pricing?.resolve(provider.model ?? 'praxis/provider');
|
|
390
|
+
return {
|
|
391
|
+
sessionId: activeSessionId,
|
|
392
|
+
text,
|
|
393
|
+
usage,
|
|
394
|
+
...(pricing ? { costUsd: usageCostUsd(usage, pricing) } : {}),
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
async forkSideQuestion(sessionId, question, signal) {
|
|
398
|
+
const prompt = question.trim();
|
|
399
|
+
if (!prompt)
|
|
400
|
+
throw new Error('Side question must not be empty');
|
|
401
|
+
const name = prompt
|
|
402
|
+
.toLowerCase()
|
|
403
|
+
.replace(/[^a-z0-9]+/gu, '-')
|
|
404
|
+
.replace(/^-+|-+$/gu, '')
|
|
405
|
+
.split('-')
|
|
406
|
+
.filter(Boolean)
|
|
407
|
+
.slice(0, 3)
|
|
408
|
+
.join('-')
|
|
409
|
+
.slice(0, 64) || 'side-question';
|
|
410
|
+
const registry = this.createHostedToolRegistry(sessionId);
|
|
411
|
+
const call = {
|
|
412
|
+
id: randomUUID(),
|
|
413
|
+
name: 'Agent',
|
|
414
|
+
input: {
|
|
415
|
+
description: prompt,
|
|
416
|
+
prompt,
|
|
417
|
+
subagent_type: 'general-purpose',
|
|
418
|
+
run_in_background: true,
|
|
419
|
+
name,
|
|
420
|
+
},
|
|
421
|
+
};
|
|
422
|
+
const context = {
|
|
423
|
+
cwd: this.activeCwd(),
|
|
424
|
+
...(signal ? { signal } : {}),
|
|
425
|
+
};
|
|
426
|
+
const prepared = await registry.prepare(call, context);
|
|
427
|
+
const result = await registry.execute(prepared, context);
|
|
428
|
+
const agentId = result.nativeToolUseResult?.agentId;
|
|
429
|
+
if (result.isError || typeof agentId !== 'string') {
|
|
430
|
+
throw new Error(result.content || 'Could not fork side question');
|
|
431
|
+
}
|
|
432
|
+
await this.appendSystemLocalCommand(sessionId, 'btw', prompt, `⑂ forked ${name} (${agentId.slice(-4)})`);
|
|
433
|
+
const executor = this.hostedSubagentsByRegistry.get(registry);
|
|
434
|
+
if (executor) {
|
|
435
|
+
void executor
|
|
436
|
+
.notifications(true)
|
|
437
|
+
.then(({ messages }) => this.enqueueBackgroundNotifications(sessionId, messages))
|
|
438
|
+
.catch((error) => this.options.eventSink?.({
|
|
439
|
+
type: 'warning',
|
|
440
|
+
message: error instanceof Error
|
|
441
|
+
? error.message
|
|
442
|
+
: `Could not persist background notification: ${String(error)}`,
|
|
443
|
+
}));
|
|
444
|
+
}
|
|
445
|
+
return { agentId, name };
|
|
446
|
+
}
|
|
326
447
|
async promptSuggestion(sessionId, signal) {
|
|
327
448
|
const provider = this.provider();
|
|
328
449
|
const loaded = this.options.sessionPersistence === false
|
|
@@ -689,6 +810,113 @@ export class ClaudeSessionService {
|
|
|
689
810
|
throw new Error(`Claude cd usage conflict: ${result.reason}`);
|
|
690
811
|
}
|
|
691
812
|
}
|
|
813
|
+
async recordBtwUsage(sessionId, permissionMode = 'default') {
|
|
814
|
+
const activeSessionId = await this.ensureLocalSession(sessionId, permissionMode);
|
|
815
|
+
await this.appendInputHistory('/btw', activeSessionId);
|
|
816
|
+
while (true) {
|
|
817
|
+
const result = await this.turnStore(activeSessionId).withLease(async (lease) => {
|
|
818
|
+
const snapshot = await lease.load();
|
|
819
|
+
if (snapshot.entries.length === 0) {
|
|
820
|
+
throw new Error(`Claude session not found: ${activeSessionId}`);
|
|
821
|
+
}
|
|
822
|
+
const entries = this.localCommandEntries(activeSessionId, this.activeCwd(), this.logicalTailUuid(snapshot.tail), 'btw', '', 'Usage: /btw <your question>');
|
|
823
|
+
const leafUuid = entries.at(-1)?.uuid;
|
|
824
|
+
if (typeof leafUuid !== 'string') {
|
|
825
|
+
throw new Error('Claude btw local command pair is incomplete');
|
|
826
|
+
}
|
|
827
|
+
const appended = await lease.appendMany(snapshot.tail, [
|
|
828
|
+
...entries,
|
|
829
|
+
{ type: 'last-prompt', leafUuid, sessionId: activeSessionId },
|
|
830
|
+
]);
|
|
831
|
+
if (appended.status === 'conflict') {
|
|
832
|
+
throw new Error(`Claude local command append conflict: ${appended.reason}`);
|
|
833
|
+
}
|
|
834
|
+
});
|
|
835
|
+
if (result.status === 'completed')
|
|
836
|
+
return activeSessionId;
|
|
837
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
async recordBackgroundUsage(sessionId, permissionMode = 'default') {
|
|
841
|
+
this.assertWritable();
|
|
842
|
+
const activeSessionId = await this.ensureLocalSession(sessionId, permissionMode);
|
|
843
|
+
await this.appendInputHistory('/background', activeSessionId);
|
|
844
|
+
while (true) {
|
|
845
|
+
const result = await this.turnStore(activeSessionId).withLease(async (lease) => {
|
|
846
|
+
const snapshot = await lease.load();
|
|
847
|
+
if (snapshot.entries.length === 0) {
|
|
848
|
+
throw new Error(`Claude session not found: ${activeSessionId}`);
|
|
849
|
+
}
|
|
850
|
+
const timestamp = new Date().toISOString();
|
|
851
|
+
const promptId = randomUUID();
|
|
852
|
+
const firstUuid = randomUUID();
|
|
853
|
+
const commandUuid = randomUUID();
|
|
854
|
+
const common = {
|
|
855
|
+
isSidechain: false,
|
|
856
|
+
promptId,
|
|
857
|
+
timestamp,
|
|
858
|
+
userType: 'external',
|
|
859
|
+
entrypoint: 'cli',
|
|
860
|
+
cwd: this.activeCwd(),
|
|
861
|
+
sessionId: activeSessionId,
|
|
862
|
+
version: this.options.claudeVersion,
|
|
863
|
+
gitBranch: null,
|
|
864
|
+
};
|
|
865
|
+
const appended = await lease.appendMany(snapshot.tail, [
|
|
866
|
+
{
|
|
867
|
+
...common,
|
|
868
|
+
parentUuid: this.logicalTailUuid(snapshot.tail),
|
|
869
|
+
type: 'user',
|
|
870
|
+
message: {
|
|
871
|
+
role: 'user',
|
|
872
|
+
content: '<local-command-caveat>Caveat: The messages below were generated by the user while running local commands. DO NOT respond to these messages or otherwise consider them in your response unless the user explicitly asks you to.</local-command-caveat>',
|
|
873
|
+
},
|
|
874
|
+
isMeta: true,
|
|
875
|
+
uuid: firstUuid,
|
|
876
|
+
},
|
|
877
|
+
{
|
|
878
|
+
...common,
|
|
879
|
+
parentUuid: firstUuid,
|
|
880
|
+
type: 'user',
|
|
881
|
+
message: {
|
|
882
|
+
role: 'user',
|
|
883
|
+
content: '<command-name>/background</command-name>\n <command-message>background</command-message>\n <command-args></command-args>',
|
|
884
|
+
},
|
|
885
|
+
uuid: commandUuid,
|
|
886
|
+
},
|
|
887
|
+
{
|
|
888
|
+
...common,
|
|
889
|
+
parentUuid: commandUuid,
|
|
890
|
+
type: 'user',
|
|
891
|
+
message: {
|
|
892
|
+
role: 'user',
|
|
893
|
+
content: '<local-command-stdout>Nothing to background yet — send a message first.</local-command-stdout>',
|
|
894
|
+
},
|
|
895
|
+
uuid: randomUUID(),
|
|
896
|
+
},
|
|
897
|
+
]);
|
|
898
|
+
if (appended.status === 'conflict') {
|
|
899
|
+
throw new Error(`Claude background command append conflict: ${appended.reason}`);
|
|
900
|
+
}
|
|
901
|
+
});
|
|
902
|
+
if (result.status === 'completed')
|
|
903
|
+
return activeSessionId;
|
|
904
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
async recordBackgroundLaunch(sessionId) {
|
|
908
|
+
this.assertWritable();
|
|
909
|
+
const result = await this.turnStore(sessionId).withLease((lease) => lease.load());
|
|
910
|
+
if (result.status === 'conflict') {
|
|
911
|
+
throw new Error(`Claude background checkpoint conflict: ${result.reason}`);
|
|
912
|
+
}
|
|
913
|
+
const resumeSessionAt = this.logicalTailUuid(result.value.tail);
|
|
914
|
+
if (!resumeSessionAt) {
|
|
915
|
+
throw new Error(`Claude session not found: ${sessionId}`);
|
|
916
|
+
}
|
|
917
|
+
await this.appendInputHistory('/background', sessionId);
|
|
918
|
+
return { resumeSessionAt, entryCount: result.value.entries.length };
|
|
919
|
+
}
|
|
692
920
|
async compact(sessionId, signal, selection) {
|
|
693
921
|
this.assertWritable();
|
|
694
922
|
const provider = this.provider();
|
|
@@ -811,6 +1039,33 @@ export class ClaudeSessionService {
|
|
|
811
1039
|
async fork(parentSessionId, sessionId = randomUUID(), resumeSessionAt) {
|
|
812
1040
|
this.assertSessionPersistence();
|
|
813
1041
|
this.assertWritable();
|
|
1042
|
+
const entries = await this.nativeForkEntries(parentSessionId, sessionId, resumeSessionAt);
|
|
1043
|
+
const result = await this.store(sessionId).create(entries);
|
|
1044
|
+
if (result.status === 'conflict') {
|
|
1045
|
+
throw new Error('Generated Claude fork session already exists');
|
|
1046
|
+
}
|
|
1047
|
+
return { sessionId, parentSessionId };
|
|
1048
|
+
}
|
|
1049
|
+
async ensureFork(parentSessionId, sessionId, checkpoint) {
|
|
1050
|
+
this.assertSessionPersistence();
|
|
1051
|
+
this.assertWritable();
|
|
1052
|
+
const expected = await this.nativeForkEntries(parentSessionId, sessionId, checkpoint?.resumeSessionAt, checkpoint?.entryCount);
|
|
1053
|
+
const target = this.store(sessionId);
|
|
1054
|
+
const created = await target.create(expected);
|
|
1055
|
+
if (created.status === 'created')
|
|
1056
|
+
return { sessionId, parentSessionId };
|
|
1057
|
+
const existing = await target.withLease((lease) => lease.load());
|
|
1058
|
+
if (existing.status === 'conflict') {
|
|
1059
|
+
throw new Error(`Claude transcript fork conflict: ${existing.reason}`);
|
|
1060
|
+
}
|
|
1061
|
+
if (existing.value.entries.length < expected.length ||
|
|
1062
|
+
expected.some((entry, index) => JSON.stringify(existing.value.entries[index]) !==
|
|
1063
|
+
JSON.stringify(entry))) {
|
|
1064
|
+
throw new Error('Claude handoff target is not the expected native fork');
|
|
1065
|
+
}
|
|
1066
|
+
return { sessionId, parentSessionId };
|
|
1067
|
+
}
|
|
1068
|
+
async nativeForkEntries(parentSessionId, sessionId, resumeSessionAt, sourceEntryCount) {
|
|
814
1069
|
const sourceResult = await this.store(parentSessionId).withLease((lease) => lease.load());
|
|
815
1070
|
if (sourceResult.status === 'conflict') {
|
|
816
1071
|
throw new Error(`Claude transcript fork conflict: ${sourceResult.reason}`);
|
|
@@ -819,17 +1074,18 @@ export class ClaudeSessionService {
|
|
|
819
1074
|
if (source.entries.length === 0) {
|
|
820
1075
|
throw new Error(`Claude session not found: ${parentSessionId}`);
|
|
821
1076
|
}
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
source
|
|
1077
|
+
if (sourceEntryCount !== undefined &&
|
|
1078
|
+
(sourceEntryCount <= 0 || sourceEntryCount > source.entries.length)) {
|
|
1079
|
+
throw new Error('Claude handoff source checkpoint is no longer available');
|
|
1080
|
+
}
|
|
1081
|
+
return createClaudeNativeFork({
|
|
1082
|
+
source: sourceEntryCount === undefined
|
|
1083
|
+
? source.entries
|
|
1084
|
+
: source.entries.slice(0, sourceEntryCount),
|
|
825
1085
|
sourceSessionId: parentSessionId,
|
|
826
1086
|
sessionId,
|
|
827
1087
|
...(resumeSessionAt === undefined ? {} : { resumeSessionAt }),
|
|
828
|
-
})
|
|
829
|
-
if (result.status === 'conflict') {
|
|
830
|
-
throw new Error('Generated Claude fork session already exists');
|
|
831
|
-
}
|
|
832
|
-
return { sessionId, parentSessionId };
|
|
1088
|
+
});
|
|
833
1089
|
}
|
|
834
1090
|
async rewindFiles(sessionId, userMessageId) {
|
|
835
1091
|
const result = await this.store(sessionId).withLease(async (lease) => {
|
|
@@ -2183,9 +2439,128 @@ export class ClaudeSessionService {
|
|
|
2183
2439
|
throw new Error(`Claude local command conflict: ${result.reason}`);
|
|
2184
2440
|
}
|
|
2185
2441
|
}
|
|
2442
|
+
async appendSystemLocalCommand(sessionId, command, args, output) {
|
|
2443
|
+
while (true) {
|
|
2444
|
+
const result = await this.turnStore(sessionId).withLease(async (lease) => {
|
|
2445
|
+
const snapshot = await lease.load();
|
|
2446
|
+
if (snapshot.entries.length === 0) {
|
|
2447
|
+
throw new Error(`Claude session not found: ${sessionId}`);
|
|
2448
|
+
}
|
|
2449
|
+
const appendResult = await lease.appendMany(snapshot.tail, this.localCommandEntries(sessionId, this.activeCwd(), this.logicalTailUuid(snapshot.tail), command, args, output));
|
|
2450
|
+
if (appendResult.status === 'conflict') {
|
|
2451
|
+
throw new Error(`Claude local command append conflict: ${appendResult.reason}`);
|
|
2452
|
+
}
|
|
2453
|
+
});
|
|
2454
|
+
if (result.status === 'completed')
|
|
2455
|
+
return;
|
|
2456
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
2457
|
+
}
|
|
2458
|
+
}
|
|
2459
|
+
async ensureLocalSession(sessionId, permissionMode) {
|
|
2460
|
+
if (sessionId)
|
|
2461
|
+
return sessionId;
|
|
2462
|
+
this.assertWritable();
|
|
2463
|
+
const createdSessionId = randomUUID();
|
|
2464
|
+
const store = this.turnStore(createdSessionId);
|
|
2465
|
+
const created = await store.create([
|
|
2466
|
+
{ type: 'mode', mode: 'normal', sessionId: createdSessionId },
|
|
2467
|
+
{
|
|
2468
|
+
type: 'permission-mode',
|
|
2469
|
+
permissionMode,
|
|
2470
|
+
sessionId: createdSessionId,
|
|
2471
|
+
},
|
|
2472
|
+
]);
|
|
2473
|
+
if (created.status === 'conflict') {
|
|
2474
|
+
throw new Error(`Session ID ${createdSessionId} is already in use`);
|
|
2475
|
+
}
|
|
2476
|
+
this.sessionCwds.set(createdSessionId, this.activeCwd());
|
|
2477
|
+
this.options.interactiveTools?.restore(createdSessionId, [
|
|
2478
|
+
{ type: 'mode', mode: 'normal', sessionId: createdSessionId },
|
|
2479
|
+
{
|
|
2480
|
+
type: 'permission-mode',
|
|
2481
|
+
permissionMode,
|
|
2482
|
+
sessionId: createdSessionId,
|
|
2483
|
+
},
|
|
2484
|
+
]);
|
|
2485
|
+
return createdSessionId;
|
|
2486
|
+
}
|
|
2487
|
+
async appendInputHistory(display, sessionId) {
|
|
2488
|
+
await mkdir(this.options.configRoot, { recursive: true });
|
|
2489
|
+
await appendFile(join(this.options.configRoot, 'history.jsonl'), `${JSON.stringify({
|
|
2490
|
+
display,
|
|
2491
|
+
pastedContents: {},
|
|
2492
|
+
timestamp: Date.now(),
|
|
2493
|
+
project: this.activeCwd(),
|
|
2494
|
+
sessionId,
|
|
2495
|
+
})}\n`, 'utf8');
|
|
2496
|
+
}
|
|
2497
|
+
async appendBackgroundNotification(sessionId, content) {
|
|
2498
|
+
while (true) {
|
|
2499
|
+
const result = await this.turnStore(sessionId).withLease(async (lease) => {
|
|
2500
|
+
const snapshot = await lease.load();
|
|
2501
|
+
if (snapshot.entries.length === 0) {
|
|
2502
|
+
throw new Error(`Claude session not found: ${sessionId}`);
|
|
2503
|
+
}
|
|
2504
|
+
const timestamp = new Date().toISOString();
|
|
2505
|
+
const entries = [
|
|
2506
|
+
{
|
|
2507
|
+
type: 'queue-operation',
|
|
2508
|
+
operation: 'enqueue',
|
|
2509
|
+
timestamp,
|
|
2510
|
+
sessionId,
|
|
2511
|
+
content,
|
|
2512
|
+
},
|
|
2513
|
+
{
|
|
2514
|
+
type: 'queue-operation',
|
|
2515
|
+
operation: 'dequeue',
|
|
2516
|
+
timestamp,
|
|
2517
|
+
sessionId,
|
|
2518
|
+
},
|
|
2519
|
+
{
|
|
2520
|
+
parentUuid: this.logicalTailUuid(snapshot.tail),
|
|
2521
|
+
isSidechain: false,
|
|
2522
|
+
promptId: randomUUID(),
|
|
2523
|
+
type: 'user',
|
|
2524
|
+
message: { role: 'user', content },
|
|
2525
|
+
uuid: randomUUID(),
|
|
2526
|
+
timestamp,
|
|
2527
|
+
permissionMode: 'default',
|
|
2528
|
+
origin: { kind: 'task-notification' },
|
|
2529
|
+
promptSource: 'system',
|
|
2530
|
+
userType: 'external',
|
|
2531
|
+
entrypoint: 'cli',
|
|
2532
|
+
cwd: this.activeCwd(),
|
|
2533
|
+
sessionId,
|
|
2534
|
+
version: this.options.claudeVersion,
|
|
2535
|
+
gitBranch: null,
|
|
2536
|
+
},
|
|
2537
|
+
];
|
|
2538
|
+
const appended = await lease.appendMany(snapshot.tail, entries);
|
|
2539
|
+
if (appended.status === 'conflict') {
|
|
2540
|
+
throw new Error(`Claude background notification append conflict: ${appended.reason}`);
|
|
2541
|
+
}
|
|
2542
|
+
});
|
|
2543
|
+
if (result.status === 'completed')
|
|
2544
|
+
return;
|
|
2545
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
2546
|
+
}
|
|
2547
|
+
}
|
|
2548
|
+
enqueueBackgroundNotifications(sessionId, messages) {
|
|
2549
|
+
const previous = this.backgroundNotificationWrites.get(sessionId);
|
|
2550
|
+
const queued = (previous ?? Promise.resolve()).then(async () => {
|
|
2551
|
+
for (const message of messages) {
|
|
2552
|
+
await this.appendBackgroundNotification(sessionId, message);
|
|
2553
|
+
}
|
|
2554
|
+
});
|
|
2555
|
+
this.backgroundNotificationWrites.set(sessionId, queued);
|
|
2556
|
+
const cleanup = () => {
|
|
2557
|
+
if (this.backgroundNotificationWrites.get(sessionId) === queued)
|
|
2558
|
+
this.backgroundNotificationWrites.delete(sessionId);
|
|
2559
|
+
};
|
|
2560
|
+
void queued.then(cleanup, cleanup);
|
|
2561
|
+
return queued;
|
|
2562
|
+
}
|
|
2186
2563
|
cdCommandEntries(sessionId, cwd, parentUuid) {
|
|
2187
|
-
const firstUuid = randomUUID();
|
|
2188
|
-
const secondUuid = randomUUID();
|
|
2189
2564
|
const timestamp = new Date().toISOString();
|
|
2190
2565
|
const common = {
|
|
2191
2566
|
isSidechain: false,
|
|
@@ -2197,38 +2572,63 @@ export class ClaudeSessionService {
|
|
|
2197
2572
|
version: this.options.claudeVersion,
|
|
2198
2573
|
gitBranch: null,
|
|
2199
2574
|
};
|
|
2575
|
+
const localCommands = this.localCommandEntries(sessionId, cwd, parentUuid, 'cd', cwd, `Moved to ${cwd}`, timestamp);
|
|
2576
|
+
const secondUuid = localCommands[1]?.uuid;
|
|
2577
|
+
if (typeof secondUuid !== 'string') {
|
|
2578
|
+
throw new Error('Claude cd local command pair is incomplete');
|
|
2579
|
+
}
|
|
2200
2580
|
return [
|
|
2581
|
+
...localCommands,
|
|
2201
2582
|
{
|
|
2202
2583
|
...common,
|
|
2584
|
+
parentUuid: secondUuid,
|
|
2585
|
+
promptId: randomUUID(),
|
|
2586
|
+
type: 'user',
|
|
2587
|
+
message: {
|
|
2588
|
+
role: 'user',
|
|
2589
|
+
content: `<system-reminder>\nThe session's working directory has changed to ${cwd} (via /cd). The environment block at the start of this conversation still names the previous directory — that information is stale. All tool calls and relative paths now resolve from ${cwd}.\n</system-reminder>`,
|
|
2590
|
+
},
|
|
2591
|
+
isMeta: true,
|
|
2592
|
+
uuid: randomUUID(),
|
|
2593
|
+
},
|
|
2594
|
+
];
|
|
2595
|
+
}
|
|
2596
|
+
localCommandEntries(sessionId, cwd, parentUuid, command, args, output, timestamp = new Date().toISOString()) {
|
|
2597
|
+
const firstUuid = randomUUID();
|
|
2598
|
+
return [
|
|
2599
|
+
{
|
|
2600
|
+
parentUuid,
|
|
2601
|
+
isSidechain: false,
|
|
2203
2602
|
type: 'system',
|
|
2204
2603
|
subtype: 'local_command',
|
|
2205
|
-
|
|
2604
|
+
content: `<command-name>/${command}</command-name>\n <command-message>${command}</command-message>\n <command-args>${args}</command-args>`,
|
|
2206
2605
|
level: 'info',
|
|
2207
|
-
|
|
2606
|
+
timestamp,
|
|
2208
2607
|
uuid: firstUuid,
|
|
2209
|
-
|
|
2608
|
+
isMeta: false,
|
|
2609
|
+
userType: 'external',
|
|
2610
|
+
entrypoint: 'cli',
|
|
2611
|
+
cwd,
|
|
2612
|
+
sessionId,
|
|
2613
|
+
version: this.options.claudeVersion,
|
|
2614
|
+
gitBranch: null,
|
|
2210
2615
|
},
|
|
2211
2616
|
{
|
|
2212
|
-
|
|
2617
|
+
parentUuid: firstUuid,
|
|
2618
|
+
isSidechain: false,
|
|
2213
2619
|
type: 'system',
|
|
2214
2620
|
subtype: 'local_command',
|
|
2215
|
-
|
|
2621
|
+
content: `<local-command-stdout>${output}</local-command-stdout>`,
|
|
2216
2622
|
level: 'info',
|
|
2217
|
-
|
|
2218
|
-
uuid: secondUuid,
|
|
2219
|
-
content: `<local-command-stdout>Moved to ${cwd}</local-command-stdout>`,
|
|
2220
|
-
},
|
|
2221
|
-
{
|
|
2222
|
-
...common,
|
|
2223
|
-
parentUuid: secondUuid,
|
|
2224
|
-
promptId: randomUUID(),
|
|
2225
|
-
type: 'user',
|
|
2226
|
-
message: {
|
|
2227
|
-
role: 'user',
|
|
2228
|
-
content: `<system-reminder>\nThe session's working directory has changed to ${cwd} (via /cd). The environment block at the start of this conversation still names the previous directory — that information is stale. All tool calls and relative paths now resolve from ${cwd}.\n</system-reminder>`,
|
|
2229
|
-
},
|
|
2230
|
-
isMeta: true,
|
|
2623
|
+
timestamp,
|
|
2231
2624
|
uuid: randomUUID(),
|
|
2625
|
+
isMeta: false,
|
|
2626
|
+
userType: 'external',
|
|
2627
|
+
entrypoint: 'cli',
|
|
2628
|
+
cwd,
|
|
2629
|
+
sessionId,
|
|
2630
|
+
version: this.options.claudeVersion,
|
|
2631
|
+
gitBranch: null,
|
|
2232
2632
|
},
|
|
2233
2633
|
];
|
|
2234
2634
|
}
|