praxis-agent 0.46.1 → 0.46.2
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 +25 -18
- package/dist/application/session-service.d.ts +7 -1
- package/dist/application/session-service.js +42 -9
- package/dist/cli/interactive.d.ts +3 -2
- package/dist/cli/interactive.js +126 -16
- package/dist/cli/tui/decision-surface-model.d.ts +18 -1
- package/dist/cli/tui/decision-surface-model.js +26 -0
- package/dist/cli/tui/decision-surface.js +4 -1
- package/dist/cli/tui/open-url.d.ts +11 -1
- package/dist/cli/tui/open-url.js +27 -7
- package/dist/cli/tui/quiet-choice-rows.js +14 -1
- package/dist/cli/tui/sandbox-settings.d.ts +10 -1
- package/dist/cli/tui/sandbox-settings.js +7 -7
- package/dist/cli/tui/tui-focus-stack.d.ts +1 -0
- package/dist/cli/tui/tui-focus-stack.js +6 -0
- package/dist/cli/tui/tui-interaction-router.d.ts +1 -1
- package/dist/cli-runtime.d.ts +3 -2
- package/dist/cli-runtime.js +2 -1
- package/dist/native/schema.d.ts +0 -4
- package/dist/native/schema.js +1 -23
- package/dist/tools/team-lead-tools.js +24 -5
- package/package.json +8 -5
- package/dist/native/fork.d.ts +0 -9
- package/dist/native/fork.js +0 -260
package/README.md
CHANGED
|
@@ -212,9 +212,13 @@ troubleshooting. Run `praxis --help` for the authoritative command surface.
|
|
|
212
212
|
installing it, rejects concurrent updates, and can roll back after an
|
|
213
213
|
interruption or crash.
|
|
214
214
|
|
|
215
|
-
|
|
216
|
-
[
|
|
217
|
-
|
|
215
|
+
Current qualification status and executable evidence live in the
|
|
216
|
+
[Native Fixture Contracts](https://github.com/Forest-Isle/Praxis/blob/main/docs/NATIVE_FIXTURE_CONTRACTS.md)
|
|
217
|
+
and its machine-readable
|
|
218
|
+
[fixture manifest](https://github.com/Forest-Isle/Praxis/blob/main/test/fixtures/manifest.json).
|
|
219
|
+
The [parity matrix](https://github.com/Forest-Isle/Praxis/blob/main/docs/PARITY_MATRIX.md)
|
|
220
|
+
and [roadmap](https://github.com/Forest-Isle/Praxis/blob/main/docs/ROADMAP.md) are
|
|
221
|
+
historical clean-room records.
|
|
218
222
|
|
|
219
223
|
## Native data plane
|
|
220
224
|
|
|
@@ -234,17 +238,17 @@ only; they do not change Praxis data ownership.
|
|
|
234
238
|
|
|
235
239
|
## Documentation
|
|
236
240
|
|
|
237
|
-
| Need | Document
|
|
238
|
-
| ------------------------------------------ |
|
|
239
|
-
| Install and run the first session | [Getting Started](https://github.com/Forest-Isle/Praxis/blob/main/docs/GETTING_STARTED.md)
|
|
240
|
-
| Common commands and environment variables | [CLI Reference](https://github.com/Forest-Isle/Praxis/blob/main/docs/CLI_REFERENCE.md)
|
|
241
|
-
| Find all user and maintainer documentation | [Documentation Index](https://github.com/Forest-Isle/Praxis/blob/main/docs/README.md)
|
|
242
|
-
| Understand module and data-flow boundaries | [Architecture](https://github.com/Forest-Isle/Praxis/blob/main/docs/ARCHITECTURE.md)
|
|
243
|
-
| Review security assumptions | [Threat Model](https://github.com/Forest-Isle/Praxis/blob/main/docs/THREAT_MODEL.md)
|
|
244
|
-
|
|
|
245
|
-
| Review interactive TUI design and evidence | [Quiet Operator Spec](https://github.com/Forest-Isle/Praxis/blob/main/docs/TUI_REDESIGN_SPEC.md)
|
|
246
|
-
| Build, test, and contribute | [Contributing](https://github.com/Forest-Isle/Praxis/blob/main/CONTRIBUTING.md)
|
|
247
|
-
| Verify release and supply-chain controls | [Release Contract](https://github.com/Forest-Isle/Praxis/blob/main/docs/RELEASE.md)
|
|
241
|
+
| Need | Document |
|
|
242
|
+
| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------ |
|
|
243
|
+
| Install and run the first session | [Getting Started](https://github.com/Forest-Isle/Praxis/blob/main/docs/GETTING_STARTED.md) |
|
|
244
|
+
| Common commands and environment variables | [CLI Reference](https://github.com/Forest-Isle/Praxis/blob/main/docs/CLI_REFERENCE.md) |
|
|
245
|
+
| Find all user and maintainer documentation | [Documentation Index](https://github.com/Forest-Isle/Praxis/blob/main/docs/README.md) |
|
|
246
|
+
| Understand module and data-flow boundaries | [Architecture](https://github.com/Forest-Isle/Praxis/blob/main/docs/ARCHITECTURE.md) |
|
|
247
|
+
| Review security assumptions | [Threat Model](https://github.com/Forest-Isle/Praxis/blob/main/docs/THREAT_MODEL.md) |
|
|
248
|
+
| Qualify native behavior and evidence | [Native Fixture Contracts](https://github.com/Forest-Isle/Praxis/blob/main/docs/NATIVE_FIXTURE_CONTRACTS.md) |
|
|
249
|
+
| Review interactive TUI design and evidence | [Quiet Operator Spec](https://github.com/Forest-Isle/Praxis/blob/main/docs/TUI_REDESIGN_SPEC.md) |
|
|
250
|
+
| Build, test, and contribute | [Contributing](https://github.com/Forest-Isle/Praxis/blob/main/CONTRIBUTING.md) |
|
|
251
|
+
| Verify release and supply-chain controls | [Release Contract](https://github.com/Forest-Isle/Praxis/blob/main/docs/RELEASE.md) |
|
|
248
252
|
|
|
249
253
|
## Project boundary
|
|
250
254
|
|
|
@@ -286,10 +290,13 @@ injected regression protection, plus Quiet Operator input echo `<50 ms` and
|
|
|
286
290
|
normal/low-capability full-frame p95 budgets of `<16.7/<33 ms`.
|
|
287
291
|
`npm run check` also enforces the corresponding source dependency direction.
|
|
288
292
|
`npm run test:coverage` measures all production code under `src/**` with V8 and
|
|
289
|
-
enforces global floors of 79% statements, 70% branches, 85% functions, and 81% lines
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
+
enforces global floors of 79% statements, 70% branches, 85% functions, and 81% lines,
|
|
294
|
+
and rejects any production runtime module with zero covered statements (while allowing
|
|
295
|
+
type-only modules). `npm run test:fixtures` executes the 67-behavior native contract; 59 behaviors
|
|
296
|
+
are qualified and 8 are explicitly excluded. `npm run verify:fixture-contracts`
|
|
297
|
+
performs the structural check and is part of `npm run check`.
|
|
298
|
+
`npm run test:core-completion` is retained as a compatibility alias for
|
|
299
|
+
`npm run test:fixtures`.
|
|
293
300
|
|
|
294
301
|
Contributions use Conventional Commit pull-request titles and the protected
|
|
295
302
|
squash-merge workflow. Read
|
|
@@ -123,6 +123,11 @@ export interface SessionRunResult {
|
|
|
123
123
|
costUsd?: number;
|
|
124
124
|
modelUsage?: Readonly<Record<string, ModelUsage>>;
|
|
125
125
|
}
|
|
126
|
+
export interface CwdInspection {
|
|
127
|
+
readonly canonicalTarget: string;
|
|
128
|
+
readonly canonicalCurrentCwd: string;
|
|
129
|
+
readonly sameDirectory: boolean;
|
|
130
|
+
}
|
|
126
131
|
export interface SideQuestionResult {
|
|
127
132
|
sessionId: string;
|
|
128
133
|
text: string;
|
|
@@ -290,7 +295,8 @@ export declare class ClaudeSessionService {
|
|
|
290
295
|
metadata(sessionId: string): Promise<ClaudeSessionMetadata>;
|
|
291
296
|
rename(sessionId: string, name: string): Promise<void>;
|
|
292
297
|
tag(sessionId: string, tag: string): Promise<void>;
|
|
293
|
-
changeCwd(sessionId: string | undefined, requestedCwd: string): Promise<string>;
|
|
298
|
+
changeCwd(sessionId: string | undefined, requestedCwd: string, expectedCanonicalTarget?: string): Promise<string>;
|
|
299
|
+
inspectCwd(requestedCwd: string): Promise<CwdInspection>;
|
|
294
300
|
recordCdUsage(sessionId: string): Promise<void>;
|
|
295
301
|
approveRecentlyDenied(sessionId: string, display: string): Promise<void>;
|
|
296
302
|
retryRecentlyDenied(sessionId: string, display: string, signal?: AbortSignal): Promise<SessionRunResult>;
|
|
@@ -1806,17 +1806,23 @@ export class ClaudeSessionService {
|
|
|
1806
1806
|
const normalized = tag.trim();
|
|
1807
1807
|
await this.appendNativeCommand(sessionId, `/tag ${normalized}`);
|
|
1808
1808
|
}
|
|
1809
|
-
async changeCwd(sessionId, requestedCwd) {
|
|
1809
|
+
async changeCwd(sessionId, requestedCwd, expectedCanonicalTarget) {
|
|
1810
1810
|
this.assertWritable();
|
|
1811
1811
|
const previousCwd = this.activeCwd();
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1812
|
+
let inspection;
|
|
1813
|
+
try {
|
|
1814
|
+
inspection = await this.inspectCwd(requestedCwd);
|
|
1815
|
+
}
|
|
1816
|
+
catch (error) {
|
|
1817
|
+
if (error instanceof Error &&
|
|
1818
|
+
error.message.includes(' is not a directory.'))
|
|
1819
|
+
throw new Error(`Not a directory: ${requestedCwd}; ${error.message}`);
|
|
1820
|
+
throw error;
|
|
1821
|
+
}
|
|
1822
|
+
const cwd = inspection.canonicalTarget;
|
|
1823
|
+
if (expectedCanonicalTarget !== undefined &&
|
|
1824
|
+
cwd !== expectedCanonicalTarget) {
|
|
1825
|
+
throw new Error(`Directory changed after approval: expected ${expectedCanonicalTarget}, resolved to ${cwd}. Review the target and try again.`);
|
|
1820
1826
|
}
|
|
1821
1827
|
if (sessionId && this.options.sessionPersistence !== false) {
|
|
1822
1828
|
const sourcePaths = this.pathsForCwd(sessionId, this.sessionCwds.get(sessionId) ?? previousCwd);
|
|
@@ -1896,6 +1902,33 @@ export class ClaudeSessionService {
|
|
|
1896
1902
|
}
|
|
1897
1903
|
return cwd;
|
|
1898
1904
|
}
|
|
1905
|
+
async inspectCwd(requestedCwd) {
|
|
1906
|
+
const current = await realpath(this.activeCwd());
|
|
1907
|
+
const expanded = requestedCwd === '~'
|
|
1908
|
+
? homedir()
|
|
1909
|
+
: requestedCwd.startsWith('~/')
|
|
1910
|
+
? join(homedir(), requestedCwd.slice(2))
|
|
1911
|
+
: requestedCwd;
|
|
1912
|
+
const resolved = isAbsolute(expanded) ? expanded : join(current, expanded);
|
|
1913
|
+
let target;
|
|
1914
|
+
try {
|
|
1915
|
+
target = await realpath(resolved);
|
|
1916
|
+
}
|
|
1917
|
+
catch (error) {
|
|
1918
|
+
if (error.code === 'ENOENT')
|
|
1919
|
+
throw new Error(`Could not find a directory at ${resolved}.`);
|
|
1920
|
+
throw error;
|
|
1921
|
+
}
|
|
1922
|
+
const info = await stat(target);
|
|
1923
|
+
if (!info.isDirectory()) {
|
|
1924
|
+
throw new Error(`${target} is not a directory. Did you mean ${dirname(target)}?`);
|
|
1925
|
+
}
|
|
1926
|
+
return {
|
|
1927
|
+
canonicalTarget: target,
|
|
1928
|
+
canonicalCurrentCwd: current,
|
|
1929
|
+
sameDirectory: target === current,
|
|
1930
|
+
};
|
|
1931
|
+
}
|
|
1899
1932
|
async recordCdUsage(sessionId) {
|
|
1900
1933
|
this.assertWritable();
|
|
1901
1934
|
const transcript = new NativeSessionTranscript({
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ForkResult, ManualCompactResult, ManualCompactSelection, RewindPoint, SessionForkCheckpoint, SessionRunResult, SessionSummary, SideQuestionForkResult, SideQuestionResult } from '../application/session-service.js';
|
|
1
|
+
import type { CwdInspection, 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
4
|
import type { ActiveTurnInputCommandResult } from '../core/active-turn-input.js';
|
|
@@ -46,7 +46,8 @@ interface InteractiveSessionCommands {
|
|
|
46
46
|
compact?(sessionId: string, signal?: AbortSignal, selection?: ManualCompactSelection): Promise<ManualCompactResult>;
|
|
47
47
|
rewindFiles?(sessionId: string, userMessageId: string): Promise<void>;
|
|
48
48
|
rewindPoints?(sessionId: string): Promise<RewindPoint[]>;
|
|
49
|
-
changeCwd?(sessionId: string | undefined, cwd: string): Promise<string>;
|
|
49
|
+
changeCwd?(sessionId: string | undefined, cwd: string, expectedCanonicalTarget?: string): Promise<string>;
|
|
50
|
+
inspectCwd?(cwd: string): Promise<CwdInspection>;
|
|
50
51
|
notify?(sessionId: string | undefined, message: string, notificationType: string, title?: string): void;
|
|
51
52
|
recordCdUsage?(sessionId: string): Promise<void>;
|
|
52
53
|
approveRecentlyDenied?(sessionId: string, display: string): Promise<void>;
|
package/dist/cli/interactive.js
CHANGED
|
@@ -610,6 +610,35 @@ export function InteractiveApp({ dataPlane = resolveDataPlane(), configRoot: sup
|
|
|
610
610
|
const planApprovalRef = useRef(null);
|
|
611
611
|
const [planApprovalSelection, setPlanApprovalSelection] = useState(0);
|
|
612
612
|
const [planApprovalFeedbackMode, setPlanApprovalFeedbackMode] = useState(false);
|
|
613
|
+
const [cdTrust, setCdTrust] = useState(null);
|
|
614
|
+
const [cdTrustSelection, setCdTrustSelection] = useState(0);
|
|
615
|
+
const cdTrustRef = useRef(null);
|
|
616
|
+
const trustedCwdsBySessionRef = useRef(new Map());
|
|
617
|
+
const trustedCwdSessionRef = useRef(sessionId);
|
|
618
|
+
useEffect(() => {
|
|
619
|
+
const previousSessionId = trustedCwdSessionRef.current;
|
|
620
|
+
if (previousSessionId === sessionId)
|
|
621
|
+
return;
|
|
622
|
+
if (previousSessionId === null && sessionId !== null) {
|
|
623
|
+
const pendingSessionTrust = trustedCwdsBySessionRef.current.get(null);
|
|
624
|
+
if (pendingSessionTrust)
|
|
625
|
+
trustedCwdsBySessionRef.current.set(sessionId, pendingSessionTrust);
|
|
626
|
+
trustedCwdsBySessionRef.current.delete(null);
|
|
627
|
+
}
|
|
628
|
+
else if (sessionId === null) {
|
|
629
|
+
trustedCwdsBySessionRef.current.delete(null);
|
|
630
|
+
}
|
|
631
|
+
trustedCwdSessionRef.current = sessionId;
|
|
632
|
+
}, [sessionId]);
|
|
633
|
+
const trustedCwds = () => {
|
|
634
|
+
const key = sessionIdRef.current;
|
|
635
|
+
const existing = trustedCwdsBySessionRef.current.get(key);
|
|
636
|
+
if (existing)
|
|
637
|
+
return existing;
|
|
638
|
+
const created = new Set();
|
|
639
|
+
trustedCwdsBySessionRef.current.set(key, created);
|
|
640
|
+
return created;
|
|
641
|
+
};
|
|
613
642
|
const serviceRef = useRef(null);
|
|
614
643
|
const serviceCreationRef = useRef(undefined);
|
|
615
644
|
const onCleanupRef = useRef(onCleanup);
|
|
@@ -868,6 +897,13 @@ export function InteractiveApp({ dataPlane = resolveDataPlane(), configRoot: sup
|
|
|
868
897
|
answer: input,
|
|
869
898
|
})
|
|
870
899
|
: null, [question, input]);
|
|
900
|
+
const cdTrustSurface = useMemo(() => cdTrust
|
|
901
|
+
? projectTuiDecisionSurface({
|
|
902
|
+
kind: 'cd-trust',
|
|
903
|
+
canonicalPath: cdTrust.canonicalPath,
|
|
904
|
+
selectedIndex: cdTrustSelection,
|
|
905
|
+
})
|
|
906
|
+
: null, [cdTrust, cdTrustSelection]);
|
|
871
907
|
const elicitationSurface = useMemo(() => elicitation === null
|
|
872
908
|
? null
|
|
873
909
|
: projectTuiElicitationSurface({
|
|
@@ -1129,16 +1165,18 @@ export function InteractiveApp({ dataPlane = resolveDataPlane(), configRoot: sup
|
|
|
1129
1165
|
}
|
|
1130
1166
|
: planApproval !== null && planDecisionSurface !== null
|
|
1131
1167
|
? { priority: planDecisionSurface }
|
|
1132
|
-
:
|
|
1133
|
-
? { priority:
|
|
1134
|
-
:
|
|
1135
|
-
? {
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1168
|
+
: cdTrust !== null && cdTrustSurface !== null
|
|
1169
|
+
? { priority: cdTrustSurface }
|
|
1170
|
+
: question !== null && questionDecisionSurface !== null
|
|
1171
|
+
? { priority: questionDecisionSurface }
|
|
1172
|
+
: elicitation !== null && elicitationSurface !== null
|
|
1173
|
+
? {
|
|
1174
|
+
priority: {
|
|
1175
|
+
kind: 'elicitation',
|
|
1176
|
+
surface: elicitationSurface,
|
|
1177
|
+
},
|
|
1178
|
+
}
|
|
1179
|
+
: {}),
|
|
1142
1180
|
...(secondarySurface === undefined
|
|
1143
1181
|
? {}
|
|
1144
1182
|
: { secondary: secondarySurface }),
|
|
@@ -1156,6 +1194,8 @@ export function InteractiveApp({ dataPlane = resolveDataPlane(), configRoot: sup
|
|
|
1156
1194
|
permission,
|
|
1157
1195
|
permissionPrioritySurface,
|
|
1158
1196
|
planApproval,
|
|
1197
|
+
cdTrust,
|
|
1198
|
+
cdTrustSurface,
|
|
1159
1199
|
question,
|
|
1160
1200
|
planDecisionSurface,
|
|
1161
1201
|
questionDecisionSurface,
|
|
@@ -2739,23 +2779,58 @@ export function InteractiveApp({ dataPlane = resolveDataPlane(), configRoot: sup
|
|
|
2739
2779
|
onTurnChange?.(loading);
|
|
2740
2780
|
void loading.finally(() => onTurnChange?.(null));
|
|
2741
2781
|
};
|
|
2742
|
-
const changeWorkingDirectory = (requestedCwd
|
|
2782
|
+
const changeWorkingDirectory = (requestedCwd, authorization = {
|
|
2783
|
+
kind: 'unapproved',
|
|
2784
|
+
}) => {
|
|
2743
2785
|
const changing = (async () => {
|
|
2744
|
-
setBusy(true);
|
|
2745
|
-
setStatus('changing directory');
|
|
2746
2786
|
try {
|
|
2747
2787
|
const commands = await service();
|
|
2748
2788
|
if (!commands.changeCwd) {
|
|
2749
2789
|
throw new Error('This interactive service cannot change working directories.');
|
|
2750
2790
|
}
|
|
2751
|
-
|
|
2791
|
+
if (!commands.inspectCwd) {
|
|
2792
|
+
throw new Error('This interactive service cannot inspect or change working directories.');
|
|
2793
|
+
}
|
|
2794
|
+
const inspection = await commands.inspectCwd(requestedCwd);
|
|
2795
|
+
if (authorization.kind === 'approved' &&
|
|
2796
|
+
inspection.canonicalTarget !== authorization.canonicalTarget) {
|
|
2797
|
+
throw new Error(`Directory changed after approval: expected ${authorization.canonicalTarget}, resolved to ${inspection.canonicalTarget}. Review the target and try again.`);
|
|
2798
|
+
}
|
|
2799
|
+
const activeSessionTrust = trustedCwds();
|
|
2800
|
+
activeSessionTrust.add(inspection.canonicalCurrentCwd);
|
|
2801
|
+
if (inspection.sameDirectory) {
|
|
2802
|
+
append({
|
|
2803
|
+
kind: 'local-result',
|
|
2804
|
+
text: `Already in ${inspection.canonicalTarget}.`,
|
|
2805
|
+
});
|
|
2806
|
+
return;
|
|
2807
|
+
}
|
|
2808
|
+
if (authorization.kind === 'unapproved' &&
|
|
2809
|
+
!activeSessionTrust.has(inspection.canonicalTarget)) {
|
|
2810
|
+
const pending = {
|
|
2811
|
+
canonicalPath: inspection.canonicalTarget,
|
|
2812
|
+
};
|
|
2813
|
+
cdTrustRef.current = pending;
|
|
2814
|
+
setCdTrust(pending);
|
|
2815
|
+
return;
|
|
2816
|
+
}
|
|
2817
|
+
setBusy(true);
|
|
2818
|
+
setStatus('changing directory');
|
|
2819
|
+
const cwd = await commands.changeCwd(sessionId ?? undefined, inspection.canonicalTarget, inspection.canonicalTarget);
|
|
2820
|
+
activeSessionTrust.add(cwd);
|
|
2752
2821
|
runtimeCwdRef.current = cwd;
|
|
2753
2822
|
setRuntimeCwd(cwd);
|
|
2754
2823
|
await retireService();
|
|
2755
2824
|
append({ kind: 'local-result', text: `Moved to ${cwd}` });
|
|
2756
2825
|
}
|
|
2757
2826
|
catch (error) {
|
|
2758
|
-
|
|
2827
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2828
|
+
if (authorization.kind === 'approved')
|
|
2829
|
+
trustedCwds().delete(authorization.canonicalTarget);
|
|
2830
|
+
if (/^(Could not find a directory at .*\.|.* is not a directory\. Did you mean .+\?)$/u.test(message))
|
|
2831
|
+
append({ kind: 'local-result', text: message });
|
|
2832
|
+
else
|
|
2833
|
+
warn(error);
|
|
2759
2834
|
}
|
|
2760
2835
|
finally {
|
|
2761
2836
|
setBusy(false);
|
|
@@ -3688,6 +3763,11 @@ export function InteractiveApp({ dataPlane = resolveDataPlane(), configRoot: sup
|
|
|
3688
3763
|
case 'command-palette':
|
|
3689
3764
|
setCommandPaletteOpen(false);
|
|
3690
3765
|
break;
|
|
3766
|
+
case 'cd-trust':
|
|
3767
|
+
cdTrustRef.current = null;
|
|
3768
|
+
setCdTrust(null);
|
|
3769
|
+
setCdTrustSelection(0);
|
|
3770
|
+
break;
|
|
3691
3771
|
default: {
|
|
3692
3772
|
const unhandledTarget = effect.target;
|
|
3693
3773
|
return unhandledTarget;
|
|
@@ -3738,7 +3818,7 @@ export function InteractiveApp({ dataPlane = resolveDataPlane(), configRoot: sup
|
|
|
3738
3818
|
: ['Select']
|
|
3739
3819
|
: commandPaletteVisible || filePickerVisible
|
|
3740
3820
|
? ['Autocomplete', 'Chat']
|
|
3741
|
-
: permission || planApproval
|
|
3821
|
+
: permission || planApproval || cdTrust
|
|
3742
3822
|
? ['Confirmation']
|
|
3743
3823
|
: selectingSession
|
|
3744
3824
|
? ['Select']
|
|
@@ -3809,6 +3889,7 @@ export function InteractiveApp({ dataPlane = resolveDataPlane(), configRoot: sup
|
|
|
3809
3889
|
pendingPrefix: hasPendingPrefix,
|
|
3810
3890
|
permission: Boolean(permission),
|
|
3811
3891
|
planApproval: Boolean(planApproval),
|
|
3892
|
+
cdTrust: Boolean(cdTrust),
|
|
3812
3893
|
question: Boolean(question),
|
|
3813
3894
|
elicitation: elicitation
|
|
3814
3895
|
? elicitationUrlWaiting
|
|
@@ -4063,6 +4144,35 @@ export function InteractiveApp({ dataPlane = resolveDataPlane(), configRoot: sup
|
|
|
4063
4144
|
}
|
|
4064
4145
|
return;
|
|
4065
4146
|
}
|
|
4147
|
+
if (cdTrust) {
|
|
4148
|
+
const resolveCdTrust = (selectedIndex) => {
|
|
4149
|
+
const pending = cdTrustRef.current;
|
|
4150
|
+
cdTrustRef.current = null;
|
|
4151
|
+
setCdTrust(null);
|
|
4152
|
+
setCdTrustSelection(0);
|
|
4153
|
+
if (!pending || selectedIndex !== 1)
|
|
4154
|
+
return;
|
|
4155
|
+
changeWorkingDirectory(pending.canonicalPath, {
|
|
4156
|
+
kind: 'approved',
|
|
4157
|
+
canonicalTarget: pending.canonicalPath,
|
|
4158
|
+
});
|
|
4159
|
+
};
|
|
4160
|
+
if (key.upArrow) {
|
|
4161
|
+
setCdTrustSelection((current) => Math.max(0, current - 1));
|
|
4162
|
+
}
|
|
4163
|
+
else if (key.downArrow) {
|
|
4164
|
+
setCdTrustSelection((current) => Math.min(1, current + 1));
|
|
4165
|
+
}
|
|
4166
|
+
else if (lower === 'y')
|
|
4167
|
+
resolveCdTrust(1);
|
|
4168
|
+
else if (lower === 'n')
|
|
4169
|
+
resolveCdTrust(0);
|
|
4170
|
+
else if (/^[12]$/u.test(value))
|
|
4171
|
+
resolveCdTrust(Number(value) - 1);
|
|
4172
|
+
else if (key.return)
|
|
4173
|
+
resolveCdTrust(cdTrustSelection);
|
|
4174
|
+
return;
|
|
4175
|
+
}
|
|
4066
4176
|
if (planApproval) {
|
|
4067
4177
|
const elevatedMode = runtimeSettingsRef.current
|
|
4068
4178
|
.useAutoModeDuringPlan
|
|
@@ -32,6 +32,10 @@ export type TuiDecisionSurfaceInput = {
|
|
|
32
32
|
readonly questions: readonly ClaudeQuestion[];
|
|
33
33
|
readonly questionIndex: number;
|
|
34
34
|
readonly answer: string;
|
|
35
|
+
} | {
|
|
36
|
+
readonly kind: 'cd-trust';
|
|
37
|
+
readonly canonicalPath: string;
|
|
38
|
+
readonly selectedIndex: number;
|
|
35
39
|
};
|
|
36
40
|
export interface TuiPlanApprovalSurfaceModel {
|
|
37
41
|
readonly kind: 'plan-approval';
|
|
@@ -66,6 +70,19 @@ export interface TuiQuestionSurfaceModel {
|
|
|
66
70
|
readonly cancellation: TuiDecisionSurfaceCancellation;
|
|
67
71
|
readonly emptyState?: string;
|
|
68
72
|
}
|
|
69
|
-
export
|
|
73
|
+
export interface TuiCdTrustSurfaceModel {
|
|
74
|
+
readonly kind: 'cd-trust';
|
|
75
|
+
readonly heading: 'Moving to a new directory:';
|
|
76
|
+
readonly canonicalPath: string;
|
|
77
|
+
readonly explanation: "This session hasn't worked here before. Is this a directory you created or one you trust?";
|
|
78
|
+
readonly scope: 'Praxis can read, edit, and execute files in this directory.';
|
|
79
|
+
readonly securityGuide: 'Security guide: https://code.claude.com/docs/en/security';
|
|
80
|
+
readonly options: readonly TuiDecisionSurfaceOption[];
|
|
81
|
+
readonly selectedIndex: number;
|
|
82
|
+
readonly range: TuiDecisionSurfaceRange;
|
|
83
|
+
readonly actions: readonly TuiDecisionSurfaceAction[];
|
|
84
|
+
readonly cancellation: TuiDecisionSurfaceCancellation;
|
|
85
|
+
}
|
|
86
|
+
export type TuiDecisionSurfaceModel = TuiPlanApprovalSurfaceModel | TuiQuestionSurfaceModel | TuiCdTrustSurfaceModel;
|
|
70
87
|
export declare function projectTuiDecisionSurface(input: TuiDecisionSurfaceInput): TuiDecisionSurfaceModel;
|
|
71
88
|
//# sourceMappingURL=decision-surface-model.d.ts.map
|
|
@@ -100,6 +100,32 @@ export function projectTuiDecisionSurface(input) {
|
|
|
100
100
|
cancellation,
|
|
101
101
|
};
|
|
102
102
|
}
|
|
103
|
+
if (input.kind === 'cd-trust') {
|
|
104
|
+
const selectedIndex = normalizeIndex(input.selectedIndex, 2);
|
|
105
|
+
const options = projectedOptions([{ label: 'No, stay put' }, { label: 'Yes, move here' }], selectedIndex);
|
|
106
|
+
return {
|
|
107
|
+
kind: 'cd-trust',
|
|
108
|
+
heading: 'Moving to a new directory:',
|
|
109
|
+
canonicalPath: input.canonicalPath,
|
|
110
|
+
explanation: "This session hasn't worked here before. Is this a directory you created or one you trust?",
|
|
111
|
+
scope: 'Praxis can read, edit, and execute files in this directory.',
|
|
112
|
+
securityGuide: 'Security guide: https://code.claude.com/docs/en/security',
|
|
113
|
+
options,
|
|
114
|
+
selectedIndex,
|
|
115
|
+
range: range(options.length),
|
|
116
|
+
actions: [
|
|
117
|
+
{
|
|
118
|
+
visualLabel: 'Enter to confirm',
|
|
119
|
+
screenReaderLabel: 'Enter to confirm',
|
|
120
|
+
},
|
|
121
|
+
{ screenReaderLabel: 'Use up and down arrows to change selection' },
|
|
122
|
+
{ screenReaderLabel: 'Press 1 or 2 to choose directly' },
|
|
123
|
+
{ screenReaderLabel: 'Press y to move here' },
|
|
124
|
+
{ screenReaderLabel: 'Press n to stay put' },
|
|
125
|
+
],
|
|
126
|
+
cancellation,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
103
129
|
const count = input.questions.length;
|
|
104
130
|
const questionIndex = normalizeIndex(input.questionIndex, count);
|
|
105
131
|
if (count === 0)
|
|
@@ -31,8 +31,11 @@ function PlanSurface({ model, screenReader, }) {
|
|
|
31
31
|
function QuestionSurface({ model, screenReader, }) {
|
|
32
32
|
return (_jsxs(DialogFrame, { title: model.heading, screenReader: screenReader, children: [_jsx(Text, { children: model.progress }), model.emptyState ? _jsx(Text, { dimColor: true, children: model.emptyState }) : null, model.options.map((option) => (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [option.index, ". ", option.label, option.description ? ` — ${option.description}` : ''] }), option.preview ? _jsx(Text, { dimColor: true, children: option.preview }) : null] }, `${option.index}-${option.label}`))), _jsxs(Text, { children: [screenReader ? 'Current answer: ' : '› ', model.answer || (screenReader ? '(empty)' : '')] }), _jsx(Footer, { ...model, screenReader: screenReader })] }));
|
|
33
33
|
}
|
|
34
|
+
function CdTrustSurface({ model, screenReader, }) {
|
|
35
|
+
return (_jsxs(DialogFrame, { title: model.heading, screenReader: screenReader, children: [_jsx(Text, { children: model.canonicalPath }), _jsx(Text, { dimColor: true, children: model.explanation }), _jsx(Text, { dimColor: true, children: model.scope }), _jsx(Text, { dimColor: true, children: model.securityGuide }), model.options.map((option) => (_jsx(Option, { option: option, screenReader: screenReader }, option.index))), _jsx(Footer, { ...model, screenReader: screenReader })] }));
|
|
36
|
+
}
|
|
34
37
|
export function DecisionSurface({ model, width, screenReader, }) {
|
|
35
|
-
const content = model.kind === 'plan-approval' ? (_jsx(PlanSurface, { model: model, screenReader: screenReader })) : (_jsx(QuestionSurface, { model: model, screenReader: screenReader }));
|
|
38
|
+
const content = model.kind === 'plan-approval' ? (_jsx(PlanSurface, { model: model, screenReader: screenReader })) : model.kind === 'question' ? (_jsx(QuestionSurface, { model: model, screenReader: screenReader })) : (_jsx(CdTrustSurface, { model: model, screenReader: screenReader }));
|
|
36
39
|
const normalizedWidth = Number.isFinite(width)
|
|
37
40
|
? Math.min(100, Math.max(1, Math.trunc(width)))
|
|
38
41
|
: 100;
|
|
@@ -1,2 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
type OpenUrlOptions = {
|
|
2
|
+
timeout: number;
|
|
3
|
+
shell: false;
|
|
4
|
+
};
|
|
5
|
+
type OpenUrlExecutor = (command: string, args: string[], options: OpenUrlOptions, callback: (error?: Error | null) => void) => unknown;
|
|
6
|
+
type OpenUrlDependencies = {
|
|
7
|
+
platform?: NodeJS.Platform;
|
|
8
|
+
execFile?: OpenUrlExecutor;
|
|
9
|
+
};
|
|
10
|
+
export declare function openTuiUrl(url: string, dependencies?: OpenUrlDependencies): Promise<void>;
|
|
11
|
+
export {};
|
|
2
12
|
//# sourceMappingURL=open-url.d.ts.map
|
package/dist/cli/tui/open-url.js
CHANGED
|
@@ -1,13 +1,33 @@
|
|
|
1
1
|
import { execFile } from 'node:child_process';
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
const defaultExecFile = (command, args, options, callback) => execFile(command, args, options, callback);
|
|
3
|
+
export async function openTuiUrl(url, dependencies = {}) {
|
|
4
|
+
let parsedUrl;
|
|
5
|
+
try {
|
|
6
|
+
parsedUrl = new URL(url);
|
|
7
|
+
}
|
|
8
|
+
catch {
|
|
9
|
+
throw new Error('TUI URL must be an absolute HTTP(S) URL');
|
|
10
|
+
}
|
|
11
|
+
if (parsedUrl.protocol !== 'http:' && parsedUrl.protocol !== 'https:')
|
|
12
|
+
throw new Error('TUI URL must be an absolute HTTP(S) URL');
|
|
13
|
+
const canonicalUrl = parsedUrl.href;
|
|
14
|
+
const platform = dependencies.platform ?? process.platform;
|
|
15
|
+
const command = platform === 'darwin'
|
|
4
16
|
? 'open'
|
|
5
|
-
:
|
|
6
|
-
? '
|
|
17
|
+
: platform === 'win32'
|
|
18
|
+
? 'rundll32.exe'
|
|
7
19
|
: 'xdg-open';
|
|
8
|
-
const args =
|
|
9
|
-
|
|
10
|
-
|
|
20
|
+
const args = platform === 'win32'
|
|
21
|
+
? ['url.dll,FileProtocolHandler', canonicalUrl]
|
|
22
|
+
: [canonicalUrl];
|
|
23
|
+
const executor = dependencies.execFile ?? defaultExecFile;
|
|
24
|
+
await new Promise((resolvePromise, rejectPromise) => {
|
|
25
|
+
try {
|
|
26
|
+
executor(command, args, { timeout: 10_000, shell: false }, (error) => error == null ? resolvePromise() : rejectPromise(error));
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
rejectPromise(error);
|
|
30
|
+
}
|
|
11
31
|
});
|
|
12
32
|
}
|
|
13
33
|
//# sourceMappingURL=open-url.js.map
|
|
@@ -176,6 +176,17 @@ function decision(surface, density, sr) {
|
|
|
176
176
|
explicitFooter('quiet:plan:footer', '↑/↓ select Enter confirm Esc cancel', 'Use up and down arrows to select. Press Enter to confirm. Press Escape to cancel.'),
|
|
177
177
|
];
|
|
178
178
|
}
|
|
179
|
+
if (surface.kind === 'cd-trust') {
|
|
180
|
+
out.push(row('quiet:cd-trust:path', surface.canonicalPath, 'body'));
|
|
181
|
+
out.push(row('quiet:cd-trust:explanation', surface.explanation, 'muted'));
|
|
182
|
+
out.push(row('quiet:cd-trust:scope', surface.scope, 'muted'));
|
|
183
|
+
out.push(row('quiet:cd-trust:security-guide', surface.securityGuide, 'muted'));
|
|
184
|
+
out.push(...optionsRows(surface.options, 'quiet:cd-trust:option', density, sr));
|
|
185
|
+
return [
|
|
186
|
+
...out,
|
|
187
|
+
explicitFooter('quiet:cd-trust:footer', '↑/↓ select Enter confirm Esc cancel', 'Use up and down arrows to select. Press Enter to confirm. Press Escape to cancel.'),
|
|
188
|
+
];
|
|
189
|
+
}
|
|
179
190
|
if (surface.progress)
|
|
180
191
|
out.push(row('quiet:question:progress', surface.progress, 'muted'));
|
|
181
192
|
if (surface.question)
|
|
@@ -291,7 +302,9 @@ export function projectQuietChoiceRows(surface, options) {
|
|
|
291
302
|
row('quiet:exit:heading', 'Exit Praxis?', 'heading'),
|
|
292
303
|
explicitFooter('quiet:exit:footer', 'Enter confirm Esc cancel', 'Press Enter to confirm. Press Escape to cancel.'),
|
|
293
304
|
];
|
|
294
|
-
if (surface.kind === 'plan-approval' ||
|
|
305
|
+
if (surface.kind === 'plan-approval' ||
|
|
306
|
+
surface.kind === 'question' ||
|
|
307
|
+
surface.kind === 'cd-trust')
|
|
295
308
|
return decision(surface, options.density, sr);
|
|
296
309
|
if (surface.kind === 'session-picker' ||
|
|
297
310
|
surface.kind === 'command-palette' ||
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
import type { SandboxDependencyCheck } from '@anthropic-ai/sandbox-runtime';
|
|
2
2
|
import type { JsonResource } from '../../core/resources.js';
|
|
3
|
+
import { type ClaudeSandboxPlatform } from '../../sandbox/claude-sandbox-runtime.js';
|
|
3
4
|
import { type ClaudeSandboxSettings } from '../../sandbox/claude-sandbox-settings.js';
|
|
4
5
|
export type TuiSandboxMode = 'auto-allow' | 'regular' | 'disabled';
|
|
5
6
|
export type TuiSandboxTab = 'mode' | 'dependencies' | 'overrides' | 'config';
|
|
7
|
+
export interface TuiSandboxRuntime {
|
|
8
|
+
initialize(settings: ClaudeSandboxSettings): Promise<void>;
|
|
9
|
+
unavailableReason(settings: ClaudeSandboxSettings): string | undefined;
|
|
10
|
+
platformName(): ClaudeSandboxPlatform;
|
|
11
|
+
dependencyCheck(settings: ClaudeSandboxSettings): SandboxDependencyCheck;
|
|
12
|
+
isSupportedPlatform(): boolean;
|
|
13
|
+
}
|
|
6
14
|
export interface TuiSandboxSnapshot {
|
|
7
15
|
settings: ClaudeSandboxSettings;
|
|
8
16
|
dependencies: SandboxDependencyCheck;
|
|
@@ -22,11 +30,12 @@ export interface TuiSandboxStore {
|
|
|
22
30
|
}>;
|
|
23
31
|
}
|
|
24
32
|
export declare function linuxGlobPatternWarnings(resources: readonly JsonResource[], platform: 'macos' | 'linux' | 'windows' | 'wsl'): string[];
|
|
25
|
-
export declare function createTuiSandboxStore({ configRoot, cwd, homeDirectory, additionalDirectories, environment, }: {
|
|
33
|
+
export declare function createTuiSandboxStore({ configRoot, cwd, homeDirectory, additionalDirectories, environment, runtime, }: {
|
|
26
34
|
configRoot: string;
|
|
27
35
|
cwd: string;
|
|
28
36
|
homeDirectory: string;
|
|
29
37
|
additionalDirectories?: readonly string[];
|
|
30
38
|
environment?: NodeJS.ProcessEnv;
|
|
39
|
+
runtime?: TuiSandboxRuntime;
|
|
31
40
|
}): TuiSandboxStore;
|
|
32
41
|
//# sourceMappingURL=sandbox-settings.d.ts.map
|
|
@@ -2,7 +2,7 @@ import { readFile } from 'node:fs/promises';
|
|
|
2
2
|
import { join, relative } from 'node:path';
|
|
3
3
|
import { writeFileAtomically } from '../../platform/atomic-write.js';
|
|
4
4
|
import { loadNativeSharedResources } from '../../persistence/native-resources.js';
|
|
5
|
-
import { claudeSandboxRuntime } from '../../sandbox/claude-sandbox-runtime.js';
|
|
5
|
+
import { claudeSandboxRuntime, } from '../../sandbox/claude-sandbox-runtime.js';
|
|
6
6
|
import { nativeSandboxTempDirectory, loadClaudeSandboxSettings, } from '../../sandbox/claude-sandbox-settings.js';
|
|
7
7
|
function isRecord(value) {
|
|
8
8
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
@@ -75,7 +75,7 @@ export function linuxGlobPatternWarnings(resources, platform) {
|
|
|
75
75
|
}
|
|
76
76
|
return [...new Set(warnings)];
|
|
77
77
|
}
|
|
78
|
-
export function createTuiSandboxStore({ configRoot, cwd, homeDirectory, additionalDirectories = [], environment = process.env, }) {
|
|
78
|
+
export function createTuiSandboxStore({ configRoot, cwd, homeDirectory, additionalDirectories = [], environment = process.env, runtime = claudeSandboxRuntime, }) {
|
|
79
79
|
const localSettingsPath = join(cwd, '.praxis', 'settings.local.json');
|
|
80
80
|
const load = async () => {
|
|
81
81
|
const resources = (await loadNativeSharedResources({ root: configRoot, cwd })).settings;
|
|
@@ -87,13 +87,13 @@ export function createTuiSandboxStore({ configRoot, cwd, homeDirectory, addition
|
|
|
87
87
|
additionalDirectories,
|
|
88
88
|
tempDirectory: nativeSandboxTempDirectory(environment),
|
|
89
89
|
});
|
|
90
|
-
await
|
|
91
|
-
const unavailableReason =
|
|
92
|
-
const platform =
|
|
90
|
+
await runtime.initialize(settings);
|
|
91
|
+
const unavailableReason = runtime.unavailableReason(settings);
|
|
92
|
+
const platform = runtime.platformName();
|
|
93
93
|
return {
|
|
94
94
|
settings,
|
|
95
|
-
dependencies:
|
|
96
|
-
supported:
|
|
95
|
+
dependencies: runtime.dependencyCheck(settings),
|
|
96
|
+
supported: runtime.isSupportedPlatform(),
|
|
97
97
|
platform,
|
|
98
98
|
globPatternWarnings: linuxGlobPatternWarnings(resources, platform),
|
|
99
99
|
...(unavailableReason ? { unavailableReason } : {}),
|
|
@@ -7,6 +7,7 @@ export interface TuiFocusProjectionInput {
|
|
|
7
7
|
readonly pendingPrefix: boolean;
|
|
8
8
|
readonly permission: boolean;
|
|
9
9
|
readonly planApproval: boolean;
|
|
10
|
+
readonly cdTrust?: boolean;
|
|
10
11
|
readonly question: boolean;
|
|
11
12
|
readonly elicitation: 'plain' | 'url-waiting' | 'expanded-options' | false;
|
|
12
13
|
readonly selectingSession: boolean;
|
|
@@ -19,6 +19,12 @@ export function projectTuiFocusStack(input) {
|
|
|
19
19
|
layer: { kind: 'cancelable', target: 'plan-approval' },
|
|
20
20
|
};
|
|
21
21
|
}
|
|
22
|
+
else if (input.cdTrust) {
|
|
23
|
+
higher = {
|
|
24
|
+
id: 'cd-trust',
|
|
25
|
+
layer: { kind: 'cancelable', target: 'cd-trust' },
|
|
26
|
+
};
|
|
27
|
+
}
|
|
22
28
|
else if (input.question) {
|
|
23
29
|
higher = {
|
|
24
30
|
id: 'question',
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type ComposerKeyProjection } from './composer-key-router.js';
|
|
2
2
|
import type { ComposerEditorState } from './composer-editor.js';
|
|
3
3
|
export type TuiScrollIntent = 'page-older' | 'page-newer' | 'half-page-older' | 'half-page-newer' | 'line-older' | 'line-newer' | 'none';
|
|
4
|
-
export type TuiCancellationTarget = 'permission' | 'plan-approval' | 'question' | 'elicitation' | 'elicitation-url-waiting' | 'elicitation-options' | 'file-picker' | 'command-palette';
|
|
4
|
+
export type TuiCancellationTarget = 'permission' | 'plan-approval' | 'cd-trust' | 'question' | 'elicitation' | 'elicitation-url-waiting' | 'elicitation-options' | 'file-picker' | 'command-palette';
|
|
5
5
|
export type TuiInteractionLayer = {
|
|
6
6
|
readonly kind: 'none';
|
|
7
7
|
} | {
|
package/dist/cli-runtime.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { type ForkResult, type ManualCompactResult, type ManualCompactSelection, type RewindPoint, type SessionForkCheckpoint, type SessionInspection, type SessionRunResult, type SessionSummary, type SideQuestionForkResult, type SideQuestionResult } from './application/session-service.js';
|
|
2
|
+
import { type CwdInspection, type ForkResult, type ManualCompactResult, type ManualCompactSelection, type RewindPoint, type SessionForkCheckpoint, type SessionInspection, type SessionRunResult, type SessionSummary, type SideQuestionForkResult, type SideQuestionResult } from './application/session-service.js';
|
|
3
3
|
import type { TeamLeadOperations } from './application/team-lead-operations.js';
|
|
4
4
|
import type { ClaudeSessionCostSnapshot } from './application/session-cost-tracker.js';
|
|
5
5
|
import { type AgentColorName, type AgentColorSelection } from './core/agent-color.js';
|
|
@@ -43,7 +43,8 @@ interface SessionCommands {
|
|
|
43
43
|
setPermissionMode?(sessionId: string, mode: ClaudePermissionMode): Promise<void>;
|
|
44
44
|
rewindFiles?(sessionId: string, userMessageId: string): Promise<void>;
|
|
45
45
|
rewindPoints?(sessionId: string): Promise<RewindPoint[]>;
|
|
46
|
-
changeCwd?(sessionId: string | undefined, cwd: string): Promise<string>;
|
|
46
|
+
changeCwd?(sessionId: string | undefined, cwd: string, expectedCanonicalTarget?: string): Promise<string>;
|
|
47
|
+
inspectCwd?(cwd: string): Promise<CwdInspection>;
|
|
47
48
|
notify?(sessionId: string | undefined, message: string, notificationType: string, title?: string): void;
|
|
48
49
|
recordCdUsage?(sessionId: string): Promise<void>;
|
|
49
50
|
approveRecentlyDenied?(sessionId: string, display: string): Promise<void>;
|
package/dist/cli-runtime.js
CHANGED
|
@@ -2049,7 +2049,8 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
2049
2049
|
setPermissionMode: (sessionId, permissionMode) => service.setPermissionMode(sessionId, permissionMode),
|
|
2050
2050
|
rewindFiles: (sessionId, userMessageId) => service.rewindFiles(sessionId, userMessageId),
|
|
2051
2051
|
rewindPoints: (sessionId) => service.rewindPoints(sessionId),
|
|
2052
|
-
changeCwd: (sessionId, cwd) => service.changeCwd(sessionId, cwd),
|
|
2052
|
+
changeCwd: (sessionId, cwd, expectedCanonicalTarget) => service.changeCwd(sessionId, cwd, expectedCanonicalTarget),
|
|
2053
|
+
inspectCwd: (cwd) => service.inspectCwd(cwd),
|
|
2053
2054
|
notify: (sessionId, message, notificationType, title) => service.notifyDetached(sessionId, message, notificationType, title),
|
|
2054
2055
|
recordCdUsage: (sessionId) => service.recordCdUsage(sessionId),
|
|
2055
2056
|
approveRecentlyDenied: (sessionId, display) => service.approveRecentlyDenied(sessionId, display),
|
package/dist/native/schema.d.ts
CHANGED
|
@@ -7,8 +7,4 @@
|
|
|
7
7
|
export type NativeTranscriptEntry = Record<string, unknown> & {
|
|
8
8
|
type: string;
|
|
9
9
|
};
|
|
10
|
-
export declare function isNativeForkableEntryType(type: string): boolean;
|
|
11
|
-
/** Copy only projection metadata; persisted native events are copied by
|
|
12
|
-
* NativeSessionTranscript.forkTo and retain their event identity semantics. */
|
|
13
|
-
export declare function copyNativeEntryWithSessionId(entry: NativeTranscriptEntry, sessionId: string): NativeTranscriptEntry;
|
|
14
10
|
//# sourceMappingURL=schema.d.ts.map
|
package/dist/native/schema.js
CHANGED
|
@@ -1,24 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
'agent-color',
|
|
3
|
-
'agent-name',
|
|
4
|
-
'agent-setting',
|
|
5
|
-
'ai-title',
|
|
6
|
-
'assistant',
|
|
7
|
-
'attachment',
|
|
8
|
-
'custom-title',
|
|
9
|
-
'last-prompt',
|
|
10
|
-
'mode',
|
|
11
|
-
'permission-mode',
|
|
12
|
-
'pr-link',
|
|
13
|
-
'system',
|
|
14
|
-
'user',
|
|
15
|
-
]);
|
|
16
|
-
export function isNativeForkableEntryType(type) {
|
|
17
|
-
return FORKABLE_ENTRY_TYPES.has(type);
|
|
18
|
-
}
|
|
19
|
-
/** Copy only projection metadata; persisted native events are copied by
|
|
20
|
-
* NativeSessionTranscript.forkTo and retain their event identity semantics. */
|
|
21
|
-
export function copyNativeEntryWithSessionId(entry, sessionId) {
|
|
22
|
-
return { ...entry, sessionId };
|
|
23
|
-
}
|
|
1
|
+
export {};
|
|
24
2
|
//# sourceMappingURL=schema.js.map
|
|
@@ -304,11 +304,12 @@ function validate(input, name) {
|
|
|
304
304
|
for (const member of input.roster) {
|
|
305
305
|
if (!member || typeof member !== 'object' || Array.isArray(member))
|
|
306
306
|
throw new Error('Invalid Team member');
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
307
|
+
const value = member;
|
|
308
|
+
objectInput(value, ['name', 'agentType', 'access']);
|
|
309
|
+
stringValue(value, 'name');
|
|
310
|
+
stringValue(value, 'agentType');
|
|
311
|
+
if (value.access !== 'read-only' && value.access !== 'write')
|
|
312
|
+
throw new Error('Invalid Team member access');
|
|
312
313
|
}
|
|
313
314
|
for (const task of input.tasks) {
|
|
314
315
|
if (!task || typeof task !== 'object' || Array.isArray(task))
|
|
@@ -321,6 +322,12 @@ function validate(input, name) {
|
|
|
321
322
|
'blockedBy',
|
|
322
323
|
'claims',
|
|
323
324
|
]);
|
|
325
|
+
stringValue(value, 'id');
|
|
326
|
+
stringValue(value, 'description');
|
|
327
|
+
stringValue(value, 'assignee');
|
|
328
|
+
if (!Array.isArray(value.blockedBy) ||
|
|
329
|
+
value.blockedBy.some((entry) => typeof entry !== 'string' || entry.trim() === ''))
|
|
330
|
+
throw new Error('Invalid Team blockedBy');
|
|
324
331
|
if (!value.claims ||
|
|
325
332
|
typeof value.claims !== 'object' ||
|
|
326
333
|
Array.isArray(value.claims))
|
|
@@ -332,6 +339,18 @@ function validate(input, name) {
|
|
|
332
339
|
'migrations',
|
|
333
340
|
'mergeTargets',
|
|
334
341
|
]);
|
|
342
|
+
for (const key of [
|
|
343
|
+
'files',
|
|
344
|
+
'publicContracts',
|
|
345
|
+
'generatedArtifacts',
|
|
346
|
+
'migrations',
|
|
347
|
+
'mergeTargets',
|
|
348
|
+
]) {
|
|
349
|
+
const entries = value.claims[key];
|
|
350
|
+
if (!Array.isArray(entries) ||
|
|
351
|
+
entries.some((entry) => typeof entry !== 'string' || entry.trim() === ''))
|
|
352
|
+
throw new Error(`Invalid Team claim list: ${key}`);
|
|
353
|
+
}
|
|
335
354
|
}
|
|
336
355
|
return input;
|
|
337
356
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "praxis-agent",
|
|
3
|
-
"version": "0.46.
|
|
3
|
+
"version": "0.46.2",
|
|
4
4
|
"description": "Local-first, single-user general agent for the command line.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "wuqisen",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
},
|
|
30
30
|
"scripts": {
|
|
31
31
|
"build": "tsc -p tsconfig.build.json",
|
|
32
|
-
"check": "npm run format:check && npm run lint && npm run test:docs && npm run verify:release-automation && npm run verify:ci-coverage && npm run check:boundaries && npm run typecheck && npm run build:native && npm run build && npm test",
|
|
32
|
+
"check": "npm run format:check && npm run lint && npm run test:docs && npm run verify:release-automation && npm run verify:ci-coverage && npm run verify:fixture-contracts && npm run check:boundaries && npm run typecheck && npm run build:native && npm run build && npm test",
|
|
33
33
|
"check:boundaries": "node scripts/check-boundaries.mjs",
|
|
34
34
|
"dev": "tsx src/cli.ts",
|
|
35
35
|
"format": "prettier --write .",
|
|
@@ -44,11 +44,11 @@
|
|
|
44
44
|
"release:artifacts": "node scripts/build-release-artifacts.mjs",
|
|
45
45
|
"release:verify": "node scripts/verify-release-ref.mjs",
|
|
46
46
|
"test": "vitest run",
|
|
47
|
-
"test:coverage": "vitest run --coverage",
|
|
47
|
+
"test:coverage": "vitest run --coverage && node scripts/verify-nonzero-runtime-coverage.mjs",
|
|
48
48
|
"test:docs": "node scripts/verify-docs.mjs",
|
|
49
49
|
"test:tui:pty": "npm run build && node scripts/verify-fullscreen-rendering.mjs && node scripts/verify-interactive-ansi.mjs",
|
|
50
50
|
"test:self-update": "npm run build && node scripts/verify-self-update-contract.mjs",
|
|
51
|
-
"test:core-completion": "npm run
|
|
51
|
+
"test:core-completion": "npm run test:fixtures",
|
|
52
52
|
"test:performance": "npm run build && node scripts/verify-projection-scaling.mjs && node scripts/verify-projection-regression.mjs && node scripts/verify-quiet-frame-performance.mjs",
|
|
53
53
|
"test:performance:projection": "npm run build && node scripts/verify-projection-scaling.mjs",
|
|
54
54
|
"test:performance:projection-regression": "npm run build && node scripts/verify-projection-regression.mjs",
|
|
@@ -56,9 +56,12 @@
|
|
|
56
56
|
"test:prompt-suggestions": "npm run build && node scripts/verify-prompt-suggestions.mjs",
|
|
57
57
|
"test:mcp-oauth-serve": "npm run build && node scripts/verify-mcp-oauth-serve.mjs",
|
|
58
58
|
"test:package": "npm run build && node scripts/verify-native-release-package.mjs",
|
|
59
|
+
"test:security": "npm audit --omit=dev",
|
|
59
60
|
"typecheck": "tsc --noEmit",
|
|
60
61
|
"verify:release-automation": "node scripts/verify-release-automation.mjs",
|
|
61
|
-
"verify:ci-coverage": "node scripts/verify-ci-coverage.mjs"
|
|
62
|
+
"verify:ci-coverage": "node scripts/verify-ci-coverage.mjs",
|
|
63
|
+
"verify:fixture-contracts": "node scripts/verify-fixture-contracts.mjs",
|
|
64
|
+
"test:fixtures": "node scripts/run-fixture-contracts.mjs"
|
|
62
65
|
},
|
|
63
66
|
"engines": {
|
|
64
67
|
"node": ">=24"
|
package/dist/native/fork.d.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { type NativeTranscriptEntry } from './schema.js';
|
|
2
|
-
export interface ClaudeNativeForkOptions {
|
|
3
|
-
source: readonly NativeTranscriptEntry[];
|
|
4
|
-
sourceSessionId: string;
|
|
5
|
-
sessionId: string;
|
|
6
|
-
resumeSessionAt?: string;
|
|
7
|
-
}
|
|
8
|
-
export declare function createClaudeNativeFork({ source, sourceSessionId, sessionId, resumeSessionAt, }: ClaudeNativeForkOptions): NativeTranscriptEntry[];
|
|
9
|
-
//# sourceMappingURL=fork.d.ts.map
|
package/dist/native/fork.js
DELETED
|
@@ -1,260 +0,0 @@
|
|
|
1
|
-
import { getClaudeContentBlocks } from './tool-links.js';
|
|
2
|
-
import { getClaudePreservedMessageUuids } from './compaction.js';
|
|
3
|
-
import { selectClaudeActiveTranscript, selectClaudeTranscriptAtMessage, } from './history.js';
|
|
4
|
-
import { copyNativeEntryWithSessionId, isNativeForkableEntryType, } from './schema.js';
|
|
5
|
-
const TRANSIENT_ENTRY_TYPES = new Set([
|
|
6
|
-
'atis-latch',
|
|
7
|
-
'file-history-delta',
|
|
8
|
-
'file-history-snapshot',
|
|
9
|
-
'queue-operation',
|
|
10
|
-
'relocated',
|
|
11
|
-
]);
|
|
12
|
-
function advancesLogicalTail(entry) {
|
|
13
|
-
if (entry.type === 'system' && entry.subtype === 'compact_boundary') {
|
|
14
|
-
return false;
|
|
15
|
-
}
|
|
16
|
-
if (entry.type !== 'attachment')
|
|
17
|
-
return true;
|
|
18
|
-
const attachment = entry.attachment;
|
|
19
|
-
if (typeof attachment !== 'object' || attachment === null)
|
|
20
|
-
return true;
|
|
21
|
-
const attachmentType = attachment.type;
|
|
22
|
-
return (attachmentType !== 'hook_success' &&
|
|
23
|
-
attachmentType !== 'hook_error' &&
|
|
24
|
-
attachmentType !== 'hook_additional_context');
|
|
25
|
-
}
|
|
26
|
-
function validateNativeHistory(entries) {
|
|
27
|
-
const entriesByUuid = new Map();
|
|
28
|
-
const toolCalls = new Map();
|
|
29
|
-
for (const entry of entries) {
|
|
30
|
-
if (typeof entry.uuid !== 'string')
|
|
31
|
-
continue;
|
|
32
|
-
if (entriesByUuid.has(entry.uuid)) {
|
|
33
|
-
throw new Error(`Claude fork source has duplicate UUID ${entry.uuid}`);
|
|
34
|
-
}
|
|
35
|
-
entriesByUuid.set(entry.uuid, entry);
|
|
36
|
-
if (entry.type !== 'assistant')
|
|
37
|
-
continue;
|
|
38
|
-
for (const block of getClaudeContentBlocks(entry)) {
|
|
39
|
-
if (block.type !== 'tool_use')
|
|
40
|
-
continue;
|
|
41
|
-
const id = block.id;
|
|
42
|
-
if (typeof id !== 'string')
|
|
43
|
-
continue;
|
|
44
|
-
if (toolCalls.has(id)) {
|
|
45
|
-
throw new Error(`Claude fork source has duplicate tool_use ${id}`);
|
|
46
|
-
}
|
|
47
|
-
toolCalls.set(id, entry.uuid);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
const childParentUuids = new Set();
|
|
51
|
-
const externalParentUuids = new Set();
|
|
52
|
-
let externalParentReferenceCount = 0;
|
|
53
|
-
const completedToolCalls = new Set();
|
|
54
|
-
let ordinaryRootCount = 0;
|
|
55
|
-
let compactBoundary;
|
|
56
|
-
let logicalTailUuid;
|
|
57
|
-
for (const entry of entries) {
|
|
58
|
-
const isCompactSummary = entry.isCompactSummary === true;
|
|
59
|
-
if (compactBoundary && !isCompactSummary) {
|
|
60
|
-
throw new Error('Claude compact boundary has no adjacent summary');
|
|
61
|
-
}
|
|
62
|
-
if (isCompactSummary) {
|
|
63
|
-
if (!compactBoundary || entry.parentUuid !== compactBoundary.uuid) {
|
|
64
|
-
throw new Error('Claude compact summary has no matching boundary');
|
|
65
|
-
}
|
|
66
|
-
compactBoundary = undefined;
|
|
67
|
-
}
|
|
68
|
-
else if (entry.type === 'system' &&
|
|
69
|
-
entry.subtype === 'compact_boundary') {
|
|
70
|
-
const metadata = entry.compactMetadata;
|
|
71
|
-
const preservedSegment = typeof metadata === 'object' && metadata !== null
|
|
72
|
-
? metadata.preservedSegment
|
|
73
|
-
: undefined;
|
|
74
|
-
const preservedUuids = getClaudePreservedMessageUuids(entry);
|
|
75
|
-
const segment = typeof preservedSegment === 'object' && preservedSegment !== null
|
|
76
|
-
? preservedSegment
|
|
77
|
-
: {};
|
|
78
|
-
const segmentMatchesLogicalTail = segment.headUuid === logicalTailUuid &&
|
|
79
|
-
segment.tailUuid === logicalTailUuid;
|
|
80
|
-
const segmentMatchesPreserved = segment.headUuid === preservedUuids[0] &&
|
|
81
|
-
segment.tailUuid === preservedUuids[preservedUuids.length - 1];
|
|
82
|
-
if (typeof preservedSegment !== 'object' ||
|
|
83
|
-
preservedSegment === null ||
|
|
84
|
-
entry.logicalParentUuid !== logicalTailUuid ||
|
|
85
|
-
(!segmentMatchesLogicalTail && !segmentMatchesPreserved)) {
|
|
86
|
-
throw new Error('Claude compact boundary has invalid logical parent');
|
|
87
|
-
}
|
|
88
|
-
compactBoundary = entry;
|
|
89
|
-
}
|
|
90
|
-
if (typeof entry.uuid === 'string') {
|
|
91
|
-
if (entry.parentUuid === null &&
|
|
92
|
-
!(entry.type === 'system' && entry.subtype === 'compact_boundary')) {
|
|
93
|
-
ordinaryRootCount += 1;
|
|
94
|
-
}
|
|
95
|
-
else if (typeof entry.parentUuid === 'string') {
|
|
96
|
-
if (entriesByUuid.has(entry.parentUuid)) {
|
|
97
|
-
childParentUuids.add(entry.parentUuid);
|
|
98
|
-
}
|
|
99
|
-
else {
|
|
100
|
-
externalParentUuids.add(entry.parentUuid);
|
|
101
|
-
externalParentReferenceCount += 1;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
if (typeof entry.uuid === 'string' && advancesLogicalTail(entry)) {
|
|
106
|
-
logicalTailUuid = entry.uuid;
|
|
107
|
-
}
|
|
108
|
-
for (const block of getClaudeContentBlocks(entry)) {
|
|
109
|
-
if (entry.type !== 'user' || block.type !== 'tool_result')
|
|
110
|
-
continue;
|
|
111
|
-
const id = block.tool_use_id;
|
|
112
|
-
if (typeof id !== 'string' ||
|
|
113
|
-
typeof entry.sourceToolAssistantUUID !== 'string' ||
|
|
114
|
-
toolCalls.get(id) !== entry.sourceToolAssistantUUID) {
|
|
115
|
-
throw new Error('Claude fork source has an unmatched tool_result');
|
|
116
|
-
}
|
|
117
|
-
if (completedToolCalls.has(id)) {
|
|
118
|
-
throw new Error(`Claude fork source has duplicate tool_result ${id}`);
|
|
119
|
-
}
|
|
120
|
-
completedToolCalls.add(id);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
if (compactBoundary) {
|
|
124
|
-
throw new Error('Claude compact boundary has no adjacent summary');
|
|
125
|
-
}
|
|
126
|
-
const omittedSeedUserParent = externalParentUuids.size === 1 ? [...externalParentUuids][0] : undefined;
|
|
127
|
-
const firstEntry = entries[0];
|
|
128
|
-
const isOmittedSeedUserChain = externalParentUuids.size === 1 &&
|
|
129
|
-
ordinaryRootCount === 0 &&
|
|
130
|
-
externalParentReferenceCount === 1 &&
|
|
131
|
-
firstEntry?.type === 'assistant' &&
|
|
132
|
-
firstEntry?.parentUuid === omittedSeedUserParent;
|
|
133
|
-
if ((externalParentUuids.size > 0 && !isOmittedSeedUserChain) ||
|
|
134
|
-
ordinaryRootCount > 1) {
|
|
135
|
-
throw new Error('Claude fork source has a dangling parentUuid');
|
|
136
|
-
}
|
|
137
|
-
const states = new Map();
|
|
138
|
-
for (const startUuid of entriesByUuid.keys()) {
|
|
139
|
-
if (states.get(startUuid) === 'visited')
|
|
140
|
-
continue;
|
|
141
|
-
const path = [];
|
|
142
|
-
let uuid = startUuid;
|
|
143
|
-
while (uuid !== undefined && entriesByUuid.has(uuid)) {
|
|
144
|
-
const state = states.get(uuid);
|
|
145
|
-
if (state === 'visiting') {
|
|
146
|
-
throw new Error('Claude fork source parentUuid graph has a cycle');
|
|
147
|
-
}
|
|
148
|
-
if (state === 'visited')
|
|
149
|
-
break;
|
|
150
|
-
states.set(uuid, 'visiting');
|
|
151
|
-
path.push(uuid);
|
|
152
|
-
const parentUuid = entriesByUuid.get(uuid)?.parentUuid;
|
|
153
|
-
uuid = typeof parentUuid === 'string' ? parentUuid : undefined;
|
|
154
|
-
}
|
|
155
|
-
for (const pathUuid of path)
|
|
156
|
-
states.set(pathUuid, 'visited');
|
|
157
|
-
}
|
|
158
|
-
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
159
|
-
const uuid = entries[index]?.uuid;
|
|
160
|
-
if (typeof uuid === 'string' && !childParentUuids.has(uuid))
|
|
161
|
-
return uuid;
|
|
162
|
-
}
|
|
163
|
-
return undefined;
|
|
164
|
-
}
|
|
165
|
-
export function createClaudeNativeFork({ source, sourceSessionId, sessionId, resumeSessionAt, }) {
|
|
166
|
-
const agentColors = [];
|
|
167
|
-
const titles = [];
|
|
168
|
-
const modes = [];
|
|
169
|
-
const permissionModes = [];
|
|
170
|
-
const prLinks = [];
|
|
171
|
-
const history = [];
|
|
172
|
-
const nativeHistory = [];
|
|
173
|
-
let lastPrompt;
|
|
174
|
-
let nativeLastPrompt;
|
|
175
|
-
const hasSelectiveSummary = source.some((entry) => {
|
|
176
|
-
if (entry.isCompactSummary !== true)
|
|
177
|
-
return false;
|
|
178
|
-
const metadata = entry.summarizeMetadata;
|
|
179
|
-
return (typeof metadata === 'object' &&
|
|
180
|
-
metadata !== null &&
|
|
181
|
-
(metadata.direction === 'from' ||
|
|
182
|
-
metadata.direction === 'up_to'));
|
|
183
|
-
});
|
|
184
|
-
const hasCompactHistory = source.some((entry) => entry.isCompactSummary === true ||
|
|
185
|
-
(entry.type === 'system' && entry.subtype === 'compact_boundary'));
|
|
186
|
-
const activeSource = resumeSessionAt === undefined
|
|
187
|
-
? hasCompactHistory || hasSelectiveSummary
|
|
188
|
-
? selectClaudeActiveTranscript(source)
|
|
189
|
-
: source
|
|
190
|
-
: selectClaudeTranscriptAtMessage(source, resumeSessionAt);
|
|
191
|
-
for (const entry of activeSource) {
|
|
192
|
-
if (TRANSIENT_ENTRY_TYPES.has(entry.type)) {
|
|
193
|
-
if ((entry.type === 'queue-operation' &&
|
|
194
|
-
entry.sessionId !== sourceSessionId) ||
|
|
195
|
-
(entry.type !== 'queue-operation' &&
|
|
196
|
-
entry.sessionId !== undefined &&
|
|
197
|
-
entry.sessionId !== sourceSessionId)) {
|
|
198
|
-
throw new Error('Claude fork source entry has the wrong sessionId');
|
|
199
|
-
}
|
|
200
|
-
continue;
|
|
201
|
-
}
|
|
202
|
-
if (entry.isSidechain === true) {
|
|
203
|
-
if (!isNativeForkableEntryType(entry.type)) {
|
|
204
|
-
throw new Error(`Claude transcript entry type ${entry.type} is not forkable by Praxis`);
|
|
205
|
-
}
|
|
206
|
-
if (typeof entry.sessionId !== 'string' || entry.sessionId.length === 0) {
|
|
207
|
-
throw new Error('Claude fork entry has no sessionId');
|
|
208
|
-
}
|
|
209
|
-
continue;
|
|
210
|
-
}
|
|
211
|
-
if (!isNativeForkableEntryType(entry.type)) {
|
|
212
|
-
throw new Error(`Claude transcript entry type ${entry.type} is not forkable by Praxis`);
|
|
213
|
-
}
|
|
214
|
-
if (typeof entry.sessionId !== 'string' || entry.sessionId.length === 0) {
|
|
215
|
-
throw new Error('Claude fork entry has no sessionId');
|
|
216
|
-
}
|
|
217
|
-
if (entry.sessionId !== sourceSessionId) {
|
|
218
|
-
throw new Error('Claude fork source entry has the wrong sessionId');
|
|
219
|
-
}
|
|
220
|
-
const copied = copyNativeEntryWithSessionId(entry, sessionId);
|
|
221
|
-
if (entry.type === 'agent-color')
|
|
222
|
-
agentColors.push(copied);
|
|
223
|
-
else if (entry.type === 'ai-title')
|
|
224
|
-
titles.push(copied);
|
|
225
|
-
else if (entry.type === 'mode')
|
|
226
|
-
modes.push(copied);
|
|
227
|
-
else if (entry.type === 'permission-mode')
|
|
228
|
-
permissionModes.push(copied);
|
|
229
|
-
else if (entry.type === 'pr-link')
|
|
230
|
-
prLinks.push(copied);
|
|
231
|
-
else if (entry.type === 'last-prompt') {
|
|
232
|
-
lastPrompt = copied;
|
|
233
|
-
nativeLastPrompt = entry;
|
|
234
|
-
}
|
|
235
|
-
else {
|
|
236
|
-
history.push(copied);
|
|
237
|
-
nativeHistory.push(entry);
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
if (!nativeHistory.some((entry) => typeof entry.uuid === 'string')) {
|
|
241
|
-
throw new Error('Claude session has no native history to fork');
|
|
242
|
-
}
|
|
243
|
-
const logicalTailUuid = validateNativeHistory(nativeHistory);
|
|
244
|
-
if (nativeLastPrompt &&
|
|
245
|
-
(typeof nativeLastPrompt.leafUuid !== 'string' ||
|
|
246
|
-
nativeLastPrompt.leafUuid !== logicalTailUuid)) {
|
|
247
|
-
nativeLastPrompt = undefined;
|
|
248
|
-
lastPrompt = undefined;
|
|
249
|
-
}
|
|
250
|
-
return [
|
|
251
|
-
...agentColors.slice(-1),
|
|
252
|
-
...titles.slice(-1),
|
|
253
|
-
...modes.slice(-1),
|
|
254
|
-
...permissionModes.slice(-1),
|
|
255
|
-
...prLinks.slice(-1),
|
|
256
|
-
...history,
|
|
257
|
-
...(lastPrompt ? [lastPrompt] : []),
|
|
258
|
-
];
|
|
259
|
-
}
|
|
260
|
-
//# sourceMappingURL=fork.js.map
|