praxis-agent 0.61.0 → 0.62.1

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.
Files changed (36) hide show
  1. package/README.md +32 -8
  2. package/dist/application/agent-worktree-owner.d.ts +12 -0
  3. package/dist/application/agent-worktree-owner.js +35 -0
  4. package/dist/application/agent-worktree.d.ts +19 -0
  5. package/dist/application/agent-worktree.js +96 -0
  6. package/dist/application/managed-worktree-hooks.d.ts +11 -0
  7. package/dist/application/managed-worktree-hooks.js +23 -0
  8. package/dist/application/managed-worktree.d.ts +70 -0
  9. package/dist/application/managed-worktree.js +1081 -108
  10. package/dist/application/subagent-service.d.ts +3 -1
  11. package/dist/application/subagent-service.js +106 -23
  12. package/dist/application/team-capability.d.ts +4 -0
  13. package/dist/application/team-capability.js +20 -0
  14. package/dist/application/team-manager.d.ts +2 -0
  15. package/dist/application/team-manager.js +207 -52
  16. package/dist/application/team-observability.d.ts +5 -0
  17. package/dist/application/team-observability.js +152 -32
  18. package/dist/application/team-workspace.d.ts +18 -1
  19. package/dist/application/team-workspace.js +217 -71
  20. package/dist/application/workflow-worktree.d.ts +2 -8
  21. package/dist/application/workflow-worktree.js +2 -23
  22. package/dist/cli/tui/doctor-dashboard.js +1 -0
  23. package/dist/cli-runtime.js +5 -0
  24. package/dist/core/context-budget.js +8 -4
  25. package/dist/maintenance/doctor.d.ts +1 -1
  26. package/dist/maintenance/doctor.js +55 -0
  27. package/dist/persistence/managed-worktree-store.d.ts +16 -1
  28. package/dist/persistence/managed-worktree-store.js +61 -21
  29. package/dist/platform/exclusive-file-lease.d.ts +6 -1
  30. package/dist/platform/exclusive-file-lease.js +48 -7
  31. package/dist/providers/anthropic-compatible.d.ts +6 -0
  32. package/dist/providers/anthropic-compatible.js +20 -14
  33. package/dist/providers/anthropic-model-spec.d.ts +9 -0
  34. package/dist/providers/anthropic-model-spec.js +15 -0
  35. package/dist/providers/provider-registry.js +1 -4
  36. package/package.json +1 -1
package/README.md CHANGED
@@ -98,6 +98,13 @@ cd /path/to/project
98
98
  praxis
99
99
  ```
100
100
 
101
+ Anthropic models use a 200,000-token context window by default, including
102
+ unknown model IDs. Add the exact terminal `[1m]` suffix (for example,
103
+ `claude-sonnet-4-20250514[1m]`) to request a 1,000,000-token context window;
104
+ Praxis keeps that selected model public, removes the suffix on the wire, and
105
+ adds the `context-1m-2025-08-07` Anthropic beta once. An explicit
106
+ `PRAXIS_CONTEXT_WINDOW_TOKENS` value overrides either inferred window.
107
+
101
108
  Common non-interactive operations:
102
109
 
103
110
  ```sh
@@ -177,7 +184,7 @@ troubleshooting. Run `praxis --help` for the authoritative command surface.
177
184
  patches and readable binary/conflict/transient-path notes, semantic plan/question decision panels with complete
178
185
  screen-reader actions, semantic screen projection across selectable surfaces,
179
186
  deterministic resize-aware URL/form elicitation rendering, and measured
180
- context budgets; print mode,
187
+ context budgets with base64-payload-independent image estimates; print mode,
181
188
  structured JSON/JSONL, context compaction, tool loops, and bounded execution.
182
189
  - **Built-in tools** — read, write, edit, `ApplyPatch` for bounded ordered exact
183
190
  multi-file replacements, configured plugin LSP navigation with fresh bounded
@@ -215,11 +222,16 @@ troubleshooting. Run `praxis --help` for the authoritative command surface.
215
222
  tool, memory, first-turn, and resume behavior. Agent execution uses one
216
223
  durable lifecycle vocabulary with bounded cancellation and drain,
217
224
  continuation, notifications, and single-owner orphan recovery. Isolated
218
- Workflow turns use ownership-recorded repo-local temporary worktrees, run
219
- trust-admitted synchronous lifecycle hooks, safely roll back blocked
220
- creation, and retain dirty, committed, unsafe, or removal-blocked results
221
- for inspection. Experimental
222
- local Teams (`PRAXIS_ENABLE_TEAMS=true`) stay absent from ordinary startup by
225
+ Workflow and Agent turns use isolated repo-local worktrees with trusted
226
+ synchronous lifecycle hooks. Blocked creation rolls back safely; unchanged
227
+ worktrees are cleaned up, while failed, dirty, committed, or otherwise
228
+ unsafe worktrees retain evidence for inspection. Restore accepts only an
229
+ owned current or exact historical Agent path; invalid or unavailable
230
+ evidence safely falls back to the parent cwd. Managed worktrees expose
231
+ bounded five-state lifecycle diagnostics through `praxis doctor`, while
232
+ Team `status`, `logs`, and `attach` surface the same read-only lifecycle
233
+ evidence. Experimental local Teams (`PRAXIS_ENABLE_TEAMS=true`) stay absent
234
+ from ordinary startup by
223
235
  default and add durable task ownership plus one ordered mailbox with stable
224
236
  identities, fixed broadcast recipients, durable cursors, bounded retention,
225
237
  and bounded model-context projection. Teams are explicitly experimental and
@@ -235,7 +247,17 @@ troubleshooting. Run `praxis --help` for the authoritative command surface.
235
247
  CLI also exposes `praxis team status`, `logs`, and `attach` in human or JSON
236
248
  form; durable-local attach does not require tmux. Native task, notification,
237
249
  context, and Team resume/inbox seams are implemented with fail-closed
238
- validation for unsupported payloads.
250
+ validation for unsupported payloads. New write-capable Team generations use
251
+ repo-local durable worktrees under `.praxis/worktrees/team/<team-id>/<generation-hash>`
252
+ with exact ownership records, markers, branches, and execution tokens. Read-only
253
+ Team members continue to use the invocation checkout. Completion, failure,
254
+ cancellation, orphaning, stop, and persistence uncertainty retain writer
255
+ evidence and relinquish the active lease; only a durably persisted explicit
256
+ Lead `accepted` decision may release that exact generation, while rejection
257
+ retains it. The ownership-verified accepted path may explicitly dispose of
258
+ reviewed dirty or committed evidence; hook blocks and ownership uncertainty
259
+ fail closed. Exact historical global Team worktrees remain validation-only
260
+ compatibility and are never implicitly adopted.
239
261
  - **Native resource ecosystem** — shared Praxis instructions with recursive `@`
240
262
  imports, memory, skills, commands, agents, hooks, settings, MCP servers,
241
263
  plugins, and append-only `praxis.transcript` JSONL sessions under `~/.praxis`,
@@ -279,7 +301,9 @@ troubleshooting. Run `praxis --help` for the authoritative command surface.
279
301
  route, whether primary or fallback, stays sticky only through that logical
280
302
  Turn's tool continuations; incompatible routes fail closed, and the next
281
303
  independent Turn starts from primary. Recovery may persist only an optional
282
- selected model, never provider route or wire state.
304
+ selected model, never provider route or wire state. Anthropic uses a
305
+ 200,000-token default or exact terminal `[1m]` model syntax for 1,000,000
306
+ tokens; `PRAXIS_CONTEXT_WINDOW_TOKENS` overrides the advertised window.
283
307
  - **Transactional self-update** — `praxis update` verifies the package before
284
308
  installing it, rejects concurrent updates, and can roll back after an
285
309
  interruption or crash.
@@ -0,0 +1,12 @@
1
+ export interface AgentWorktreeOwnerIdentity {
2
+ readonly sessionId: string;
3
+ readonly agentId: string;
4
+ readonly executionToken: string;
5
+ }
6
+ export declare function formatAgentWorktreeOwner(identity: AgentWorktreeOwnerIdentity): string;
7
+ export declare function formatAgentWorktreeOwnerPrefix(identity: {
8
+ readonly sessionId: string;
9
+ readonly agentId: string;
10
+ }): string;
11
+ export declare function parseAgentWorktreeOwner(ownerId: string): AgentWorktreeOwnerIdentity | null;
12
+ //# sourceMappingURL=agent-worktree-owner.d.ts.map
@@ -0,0 +1,35 @@
1
+ import { isSessionId } from '../core/session.js';
2
+ const AGENT_ID_PATTERN = /^a(?:[A-Za-z0-9][A-Za-z0-9_-]{0,62}-)?[0-9a-f]{16}$/u;
3
+ const EXECUTION_TOKEN_PATTERN = /^[A-Za-z0-9_-]{1,128}$/u;
4
+ function isAgentWorktreeOwnerIdentity(value) {
5
+ return (isSessionId(value.sessionId) &&
6
+ AGENT_ID_PATTERN.test(value.agentId) &&
7
+ EXECUTION_TOKEN_PATTERN.test(value.executionToken));
8
+ }
9
+ function isAgentWorktreeIdentity(value) {
10
+ return isSessionId(value.sessionId) && AGENT_ID_PATTERN.test(value.agentId);
11
+ }
12
+ function assertAgentWorktreeOwnerIdentity(value) {
13
+ if (!isAgentWorktreeOwnerIdentity(value))
14
+ throw new Error('Invalid Agent worktree owner identity');
15
+ }
16
+ export function formatAgentWorktreeOwner(identity) {
17
+ assertAgentWorktreeOwnerIdentity(identity);
18
+ return `agent:${identity.sessionId}:${identity.agentId}:${identity.executionToken}`;
19
+ }
20
+ export function formatAgentWorktreeOwnerPrefix(identity) {
21
+ if (!isAgentWorktreeIdentity(identity))
22
+ throw new Error('Invalid Agent worktree owner identity');
23
+ return `agent:${identity.sessionId}:${identity.agentId}:`;
24
+ }
25
+ export function parseAgentWorktreeOwner(ownerId) {
26
+ const parts = ownerId.split(':');
27
+ if (parts.length !== 4 || parts[0] !== 'agent')
28
+ return null;
29
+ const sessionId = parts[1] ?? '';
30
+ const agentId = parts[2] ?? '';
31
+ const executionToken = parts[3] ?? '';
32
+ const identity = { sessionId, agentId, executionToken };
33
+ return isAgentWorktreeOwnerIdentity(identity) ? identity : null;
34
+ }
35
+ //# sourceMappingURL=agent-worktree-owner.js.map
@@ -0,0 +1,19 @@
1
+ import { type ManagedWorktree } from './managed-worktree.js';
2
+ import { type ManagedWorktreeHookContext } from './managed-worktree-hooks.js';
3
+ export declare function createAgentWorktree(options: {
4
+ cwd: string;
5
+ stateRoot: string;
6
+ sessionId: string;
7
+ agentId: string;
8
+ executionToken: string;
9
+ hookContext?: ManagedWorktreeHookContext;
10
+ }): Promise<ManagedWorktree>;
11
+ export declare function restoreAgentWorktree(options: {
12
+ cwd: string;
13
+ stateRoot: string;
14
+ sessionId: string;
15
+ agentId: string;
16
+ path: string;
17
+ hookContext?: ManagedWorktreeHookContext;
18
+ }): Promise<ManagedWorktree>;
19
+ //# sourceMappingURL=agent-worktree.d.ts.map
@@ -0,0 +1,96 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { lstat, realpath } from 'node:fs/promises';
3
+ import { isAbsolute, join, resolve } from 'node:path';
4
+ import { promisify } from 'node:util';
5
+ import { createOwnedManagedWorktree, restoreManagedWorktree, restoreOwnedManagedWorktree, } from './managed-worktree.js';
6
+ import { createManagedWorktreeHooks, } from './managed-worktree-hooks.js';
7
+ import { resolveProjectIdentity } from '../platform/project-identity.js';
8
+ import { formatAgentWorktreeOwner, formatAgentWorktreeOwnerPrefix, } from './agent-worktree-owner.js';
9
+ const execFileAsync = promisify(execFile);
10
+ function agentWorktreeDirectoryName(sessionId, agentId) {
11
+ return `${sessionId}-${agentId}`;
12
+ }
13
+ export async function createAgentWorktree(options) {
14
+ return createOwnedManagedWorktree({
15
+ cwd: options.cwd,
16
+ stateRoot: options.stateRoot,
17
+ directoryName: agentWorktreeDirectoryName(options.sessionId, options.agentId),
18
+ ownerId: formatAgentWorktreeOwner({
19
+ sessionId: options.sessionId,
20
+ agentId: options.agentId,
21
+ executionToken: options.executionToken,
22
+ }),
23
+ label: 'Agent',
24
+ kind: 'agent',
25
+ policy: 'ephemeral',
26
+ ...(options.hookContext
27
+ ? { hooks: createManagedWorktreeHooks(options.hookContext) }
28
+ : {}),
29
+ });
30
+ }
31
+ async function git(cwd, args) {
32
+ return (await execFileAsync('git', ['-C', cwd, ...args], { encoding: 'utf8' })).stdout.trim();
33
+ }
34
+ async function assertLegacyPath(options) {
35
+ if (!isAbsolute(options.path) ||
36
+ options.path.includes('\0') ||
37
+ resolve(options.path) !== options.expectedPath) {
38
+ throw new Error('retained Agent worktree path is not the legacy path');
39
+ }
40
+ const entry = await lstat(options.path);
41
+ if (entry.isSymbolicLink() || !entry.isDirectory()) {
42
+ throw new Error('retained Agent worktree must be a real directory');
43
+ }
44
+ const canonicalPath = await realpath(options.path);
45
+ const repositoryRoot = await resolveProjectIdentity(options.cwd);
46
+ if (repositoryRoot !== (await resolveProjectIdentity(options.path))) {
47
+ throw new Error('retained Agent worktree repository identity does not match');
48
+ }
49
+ const registrations = await git(options.cwd, [
50
+ 'worktree',
51
+ 'list',
52
+ '--porcelain',
53
+ '-z',
54
+ ]);
55
+ const registered = registrations
56
+ .split('\0')
57
+ .filter((line) => line.startsWith('worktree '))
58
+ .map((line) => resolve(repositoryRoot, line.slice('worktree '.length)));
59
+ if (!registered.includes(options.path) &&
60
+ !registered.includes(canonicalPath)) {
61
+ throw new Error('retained Agent worktree is not registered');
62
+ }
63
+ }
64
+ export async function restoreAgentWorktree(options) {
65
+ if (!isAbsolute(options.path) || options.path.includes('\0')) {
66
+ throw new Error('retained Agent worktree path is invalid');
67
+ }
68
+ const managedPath = join(await resolveProjectIdentity(options.cwd), '.praxis', 'worktrees', 'agent', agentWorktreeDirectoryName(options.sessionId, options.agentId));
69
+ const path = resolve(options.path);
70
+ if (path === managedPath) {
71
+ return restoreOwnedManagedWorktree({
72
+ cwd: options.cwd,
73
+ stateRoot: options.stateRoot,
74
+ path,
75
+ directoryName: agentWorktreeDirectoryName(options.sessionId, options.agentId),
76
+ ownerPrefix: formatAgentWorktreeOwnerPrefix(options),
77
+ label: 'Agent',
78
+ kind: 'agent',
79
+ policy: 'ephemeral',
80
+ ...(options.hookContext
81
+ ? { hooks: createManagedWorktreeHooks(options.hookContext) }
82
+ : {}),
83
+ });
84
+ }
85
+ const legacyPath = join(resolve(options.stateRoot), 'agent-worktrees', agentWorktreeDirectoryName(options.sessionId, options.agentId));
86
+ if (path !== legacyPath) {
87
+ throw new Error('retained Agent worktree path is not an accepted path');
88
+ }
89
+ await assertLegacyPath({
90
+ cwd: options.cwd,
91
+ path,
92
+ expectedPath: legacyPath,
93
+ });
94
+ return restoreManagedWorktree({ cwd: options.cwd, path, label: 'Agent' });
95
+ }
96
+ //# sourceMappingURL=agent-worktree.js.map
@@ -0,0 +1,11 @@
1
+ import type { ClaudeHookRunner } from '../hooks/claude-hooks.js';
2
+ import type { ManagedWorktreeHooks } from './managed-worktree.js';
3
+ export interface ManagedWorktreeHookContext {
4
+ runner: ClaudeHookRunner;
5
+ sessionId: string;
6
+ transcriptPath: string;
7
+ permissionMode: string;
8
+ signal?: AbortSignal;
9
+ }
10
+ export declare function createManagedWorktreeHooks(context: ManagedWorktreeHookContext): ManagedWorktreeHooks;
11
+ //# sourceMappingURL=managed-worktree-hooks.d.ts.map
@@ -0,0 +1,23 @@
1
+ function lifecycleHookInput(input, context, event) {
2
+ const reason = 'reason' in input ? input.reason : undefined;
3
+ return {
4
+ session_id: context.sessionId,
5
+ transcript_path: context.transcriptPath,
6
+ cwd: input.worktreePath,
7
+ permission_mode: context.permissionMode,
8
+ hook_event_name: event,
9
+ worktree_path: input.worktreePath,
10
+ worktree_kind: input.worktreeKind,
11
+ worktree_id: input.worktreeId,
12
+ owner_id: input.ownerId,
13
+ base_commit: input.baseCommit,
14
+ ...(event === 'WorktreeRemove' && reason !== undefined ? { reason } : {}),
15
+ };
16
+ }
17
+ export function createManagedWorktreeHooks(context) {
18
+ return {
19
+ afterCreate: async (input) => context.runner.run(lifecycleHookInput(input, context, 'WorktreeCreate'), input.worktreeKind, context.signal),
20
+ beforeRemove: async (input) => context.runner.run(lifecycleHookInput(input, context, 'WorktreeRemove'), input.worktreeKind, context.signal),
21
+ };
22
+ }
23
+ //# sourceMappingURL=managed-worktree-hooks.js.map
@@ -1,3 +1,4 @@
1
+ import { type ManagedWorktreeRecord } from '../persistence/managed-worktree-store.js';
1
2
  export interface ManagedWorktreeCleanup {
2
3
  retained: boolean;
3
4
  reason?: string;
@@ -5,6 +6,8 @@ export interface ManagedWorktreeCleanup {
5
6
  export interface ManagedWorktree {
6
7
  cwd: string;
7
8
  cleanup(): Promise<ManagedWorktreeCleanup>;
9
+ retain(reason: string): Promise<ManagedWorktreeCleanup>;
10
+ release(): Promise<ManagedWorktreeCleanup>;
8
11
  }
9
12
  export interface ManagedWorktreeHookInput {
10
13
  readonly worktreePath: string;
@@ -26,14 +29,81 @@ export interface ManagedWorktreeHooks {
26
29
  export interface OwnedManagedWorktreeOptions {
27
30
  cwd: string;
28
31
  stateRoot: string;
32
+ parentDirectoryName?: string;
29
33
  directoryName: string;
34
+ branch?: string;
30
35
  ownerId: string;
31
36
  label: 'Agent' | 'Workflow' | 'Team';
32
37
  kind: 'workflow' | 'agent' | 'team';
33
38
  policy: 'ephemeral' | 'durable';
34
39
  hooks?: ManagedWorktreeHooks;
35
40
  }
41
+ export type ManagedWorktreeReconciliationDisposition = 'released' | 'retained' | 'skipped' | 'invalid';
42
+ export interface ManagedWorktreeReconciliationEntry {
43
+ recordPath: string;
44
+ worktreeId?: string;
45
+ disposition: ManagedWorktreeReconciliationDisposition;
46
+ reason: string;
47
+ }
48
+ export interface ManagedWorktreeReconciliationResult {
49
+ repositoryRoot: string;
50
+ inspected: number;
51
+ truncated: boolean;
52
+ entries: readonly ManagedWorktreeReconciliationEntry[];
53
+ }
54
+ export type ManagedWorktreeHealthStatus = 'active' | 'retained' | 'safely-releasable' | 'released' | 'unsafe';
55
+ export interface ManagedWorktreeHealthEntry {
56
+ recordPath: string;
57
+ worktreeId: string | null;
58
+ kind: ManagedWorktreeRecord['kind'] | null;
59
+ policy: ManagedWorktreeRecord['policy'] | null;
60
+ recordState: ManagedWorktreeRecord['state'] | null;
61
+ worktreePath: string | null;
62
+ branch: string | null;
63
+ present: boolean | null;
64
+ status: ManagedWorktreeHealthStatus;
65
+ reason: string;
66
+ }
67
+ export interface ManagedWorktreeHealthReport {
68
+ repositoryRoot: string;
69
+ inspected: number;
70
+ truncated: boolean;
71
+ counts: {
72
+ active: number;
73
+ retained: number;
74
+ safelyReleasable: number;
75
+ released: number;
76
+ unsafe: number;
77
+ };
78
+ entries: readonly ManagedWorktreeHealthEntry[];
79
+ }
80
+ export declare function inspectManagedWorktreeHealth(options: {
81
+ cwd: string;
82
+ stateRoot: string;
83
+ limit?: number;
84
+ }): Promise<ManagedWorktreeHealthReport>;
85
+ export declare function reconcileManagedWorktrees(options: {
86
+ cwd: string;
87
+ stateRoot: string;
88
+ hooks?: ManagedWorktreeHooks;
89
+ }): Promise<ManagedWorktreeReconciliationResult>;
36
90
  export declare function createOwnedManagedWorktree(options: OwnedManagedWorktreeOptions): Promise<ManagedWorktree>;
91
+ export interface OwnedManagedWorktreeRestoreOptions {
92
+ cwd: string;
93
+ stateRoot: string;
94
+ path: string;
95
+ parentDirectoryName?: string;
96
+ directoryName: string;
97
+ ownerPrefix: string;
98
+ ownerId?: string;
99
+ branch?: string;
100
+ label: 'Agent' | 'Workflow' | 'Team';
101
+ kind: 'workflow' | 'agent' | 'team';
102
+ policy: 'ephemeral' | 'durable';
103
+ hooks?: ManagedWorktreeHooks;
104
+ }
105
+ /** Restore a checkout only when its complete managed ownership proof matches. */
106
+ export declare function restoreOwnedManagedWorktree(options: OwnedManagedWorktreeRestoreOptions): Promise<ManagedWorktree>;
37
107
  export declare function createManagedWorktree(options: {
38
108
  cwd: string;
39
109
  parentDirectory: string;