praxis-agent 0.36.0 → 0.37.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.
Files changed (86) hide show
  1. package/README.md +16 -2
  2. package/dist/application/durable-follow-up.d.ts +14 -0
  3. package/dist/application/durable-follow-up.js +52 -0
  4. package/dist/application/native-session-transcript.d.ts +72 -0
  5. package/dist/application/native-session-transcript.js +351 -0
  6. package/dist/application/native-sidechain-transcript.d.ts +34 -0
  7. package/dist/application/native-sidechain-transcript.js +156 -0
  8. package/dist/application/session-service.d.ts +15 -0
  9. package/dist/application/session-service.js +611 -69
  10. package/dist/application/subagent-service.d.ts +5 -1
  11. package/dist/application/subagent-service.js +391 -143
  12. package/dist/application/team-agent-runtime.d.ts +29 -0
  13. package/dist/application/team-agent-runtime.js +169 -0
  14. package/dist/application/team-capability.d.ts +27 -0
  15. package/dist/application/team-capability.js +41 -0
  16. package/dist/application/team-lead-operations.d.ts +38 -0
  17. package/dist/application/team-lead-operations.js +155 -0
  18. package/dist/application/team-mailbox.d.ts +54 -0
  19. package/dist/application/team-mailbox.js +196 -0
  20. package/dist/application/team-manager.d.ts +113 -0
  21. package/dist/application/team-manager.js +681 -0
  22. package/dist/application/team-workspace.d.ts +29 -0
  23. package/dist/application/team-workspace.js +197 -0
  24. package/dist/application/transcript-projection.d.ts +2 -0
  25. package/dist/application/transcript-projection.js +30 -1
  26. package/dist/cli/interactive.d.ts +7 -0
  27. package/dist/cli/interactive.js +387 -136
  28. package/dist/cli/tui/claude-style.d.ts +8 -25
  29. package/dist/cli/tui/claude-style.js +158 -160
  30. package/dist/cli/tui/decision-surface-model.d.ts +71 -0
  31. package/dist/cli/tui/decision-surface-model.js +131 -0
  32. package/dist/cli/tui/decision-surface.d.ts +7 -0
  33. package/dist/cli/tui/decision-surface.js +41 -0
  34. package/dist/cli/tui/help-surface-model.d.ts +68 -0
  35. package/dist/cli/tui/help-surface-model.js +131 -0
  36. package/dist/cli/tui/mcp-panel.js +1 -2
  37. package/dist/cli/tui/permission-surface-model.d.ts +195 -0
  38. package/dist/cli/tui/permission-surface-model.js +360 -0
  39. package/dist/cli/tui/permission-surface.d.ts +7 -0
  40. package/dist/cli/tui/permission-surface.js +69 -0
  41. package/dist/cli/tui/presentation-environment.d.ts +32 -0
  42. package/dist/cli/tui/presentation-environment.js +149 -0
  43. package/dist/cli/tui/tool-permission.js +7 -3
  44. package/dist/cli/tui/transcript-presentation.d.ts +19 -1
  45. package/dist/cli/tui/transcript-presentation.js +26 -6
  46. package/dist/cli/tui/transcript-viewport.d.ts +18 -0
  47. package/dist/cli/tui/transcript-viewport.js +517 -111
  48. package/dist/cli/tui/transcript-window-model.d.ts +108 -0
  49. package/dist/cli/tui/transcript-window-model.js +772 -0
  50. package/dist/cli/tui/tui-screen-model.d.ts +114 -0
  51. package/dist/cli/tui/tui-screen-model.js +210 -0
  52. package/dist/cli/tui/tui-view-model.d.ts +2 -49
  53. package/dist/cli/tui/tui-view-model.js +1 -68
  54. package/dist/cli-runtime.d.ts +2 -0
  55. package/dist/cli-runtime.js +397 -32
  56. package/dist/compatibility/claude/transcript-codec.js +6 -0
  57. package/dist/core/permission-policy.d.ts +4 -0
  58. package/dist/core/permission-policy.js +21 -0
  59. package/dist/core/runtime.d.ts +1 -0
  60. package/dist/core/runtime.js +5 -1
  61. package/dist/core/team-mailbox.d.ts +54 -0
  62. package/dist/core/team-mailbox.js +212 -0
  63. package/dist/core/team-ownership.d.ts +84 -0
  64. package/dist/core/team-ownership.js +505 -0
  65. package/dist/core/tool-execution-scheduler.js +6 -1
  66. package/dist/core/transcript-event.d.ts +5 -1
  67. package/dist/core/transcript-event.js +12 -0
  68. package/dist/persistence/claude-transcript-store.d.ts +2 -1
  69. package/dist/persistence/claude-transcript-store.js +2 -10
  70. package/dist/persistence/native-team-mailbox.d.ts +68 -0
  71. package/dist/persistence/native-team-mailbox.js +542 -0
  72. package/dist/persistence/native-team-store.d.ts +34 -0
  73. package/dist/persistence/native-team-store.js +257 -0
  74. package/dist/persistence/native-transcript-store.d.ts +69 -0
  75. package/dist/persistence/native-transcript-store.js +321 -0
  76. package/dist/persistence/transcript-file-append.d.ts +2 -0
  77. package/dist/persistence/transcript-file-append.js +10 -0
  78. package/dist/tools/claude-capabilities.d.ts +4 -0
  79. package/dist/tools/claude-capabilities.js +18 -2
  80. package/dist/tools/team-lead-tools.d.ts +19 -0
  81. package/dist/tools/team-lead-tools.js +479 -0
  82. package/dist/tools/team-member-tools.d.ts +21 -0
  83. package/dist/tools/team-member-tools.js +259 -0
  84. package/package.json +1 -1
  85. package/dist/cli/tui/fullscreen-renderer.d.ts +0 -14
  86. package/dist/cli/tui/fullscreen-renderer.js +0 -13
@@ -0,0 +1,29 @@
1
+ export interface TeamWorkspace {
2
+ readonly cwd: string;
3
+ readonly branch: string | null;
4
+ }
5
+ export interface TeamWorkspaceInput {
6
+ readonly teamId: string;
7
+ readonly taskId: string;
8
+ readonly generation: number;
9
+ readonly access: 'read-only' | 'write';
10
+ }
11
+ export interface TeamWorkspaceProvider {
12
+ acquire(input: TeamWorkspaceInput): Promise<TeamWorkspace>;
13
+ }
14
+ export declare class NativeTeamWorkspaceProvider implements TeamWorkspaceProvider {
15
+ private readonly nativeRoot;
16
+ private readonly cwd;
17
+ private readonly projectIdentity;
18
+ private readonly gitRoot;
19
+ private readonly initialHead;
20
+ private constructor();
21
+ static open(options: {
22
+ nativeRoot: string;
23
+ cwd: string;
24
+ projectIdentity: string;
25
+ }): Promise<NativeTeamWorkspaceProvider>;
26
+ acquire(input: TeamWorkspaceInput): Promise<TeamWorkspace>;
27
+ private hasBranch;
28
+ }
29
+ //# sourceMappingURL=team-workspace.d.ts.map
@@ -0,0 +1,197 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { lstat, mkdir, realpath } from 'node:fs/promises';
3
+ import { createHash } from 'node:crypto';
4
+ import { isAbsolute, relative, resolve } from 'node:path';
5
+ import { promisify } from 'node:util';
6
+ import { resolveProjectIdentity } from '../platform/project-identity.js';
7
+ import { sanitizeProjectPath } from '../platform/project-path-key.js';
8
+ import { parseTeamId } from '../core/team-ownership.js';
9
+ const execFileAsync = promisify(execFile);
10
+ async function git(cwd, args) {
11
+ return (await execFileAsync('git', ['-C', cwd, ...args], { encoding: 'utf8' })).stdout.trim();
12
+ }
13
+ async function canonical(path) {
14
+ try {
15
+ return await realpath(path);
16
+ }
17
+ catch (error) {
18
+ if (error.code === 'ENOENT')
19
+ return resolve(path);
20
+ throw error;
21
+ }
22
+ }
23
+ function assertInput(input) {
24
+ parseTeamId(input.teamId);
25
+ if (typeof input.taskId !== 'string' || input.taskId.trim() === '')
26
+ throw new Error('Invalid Team task ID');
27
+ if (!Number.isSafeInteger(input.generation) || input.generation <= 0)
28
+ throw new Error('Invalid Team generation');
29
+ if (input.access !== 'read-only' && input.access !== 'write')
30
+ throw new Error('Invalid Team workspace access');
31
+ }
32
+ async function registeredWorktrees(root) {
33
+ const output = await git(root, ['worktree', 'list', '--porcelain']);
34
+ const result = new Map();
35
+ let path;
36
+ for (const line of output.split('\n')) {
37
+ if (line.startsWith('worktree '))
38
+ path = resolve(line.slice('worktree '.length));
39
+ else if (line.startsWith('branch ') && path) {
40
+ result.set(await canonical(path), line.slice('branch '.length));
41
+ path = undefined;
42
+ }
43
+ }
44
+ if (path)
45
+ result.set(await canonical(path), '');
46
+ return result;
47
+ }
48
+ export class NativeTeamWorkspaceProvider {
49
+ nativeRoot;
50
+ cwd;
51
+ projectIdentity;
52
+ gitRoot;
53
+ initialHead;
54
+ constructor(nativeRoot, cwd, projectIdentity, gitRoot, initialHead) {
55
+ this.nativeRoot = nativeRoot;
56
+ this.cwd = cwd;
57
+ this.projectIdentity = projectIdentity;
58
+ this.gitRoot = gitRoot;
59
+ this.initialHead = initialHead;
60
+ }
61
+ static async open(options) {
62
+ if (typeof options.nativeRoot !== 'string' ||
63
+ options.nativeRoot.trim() === '')
64
+ throw new Error('Invalid native Team root');
65
+ const canonicalCwd = await canonical(options.cwd);
66
+ const identity = await resolveProjectIdentity(canonicalCwd);
67
+ if (identity !== options.projectIdentity)
68
+ throw new Error('Team workspace project identity mismatch');
69
+ let gitRoot = null;
70
+ let initialHead = null;
71
+ try {
72
+ gitRoot = await canonical(await git(canonicalCwd, ['rev-parse', '--show-toplevel']));
73
+ initialHead = await git(gitRoot, ['rev-parse', 'HEAD']);
74
+ }
75
+ catch {
76
+ // A read-only member can safely use a non-Git project root.
77
+ }
78
+ return new NativeTeamWorkspaceProvider(await canonical(options.nativeRoot), canonicalCwd, options.projectIdentity, gitRoot, initialHead);
79
+ }
80
+ async acquire(input) {
81
+ assertInput(input);
82
+ if (input.access === 'read-only') {
83
+ return { cwd: this.cwd, branch: null };
84
+ }
85
+ if (this.gitRoot === null || this.initialHead === null)
86
+ throw new Error('Team write workspace requires a Git repository');
87
+ const hash = createHash('sha256')
88
+ .update(`${this.projectIdentity}\0${input.teamId}\0${input.taskId}\0${input.generation}`)
89
+ .digest('hex')
90
+ .slice(0, 24);
91
+ const branch = `praxis/team/${input.teamId}/${hash}`;
92
+ const path = resolve(this.nativeRoot, 'state', 'team-worktrees', sanitizeProjectPath(this.projectIdentity), input.teamId, hash);
93
+ if (!isAbsolute(path) ||
94
+ !isAbsolute(this.nativeRoot) ||
95
+ !isPathWithin(this.nativeRoot, path))
96
+ throw new Error('Invalid Team workspace path');
97
+ await assertSafeParents(this.nativeRoot, path);
98
+ let pathExists = false;
99
+ try {
100
+ const entry = await lstat(path);
101
+ if (entry.isSymbolicLink())
102
+ throw new Error('Team worktree path must not be a symlink');
103
+ if (!entry.isDirectory())
104
+ throw new Error(`Team worktree path is not a directory: ${path}`);
105
+ pathExists = true;
106
+ }
107
+ catch (error) {
108
+ if (error.code !== 'ENOENT')
109
+ throw error;
110
+ }
111
+ const currentHead = await git(this.gitRoot, ['rev-parse', 'HEAD']);
112
+ const registered = await registeredWorktrees(this.gitRoot);
113
+ const registeredBranch = registered.get(path);
114
+ const branchExists = await this.hasBranch(branch);
115
+ if (!pathExists && !branchExists) {
116
+ await mkdir(resolve(path, '..'), { recursive: true });
117
+ try {
118
+ await git(this.gitRoot, [
119
+ 'worktree',
120
+ 'add',
121
+ '-b',
122
+ branch,
123
+ path,
124
+ currentHead,
125
+ ]);
126
+ }
127
+ catch (error) {
128
+ throw new Error(`Could not create Team worktree: ${error.message}`);
129
+ }
130
+ return { cwd: path, branch };
131
+ }
132
+ if (!pathExists || !branchExists)
133
+ throw new Error(`Team workspace collision at ${path}`);
134
+ if (registeredBranch !== `refs/heads/${branch}`)
135
+ throw new Error(`Team worktree registration mismatch at ${path}`);
136
+ if ((await canonical(path)) !== path)
137
+ throw new Error(`Team worktree path must be canonical: ${path}`);
138
+ const worktreeRoot = await canonical(await git(path, ['rev-parse', '--show-toplevel']));
139
+ if (worktreeRoot !== path ||
140
+ (await resolveProjectIdentity(path)) !== this.projectIdentity)
141
+ throw new Error(`Team worktree repository mismatch at ${path}`);
142
+ const currentBranch = await git(path, [
143
+ 'symbolic-ref',
144
+ '--quiet',
145
+ '--short',
146
+ 'HEAD',
147
+ ]);
148
+ if (currentBranch !== branch)
149
+ throw new Error(`Team worktree branch mismatch at ${path}`);
150
+ return { cwd: path, branch };
151
+ }
152
+ async hasBranch(branch) {
153
+ try {
154
+ await git(this.gitRoot, [
155
+ 'show-ref',
156
+ '--verify',
157
+ '--quiet',
158
+ `refs/heads/${branch}`,
159
+ ]);
160
+ return true;
161
+ }
162
+ catch (error) {
163
+ const code = error.code;
164
+ if (code === 1)
165
+ return false;
166
+ throw error;
167
+ }
168
+ }
169
+ }
170
+ function isPathWithin(root, candidate) {
171
+ const path = relative(resolve(root), resolve(candidate));
172
+ return path === '' || (!path.startsWith('..') && !isAbsolute(path));
173
+ }
174
+ async function assertSafeParents(root, path) {
175
+ let current = resolve(path, '..');
176
+ const nativeRoot = resolve(root);
177
+ for (;;) {
178
+ try {
179
+ const entry = await lstat(current);
180
+ if (entry.isSymbolicLink())
181
+ throw new Error(`Team worktree parent must not be a symlink: ${current}`);
182
+ if (!entry.isDirectory())
183
+ throw new Error(`Team worktree parent is not a directory: ${current}`);
184
+ }
185
+ catch (error) {
186
+ if (error.code !== 'ENOENT')
187
+ throw error;
188
+ }
189
+ if (current === nativeRoot || !isPathWithin(nativeRoot, current))
190
+ return;
191
+ const parent = resolve(current, '..');
192
+ if (parent === current)
193
+ return;
194
+ current = parent;
195
+ }
196
+ }
197
+ //# sourceMappingURL=team-workspace.js.map
@@ -28,6 +28,8 @@ export type TranscriptDisplayItem = {
28
28
  };
29
29
  export declare function activeEvents(events: readonly TranscriptEvent[], checkpoint?: string): TranscriptEvent[];
30
30
  export declare function projectActiveMessages(events: readonly TranscriptEvent[], checkpoint?: string): ModelMessage[];
31
+ /** Return tool calls whose results are absent from the selected active branch. */
32
+ export declare function unresolvedActiveToolCallIds(messages: readonly ModelMessage[]): string[];
31
33
  export declare function projectTranscriptDisplay(events: readonly TranscriptEvent[], checkpoint?: string): TranscriptDisplayItem[];
32
34
  export declare function lastUserPrompt(events: readonly TranscriptEvent[], checkpoint?: string): string | null;
33
35
  //# sourceMappingURL=transcript-projection.d.ts.map
@@ -40,7 +40,36 @@ export function activeEvents(events, checkpoint) {
40
40
  return selected.reverse();
41
41
  }
42
42
  export function projectActiveMessages(events, checkpoint) {
43
- return activeEvents(events, checkpoint).flatMap((event) => event.kind === 'messages' ? [...event.messages] : []);
43
+ const messages = [];
44
+ for (const event of activeEvents(events, checkpoint)) {
45
+ if (event.kind === 'context-boundary') {
46
+ messages.length = 0;
47
+ continue;
48
+ }
49
+ if (event.kind === 'context-summary') {
50
+ messages.push({ role: 'user', content: event.summary });
51
+ continue;
52
+ }
53
+ if (event.kind === 'tool-execution-started')
54
+ continue;
55
+ messages.push(...event.messages);
56
+ }
57
+ return messages;
58
+ }
59
+ /** Return tool calls whose results are absent from the selected active branch. */
60
+ export function unresolvedActiveToolCallIds(messages) {
61
+ const calls = new Set();
62
+ const results = new Set();
63
+ for (const message of messages) {
64
+ if (message.role === 'assistant') {
65
+ for (const call of message.toolCalls ?? [])
66
+ calls.add(call.id);
67
+ }
68
+ else if (message.role === 'tool') {
69
+ results.add(message.toolCallId);
70
+ }
71
+ }
72
+ return [...calls].filter((id) => !results.has(id));
44
73
  }
45
74
  export function projectTranscriptDisplay(events, checkpoint) {
46
75
  const items = [];
@@ -7,6 +7,7 @@ import type { ClaudeInteractiveToolCallbacks } from '../tools/claude-interactive
7
7
  import { type ClaudePermissionMode } from '../permissions/claude-permission-resolver.js';
8
8
  import { type TranscriptItem, type TuiDisplayMetadata } from './tui/claude-style.js';
9
9
  import { type TuiMemoryFiles } from './tui/memory-files.js';
10
+ import { type TuiHistoryChange } from './tui/tui-view-model.js';
10
11
  import { type TuiDiffSnapshot } from './tui/git-diff.js';
11
12
  import { type TuiPermissionBehavior, type TuiPermissionRule } from './tui/permission-settings.js';
12
13
  import { type RecentlyDeniedStore } from './tui/recently-denied.js';
@@ -196,6 +197,12 @@ interface InteractiveAppProps {
196
197
  releaseNotesLoader?: (configRoot: string) => Promise<string>;
197
198
  settingSources?: readonly ClaudeResourceScope[];
198
199
  }
200
+ export interface InteractiveHistoryState {
201
+ readonly items: TranscriptItem[];
202
+ readonly change: TuiHistoryChange;
203
+ }
204
+ /** Advances the React-owned transcript plus its exact local mutation fact. */
205
+ export declare function advanceInteractiveHistoryState(current: InteractiveHistoryState, items: TranscriptItem[], changedFrom: number): InteractiveHistoryState;
199
206
  export declare function InteractiveApp({ dataPlane, configRoot: suppliedConfigRoot, statePath: suppliedStatePath, factory, initialSessions, initialPrompt, initialHistory, initialSessionColor, signal, onCancel, onTurnChange, onCleanup, onRendererChange, onBackground, axScreenReader, allowNewSession, resume, display, terminalWidth, slashCommands, agents, allowDangerouslySkipPermissions, additionalDirectories, diffLoader, doctorLoader, fileLoader, externalEditor, keybindingsConfigRoot, keybindingsFile, keybindingsLoader, keybindingsEditor, memoryFilesLoader, memoryEditor, memoryFolderOpener, suspendProcess, clipboardReader, clipboardWriter, sideQuestionClipboardWriter, exportWriter, permissionRuleStore, sandboxStore: suppliedSandboxStore, recentlyDeniedStore: suppliedRecentlyDeniedStore, terminalSetup: terminalSetupOverride, themeStore, initialThemeSettings, initialThemeLoadError, workspaceDirectoryResolver, workspaceDirectoryCompleter, runtimeSettings: suppliedRuntimeSettings, runtimeSettingsTarget, notificationWriter, notificationDelayMs, elicitationUrlOpener, releaseNotesLoader, settingSources, }: InteractiveAppProps): import("react").JSX.Element;
200
207
  export declare function runInteractive(options: {
201
208
  dataPlane?: DataPlane;