praxis-agent 0.43.0 → 0.44.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 +48 -47
- package/dist/application/native-session-projection.d.ts +4 -0
- package/dist/application/native-session-projection.js +135 -0
- package/dist/application/session-service.d.ts +0 -1
- package/dist/application/session-service.js +9 -170
- package/dist/application/top-level-agent-manager.d.ts +7 -0
- package/dist/application/top-level-agent-manager.js +21 -3
- package/dist/cli/controls.js +4 -0
- package/dist/cli/interactive.js +112 -156
- package/dist/cli/protocol.d.ts +5 -0
- package/dist/cli/protocol.js +45 -0
- package/dist/cli/provider-auth-command.d.ts +32 -0
- package/dist/cli/provider-auth-command.js +249 -0
- package/dist/cli/tui/ansi-frame-renderer.d.ts +2 -0
- package/dist/cli/tui/ansi-frame-renderer.js +109 -21
- package/dist/cli/tui/ansi-surface.d.ts +6 -22
- package/dist/cli/tui/ansi-surface.js +6 -126
- package/dist/cli/tui/ansi-theme.d.ts +3 -2
- package/dist/cli/tui/ansi-theme.js +22 -1
- package/dist/cli/tui/quiet-choice-rows.d.ts +26 -0
- package/dist/cli/tui/quiet-choice-rows.js +302 -0
- package/dist/cli/tui/quiet-frame-adapter.d.ts +18 -0
- package/dist/cli/tui/quiet-frame-adapter.js +90 -0
- package/dist/cli/tui/quiet-frame.d.ts +43 -0
- package/dist/cli/tui/quiet-frame.js +346 -0
- package/dist/cli/tui/quiet-operational-rows.d.ts +17 -0
- package/dist/cli/tui/quiet-operational-rows.js +413 -0
- package/dist/cli/tui/quiet-screen-projector.d.ts +25 -0
- package/dist/cli/tui/quiet-screen-projector.js +110 -0
- package/dist/cli/tui/quiet-settings-rows.d.ts +17 -0
- package/dist/cli/tui/quiet-settings-rows.js +490 -0
- package/dist/cli/tui/status-line.d.ts +15 -9
- package/dist/cli/tui/status-line.js +15 -6
- package/dist/cli/tui/theme.js +20 -20
- package/dist/cli/tui/transcript-viewport.d.ts +5 -0
- package/dist/cli/tui/transcript-viewport.js +213 -78
- package/dist/cli/tui/tui-row-ir.js +4 -4
- package/dist/cli/workspace-trust-prompt.js +2 -2
- package/dist/cli-runtime.d.ts +34 -1
- package/dist/cli-runtime.js +587 -184
- package/dist/maintenance/doctor.js +100 -22
- package/dist/persistence/in-memory-transcript-store.js +1 -7
- package/dist/persistence/native-transcript-store.d.ts +0 -9
- package/dist/persistence/native-transcript-store.js +4 -38
- package/dist/persistence/provider-credential-vault.d.ts +72 -0
- package/dist/persistence/provider-credential-vault.js +498 -0
- package/dist/providers/codex-oauth.d.ts +104 -0
- package/dist/providers/codex-oauth.js +770 -0
- package/dist/providers/codex-subscription.d.ts +33 -0
- package/dist/providers/codex-subscription.js +849 -0
- package/dist/providers/provider-auth.d.ts +71 -0
- package/dist/providers/provider-auth.js +149 -0
- package/dist/providers/provider-registry.d.ts +52 -0
- package/dist/providers/provider-registry.js +187 -0
- package/dist/providers/provider-settings.d.ts +38 -0
- package/dist/providers/provider-settings.js +334 -0
- package/dist/security/workspace-trust.d.ts +4 -3
- package/dist/security/workspace-trust.js +35 -1
- package/package.json +8 -4
package/README.md
CHANGED
|
@@ -27,7 +27,9 @@ sessions, configuration, or compatibility directories.
|
|
|
27
27
|
- macOS or Linux
|
|
28
28
|
- Node.js 24 or newer
|
|
29
29
|
- [`ripgrep`](https://github.com/BurntSushi/ripgrep) (`rg`) for the Grep tool
|
|
30
|
-
- an API key and model ID for an Anthropic or OpenAI-compatible provider
|
|
30
|
+
- an API key and model ID for an Anthropic or OpenAI-compatible provider (the
|
|
31
|
+
stable setup), or the explicitly enabled experimental ChatGPT-backed Codex
|
|
32
|
+
subscription integration
|
|
31
33
|
|
|
32
34
|
Praxis does not use Claude subscription authentication. Claude-shaped message,
|
|
33
35
|
tool, and CLI protocol forms remain supported where they are part of the
|
|
@@ -61,6 +63,15 @@ cd /path/to/project
|
|
|
61
63
|
praxis
|
|
62
64
|
```
|
|
63
65
|
|
|
66
|
+
Praxis also has an experimental `openai-codex` provider for ChatGPT-backed
|
|
67
|
+
Codex subscriptions. It is separate from OpenAI API-key access, requires
|
|
68
|
+
`experimental.codexSubscription: true`, and stores OAuth credentials in the
|
|
69
|
+
native Vault. Start with `praxis auth login openai-codex`; see [Getting
|
|
70
|
+
Started](docs/GETTING_STARTED.md) for the browser/device flow and limitations.
|
|
71
|
+
This uses an undocumented third-party subscription/backend contract and may
|
|
72
|
+
change; it is not Claude subscription authentication. Subscription runs retain
|
|
73
|
+
token usage but do not provide API-dollar cost or enforce USD budgets.
|
|
74
|
+
|
|
64
75
|
For Anthropic Messages:
|
|
65
76
|
|
|
66
77
|
```sh
|
|
@@ -89,10 +100,13 @@ troubleshooting. Run `praxis --help` for the authoritative command surface.
|
|
|
89
100
|
|
|
90
101
|
## What Praxis provides
|
|
91
102
|
|
|
92
|
-
- **Local agent runtime** —
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
103
|
+
- **Local agent runtime** — C+ Quiet Operator responsive TUI with a linear
|
|
104
|
+
`you>`/`praxis>` conversation, compact stable tool rows, responsive density,
|
|
105
|
+
terminal-native background, and a minimal composer/status row. Interactive
|
|
106
|
+
surfaces share the same presentation across terminals, with English
|
|
107
|
+
permission/configuration choices and a taught `❯` / Up/Down / Enter / Esc
|
|
108
|
+
interaction grammar, plus a
|
|
109
|
+
shared-command
|
|
96
110
|
slash palette, tabbed help and shortcut surfaces, searchable resume picker,
|
|
97
111
|
restored active-branch conversation history, streaming and expandable
|
|
98
112
|
thinking, grouped multi-file reads, globally expandable tool results,
|
|
@@ -107,26 +121,8 @@ troubleshooting. Run `praxis --help` for the authoritative command surface.
|
|
|
107
121
|
skills/hooks flow, provider-free per-session `/color` prompt-bar styling,
|
|
108
122
|
`/mcp`, `/memory` shared instruction and auto-memory
|
|
109
123
|
access, and live extension-reload controls,
|
|
110
|
-
plus
|
|
111
|
-
|
|
112
|
-
framework-free TuiStore that retains runtime and composer state together,
|
|
113
|
-
with a shared semantic transcript Row IR for renderer-neutral layout and
|
|
114
|
-
stable incremental updates,
|
|
115
|
-
backed by the authoritative grapheme/Markdown viewport layout so narrow and
|
|
116
|
-
Unicode-heavy transcripts wrap consistently across renderers,
|
|
117
|
-
and an independent ANSI fullscreen frame renderer with alternate-screen
|
|
118
|
-
lifecycle, synchronized output, and dirty-row diffing,
|
|
119
|
-
opt-in TTY wiring with automatic Ink fallback on renderer failure,
|
|
120
|
-
with complex overlays and dialogs intentionally remaining on the mature Ink
|
|
121
|
-
surface until their semantic ANSI projections are complete,
|
|
122
|
-
while welcome and identity intros also remain on Ink for a complete first
|
|
123
|
-
frame,
|
|
124
|
-
and a pure FocusStack that centralizes overlay/dialog precedence and Esc
|
|
125
|
-
cancellation routing,
|
|
126
|
-
plus a generation-aware effect runner that drops stale asynchronous results
|
|
127
|
-
and aborts cleanly on replacement or unmount,
|
|
128
|
-
with ANSI fullscreen text styles derived from the active semantic theme and
|
|
129
|
-
automatic no-color/screen-reader suppression,
|
|
124
|
+
plus automatic fallback when a terminal renderer fails, screen-reader
|
|
125
|
+
support, and no-color output,
|
|
130
126
|
cursor/history composer, provider-free `/cost` usage and pricing summaries,
|
|
131
127
|
with a hermetic PTY smoke covering real `runInteractive` ANSI entry,
|
|
132
128
|
resize-safe lifecycle, and Ctrl-C restoration,
|
|
@@ -169,11 +165,11 @@ troubleshooting. Run `praxis --help` for the authoritative command surface.
|
|
|
169
165
|
isolation, explicit ask/deny precedence, sandbox-only auto-allow,
|
|
170
166
|
write-allowlist/deny-within-allow enforcement, per-command overrides and
|
|
171
167
|
exclusions, violation reporting, and bare-repository control-file cleanup,
|
|
172
|
-
safe-property Skill auto-allow, interactive
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
168
|
+
safe-property Skill auto-allow, interactive workspace-directory add/remove
|
|
169
|
+
controls, path confinement, credential redaction, sanitized child processes,
|
|
170
|
+
and exact-fingerprint workspace trust that blocks automatically discovered
|
|
171
|
+
project/local provider selection, hooks, and MCP until the canonical
|
|
172
|
+
workspace configuration is accepted.
|
|
177
173
|
- **Durable local work** — resumable sessions, full-history forks, file
|
|
178
174
|
checkpoints, tasks, foreground/background subagents, top-level agents, and
|
|
179
175
|
Claude-compatible main-thread agent definitions with native prompt, model,
|
|
@@ -202,9 +198,10 @@ troubleshooting. Run `praxis --help` for the authoritative command surface.
|
|
|
202
198
|
plugins, and append-only `praxis.transcript` JSONL sessions under `~/.praxis`,
|
|
203
199
|
with bounded MCP connection, discovery, and tool operations plus safe
|
|
204
200
|
disconnect recovery that never replays an already-dispatched call.
|
|
205
|
-
- **Provider-neutral models** — native
|
|
206
|
-
|
|
207
|
-
|
|
201
|
+
- **Provider-neutral models** — native Provider Registry/Vault routing, API
|
|
202
|
+
adapters, an experimental Codex OAuth adapter, explicit capability checks,
|
|
203
|
+
per-attempt bounded deadlines, and token-only/no-API-dollar accounting for
|
|
204
|
+
subscription runs.
|
|
208
205
|
- **Transactional self-update** — `praxis update` verifies the package before
|
|
209
206
|
installing it, rejects concurrent updates, and can roll back after an
|
|
210
207
|
interruption or crash.
|
|
@@ -215,23 +212,23 @@ not in this entry-point README.
|
|
|
215
212
|
|
|
216
213
|
## Documentation
|
|
217
214
|
|
|
218
|
-
| Need | Document
|
|
219
|
-
| ------------------------------------------ |
|
|
220
|
-
| Install and run the first session | [Getting Started](https://github.com/Forest-Isle/Praxis/blob/main/docs/GETTING_STARTED.md)
|
|
221
|
-
| Common commands and environment variables | [CLI Reference](https://github.com/Forest-Isle/Praxis/blob/main/docs/CLI_REFERENCE.md)
|
|
222
|
-
| Find all user and maintainer documentation | [Documentation Index](https://github.com/Forest-Isle/Praxis/blob/main/docs/README.md)
|
|
223
|
-
| Understand module and data-flow boundaries | [Architecture](https://github.com/Forest-Isle/Praxis/blob/main/docs/ARCHITECTURE.md)
|
|
224
|
-
| Review security assumptions | [Threat Model](https://github.com/Forest-Isle/Praxis/blob/main/docs/THREAT_MODEL.md)
|
|
225
|
-
| Check Claude Code parity | [Parity Matrix](https://github.com/Forest-Isle/Praxis/blob/main/docs/PARITY_MATRIX.md)
|
|
226
|
-
| Review interactive TUI design and evidence | [
|
|
227
|
-
| Build, test, and contribute | [Contributing](https://github.com/Forest-Isle/Praxis/blob/main/CONTRIBUTING.md)
|
|
228
|
-
| Verify release and supply-chain controls | [Release Contract](https://github.com/Forest-Isle/Praxis/blob/main/docs/RELEASE.md)
|
|
215
|
+
| Need | Document |
|
|
216
|
+
| ------------------------------------------ | ------------------------------------------------------------------------------------------------ |
|
|
217
|
+
| Install and run the first session | [Getting Started](https://github.com/Forest-Isle/Praxis/blob/main/docs/GETTING_STARTED.md) |
|
|
218
|
+
| Common commands and environment variables | [CLI Reference](https://github.com/Forest-Isle/Praxis/blob/main/docs/CLI_REFERENCE.md) |
|
|
219
|
+
| Find all user and maintainer documentation | [Documentation Index](https://github.com/Forest-Isle/Praxis/blob/main/docs/README.md) |
|
|
220
|
+
| Understand module and data-flow boundaries | [Architecture](https://github.com/Forest-Isle/Praxis/blob/main/docs/ARCHITECTURE.md) |
|
|
221
|
+
| Review security assumptions | [Threat Model](https://github.com/Forest-Isle/Praxis/blob/main/docs/THREAT_MODEL.md) |
|
|
222
|
+
| Check Claude Code parity | [Parity Matrix](https://github.com/Forest-Isle/Praxis/blob/main/docs/PARITY_MATRIX.md) |
|
|
223
|
+
| Review interactive TUI design and evidence | [Quiet Operator Spec](https://github.com/Forest-Isle/Praxis/blob/main/docs/TUI_REDESIGN_SPEC.md) |
|
|
224
|
+
| Build, test, and contribute | [Contributing](https://github.com/Forest-Isle/Praxis/blob/main/CONTRIBUTING.md) |
|
|
225
|
+
| Verify release and supply-chain controls | [Release Contract](https://github.com/Forest-Isle/Praxis/blob/main/docs/RELEASE.md) |
|
|
229
226
|
|
|
230
227
|
## Project boundary
|
|
231
228
|
|
|
232
229
|
Praxis targets one local OS user working across multiple repositories and
|
|
233
230
|
sessions. It is CLI-only and provider-capability-aware. Organization, tenant,
|
|
234
|
-
RBAC,
|
|
231
|
+
RBAC, billing, enterprise gateway,
|
|
235
232
|
IDE/Desktop/mobile clients, Remote Control, Claude Desktop import, and hosted
|
|
236
233
|
review-product surfaces are permanent non-goals.
|
|
237
234
|
|
|
@@ -261,9 +258,13 @@ npm run check
|
|
|
261
258
|
transcript, and session profile.
|
|
262
259
|
`npm run test:native:deletion` adds an emitted-output deletion gate. These are
|
|
263
260
|
implemented profile checks, not qualification of the full native package.
|
|
264
|
-
`npm run test:performance`
|
|
265
|
-
|
|
261
|
+
`npm run test:performance` enforces TUI projection scaling, doubling ratios
|
|
262
|
+
`<=3.25`, the absolute 120k median budget of `<1000 ms`, and deterministic
|
|
263
|
+
injected regression protection, plus Quiet Operator input echo `<50 ms` and
|
|
264
|
+
normal/low-capability full-frame p95 budgets of `<16.7/<33 ms`.
|
|
266
265
|
`npm run check` also enforces the corresponding source dependency direction.
|
|
266
|
+
`npm run test:coverage` measures all production code under `src/**` with V8 and
|
|
267
|
+
enforces global floors of 79% statements, 70% branches, 85% functions, and 81% lines.
|
|
267
268
|
`npm run test:core-completion` runs the 56-story #402 audit and reports
|
|
268
269
|
implemented, qualified, blocked, deferred, and out-of-scope states separately;
|
|
269
270
|
it never treats missing live prerequisites as a pass.
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { TranscriptEvent } from '../core/transcript-event.js';
|
|
2
|
+
import type { NativeTranscriptEntry } from '../native/schema.js';
|
|
3
|
+
export declare function projectNativeSessionEntries(events: readonly TranscriptEvent[]): NativeTranscriptEntry[];
|
|
4
|
+
//# sourceMappingURL=native-session-projection.d.ts.map
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
export function projectNativeSessionEntries(events) {
|
|
2
|
+
const entries = [];
|
|
3
|
+
for (const event of events) {
|
|
4
|
+
if (event.kind === 'context-boundary') {
|
|
5
|
+
entries.push({
|
|
6
|
+
type: 'system',
|
|
7
|
+
uuid: event.id,
|
|
8
|
+
parentUuid: event.parentId,
|
|
9
|
+
subtype: 'compact_boundary',
|
|
10
|
+
logicalParentUuid: event.logicalParentId,
|
|
11
|
+
compactMetadata: {
|
|
12
|
+
trigger: event.trigger,
|
|
13
|
+
preTokens: event.preTokens,
|
|
14
|
+
postTokens: event.postTokens,
|
|
15
|
+
durationMs: event.durationMs,
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
if (event.kind === 'context-summary') {
|
|
21
|
+
entries.push({
|
|
22
|
+
type: 'user',
|
|
23
|
+
uuid: event.id,
|
|
24
|
+
parentUuid: event.parentId,
|
|
25
|
+
isCompactSummary: true,
|
|
26
|
+
message: { role: 'user', content: event.summary },
|
|
27
|
+
});
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (event.kind !== 'messages')
|
|
31
|
+
continue;
|
|
32
|
+
let parentUuid = event.parentId;
|
|
33
|
+
event.messages.forEach((message, index) => {
|
|
34
|
+
const uuid = index === event.messages.length - 1 ? event.id : `${event.id}:${index}`;
|
|
35
|
+
if (message.role === 'user' && typeof message.content === 'string') {
|
|
36
|
+
const agentMatch = message.content.match(/^<praxis-agent-setting>([^<]+)<\/praxis-agent-setting>$/u);
|
|
37
|
+
if (agentMatch?.[1]) {
|
|
38
|
+
entries.push({
|
|
39
|
+
type: 'agent-setting',
|
|
40
|
+
agentSetting: agentMatch[1],
|
|
41
|
+
uuid,
|
|
42
|
+
parentUuid,
|
|
43
|
+
sessionId: event.sessionId,
|
|
44
|
+
});
|
|
45
|
+
parentUuid = uuid;
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const nameMatch = message.content.match(/^<praxis-session-name>([^<]+)<\/praxis-session-name>$/u);
|
|
49
|
+
if (nameMatch?.[1]) {
|
|
50
|
+
entries.push({
|
|
51
|
+
type: 'custom-title',
|
|
52
|
+
customTitle: nameMatch[1],
|
|
53
|
+
uuid,
|
|
54
|
+
parentUuid,
|
|
55
|
+
sessionId: event.sessionId,
|
|
56
|
+
});
|
|
57
|
+
parentUuid = uuid;
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const match = message.content.match(/^<praxis-file-history>([\s\S]*)<\/praxis-file-history>$/u);
|
|
61
|
+
if (match?.[1]) {
|
|
62
|
+
try {
|
|
63
|
+
const operational = JSON.parse(match[1]);
|
|
64
|
+
if (operational &&
|
|
65
|
+
typeof operational === 'object' &&
|
|
66
|
+
typeof operational.type === 'string' &&
|
|
67
|
+
(operational.type === 'file-history-snapshot' ||
|
|
68
|
+
operational.type === 'file-history-delta')) {
|
|
69
|
+
entries.push({
|
|
70
|
+
...operational,
|
|
71
|
+
uuid,
|
|
72
|
+
parentUuid,
|
|
73
|
+
sessionId: event.sessionId,
|
|
74
|
+
});
|
|
75
|
+
parentUuid = uuid;
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// Keep malformed operational markers visible as ordinary native user messages.
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (message.role === 'tool') {
|
|
85
|
+
entries.push({
|
|
86
|
+
type: 'user',
|
|
87
|
+
uuid,
|
|
88
|
+
parentUuid,
|
|
89
|
+
message: {
|
|
90
|
+
role: 'user',
|
|
91
|
+
content: [
|
|
92
|
+
{
|
|
93
|
+
type: 'tool_result',
|
|
94
|
+
tool_use_id: message.toolCallId,
|
|
95
|
+
content: message.content,
|
|
96
|
+
is_error: message.isError,
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
parentUuid = uuid;
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (message.role === 'assistant') {
|
|
105
|
+
const content = [];
|
|
106
|
+
if (message.content)
|
|
107
|
+
content.push({ type: 'text', text: message.content });
|
|
108
|
+
for (const call of message.toolCalls ?? [])
|
|
109
|
+
content.push({
|
|
110
|
+
type: 'tool_use',
|
|
111
|
+
id: call.id,
|
|
112
|
+
name: call.name,
|
|
113
|
+
input: call.input,
|
|
114
|
+
});
|
|
115
|
+
entries.push({
|
|
116
|
+
type: 'assistant',
|
|
117
|
+
uuid,
|
|
118
|
+
parentUuid,
|
|
119
|
+
message: { role: 'assistant', content },
|
|
120
|
+
});
|
|
121
|
+
parentUuid = uuid;
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
entries.push({
|
|
125
|
+
type: message.role,
|
|
126
|
+
uuid,
|
|
127
|
+
parentUuid,
|
|
128
|
+
message: { role: message.role, content: message.content },
|
|
129
|
+
});
|
|
130
|
+
parentUuid = uuid;
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
return entries;
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=native-session-projection.js.map
|
|
@@ -299,7 +299,6 @@ export declare class ClaudeSessionService {
|
|
|
299
299
|
compact(sessionId: string, signal?: AbortSignal, selection?: ManualCompactSelection): Promise<ManualCompactResult>;
|
|
300
300
|
fork(parentSessionId: string, sessionId?: string, resumeSessionAt?: string): Promise<ForkResult>;
|
|
301
301
|
ensureFork(parentSessionId: string, sessionId: string, checkpoint?: SessionForkCheckpoint): Promise<ForkResult>;
|
|
302
|
-
private nativeForkEntries;
|
|
303
302
|
rewindFiles(sessionId: string, userMessageId: string): Promise<void>;
|
|
304
303
|
rewindPoints(sessionId: string): Promise<RewindPoint[]>;
|
|
305
304
|
setPermissionMode(sessionId: string, permissionMode: ClaudePermissionMode): Promise<void>;
|
|
@@ -8,10 +8,10 @@ import { formatClaudeCompactSummary } from '../native/compaction.js';
|
|
|
8
8
|
import { isClaudeAgentId } from '../native/sidechain.js';
|
|
9
9
|
import { assertNativeDataPlane, resolveDataPlanePaths, resolveScheduledTaskFile, } from '../persistence/data-plane.js';
|
|
10
10
|
import { downloadClaudeFileResources, } from '../native/file-resources.js';
|
|
11
|
-
import { createClaudeNativeFork } from '../native/fork.js';
|
|
12
11
|
import { selectClaudeActiveTranscript } from '../native/history.js';
|
|
13
12
|
import { ClaudeFileHistory } from '../native/file-history.js';
|
|
14
13
|
import { getClaudeAgentSetting, projectClaudeModelMessages, } from '../native/projection.js';
|
|
14
|
+
import { projectNativeSessionEntries } from './native-session-projection.js';
|
|
15
15
|
import { classifyClaudeInterruption, } from '../native/interruption.js';
|
|
16
16
|
import { findUnresolvedClaudeToolCalls, getClaudeContentBlocks, } from '../native/tool-links.js';
|
|
17
17
|
import { createClaudeAgentSettingEntry, createClaudeHookAttachmentEntries, createClaudeLastPromptEntry, createClaudeRuleAttachmentEntry, translateProviderEvents, } from '../native/translation.js';
|
|
@@ -21,146 +21,6 @@ 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
|
-
/** Project authoritative native events for legacy-only session helpers. */
|
|
25
|
-
function projectNativeEvents(events) {
|
|
26
|
-
const entries = [];
|
|
27
|
-
for (const event of events) {
|
|
28
|
-
if (event.kind === 'context-boundary') {
|
|
29
|
-
entries.push({
|
|
30
|
-
type: 'system',
|
|
31
|
-
uuid: event.id,
|
|
32
|
-
parentUuid: event.parentId,
|
|
33
|
-
subtype: 'compact_boundary',
|
|
34
|
-
logicalParentUuid: event.logicalParentId,
|
|
35
|
-
compactMetadata: {
|
|
36
|
-
trigger: event.trigger,
|
|
37
|
-
preTokens: event.preTokens,
|
|
38
|
-
postTokens: event.postTokens,
|
|
39
|
-
durationMs: event.durationMs,
|
|
40
|
-
},
|
|
41
|
-
});
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
|
-
if (event.kind === 'context-summary') {
|
|
45
|
-
entries.push({
|
|
46
|
-
type: 'user',
|
|
47
|
-
uuid: event.id,
|
|
48
|
-
parentUuid: event.parentId,
|
|
49
|
-
isCompactSummary: true,
|
|
50
|
-
message: { role: 'user', content: event.summary },
|
|
51
|
-
});
|
|
52
|
-
continue;
|
|
53
|
-
}
|
|
54
|
-
if (event.kind !== 'messages')
|
|
55
|
-
continue;
|
|
56
|
-
let parentUuid = event.parentId;
|
|
57
|
-
event.messages.forEach((message, index) => {
|
|
58
|
-
// Native event parent links target the event ID. Assign that ID to the
|
|
59
|
-
// final projected message so the next event and memory watermark attach
|
|
60
|
-
// to the visible tail of this event; intermediate messages get stable
|
|
61
|
-
// derived IDs within the event.
|
|
62
|
-
const uuid = index === event.messages.length - 1 ? event.id : `${event.id}:${index}`;
|
|
63
|
-
if (message.role === 'user' && typeof message.content === 'string') {
|
|
64
|
-
const agentMatch = message.content.match(/^<praxis-agent-setting>([^<]+)<\/praxis-agent-setting>$/u);
|
|
65
|
-
if (agentMatch?.[1]) {
|
|
66
|
-
entries.push({
|
|
67
|
-
type: 'agent-setting',
|
|
68
|
-
agentSetting: agentMatch[1],
|
|
69
|
-
uuid,
|
|
70
|
-
parentUuid,
|
|
71
|
-
sessionId: event.sessionId,
|
|
72
|
-
});
|
|
73
|
-
parentUuid = uuid;
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
const nameMatch = message.content.match(/^<praxis-session-name>([^<]+)<\/praxis-session-name>$/u);
|
|
77
|
-
if (nameMatch?.[1]) {
|
|
78
|
-
entries.push({
|
|
79
|
-
type: 'custom-title',
|
|
80
|
-
customTitle: nameMatch[1],
|
|
81
|
-
uuid,
|
|
82
|
-
parentUuid,
|
|
83
|
-
sessionId: event.sessionId,
|
|
84
|
-
});
|
|
85
|
-
parentUuid = uuid;
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
const match = message.content.match(/^<praxis-file-history>([\s\S]*)<\/praxis-file-history>$/u);
|
|
89
|
-
if (match?.[1]) {
|
|
90
|
-
try {
|
|
91
|
-
const operational = JSON.parse(match[1]);
|
|
92
|
-
if (operational &&
|
|
93
|
-
typeof operational === 'object' &&
|
|
94
|
-
typeof operational.type === 'string' &&
|
|
95
|
-
(operational.type === 'file-history-snapshot' ||
|
|
96
|
-
operational.type === 'file-history-delta')) {
|
|
97
|
-
entries.push({
|
|
98
|
-
...operational,
|
|
99
|
-
uuid,
|
|
100
|
-
parentUuid,
|
|
101
|
-
sessionId: event.sessionId,
|
|
102
|
-
});
|
|
103
|
-
parentUuid = uuid;
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
catch {
|
|
108
|
-
// Keep malformed operational markers visible as ordinary native
|
|
109
|
-
// user messages; the authoritative transcript remains valid.
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
if (message.role === 'tool') {
|
|
114
|
-
entries.push({
|
|
115
|
-
type: 'user',
|
|
116
|
-
uuid,
|
|
117
|
-
parentUuid,
|
|
118
|
-
message: {
|
|
119
|
-
role: 'user',
|
|
120
|
-
content: [
|
|
121
|
-
{
|
|
122
|
-
type: 'tool_result',
|
|
123
|
-
tool_use_id: message.toolCallId,
|
|
124
|
-
content: message.content,
|
|
125
|
-
is_error: message.isError,
|
|
126
|
-
},
|
|
127
|
-
],
|
|
128
|
-
},
|
|
129
|
-
});
|
|
130
|
-
parentUuid = uuid;
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
if (message.role === 'assistant') {
|
|
134
|
-
const content = [];
|
|
135
|
-
if (message.content)
|
|
136
|
-
content.push({ type: 'text', text: message.content });
|
|
137
|
-
for (const call of message.toolCalls ?? [])
|
|
138
|
-
content.push({
|
|
139
|
-
type: 'tool_use',
|
|
140
|
-
id: call.id,
|
|
141
|
-
name: call.name,
|
|
142
|
-
input: call.input,
|
|
143
|
-
});
|
|
144
|
-
entries.push({
|
|
145
|
-
type: 'assistant',
|
|
146
|
-
uuid,
|
|
147
|
-
parentUuid,
|
|
148
|
-
message: { role: 'assistant', content },
|
|
149
|
-
});
|
|
150
|
-
parentUuid = uuid;
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
entries.push({
|
|
154
|
-
type: message.role,
|
|
155
|
-
uuid,
|
|
156
|
-
parentUuid,
|
|
157
|
-
message: { role: message.role, content: message.content },
|
|
158
|
-
});
|
|
159
|
-
parentUuid = uuid;
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
return entries;
|
|
163
|
-
}
|
|
164
24
|
import { ContextBudget, estimateModelRequestTokens, isPromptTooLongError, } from '../core/context-budget.js';
|
|
165
25
|
import { ContextEngine } from './context-engine.js';
|
|
166
26
|
import { TurnMemoryCoordinator } from './turn-memory-coordinator.js';
|
|
@@ -1935,7 +1795,7 @@ export class ClaudeSessionService {
|
|
|
1935
1795
|
const sourceStore = this.store(sessionId);
|
|
1936
1796
|
const relocated = await sourceStore.withLease(async (lease) => {
|
|
1937
1797
|
const snapshot = await lease.load();
|
|
1938
|
-
if (snapshot.
|
|
1798
|
+
if (snapshot.records.length === 0) {
|
|
1939
1799
|
throw new Error(`Claude session not found: ${sessionId}`);
|
|
1940
1800
|
}
|
|
1941
1801
|
await mkdir(targetPaths.projectRoot, { recursive: true });
|
|
@@ -2112,7 +1972,7 @@ export class ClaudeSessionService {
|
|
|
2112
1972
|
: branchEvents[targetIndex - 1])?.id;
|
|
2113
1973
|
}
|
|
2114
1974
|
else {
|
|
2115
|
-
memorySelection = await this.selectMemoryPreservedCompact(sessionId,
|
|
1975
|
+
memorySelection = await this.selectMemoryPreservedCompact(sessionId, projectNativeSessionEntries(branchEvents));
|
|
2116
1976
|
if (memorySelection) {
|
|
2117
1977
|
messages = [
|
|
2118
1978
|
memorySelection.memoryMessage,
|
|
@@ -2311,35 +2171,13 @@ export class ClaudeSessionService {
|
|
|
2311
2171
|
return { sessionId, parentSessionId };
|
|
2312
2172
|
}
|
|
2313
2173
|
}
|
|
2314
|
-
async nativeForkEntries(parentSessionId, sessionId, resumeSessionAt, sourceEntryCount) {
|
|
2315
|
-
const sourceResult = await this.store(parentSessionId).withLease((lease) => lease.load());
|
|
2316
|
-
if (sourceResult.status === 'conflict') {
|
|
2317
|
-
throw new Error(`Claude transcript fork conflict: ${sourceResult.reason}`);
|
|
2318
|
-
}
|
|
2319
|
-
const source = sourceResult.value;
|
|
2320
|
-
if (source.entries.length === 0) {
|
|
2321
|
-
throw new Error(`Claude session not found: ${parentSessionId}`);
|
|
2322
|
-
}
|
|
2323
|
-
if (sourceEntryCount !== undefined &&
|
|
2324
|
-
(sourceEntryCount <= 0 || sourceEntryCount > source.entries.length)) {
|
|
2325
|
-
throw new Error('Claude handoff source checkpoint is no longer available');
|
|
2326
|
-
}
|
|
2327
|
-
return createClaudeNativeFork({
|
|
2328
|
-
source: sourceEntryCount === undefined
|
|
2329
|
-
? source.entries
|
|
2330
|
-
: source.entries.slice(0, sourceEntryCount),
|
|
2331
|
-
sourceSessionId: parentSessionId,
|
|
2332
|
-
sessionId,
|
|
2333
|
-
...(resumeSessionAt === undefined ? {} : { resumeSessionAt }),
|
|
2334
|
-
});
|
|
2335
|
-
}
|
|
2336
2174
|
async rewindFiles(sessionId, userMessageId) {
|
|
2337
2175
|
const transcript = new NativeSessionTranscript({
|
|
2338
2176
|
sessionId,
|
|
2339
2177
|
store: this.store(sessionId),
|
|
2340
2178
|
});
|
|
2341
2179
|
await transcript.withLease({ kind: 'resume' }, async (lease) => {
|
|
2342
|
-
const projected =
|
|
2180
|
+
const projected = projectNativeSessionEntries(lease.activeEvents()).filter((entry) => entry.type === 'file-history-snapshot' ||
|
|
2343
2181
|
entry.type === 'file-history-delta');
|
|
2344
2182
|
if (projected.length === 0)
|
|
2345
2183
|
throw new Error(`Native session has no file history: ${sessionId}`);
|
|
@@ -2641,7 +2479,7 @@ export class ClaudeSessionService {
|
|
|
2641
2479
|
const runUnderLease = async (lease, nativeLease) => {
|
|
2642
2480
|
const loadedSnapshot = await lease.load();
|
|
2643
2481
|
let snapshot = {
|
|
2644
|
-
entries:
|
|
2482
|
+
entries: projectNativeSessionEntries(nativeLease.activeEvents()),
|
|
2645
2483
|
tail: loadedSnapshot.tail,
|
|
2646
2484
|
};
|
|
2647
2485
|
const activeTurnMessages = () => {
|
|
@@ -2649,7 +2487,7 @@ export class ClaudeSessionService {
|
|
|
2649
2487
|
// after each append so memory/compaction sees the live branch.
|
|
2650
2488
|
snapshot = {
|
|
2651
2489
|
...snapshot,
|
|
2652
|
-
entries:
|
|
2490
|
+
entries: projectNativeSessionEntries(nativeLease.activeEvents()),
|
|
2653
2491
|
};
|
|
2654
2492
|
return nativeLease.activeMessages();
|
|
2655
2493
|
};
|
|
@@ -3654,7 +3492,7 @@ export class ClaudeSessionService {
|
|
|
3654
3492
|
}
|
|
3655
3493
|
}
|
|
3656
3494
|
const memorySelection = sessionMemory
|
|
3657
|
-
? await this.selectMemoryPreservedCompact(sessionId,
|
|
3495
|
+
? await this.selectMemoryPreservedCompact(sessionId, projectNativeSessionEntries(activeNativeLease.activeEvents()))
|
|
3658
3496
|
: null;
|
|
3659
3497
|
if (memorySelection) {
|
|
3660
3498
|
compactableMessages = [
|
|
@@ -4350,7 +4188,8 @@ export class ClaudeSessionService {
|
|
|
4350
4188
|
}
|
|
4351
4189
|
const memorySnapshot = activeTurnMessages();
|
|
4352
4190
|
const nativeMemoryMessageId = nativeLease
|
|
4353
|
-
?
|
|
4191
|
+
? projectNativeSessionEntries(nativeLease.activeEvents()).at(-1)
|
|
4192
|
+
?.uuid
|
|
4354
4193
|
: undefined;
|
|
4355
4194
|
const providerVisibleMessages = [
|
|
4356
4195
|
...contextMessages,
|
|
@@ -35,8 +35,15 @@ export interface TopLevelAgentManagerOptions {
|
|
|
35
35
|
cliPath: string;
|
|
36
36
|
executablePath?: string;
|
|
37
37
|
environment?: NodeJS.ProcessEnv;
|
|
38
|
+
resolveProviderEnvironment?: (request: {
|
|
39
|
+
cwd: string;
|
|
40
|
+
argv: readonly string[];
|
|
41
|
+
}) => Promise<ProviderEnvironmentOverride>;
|
|
38
42
|
version: string;
|
|
39
43
|
}
|
|
44
|
+
export interface ProviderEnvironmentOverride {
|
|
45
|
+
PRAXIS_API_KEY?: string;
|
|
46
|
+
}
|
|
40
47
|
export declare function topLevelAgentProcessRegistryRoot(configRoot: string, dataPlane?: DataPlane): string;
|
|
41
48
|
export declare class TopLevelAgentManager {
|
|
42
49
|
private readonly options;
|
|
@@ -24,8 +24,11 @@ const SOCKET_RETRY_INTERVAL_MS = 25;
|
|
|
24
24
|
// own provider from the same CLI/environment contract.
|
|
25
25
|
const WORKER_RUNTIME_ENVIRONMENT = [
|
|
26
26
|
'PRAXIS_API_KEY',
|
|
27
|
+
'CLAUDE_CODE_SIMPLE',
|
|
27
28
|
'PRAXIS_MODEL',
|
|
28
29
|
'PRAXIS_PROVIDER',
|
|
30
|
+
'PRAXIS_PROVIDER_PROFILE',
|
|
31
|
+
'PRAXIS_PROVIDER_DEADLINE_MS',
|
|
29
32
|
'PRAXIS_BASE_URL',
|
|
30
33
|
'PRAXIS_MAX_OUTPUT_TOKENS',
|
|
31
34
|
'PRAXIS_ANTHROPIC_VERSION',
|
|
@@ -38,10 +41,11 @@ const WORKER_RUNTIME_ENVIRONMENT = [
|
|
|
38
41
|
'PRAXIS_FILES_BASE_URL',
|
|
39
42
|
'PRAXIS_FILES_BEARER_TOKEN',
|
|
40
43
|
'PRAXIS_FILES_API_KEY',
|
|
44
|
+
'PRAXIS_PROVIDER_CREDENTIAL_STORE',
|
|
41
45
|
'PRAXIS_MCP_OAUTH_STORE',
|
|
42
46
|
'CLAUDE_CODE_ENABLE_SDK_FILE_CHECKPOINTING',
|
|
43
47
|
];
|
|
44
|
-
function workerEnvironment(source, configRoot) {
|
|
48
|
+
function workerEnvironment(source, configRoot, resolved) {
|
|
45
49
|
const environment = sanitizeChildEnvironment({ PATH: source.PATH }, {});
|
|
46
50
|
for (const name of WORKER_RUNTIME_ENVIRONMENT) {
|
|
47
51
|
const value = source[name];
|
|
@@ -50,6 +54,8 @@ function workerEnvironment(source, configRoot) {
|
|
|
50
54
|
}
|
|
51
55
|
environment.PRAXIS_DATA_PLANE = 'native';
|
|
52
56
|
environment.PRAXIS_HOME = configRoot;
|
|
57
|
+
if (resolved?.PRAXIS_API_KEY !== undefined)
|
|
58
|
+
environment.PRAXIS_API_KEY = resolved.PRAXIS_API_KEY;
|
|
53
59
|
return environment;
|
|
54
60
|
}
|
|
55
61
|
function socketPath(configRoot, id) {
|
|
@@ -285,8 +291,15 @@ export class TopLevelAgentManager {
|
|
|
285
291
|
throw new Error('Could not allocate agent ID');
|
|
286
292
|
const allocatedExecution = execution;
|
|
287
293
|
let child;
|
|
294
|
+
let resolvedProviderEnvironment;
|
|
288
295
|
const failLaunch = async (error) => {
|
|
289
|
-
const
|
|
296
|
+
const sensitiveValues = [
|
|
297
|
+
...sensitiveEnvironmentValues(this.options.environment ?? process.env),
|
|
298
|
+
...(resolvedProviderEnvironment?.PRAXIS_API_KEY === undefined
|
|
299
|
+
? []
|
|
300
|
+
: [resolvedProviderEnvironment.PRAXIS_API_KEY]),
|
|
301
|
+
];
|
|
302
|
+
const message = redactSensitiveText(error instanceof Error ? error.message : String(error), sensitiveValues);
|
|
290
303
|
const secondary = [];
|
|
291
304
|
if (child?.pid !== undefined) {
|
|
292
305
|
try {
|
|
@@ -321,9 +334,14 @@ export class TopLevelAgentManager {
|
|
|
321
334
|
throw new AggregateError([error, ...secondary], 'Could not start background agent');
|
|
322
335
|
};
|
|
323
336
|
try {
|
|
337
|
+
resolvedProviderEnvironment =
|
|
338
|
+
await this.options.resolveProviderEnvironment?.({
|
|
339
|
+
cwd,
|
|
340
|
+
argv: options.argv,
|
|
341
|
+
});
|
|
324
342
|
child = spawn(this.options.executablePath ?? process.execPath, [this.options.cliPath, '__background-worker', identity.id], {
|
|
325
343
|
cwd,
|
|
326
|
-
env: workerEnvironment(this.options.environment ?? process.env, this.options.configRoot),
|
|
344
|
+
env: workerEnvironment(this.options.environment ?? process.env, this.options.configRoot, resolvedProviderEnvironment),
|
|
327
345
|
detached: true,
|
|
328
346
|
stdio: 'ignore',
|
|
329
347
|
});
|
package/dist/cli/controls.js
CHANGED
|
@@ -206,6 +206,10 @@ export async function resolveCliControls(controls, cwd) {
|
|
|
206
206
|
name: controls.name,
|
|
207
207
|
sessionPersistence: controls.sessionPersistence,
|
|
208
208
|
...(controls.model === undefined ? {} : { model: controls.model }),
|
|
209
|
+
...(controls.provider === undefined ? {} : { provider: controls.provider }),
|
|
210
|
+
...(controls.providerProfile === undefined
|
|
211
|
+
? {}
|
|
212
|
+
: { providerProfile: controls.providerProfile }),
|
|
209
213
|
...(controls.effort === undefined ? {} : { effort: controls.effort }),
|
|
210
214
|
...(controls.thinking === undefined ? {} : { thinking: controls.thinking }),
|
|
211
215
|
...(controls.maxThinkingTokens === undefined
|