praxis-agent 0.34.0 → 0.35.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 (49) hide show
  1. package/README.md +4 -0
  2. package/dist/application/session-service.d.ts +0 -1
  3. package/dist/application/session-service.js +1619 -1617
  4. package/dist/application/subagent-service.js +27 -16
  5. package/dist/application/turn-lifecycle.d.ts +38 -0
  6. package/dist/application/turn-lifecycle.js +36 -0
  7. package/dist/cli/interactive.js +17 -7
  8. package/dist/cli/protocol.js +5 -2
  9. package/dist/cli/tui/claude-style.d.ts +5 -48
  10. package/dist/cli/tui/claude-style.js +19 -73
  11. package/dist/cli/tui/transcript-presentation.d.ts +89 -0
  12. package/dist/cli/tui/transcript-presentation.js +169 -0
  13. package/dist/cli/tui/transcript-viewport.d.ts +5 -34
  14. package/dist/cli/tui/transcript-viewport.js +639 -158
  15. package/dist/cli/tui/tui-view-model.d.ts +6 -2
  16. package/dist/cli/tui/tui-view-model.js +23 -6
  17. package/dist/compatibility/claude/context.d.ts +2 -2
  18. package/dist/compatibility/claude/context.js +2 -14
  19. package/dist/compatibility/claude/data-plane-adapter.d.ts +9 -0
  20. package/dist/compatibility/claude/data-plane-adapter.js +44 -0
  21. package/dist/compatibility/claude/paths.js +2 -2
  22. package/dist/compatibility/claude/shared-resources.d.ts +7 -38
  23. package/dist/compatibility/claude/transcript-codec.d.ts +21 -0
  24. package/dist/compatibility/claude/transcript-codec.js +512 -0
  25. package/dist/core/context.d.ts +25 -7
  26. package/dist/core/context.js +46 -0
  27. package/dist/core/prompt-composer.d.ts +8 -16
  28. package/dist/core/prompt-composer.js +65 -25
  29. package/dist/core/resources.d.ts +39 -0
  30. package/dist/core/resources.js +2 -0
  31. package/dist/core/runtime.d.ts +1 -1
  32. package/dist/core/session.d.ts +2 -0
  33. package/dist/core/session.js +5 -0
  34. package/dist/core/transcript-codec.d.ts +70 -0
  35. package/dist/core/transcript-codec.js +60 -0
  36. package/dist/core/transcript-event.d.ts +33 -0
  37. package/dist/core/transcript-event.js +198 -0
  38. package/dist/persistence/data-plane-adapter.d.ts +32 -0
  39. package/dist/persistence/data-plane-adapter.js +2 -0
  40. package/dist/persistence/data-plane.d.ts +4 -20
  41. package/dist/persistence/data-plane.js +10 -47
  42. package/dist/persistence/native-data-plane-adapter.d.ts +9 -0
  43. package/dist/persistence/native-data-plane-adapter.js +39 -0
  44. package/dist/persistence/native-resources.d.ts +4 -4
  45. package/dist/persistence/native-transcript-codec.d.ts +13 -0
  46. package/dist/persistence/native-transcript-codec.js +102 -0
  47. package/dist/tools/claude-user-message.d.ts +0 -1
  48. package/dist/tools/claude-user-message.js +0 -1
  49. package/package.json +6 -2
@@ -8,7 +8,8 @@ import { createClaudeAsyncAgentToolUseResult, createClaudeAgentToolUseResult, cr
8
8
  import { projectClaudeModelMessages, projectClaudeSidechainContinuationMessages, } from '../compatibility/claude/projection.js';
9
9
  import { selectClaudeSchemaAdapter, } from '../compatibility/claude/schema.js';
10
10
  import { createClaudeHookAttachmentEntries, translateProviderEvents, } from '../compatibility/claude/translation.js';
11
- import { injectFirstUserMessageContext, } from '../core/context.js';
11
+ import { injectFirstUserMessageContext, projectContextSnapshot, } from '../core/context.js';
12
+ import { assembleContextSnapshot } from '../core/prompt-composer.js';
12
13
  import { ContextBudget } from '../core/context-budget.js';
13
14
  import { AgentRuntime, } from '../core/runtime.js';
14
15
  import { resolveToolSchedulingPolicy } from '../core/tool-scheduling-policy.js';
@@ -1995,9 +1996,6 @@ export class ClaudeSubagentExecutor {
1995
1996
  const agentSystem = memoryPrompt
1996
1997
  ? `${baseSystem}\n\n${memoryPrompt}`
1997
1998
  : baseSystem;
1998
- const system = options.outputSchema
1999
- ? `${agentSystem}\n\nYou MUST call StructuredOutput exactly once at the end with a value matching its schema.`
2000
- : agentSystem;
2001
1999
  const preloadedSkills = agentSkillMessages(this.options.extensions, customAgent);
2002
2000
  let stopHookActive = false;
2003
2001
  const contextBudget = options.provider.capabilities.contextWindowTokens
@@ -2016,22 +2014,28 @@ export class ClaudeSubagentExecutor {
2016
2014
  ? runtimeTools.definitions()
2017
2015
  : [];
2018
2016
  let observedMessages;
2017
+ let stableSystemMessageCount = 0;
2019
2018
  const assembleMessages = async () => {
2020
- const assembledContext = await this.options.contextAssembler?.assemble({
2019
+ const assembledContext = await assembleContextSnapshot(this.options.contextAssembler, {
2021
2020
  cwd,
2022
2021
  lifecycleId: options.agentId,
2023
2022
  mode: 'subagent',
2024
- baseSystemPrompt: system,
2023
+ baseSystemPrompt: agentSystem,
2024
+ ...(options.outputSchema
2025
+ ? {
2026
+ turn: {
2027
+ structuredOutput: true,
2028
+ },
2029
+ }
2030
+ : {}),
2025
2031
  });
2026
- const composedSubagentPolicy = assembledContext?.promptSections?.some((section) => section.id === 'subagent-policy');
2032
+ const contextProjection = projectContextSnapshot(assembledContext);
2033
+ stableSystemMessageCount = contextProjection.stableSystemSectionCount;
2027
2034
  const messages = [
2028
- ...(assembledContext?.systemMessages ?? []),
2029
- ...(composedSubagentPolicy
2030
- ? []
2031
- : [{ role: 'system', content: system }]),
2035
+ ...contextProjection.systemMessages,
2032
2036
  ...injectFirstUserMessageContext(options.continuationMessage === undefined
2033
2037
  ? projectClaudeModelMessages(snapshot.entries)
2034
- : projectClaudeSidechainContinuationMessages(snapshot.entries), assembledContext?.firstUserMessageContext),
2038
+ : projectClaudeSidechainContinuationMessages(snapshot.entries), contextProjection.firstUserMessageContext),
2035
2039
  ...preloadedSkills,
2036
2040
  ];
2037
2041
  observedMessages = messages;
@@ -2042,11 +2046,17 @@ export class ClaudeSubagentExecutor {
2042
2046
  };
2043
2047
  const configuredEffort = typeof customAgent?.effort === 'string' ? customAgent.effort : undefined;
2044
2048
  const effectiveEffort = options.effort ?? configuredEffort;
2045
- const result = await runtime.run({
2049
+ const initialMessages = await assembleMessages();
2050
+ const runtimeRequest = {
2046
2051
  sessionId: String(options.root.sessionId),
2047
- messages: await assembleMessages(),
2052
+ messages: initialMessages,
2048
2053
  collectMetrics: true,
2049
- reloadMessages: assembleMessages,
2054
+ stableSystemMessageCount,
2055
+ reloadMessages: async () => {
2056
+ const messages = await assembleMessages();
2057
+ runtimeRequest.stableSystemMessageCount = stableSystemMessageCount;
2058
+ return messages;
2059
+ },
2050
2060
  cwd,
2051
2061
  ...(effectiveEffort ? { effort: effectiveEffort } : {}),
2052
2062
  toolResultDirectory: options.toolResultDirectory,
@@ -2119,7 +2129,8 @@ export class ClaudeSubagentExecutor {
2119
2129
  }
2120
2130
  : {}),
2121
2131
  ...(options.signal ? { signal: options.signal } : {}),
2122
- });
2132
+ };
2133
+ const result = await runtime.run(runtimeRequest);
2123
2134
  contextBudget?.observeUsage(result.usage, observedMessages ?? [], definitions);
2124
2135
  this.options.eventSink?.({
2125
2136
  type: 'task-progress',
@@ -0,0 +1,38 @@
1
+ import { type ModelDocument, type ModelImage, type RuntimeEventSink } from '../core/runtime.js';
2
+ export type TurnActivation = {
3
+ kind: 'start';
4
+ sessionId: string;
5
+ name?: string;
6
+ } | {
7
+ kind: 'resume';
8
+ sessionId: string;
9
+ name?: string;
10
+ atMessageId?: string;
11
+ };
12
+ export type TurnSubmission = {
13
+ kind: 'prompt';
14
+ text: string;
15
+ images?: readonly ModelImage[];
16
+ documents?: readonly ModelDocument[];
17
+ } | {
18
+ kind: 'shell';
19
+ command: string;
20
+ } | {
21
+ kind: 'retry';
22
+ prompt: string;
23
+ };
24
+ export interface TurnRequest {
25
+ activation: TurnActivation;
26
+ submission: TurnSubmission;
27
+ signal?: AbortSignal;
28
+ }
29
+ export declare class TurnTerminalController {
30
+ private readonly sink;
31
+ private terminal;
32
+ constructor(sink: RuntimeEventSink);
33
+ emit: RuntimeEventSink;
34
+ complete(): void;
35
+ fail(error: unknown, signal?: AbortSignal): void;
36
+ private transition;
37
+ }
38
+ //# sourceMappingURL=turn-lifecycle.d.ts.map
@@ -0,0 +1,36 @@
1
+ import { AgentRunCancelledError, ModelProviderError, } from '../core/runtime.js';
2
+ export class TurnTerminalController {
3
+ sink;
4
+ terminal = false;
5
+ constructor(sink) {
6
+ this.sink = sink;
7
+ }
8
+ emit = (event) => {
9
+ if (event.type === 'state' &&
10
+ (event.state === 'completed' ||
11
+ event.state === 'cancelled' ||
12
+ event.state === 'failed')) {
13
+ return;
14
+ }
15
+ this.sink(event);
16
+ };
17
+ complete() {
18
+ this.transition('completed');
19
+ }
20
+ fail(error, signal) {
21
+ if (this.terminal)
22
+ return;
23
+ const cancelled = signal?.aborted === true ||
24
+ error instanceof AgentRunCancelledError ||
25
+ (error instanceof ModelProviderError && error.kind === 'cancelled');
26
+ this.transition(cancelled ? 'cancelled' : 'failed');
27
+ }
28
+ transition(state) {
29
+ if (this.terminal) {
30
+ throw new Error('Turn terminal transition already emitted');
31
+ }
32
+ this.terminal = true;
33
+ this.sink({ type: 'state', state });
34
+ }
35
+ }
36
+ //# sourceMappingURL=turn-lifecycle.js.map
@@ -18,7 +18,6 @@ import { loadClaudeReleaseNotes } from './tui/release-notes.js';
18
18
  import { fullscreenInkRenderOptions } from './tui/fullscreen-renderer.js';
19
19
  import { StreamingFrameBuffer } from './tui/streaming-frame-buffer.js';
20
20
  import { createClaudeStatusLineInput, StatusLine } from './tui/status-line.js';
21
- import { FULLSCREEN_TRANSCRIPT_RESERVED_ROWS, transcriptLineCount, } from './tui/transcript-viewport.js';
22
21
  import { projectTuiView, resolveTuiRenderer } from './tui/tui-view-model.js';
23
22
  import { loadGitDiff, visiblePatchLines, } from './tui/git-diff.js';
24
23
  import { addTuiPermissionRule, loadTuiPermissionRules, removeTuiPermissionRule, } from './tui/permission-settings.js';
@@ -466,7 +465,7 @@ export function InteractiveApp({ dataPlane = resolveDataPlane(), configRoot: sup
466
465
  // composer/status chrome intact and keeping the active stream visible.
467
466
  // Classic and screen-reader modes always render the full history exactly as
468
467
  // before.
469
- const { projectedHistory, resumed, freshSession, hasConversationHistory } = projectTuiView({
468
+ const { transcriptEntries, transcriptPageRows, maxTranscriptScrollOffset, resumed, freshSession, hasConversationHistory, } = useMemo(() => projectTuiView({
470
469
  initialHistory,
471
470
  history,
472
471
  resume: resume !== undefined,
@@ -475,7 +474,19 @@ export function InteractiveApp({ dataPlane = resolveDataPlane(), configRoot: sup
475
474
  rows,
476
475
  width,
477
476
  scrollOffset: transcriptScrollOffset,
478
- });
477
+ detailedTranscript: thinkingExpanded || runtimeSettings.verbose,
478
+ }), [
479
+ initialHistory,
480
+ history,
481
+ resume,
482
+ fixedViewport,
483
+ axScreenReader,
484
+ rows,
485
+ width,
486
+ transcriptScrollOffset,
487
+ thinkingExpanded,
488
+ runtimeSettings.verbose,
489
+ ]);
479
490
  const sessionLoadRef = useRef(0);
480
491
  const [turnDiffs, setTurnDiffs] = useState([]);
481
492
  const turnNumberRef = useRef(0);
@@ -2979,7 +2990,7 @@ export function InteractiveApp({ dataPlane = resolveDataPlane(), configRoot: sup
2979
2990
  !question &&
2980
2991
  !elicitation &&
2981
2992
  !selectingSession) {
2982
- const page = Math.max(1, (rows ?? 0) - FULLSCREEN_TRANSCRIPT_RESERVED_ROWS);
2993
+ const page = transcriptPageRows;
2983
2994
  const scrollDelta = key.pageUp
2984
2995
  ? page
2985
2996
  : key.pageDown
@@ -2994,8 +3005,7 @@ export function InteractiveApp({ dataPlane = resolveDataPlane(), configRoot: sup
2994
3005
  ? -1
2995
3006
  : 0;
2996
3007
  if (scrollDelta !== 0) {
2997
- setTranscriptScrollOffset((current) => Math.min(Math.max(0, transcriptLineCount(history, width) -
2998
- Math.max(1, (rows ?? 0) - FULLSCREEN_TRANSCRIPT_RESERVED_ROWS)), Math.max(0, current + Math.trunc(scrollDelta))));
3008
+ setTranscriptScrollOffset((current) => Math.min(Math.max(0, maxTranscriptScrollOffset), Math.max(0, current + Math.trunc(scrollDelta))));
2999
3009
  return;
3000
3010
  }
3001
3011
  }
@@ -5857,7 +5867,7 @@ export function InteractiveApp({ dataPlane = resolveDataPlane(), configRoot: sup
5857
5867
  minHeight: 0,
5858
5868
  overflowY: 'hidden',
5859
5869
  }
5860
- : {}), children: _jsx(Transcript, { items: projectedHistory, activeText: activeText, activeThinking: activeThinking, activeStreamVisible: transcriptScrollOffset === 0, thinkingExpanded: thinkingExpanded, detailedTranscript: thinkingExpanded || runtimeSettings.verbose, screenReader: axScreenReader }) }), externalEditorRequest !== null ||
5870
+ : {}), children: _jsx(Transcript, { entries: transcriptEntries, activeText: activeText, activeThinking: activeThinking, activeStreamVisible: transcriptScrollOffset === 0, thinkingExpanded: thinkingExpanded, detailedTranscript: thinkingExpanded || runtimeSettings.verbose, screenReader: axScreenReader }) }), externalEditorRequest !== null ||
5861
5871
  keybindingsEditing ||
5862
5872
  memoryEditorRequest !== null ? (_jsx(ExternalEditorWait, { screenReader: axScreenReader })) : permission ? (permission.kind === 'tool' && toolPermissionModel ? (_jsx(ToolPermissionDialog, { model: toolPermissionModel, selection: permissionSelection, feedbackMode: permissionFeedbackMode, feedback: input, ruleEditor: permissionRuleEditor, screenReader: axScreenReader })) : (_jsxs(DialogFrame, { title: `Retry interrupted ${permission.call.name}?`, screenReader: axScreenReader, children: [_jsx(Box, { flexDirection: "column", paddingX: 1, paddingY: 1, children: _jsx(Text, { bold: true, children: describeTool(permission.call, sensitiveValues) }) }), _jsx(Text, { children: "Do you want to proceed?" }), _jsxs(Text, { inverse: !axScreenReader && permissionSelection === 0, children: [selectionPrefix(permissionSelection === 0, axScreenReader), "1. Yes"] }), _jsxs(Text, { inverse: !axScreenReader && permissionSelection === 1, children: [selectionPrefix(permissionSelection === 1, axScreenReader), "2. No"] }), permissionFeedbackMode ? (_jsxs(Text, { children: ["\u203A", ' ', input ||
5863
5873
  (permissionSelection === 0
@@ -1788,11 +1788,14 @@ export class StreamJsonOutput {
1788
1788
  event.state === 'executing-tools' ||
1789
1789
  event.state === 'persisting-results' ||
1790
1790
  event.state === 'completed' ||
1791
- event.state === 'cancelled') {
1791
+ event.state === 'cancelled' ||
1792
+ event.state === 'failed') {
1792
1793
  this.flushAssistant();
1793
1794
  if (event.state === 'awaiting-permission')
1794
1795
  this.writeSessionState('requires_action');
1795
- else if (event.state === 'completed' || event.state === 'cancelled') {
1796
+ else if (event.state === 'completed' ||
1797
+ event.state === 'cancelled' ||
1798
+ event.state === 'failed') {
1796
1799
  this.writeSessionState('idle');
1797
1800
  }
1798
1801
  }
@@ -1,5 +1,5 @@
1
1
  import { type ReactElement } from 'react';
2
- import type { ModelToolCall, ModelUsage } from '../../core/runtime.js';
2
+ import type { ModelUsage } from '../../core/runtime.js';
3
3
  import type { AgentColorName } from '../../compatibility/claude/agent-color.js';
4
4
  import type { DataPlane } from '../../persistence/data-plane.js';
5
5
  import type { TuiFileEntry, TuiMentionEntry } from './file-picker.js';
@@ -10,6 +10,7 @@ import type { TuiPermissionRule } from './permission-settings.js';
10
10
  import type { RecentlyDeniedAction } from './recently-denied.js';
11
11
  import type { CustomThemeToken, TuiCustomTheme } from './custom-themes.js';
12
12
  import type { TuiSlashCommand } from './slash-commands.js';
13
+ import type { TranscriptPresentationEntry } from './transcript-presentation.js';
13
14
  import { type TuiTheme } from './theme.js';
14
15
  export interface TuiDisplayMetadata {
15
16
  version: string;
@@ -19,51 +20,7 @@ export interface TuiDisplayMetadata {
19
20
  permissionMode?: string;
20
21
  contextWindowTokens?: number;
21
22
  }
22
- export type TranscriptItem = {
23
- kind: 'user' | 'assistant' | 'notice' | 'warning';
24
- text: string;
25
- } | {
26
- kind: 'local-result';
27
- text: string;
28
- } | {
29
- kind: 'thinking';
30
- text: string;
31
- } | {
32
- kind: 'compact';
33
- summary: string;
34
- } | {
35
- kind: 'context';
36
- usedTokens: number;
37
- contextWindowTokens: number;
38
- model?: string;
39
- skills: readonly {
40
- name: string;
41
- tokens: number;
42
- }[];
43
- memoryFiles: readonly {
44
- path: string;
45
- tokens: number;
46
- }[];
47
- } | {
48
- kind: 'tool';
49
- call: ModelToolCall;
50
- detail: string;
51
- } | {
52
- kind: 'tool-result';
53
- callId: string;
54
- text: string;
55
- isError: boolean;
56
- } | {
57
- kind: 'shell';
58
- callId: string;
59
- command: string;
60
- } | {
61
- kind: 'shell-result';
62
- callId: string;
63
- stdout: string;
64
- stderr: string;
65
- isError: boolean;
66
- };
23
+ export type { TranscriptItem } from './transcript-presentation.js';
67
24
  export interface TuiBtwEntry {
68
25
  id: number;
69
26
  question: string;
@@ -92,8 +49,8 @@ export declare function activeStreamWindow(text: string): {
92
49
  pendingText: string;
93
50
  truncated: boolean;
94
51
  };
95
- export declare function Transcript({ items, activeText, activeThinking, activeStreamVisible, thinkingExpanded, detailedTranscript, screenReader, }: {
96
- items: readonly TranscriptItem[];
52
+ export declare function Transcript({ entries, activeText, activeThinking, activeStreamVisible, thinkingExpanded, detailedTranscript, screenReader, }: {
53
+ entries: readonly TranscriptPresentationEntry[];
97
54
  activeText: string;
98
55
  activeThinking?: string;
99
56
  activeStreamVisible?: boolean;
@@ -516,94 +516,42 @@ function ActiveStreamText({ text }) {
516
516
  const { stableText, pendingText, truncated } = activeStreamWindow(text);
517
517
  return (_jsxs(Box, { flexDirection: "column", children: [truncated ? _jsx(Text, { dimColor: true, children: "\u2026 earlier streaming content \u2026" }) : null, stableText ? _jsx(MarkdownText, { text: stableText }) : null, pendingText ? _jsx(Text, { children: pendingText }) : null] }));
518
518
  }
519
- export function Transcript({ items, activeText, activeThinking = '', activeStreamVisible = true, thinkingExpanded = false, detailedTranscript = false, screenReader, }) {
519
+ export function Transcript({ entries, activeText, activeThinking = '', activeStreamVisible = true, thinkingExpanded = false, detailedTranscript = false, screenReader, }) {
520
520
  const palette = useTuiPalette();
521
521
  const detailed = thinkingExpanded || detailedTranscript;
522
- const results = new Map(items
523
- .filter((item) => item.kind === 'tool-result')
524
- .map((item) => [item.callId, item]));
525
- const pairedResults = new Set(items
526
- .filter((item) => item.kind === 'tool' && results.has(item.call.id))
527
- .map((item) => item.call.id));
528
- const shellResults = new Map(items
529
- .filter((item) => item.kind === 'shell-result')
530
- .map((item) => [item.callId, item]));
531
- const pairedShellResults = new Set(items
532
- .filter((item) => item.kind === 'shell' && shellResults.has(item.callId))
533
- .map((item) => item.callId));
534
- const readGroupStarts = new Map();
535
- const groupedReadTools = new Set();
536
- if (!detailed && !screenReader) {
537
- for (let index = 0; index < items.length; index += 1) {
538
- const first = items[index];
539
- if (first?.kind !== 'tool' || first.call.name !== 'Read')
540
- continue;
541
- let cursor = index;
542
- const memberIndexes = [];
543
- const memberCallIds = new Set();
544
- while (cursor < items.length) {
545
- const candidate = items[cursor];
546
- if (candidate?.kind === 'tool' && candidate.call.name === 'Read') {
547
- const result = results.get(candidate.call.id);
548
- if (!result || result.isError)
549
- break;
550
- memberIndexes.push(cursor);
551
- memberCallIds.add(candidate.call.id);
552
- cursor += 1;
553
- continue;
522
+ return (_jsxs(Box, { flexDirection: "column", children: [entries.map((entry, index) => {
523
+ if (entry.kind === 'read-summary') {
524
+ return (_jsxs(Text, { children: [' ', "Read ", entry.count, " file", entry.count === 1 ? '' : 's', ' ', _jsx(Text, { dimColor: true, children: "(ctrl+o to expand)" })] }, entry.key));
554
525
  }
555
- if (candidate?.kind === 'tool-result' &&
556
- memberCallIds.has(candidate.callId)) {
557
- cursor += 1;
558
- continue;
559
- }
560
- break;
561
- }
562
- if (memberIndexes.length === 0)
563
- continue;
564
- readGroupStarts.set(index, memberIndexes.length);
565
- for (const memberIndex of memberIndexes.slice(1)) {
566
- groupedReadTools.add(memberIndex);
567
- }
568
- index = cursor - 1;
569
- }
570
- }
571
- return (_jsxs(Box, { flexDirection: "column", children: [items.map((item, index) => {
572
- const readCount = readGroupStarts.get(index);
573
- if (readCount !== undefined) {
574
- return (_jsxs(Text, { children: [' ', "Read ", readCount, " file", readCount === 1 ? '' : 's', ' ', _jsx(Text, { dimColor: true, children: "(ctrl+o to expand)" })] }, index));
575
- }
576
- if (groupedReadTools.has(index))
577
- return null;
526
+ const item = entry.item;
578
527
  if (item.kind === 'user') {
579
- return (_jsxs(Box, { marginTop: index === 0 ? 0 : 1, children: [_jsx(Text, { color: palette.brand, bold: true, children: screenReader ? 'You: ' : '❯ ' }), _jsx(Text, { bold: true, children: item.text })] }, index));
528
+ return (_jsxs(Box, { marginTop: index === 0 ? 0 : 1, children: [_jsx(Text, { color: palette.brand, bold: true, children: screenReader ? 'You: ' : '❯ ' }), _jsx(Text, { bold: true, children: item.text })] }, entry.key));
580
529
  }
581
530
  if (item.kind === 'assistant') {
582
- return (_jsxs(Box, { marginTop: 1, children: [screenReader ? _jsx(Text, { children: "Praxis:" }) : null, !screenReader ? _jsx(Text, { color: palette.accent, children: "\u23FA " }) : null, _jsx(MarkdownText, { text: item.text })] }, index));
531
+ return (_jsxs(Box, { marginTop: 1, children: [screenReader ? _jsx(Text, { children: "Praxis:" }) : null, !screenReader ? _jsx(Text, { color: palette.accent, children: "\u23FA " }) : null, _jsx(MarkdownText, { text: item.text })] }, entry.key));
583
532
  }
584
533
  if (item.kind === 'thinking') {
585
- return (_jsx(ThinkingBlock, { text: item.text, active: false, expanded: detailed, screenReader: screenReader }, index));
534
+ return (_jsx(ThinkingBlock, { text: item.text, active: false, expanded: detailed, screenReader: screenReader }, entry.key));
586
535
  }
587
536
  if (item.kind === 'compact') {
588
537
  return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: palette.accent, italic: true, children: screenReader
589
538
  ? 'Conversation compacted'
590
- : '✻ Conversation compacted (ctrl+o for history)' }), detailed ? (_jsx(Box, { marginLeft: screenReader ? 0 : 2, children: _jsx(MarkdownText, { text: item.summary }) })) : null] }, index));
539
+ : '✻ Conversation compacted (ctrl+o for history)' }), detailed ? (_jsx(Box, { marginLeft: screenReader ? 0 : 2, children: _jsx(MarkdownText, { text: item.summary }) })) : null] }, entry.key));
591
540
  }
592
541
  if (item.kind === 'context') {
593
- return (_jsx(ContextUsageBlock, { ...item, screenReader: screenReader }, index));
542
+ return (_jsx(ContextUsageBlock, { ...item, screenReader: screenReader }, entry.key));
594
543
  }
595
544
  if (item.kind === 'tool') {
596
- const result = results.get(item.call.id);
597
- return (_jsx(ToolTranscriptEntry, { call: item.call, detail: item.detail, ...(result ? { result } : {}), detailed: detailed || screenReader }, index));
545
+ return (_jsx(ToolTranscriptEntry, { call: item.call, detail: item.detail, ...(entry.kind === 'tool' && entry.result
546
+ ? { result: entry.result }
547
+ : {}), detailed: detailed || screenReader }, entry.key));
598
548
  }
599
549
  if (item.kind === 'tool-result') {
600
- if (pairedResults.has(item.callId))
601
- return null;
602
550
  const text = item.text.length > 500 ? `${item.text.slice(0, 497)}...` : item.text;
603
- return (_jsxs(Box, { marginLeft: 2, flexDirection: "column", children: [_jsx(Text, { color: item.isError ? palette.error : palette.muted, children: item.isError ? '└ Error' : '└ Result' }), item.isError ? (_jsx(Text, { color: palette.error, children: text })) : (_jsx(ToolResultText, { text: text }))] }, index));
551
+ return (_jsxs(Box, { marginLeft: 2, flexDirection: "column", children: [_jsx(Text, { color: item.isError ? palette.error : palette.muted, children: item.isError ? '└ Error' : '└ Result' }), item.isError ? (_jsx(Text, { color: palette.error, children: text })) : (_jsx(ToolResultText, { text: text }))] }, entry.key));
604
552
  }
605
553
  if (item.kind === 'shell') {
606
- const result = shellResults.get(item.callId);
554
+ const result = entry.kind === 'shell' ? entry.result : undefined;
607
555
  const output = result
608
556
  ? [result.stdout, result.stderr]
609
557
  .filter(Boolean)
@@ -616,18 +564,16 @@ export function Transcript({ items, activeText, activeThinking = '', activeStrea
616
564
  const lines = contentLines(output);
617
565
  const visible = detailed || screenReader ? lines : lines.slice(0, 3);
618
566
  const hidden = lines.length - visible.length;
619
- return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { children: _jsxs(Text, { bold: true, children: [screenReader ? 'Shell command: ' : '! ', item.command] }) }), result ? (_jsxs(Box, { marginLeft: 2, flexDirection: "column", children: [lines.length === 0 ? (_jsx(Text, { dimColor: true, children: "\u23BF " })) : (visible.map((line, lineIndex) => (_jsxs(Text, { ...(result.isError ? { color: palette.error } : {}), dimColor: !result.isError, children: [lineIndex === 0 ? '⎿ ' : ' ', line || ' '] }, lineIndex)))), hidden > 0 ? (_jsxs(Text, { dimColor: true, children: [' ', "\u2026 +", hidden, " lines (ctrl+o to expand)"] })) : null] })) : null] }, index));
567
+ return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { children: _jsxs(Text, { bold: true, children: [screenReader ? 'Shell command: ' : '! ', item.command] }) }), result ? (_jsxs(Box, { marginLeft: 2, flexDirection: "column", children: [lines.length === 0 ? (_jsx(Text, { dimColor: true, children: "\u23BF " })) : (visible.map((line, lineIndex) => (_jsxs(Text, { ...(result.isError ? { color: palette.error } : {}), dimColor: !result.isError, children: [lineIndex === 0 ? '⎿ ' : ' ', line || ' '] }, lineIndex)))), hidden > 0 ? (_jsxs(Text, { dimColor: true, children: [' ', "\u2026 +", hidden, " lines (ctrl+o to expand)"] })) : null] })) : null] }, entry.key));
620
568
  }
621
569
  if (item.kind === 'shell-result') {
622
- if (pairedShellResults.has(item.callId))
623
- return null;
624
570
  const output = [item.stdout, item.stderr].filter(Boolean).join('\n');
625
- return (_jsxs(Text, { ...(item.isError ? { color: palette.error } : {}), children: ["\u23BF ", output] }, index));
571
+ return (_jsxs(Text, { ...(item.isError ? { color: palette.error } : {}), children: ["\u23BF ", output] }, entry.key));
626
572
  }
627
573
  if (item.kind === 'local-result') {
628
- return (_jsx(Box, { marginLeft: 2, children: _jsxs(Text, { dimColor: true, children: ["\u23BF ", item.text] }) }, index));
574
+ return (_jsx(Box, { marginLeft: 2, children: _jsxs(Text, { dimColor: true, children: ["\u23BF ", item.text] }) }, entry.key));
629
575
  }
630
- return (_jsxs(Text, { ...(item.kind === 'warning' ? { color: palette.error } : {}), dimColor: item.kind === 'notice', children: [item.kind === 'warning' ? '⚠ ' : '· ', item.text] }, index));
576
+ return (_jsxs(Text, { ...(item.kind === 'warning' ? { color: palette.error } : {}), dimColor: item.kind === 'notice', children: [item.kind === 'warning' ? '⚠ ' : '· ', item.text] }, entry.key));
631
577
  }), activeStreamVisible && activeThinking ? (_jsx(ThinkingBlock, { text: activeThinking, active: true, expanded: detailed, screenReader: screenReader })) : activeStreamVisible && activeText ? (_jsxs(Box, { marginTop: 1, children: [screenReader ? (_jsx(Text, { children: "Praxis: " })) : (_jsx(Text, { color: palette.accent, children: "\u2733 " })), screenReader ? (_jsx(MarkdownText, { text: activeText })) : (_jsx(ActiveStreamText, { text: activeText }))] })) : null] }));
632
578
  }
633
579
  export function DiffDashboard({ snapshots, sourceIndex, selectedIndex, viewingFile, scrollOffset, width, screenReader, }) {
@@ -0,0 +1,89 @@
1
+ import type { ModelToolCall } from '../../core/runtime.js';
2
+ export type TranscriptItem = {
3
+ kind: 'user' | 'assistant' | 'notice' | 'warning';
4
+ text: string;
5
+ } | {
6
+ kind: 'local-result';
7
+ text: string;
8
+ } | {
9
+ kind: 'thinking';
10
+ text: string;
11
+ } | {
12
+ kind: 'compact';
13
+ summary: string;
14
+ } | {
15
+ kind: 'context';
16
+ usedTokens: number;
17
+ contextWindowTokens: number;
18
+ model?: string;
19
+ skills: readonly {
20
+ name: string;
21
+ tokens: number;
22
+ }[];
23
+ memoryFiles: readonly {
24
+ path: string;
25
+ tokens: number;
26
+ }[];
27
+ } | {
28
+ kind: 'tool';
29
+ call: ModelToolCall;
30
+ detail: string;
31
+ } | {
32
+ kind: 'tool-result';
33
+ callId: string;
34
+ text: string;
35
+ isError: boolean;
36
+ } | {
37
+ kind: 'shell';
38
+ callId: string;
39
+ command: string;
40
+ } | {
41
+ kind: 'shell-result';
42
+ callId: string;
43
+ stdout: string;
44
+ stderr: string;
45
+ isError: boolean;
46
+ };
47
+ export type TranscriptPresentationMode = 'normal' | 'audit' | 'screen-reader';
48
+ type ToolItem = Extract<TranscriptItem, {
49
+ kind: 'tool';
50
+ }>;
51
+ type ToolResultItem = Extract<TranscriptItem, {
52
+ kind: 'tool-result';
53
+ }>;
54
+ type ShellItem = Extract<TranscriptItem, {
55
+ kind: 'shell';
56
+ }>;
57
+ type ShellResultItem = Extract<TranscriptItem, {
58
+ kind: 'shell-result';
59
+ }>;
60
+ export type TranscriptPresentationEntry = {
61
+ kind: 'item';
62
+ key: string;
63
+ item: Exclude<TranscriptItem, ToolItem | ToolResultItem | ShellItem | ShellResultItem>;
64
+ } | {
65
+ kind: 'tool';
66
+ key: string;
67
+ item: ToolItem;
68
+ result?: ToolResultItem;
69
+ } | {
70
+ kind: 'orphan-tool-result';
71
+ key: string;
72
+ item: ToolResultItem;
73
+ } | {
74
+ kind: 'shell';
75
+ key: string;
76
+ item: ShellItem;
77
+ result?: ShellResultItem;
78
+ } | {
79
+ kind: 'orphan-shell-result';
80
+ key: string;
81
+ item: ShellResultItem;
82
+ } | {
83
+ kind: 'read-summary';
84
+ key: string;
85
+ count: number;
86
+ };
87
+ export declare function projectTranscriptPresentation(items: readonly TranscriptItem[], mode: TranscriptPresentationMode): readonly TranscriptPresentationEntry[];
88
+ export {};
89
+ //# sourceMappingURL=transcript-presentation.d.ts.map