praxis-agent 0.45.3 → 0.46.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 -2
- package/dist/application/session-service.d.ts +4 -0
- package/dist/application/session-service.js +83 -0
- package/dist/cli/interactive.d.ts +3 -0
- package/dist/cli/interactive.js +370 -5
- package/dist/cli/tui/ansi-frame-renderer.d.ts +1 -0
- package/dist/cli/tui/ansi-frame-renderer.js +78 -3
- package/dist/cli/tui/ansi-surface.d.ts +1 -0
- package/dist/cli/tui/ansi-surface.js +6 -0
- package/dist/cli/tui/ansi-theme.js +4 -1
- package/dist/cli/tui/quiet-frame-adapter.js +1 -0
- package/dist/cli/tui/quiet-frame.d.ts +6 -1
- package/dist/cli/tui/quiet-frame.js +4 -0
- package/dist/cli/tui/quiet-screen-projector.d.ts +1 -0
- package/dist/cli/tui/quiet-screen-projector.js +3 -0
- package/dist/cli/tui/terminal-selection.d.ts +51 -0
- package/dist/cli/tui/terminal-selection.js +263 -0
- package/dist/cli/tui/tui-interaction-router.js +1 -1
- package/dist/cli/tui/tui-row-ir.d.ts +1 -1
- package/dist/core/active-turn-input.d.ts +63 -0
- package/dist/core/active-turn-input.js +53 -0
- package/dist/core/runtime.d.ts +12 -0
- package/dist/core/runtime.js +66 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -106,7 +106,10 @@ troubleshooting. Run `praxis --help` for the authoritative command surface.
|
|
|
106
106
|
terminal-native background, and a minimal composer/status row. Interactive
|
|
107
107
|
surfaces share the same presentation across terminals, with English
|
|
108
108
|
permission/configuration choices and a taught `❯` / Up/Down / Enter / Esc
|
|
109
|
-
interaction grammar,
|
|
109
|
+
interaction grammar. While a regular turn is active, the composer remains
|
|
110
|
+
editable: Enter steers at the next safe continuation boundary, Tab or
|
|
111
|
+
Alt+Enter queues a sequential follow-up turn, and pending input stays visible
|
|
112
|
+
and can be withdrawn with Up. The TUI also includes a
|
|
110
113
|
shared-command
|
|
111
114
|
slash palette, tabbed help and shortcut surfaces, searchable resume picker,
|
|
112
115
|
restored active-branch conversation history, streaming and expandable
|
|
@@ -126,7 +129,8 @@ troubleshooting. Run `praxis --help` for the authoritative command surface.
|
|
|
126
129
|
support, and no-color output,
|
|
127
130
|
cursor/history composer, provider-free `/cost` usage and pricing summaries,
|
|
128
131
|
with a hermetic PTY smoke covering real `runInteractive` ANSI entry,
|
|
129
|
-
resize-safe lifecycle,
|
|
132
|
+
resize-safe lifecycle, Ctrl-C restoration, fullscreen `Ctrl+L` redraw, and
|
|
133
|
+
mouse-wheel/drag selection with edge autoscroll and OSC 52 copy,
|
|
130
134
|
interactive `/doctor` diagnostics, per-session model/effort/permission controls,
|
|
131
135
|
context/status/skill/task dashboards, prompt stash and continuation shortcuts,
|
|
132
136
|
filterable `@` file and agent references, composer undo, `Ctrl+G` external
|
|
@@ -9,6 +9,7 @@ import { type ClaudeInterruptionClassification } from '../native/interruption.js
|
|
|
9
9
|
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';
|
|
10
10
|
import { type BackgroundTaskSnapshot } from './background-task-runtime.js';
|
|
11
11
|
import type { ModelPricingRegistry } from '../core/usage.js';
|
|
12
|
+
import { type ActiveTurnInputCommandResult } from '../core/active-turn-input.js';
|
|
12
13
|
import type { Compactor } from '../core/compaction.js';
|
|
13
14
|
import { ContextBudget } from '../core/context-budget.js';
|
|
14
15
|
import { type ContextAssembler } from '../core/context.js';
|
|
@@ -227,6 +228,7 @@ export declare class ClaudeSessionService {
|
|
|
227
228
|
private readonly hookLifecycle;
|
|
228
229
|
private readonly leadOperations;
|
|
229
230
|
private readonly fileChangeWatcher;
|
|
231
|
+
private readonly activeTurnInputs;
|
|
230
232
|
private runtimeCwd;
|
|
231
233
|
constructor(options: ClaudeSessionServiceOptions);
|
|
232
234
|
nextScheduledPrompt(signal?: AbortSignal): Promise<ScheduledPrompt | null>;
|
|
@@ -270,6 +272,8 @@ export declare class ClaudeSessionService {
|
|
|
270
272
|
run(prompt: string, signal?: AbortSignal, sessionId?: string, name?: string, images?: readonly ModelImage[], documents?: readonly ModelDocument[]): Promise<SessionRunResult>;
|
|
271
273
|
runShell(command: string, signal?: AbortSignal, sessionId?: string, name?: string): Promise<SessionRunResult>;
|
|
272
274
|
resume(sessionId: string, prompt: string, signal?: AbortSignal, name?: string, images?: readonly ModelImage[], documents?: readonly ModelDocument[], resumeSessionAt?: string): Promise<SessionRunResult>;
|
|
275
|
+
steer(sessionId: string, content: string): ActiveTurnInputCommandResult;
|
|
276
|
+
withdrawSteering(sessionId: string, id: string): ActiveTurnInputCommandResult;
|
|
273
277
|
resumeShell(sessionId: string, command: string, signal?: AbortSignal, name?: string, resumeSessionAt?: string): Promise<SessionRunResult>;
|
|
274
278
|
answerSideQuestion(sessionId: string | undefined, question: string, signal?: AbortSignal, onDelta?: (delta: string) => void, permissionMode?: ClaudePermissionMode): Promise<SideQuestionResult>;
|
|
275
279
|
forkSideQuestion(sessionId: string, question: string, signal?: AbortSignal): Promise<SideQuestionForkResult>;
|
|
@@ -21,6 +21,7 @@ import { BackgroundTaskRuntime, } from './background-task-runtime.js';
|
|
|
21
21
|
import { backgroundAgentNotificationMarkers, } from './background-agent-manager.js';
|
|
22
22
|
import { usageCostUsd } from '../core/usage.js';
|
|
23
23
|
import { isSessionId } from '../core/session.js';
|
|
24
|
+
import { ActiveTurnInputMailbox, } from '../core/active-turn-input.js';
|
|
24
25
|
import { ContextBudget, estimateModelRequestTokens, isPromptTooLongError, } from '../core/context-budget.js';
|
|
25
26
|
import { ContextEngine } from './context-engine.js';
|
|
26
27
|
import { TurnMemoryCoordinator } from './turn-memory-coordinator.js';
|
|
@@ -682,6 +683,7 @@ export class ClaudeSessionService {
|
|
|
682
683
|
hookLifecycle;
|
|
683
684
|
leadOperations;
|
|
684
685
|
fileChangeWatcher;
|
|
686
|
+
activeTurnInputs = new Map();
|
|
685
687
|
runtimeCwd;
|
|
686
688
|
constructor(options) {
|
|
687
689
|
const dataPlane = options.dataPlane ?? 'native';
|
|
@@ -887,6 +889,18 @@ export class ClaudeSessionService {
|
|
|
887
889
|
}
|
|
888
890
|
async close() {
|
|
889
891
|
this.closing = true;
|
|
892
|
+
for (const { mailbox } of this.activeTurnInputs.values()) {
|
|
893
|
+
if (!mailbox)
|
|
894
|
+
continue;
|
|
895
|
+
for (const item of mailbox.close()) {
|
|
896
|
+
this.options.eventSink?.({
|
|
897
|
+
type: 'user-input-rejected',
|
|
898
|
+
id: item.id,
|
|
899
|
+
content: item.content,
|
|
900
|
+
reason: 'closed',
|
|
901
|
+
});
|
|
902
|
+
}
|
|
903
|
+
}
|
|
890
904
|
await this.fileChangeWatcher?.close(5_000);
|
|
891
905
|
await this.hookLifecycle.close();
|
|
892
906
|
await this.drainDetachedHookRuns(5_000);
|
|
@@ -1299,6 +1313,30 @@ export class ClaudeSessionService {
|
|
|
1299
1313
|
...(signal ? { signal } : {}),
|
|
1300
1314
|
});
|
|
1301
1315
|
}
|
|
1316
|
+
steer(sessionId, content) {
|
|
1317
|
+
const active = this.activeTurnInputs.get(sessionId);
|
|
1318
|
+
if (!active)
|
|
1319
|
+
return { kind: 'no-active-turn' };
|
|
1320
|
+
const mailbox = active.mailbox;
|
|
1321
|
+
if (!mailbox)
|
|
1322
|
+
return { kind: 'not-steerable' };
|
|
1323
|
+
const result = mailbox.enqueue(content);
|
|
1324
|
+
if (result.kind === 'accepted')
|
|
1325
|
+
return result;
|
|
1326
|
+
if (result.kind === 'empty')
|
|
1327
|
+
return result;
|
|
1328
|
+
return { kind: 'turn-completing' };
|
|
1329
|
+
}
|
|
1330
|
+
withdrawSteering(sessionId, id) {
|
|
1331
|
+
const active = this.activeTurnInputs.get(sessionId);
|
|
1332
|
+
if (!active)
|
|
1333
|
+
return { kind: 'no-active-turn' };
|
|
1334
|
+
const mailbox = active.mailbox;
|
|
1335
|
+
if (!mailbox)
|
|
1336
|
+
return { kind: 'not-steerable' };
|
|
1337
|
+
const result = mailbox.withdraw(id);
|
|
1338
|
+
return result.kind === 'withdrawn' ? result : { kind: 'not-pending' };
|
|
1339
|
+
}
|
|
1302
1340
|
async resumeShell(sessionId, command, signal, name, resumeSessionAt) {
|
|
1303
1341
|
this.worktreeManager?.bindSession(sessionId);
|
|
1304
1342
|
return this.executeTurn({
|
|
@@ -2449,6 +2487,8 @@ export class ClaudeSessionService {
|
|
|
2449
2487
|
const shellCommand = submission.kind === 'shell' ? submission.command : undefined;
|
|
2450
2488
|
const skipUserPrompt = submission.kind === 'retry';
|
|
2451
2489
|
const controller = new TurnTerminalController(this.options.eventSink ?? (() => undefined));
|
|
2490
|
+
let activeTurnInput;
|
|
2491
|
+
let activeTurnRecord;
|
|
2452
2492
|
try {
|
|
2453
2493
|
this.assertTurnWritable();
|
|
2454
2494
|
if (prompt.length === 0 && images.length === 0 && documents.length === 0)
|
|
@@ -2459,6 +2499,16 @@ export class ClaudeSessionService {
|
|
|
2459
2499
|
if (shellCommand !== undefined && shellCommand.trim().length === 0) {
|
|
2460
2500
|
throw new Error('Shell command must not be empty');
|
|
2461
2501
|
}
|
|
2502
|
+
if (this.activeTurnInputs.has(sessionId)) {
|
|
2503
|
+
throw new Error(`conflict: locked (session ${sessionId} already has an active turn)`);
|
|
2504
|
+
}
|
|
2505
|
+
activeTurnRecord =
|
|
2506
|
+
shellCommand === undefined
|
|
2507
|
+
? {
|
|
2508
|
+
mailbox: (activeTurnInput = new ActiveTurnInputMailbox(randomUUID)),
|
|
2509
|
+
}
|
|
2510
|
+
: {};
|
|
2511
|
+
this.activeTurnInputs.set(sessionId, activeTurnRecord);
|
|
2462
2512
|
await this.activateSessionCostTracker(sessionId);
|
|
2463
2513
|
await this.ensureFileResources(sessionId, signal);
|
|
2464
2514
|
this.worktreeManager?.bindSession(sessionId);
|
|
@@ -3213,6 +3263,24 @@ export class ClaudeSessionService {
|
|
|
3213
3263
|
}
|
|
3214
3264
|
await durableFollowUps.followUpUserMessagesCompleted(messages);
|
|
3215
3265
|
},
|
|
3266
|
+
userInputDelivered: async (item) => {
|
|
3267
|
+
if (nativeLease) {
|
|
3268
|
+
await nativeLease.appendMessages({
|
|
3269
|
+
messages: [{ role: 'user', content: item.content }],
|
|
3270
|
+
});
|
|
3271
|
+
currentTurnUserMessages?.push(item.content);
|
|
3272
|
+
return;
|
|
3273
|
+
}
|
|
3274
|
+
const [steeringEntry] = translateProviderEvents([{ type: 'user-text-block', text: item.content }], this.translationContext(sessionId, snapshot));
|
|
3275
|
+
if (!steeringEntry)
|
|
3276
|
+
throw new Error('Could not translate steering message');
|
|
3277
|
+
const steeringTail = await this.append(lease, snapshot.tail, steeringEntry);
|
|
3278
|
+
snapshot = {
|
|
3279
|
+
entries: [...snapshot.entries, steeringEntry],
|
|
3280
|
+
tail: steeringTail,
|
|
3281
|
+
};
|
|
3282
|
+
currentTurnUserMessages?.push(item.content);
|
|
3283
|
+
},
|
|
3216
3284
|
};
|
|
3217
3285
|
let turnCompleted = false;
|
|
3218
3286
|
try {
|
|
@@ -3974,6 +4042,7 @@ export class ClaudeSessionService {
|
|
|
3974
4042
|
cwd: this.activeCwd(),
|
|
3975
4043
|
toolResultDirectory,
|
|
3976
4044
|
observer,
|
|
4045
|
+
...(activeTurnInput ? { steering: activeTurnInput } : {}),
|
|
3977
4046
|
...(this.options.effort ? { effort: this.options.effort } : {}),
|
|
3978
4047
|
...(this.options.maxModelTurns !== undefined
|
|
3979
4048
|
? { maxModelTurns: this.options.maxModelTurns }
|
|
@@ -4373,6 +4442,20 @@ export class ClaudeSessionService {
|
|
|
4373
4442
|
controller.fail(error, signal);
|
|
4374
4443
|
throw error;
|
|
4375
4444
|
}
|
|
4445
|
+
finally {
|
|
4446
|
+
if (activeTurnInput !== undefined) {
|
|
4447
|
+
for (const item of activeTurnInput.close()) {
|
|
4448
|
+
this.options.eventSink?.({
|
|
4449
|
+
type: 'user-input-rejected',
|
|
4450
|
+
id: item.id,
|
|
4451
|
+
content: item.content,
|
|
4452
|
+
reason: signal?.aborted ? 'cancelled' : 'failed',
|
|
4453
|
+
});
|
|
4454
|
+
}
|
|
4455
|
+
}
|
|
4456
|
+
if (this.activeTurnInputs.get(sessionId) === activeTurnRecord)
|
|
4457
|
+
this.activeTurnInputs.delete(sessionId);
|
|
4458
|
+
}
|
|
4376
4459
|
}
|
|
4377
4460
|
async ensureFileResources(sessionId, signal) {
|
|
4378
4461
|
const resources = this.options.fileResources ?? [];
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ForkResult, ManualCompactResult, ManualCompactSelection, RewindPoint, SessionForkCheckpoint, SessionRunResult, SessionSummary, SideQuestionForkResult, SideQuestionResult } from '../application/session-service.js';
|
|
2
2
|
import type { ClaudeSessionCostSnapshot } from '../application/session-cost-tracker.js';
|
|
3
3
|
import type { ModelImage, ModelToolCall, PermissionApproval, PermissionDecision, RuntimeEventSink } from '../core/runtime.js';
|
|
4
|
+
import type { ActiveTurnInputCommandResult } from '../core/active-turn-input.js';
|
|
4
5
|
import { type DataPlane } from '../persistence/data-plane.js';
|
|
5
6
|
import type { CliElicitationRequest, CliElicitationResult, CliRuntimeInfo } from './protocol.js';
|
|
6
7
|
import type { ClaudeInteractiveToolCallbacks } from '../tools/claude-interactive-tools.js';
|
|
@@ -34,6 +35,8 @@ import type { BackgroundTaskSnapshot } from '../application/background-task-runt
|
|
|
34
35
|
interface InteractiveSessionCommands {
|
|
35
36
|
run(prompt: string, signal?: AbortSignal, sessionId?: string, name?: string, images?: readonly ModelImage[]): Promise<SessionRunResult>;
|
|
36
37
|
resume(sessionId: string, prompt: string, signal?: AbortSignal, name?: string, images?: readonly ModelImage[]): Promise<SessionRunResult>;
|
|
38
|
+
steer?(sessionId: string, content: string): ActiveTurnInputCommandResult;
|
|
39
|
+
withdrawSteering?(sessionId: string, id: string): ActiveTurnInputCommandResult;
|
|
37
40
|
runShell?(command: string, signal?: AbortSignal, sessionId?: string, name?: string): Promise<SessionRunResult>;
|
|
38
41
|
resumeShell?(sessionId: string, command: string, signal?: AbortSignal): Promise<SessionRunResult>;
|
|
39
42
|
fork(sessionId: string, targetSessionId?: string, resumeSessionAt?: string): Promise<ForkResult>;
|