praxis-agent 0.35.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 (111) hide show
  1. package/README.md +20 -2
  2. package/dist/application/background-agent-manager.d.ts +16 -6
  3. package/dist/application/background-agent-manager.js +139 -97
  4. package/dist/application/context-engine.d.ts +56 -0
  5. package/dist/application/context-engine.js +129 -0
  6. package/dist/application/durable-follow-up.d.ts +14 -0
  7. package/dist/application/durable-follow-up.js +52 -0
  8. package/dist/application/native-session-transcript.d.ts +72 -0
  9. package/dist/application/native-session-transcript.js +351 -0
  10. package/dist/application/native-sidechain-transcript.d.ts +34 -0
  11. package/dist/application/native-sidechain-transcript.js +156 -0
  12. package/dist/application/session-service.d.ts +23 -4
  13. package/dist/application/session-service.js +1130 -494
  14. package/dist/application/subagent-service.d.ts +5 -1
  15. package/dist/application/subagent-service.js +749 -320
  16. package/dist/application/team-agent-runtime.d.ts +29 -0
  17. package/dist/application/team-agent-runtime.js +169 -0
  18. package/dist/application/team-capability.d.ts +27 -0
  19. package/dist/application/team-capability.js +41 -0
  20. package/dist/application/team-lead-operations.d.ts +38 -0
  21. package/dist/application/team-lead-operations.js +155 -0
  22. package/dist/application/team-mailbox.d.ts +54 -0
  23. package/dist/application/team-mailbox.js +196 -0
  24. package/dist/application/team-manager.d.ts +113 -0
  25. package/dist/application/team-manager.js +681 -0
  26. package/dist/application/team-workspace.d.ts +29 -0
  27. package/dist/application/team-workspace.js +197 -0
  28. package/dist/application/top-level-agent-manager.d.ts +1 -0
  29. package/dist/application/top-level-agent-manager.js +1025 -403
  30. package/dist/application/transcript-projection.d.ts +35 -0
  31. package/dist/application/transcript-projection.js +139 -0
  32. package/dist/application/turn-lifecycle.d.ts +2 -0
  33. package/dist/application/turn-memory-coordinator.d.ts +43 -0
  34. package/dist/application/turn-memory-coordinator.js +98 -0
  35. package/dist/cli/agents-dashboard.js +10 -5
  36. package/dist/cli/interactive.d.ts +7 -0
  37. package/dist/cli/interactive.js +645 -334
  38. package/dist/cli/tui/claude-style.d.ts +8 -25
  39. package/dist/cli/tui/claude-style.js +299 -277
  40. package/dist/cli/tui/decision-surface-model.d.ts +71 -0
  41. package/dist/cli/tui/decision-surface-model.js +131 -0
  42. package/dist/cli/tui/decision-surface.d.ts +7 -0
  43. package/dist/cli/tui/decision-surface.js +41 -0
  44. package/dist/cli/tui/help-surface-model.d.ts +68 -0
  45. package/dist/cli/tui/help-surface-model.js +131 -0
  46. package/dist/cli/tui/mcp-panel.js +1 -2
  47. package/dist/cli/tui/permission-surface-model.d.ts +195 -0
  48. package/dist/cli/tui/permission-surface-model.js +360 -0
  49. package/dist/cli/tui/permission-surface.d.ts +7 -0
  50. package/dist/cli/tui/permission-surface.js +69 -0
  51. package/dist/cli/tui/presentation-environment.d.ts +32 -0
  52. package/dist/cli/tui/presentation-environment.js +149 -0
  53. package/dist/cli/tui/task-panel.js +10 -1
  54. package/dist/cli/tui/theme.d.ts +46 -3
  55. package/dist/cli/tui/theme.js +414 -56
  56. package/dist/cli/tui/tool-permission.js +16 -8
  57. package/dist/cli/tui/transcript-presentation.d.ts +19 -1
  58. package/dist/cli/tui/transcript-presentation.js +26 -6
  59. package/dist/cli/tui/transcript-viewport.d.ts +18 -0
  60. package/dist/cli/tui/transcript-viewport.js +517 -111
  61. package/dist/cli/tui/transcript-window-model.d.ts +108 -0
  62. package/dist/cli/tui/transcript-window-model.js +772 -0
  63. package/dist/cli/tui/tui-interaction-router.d.ts +78 -0
  64. package/dist/cli/tui/tui-interaction-router.js +127 -0
  65. package/dist/cli/tui/tui-screen-model.d.ts +114 -0
  66. package/dist/cli/tui/tui-screen-model.js +210 -0
  67. package/dist/cli/tui/tui-view-model.d.ts +2 -49
  68. package/dist/cli/tui/tui-view-model.js +1 -68
  69. package/dist/cli-runtime.d.ts +4 -2
  70. package/dist/cli-runtime.js +397 -32
  71. package/dist/compatibility/claude/transcript-codec.js +6 -0
  72. package/dist/core/agent-orchestration.d.ts +31 -0
  73. package/dist/core/agent-orchestration.js +235 -0
  74. package/dist/core/permission-policy.d.ts +4 -0
  75. package/dist/core/permission-policy.js +21 -0
  76. package/dist/core/runtime.d.ts +6 -0
  77. package/dist/core/runtime.js +20 -2
  78. package/dist/core/team-mailbox.d.ts +54 -0
  79. package/dist/core/team-mailbox.js +212 -0
  80. package/dist/core/team-ownership.d.ts +84 -0
  81. package/dist/core/team-ownership.js +505 -0
  82. package/dist/core/tool-execution-scheduler.js +6 -1
  83. package/dist/core/transcript-event.d.ts +5 -1
  84. package/dist/core/transcript-event.js +12 -0
  85. package/dist/persistence/claude-job-store.d.ts +58 -0
  86. package/dist/persistence/claude-job-store.js +483 -1
  87. package/dist/persistence/claude-transcript-store.d.ts +2 -1
  88. package/dist/persistence/claude-transcript-store.js +2 -10
  89. package/dist/persistence/native-team-mailbox.d.ts +68 -0
  90. package/dist/persistence/native-team-mailbox.js +542 -0
  91. package/dist/persistence/native-team-store.d.ts +34 -0
  92. package/dist/persistence/native-team-store.js +257 -0
  93. package/dist/persistence/native-transcript-reader.d.ts +53 -0
  94. package/dist/persistence/native-transcript-reader.js +283 -0
  95. package/dist/persistence/native-transcript-store.d.ts +69 -0
  96. package/dist/persistence/native-transcript-store.js +321 -0
  97. package/dist/persistence/subagent-lifecycle-store.d.ts +41 -8
  98. package/dist/persistence/subagent-lifecycle-store.js +373 -146
  99. package/dist/persistence/transcript-file-append.d.ts +2 -0
  100. package/dist/persistence/transcript-file-append.js +10 -0
  101. package/dist/platform/exclusive-file-lease.d.ts +3 -0
  102. package/dist/platform/exclusive-file-lease.js +3 -0
  103. package/dist/tools/claude-capabilities.d.ts +4 -0
  104. package/dist/tools/claude-capabilities.js +18 -2
  105. package/dist/tools/team-lead-tools.d.ts +19 -0
  106. package/dist/tools/team-lead-tools.js +479 -0
  107. package/dist/tools/team-member-tools.d.ts +21 -0
  108. package/dist/tools/team-member-tools.js +259 -0
  109. package/package.json +1 -1
  110. package/dist/cli/tui/fullscreen-renderer.d.ts +0 -14
  111. package/dist/cli/tui/fullscreen-renderer.js +0 -13
@@ -0,0 +1,78 @@
1
+ import { type ComposerKeyProjection } from './composer-key-router.js';
2
+ import type { ComposerEditorState } from './composer-editor.js';
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';
5
+ export type TuiInteractionLayer = {
6
+ readonly kind: 'none';
7
+ } | {
8
+ readonly kind: 'pending-prefix';
9
+ } | {
10
+ readonly kind: 'cancelable';
11
+ readonly target: TuiCancellationTarget;
12
+ } | {
13
+ readonly kind: 'delegated';
14
+ readonly target: 'session-picker' | 'menu';
15
+ };
16
+ export type TuiCallerIntent = 'none' | 'toggle-shortcuts' | 'open-agents' | 'implicit-newline';
17
+ export interface TuiInteractionInput {
18
+ globalIntent: 'none' | 'exit' | 'suspend';
19
+ scrollIntent: TuiScrollIntent;
20
+ action: string | undefined;
21
+ composerKey: ComposerKeyProjection;
22
+ timestamp: number;
23
+ callerIntent: TuiCallerIntent;
24
+ }
25
+ export interface TuiInteractionSnapshot {
26
+ suspensionPending: boolean;
27
+ exitConfirmationArmed: boolean;
28
+ layer: TuiInteractionLayer;
29
+ busy: boolean;
30
+ viewport: {
31
+ enabled: boolean;
32
+ offset: number;
33
+ maxOffset: number;
34
+ pageRows: number;
35
+ };
36
+ composer: {
37
+ mode: 'readline' | 'vim-insert' | 'vim-normal';
38
+ editor: ComposerEditorState;
39
+ lastCancelAtMs: number;
40
+ };
41
+ }
42
+ export type TuiInteractionEffect = {
43
+ readonly kind: 'arm-exit-confirmation';
44
+ } | {
45
+ readonly kind: 'dismiss-exit-confirmation';
46
+ } | {
47
+ readonly kind: 'exit-application';
48
+ } | {
49
+ readonly kind: 'request-process-suspension';
50
+ } | {
51
+ readonly kind: 'cancel-tui-layer';
52
+ readonly target: TuiCancellationTarget;
53
+ } | {
54
+ readonly kind: 'set-transcript-scroll-offset';
55
+ readonly offset: number;
56
+ } | {
57
+ readonly kind: 'interrupt-turn';
58
+ } | {
59
+ readonly kind: 'set-vim-insert-mode';
60
+ readonly insert: boolean;
61
+ } | {
62
+ readonly kind: 'set-composer-editor';
63
+ readonly editor: ComposerEditorState;
64
+ } | {
65
+ readonly kind: 'clear-composer';
66
+ } | {
67
+ readonly kind: 'record-composer-cancel';
68
+ readonly timestamp: number;
69
+ };
70
+ export type TuiInteractionResult = {
71
+ readonly disposition: 'handled';
72
+ readonly effects: readonly TuiInteractionEffect[];
73
+ } | {
74
+ readonly disposition: 'delegated';
75
+ readonly effects: readonly TuiInteractionEffect[];
76
+ };
77
+ export declare function routeTuiInteraction(snapshot: TuiInteractionSnapshot, input: TuiInteractionInput): TuiInteractionResult;
78
+ //# sourceMappingURL=tui-interaction-router.d.ts.map
@@ -0,0 +1,127 @@
1
+ import { routeComposerKey, } from './composer-key-router.js';
2
+ const handled = (effects = []) => ({
3
+ disposition: 'handled',
4
+ effects,
5
+ });
6
+ const delegated = (effects = []) => ({
7
+ disposition: 'delegated',
8
+ effects,
9
+ });
10
+ function finiteNonNegativeInteger(value) {
11
+ return Number.isFinite(value) ? Math.max(0, Math.trunc(value)) : 0;
12
+ }
13
+ function isEscape(key) {
14
+ return key.escape || key.value === '\u001B';
15
+ }
16
+ function canUseCancelWindow(timestamp, previous) {
17
+ return Number.isFinite(timestamp) && Number.isFinite(previous);
18
+ }
19
+ export function routeTuiInteraction(snapshot, input) {
20
+ if (input.globalIntent === 'exit') {
21
+ return snapshot.exitConfirmationArmed
22
+ ? handled([{ kind: 'exit-application' }])
23
+ : handled([{ kind: 'clear-composer' }, { kind: 'arm-exit-confirmation' }]);
24
+ }
25
+ if (input.globalIntent === 'suspend') {
26
+ return snapshot.suspensionPending
27
+ ? handled()
28
+ : handled([{ kind: 'request-process-suspension' }]);
29
+ }
30
+ const confirmationEffects = snapshot.exitConfirmationArmed
31
+ ? [{ kind: 'dismiss-exit-confirmation' }]
32
+ : [];
33
+ if (snapshot.layer.kind === 'pending-prefix')
34
+ return handled(confirmationEffects);
35
+ if (snapshot.layer.kind === 'cancelable') {
36
+ if (isEscape(input.composerKey) || input.action === 'chat:cancel') {
37
+ return handled([
38
+ ...confirmationEffects,
39
+ { kind: 'cancel-tui-layer', target: snapshot.layer.target },
40
+ ]);
41
+ }
42
+ return delegated(confirmationEffects);
43
+ }
44
+ if (snapshot.layer.kind === 'delegated')
45
+ return delegated(confirmationEffects);
46
+ const viewport = snapshot.viewport;
47
+ if (viewport.enabled && input.scrollIntent !== 'none') {
48
+ const pageRows = finiteNonNegativeInteger(viewport.pageRows);
49
+ const offset = finiteNonNegativeInteger(viewport.offset);
50
+ const maxOffset = finiteNonNegativeInteger(viewport.maxOffset);
51
+ const halfPage = Math.max(1, Math.trunc(pageRows / 2));
52
+ const movement = input.scrollIntent === 'page-older' || input.scrollIntent === 'page-newer'
53
+ ? pageRows
54
+ : input.scrollIntent === 'half-page-older' ||
55
+ input.scrollIntent === 'half-page-newer'
56
+ ? halfPage
57
+ : 1;
58
+ const older = input.scrollIntent === 'page-older' ||
59
+ input.scrollIntent === 'half-page-older' ||
60
+ input.scrollIntent === 'line-older';
61
+ const nextOffset = Math.min(maxOffset, Math.max(0, offset + (older ? movement : -movement)));
62
+ return handled([
63
+ ...confirmationEffects,
64
+ { kind: 'set-transcript-scroll-offset', offset: nextOffset },
65
+ ]);
66
+ }
67
+ if (snapshot.busy) {
68
+ if (input.action === 'app:toggleTranscript' ||
69
+ input.action === 'task:background') {
70
+ return delegated(confirmationEffects);
71
+ }
72
+ if (input.action === 'chat:cancel')
73
+ return handled([...confirmationEffects, { kind: 'interrupt-turn' }]);
74
+ return handled(confirmationEffects);
75
+ }
76
+ if (input.callerIntent !== 'none')
77
+ return delegated(confirmationEffects);
78
+ if (snapshot.composer.mode === 'vim-normal') {
79
+ if (input.composerKey.value === 'i' || input.composerKey.value === 'a') {
80
+ return handled([
81
+ ...confirmationEffects,
82
+ { kind: 'set-vim-insert-mode', insert: true },
83
+ ]);
84
+ }
85
+ if (input.composerKey.left || input.composerKey.right) {
86
+ const transition = routeComposerKey(snapshot.composer.editor, input.composerKey);
87
+ if (transition.kind === 'edit')
88
+ return handled([
89
+ ...confirmationEffects,
90
+ { kind: 'set-composer-editor', editor: transition.editor },
91
+ ]);
92
+ }
93
+ return handled(confirmationEffects);
94
+ }
95
+ if (snapshot.composer.mode === 'vim-insert' && isEscape(input.composerKey)) {
96
+ return handled([
97
+ ...confirmationEffects,
98
+ { kind: 'set-vim-insert-mode', insert: false },
99
+ ]);
100
+ }
101
+ if (input.action === 'chat:cancel') {
102
+ const timestamp = input.timestamp;
103
+ const cancelDelta = timestamp - snapshot.composer.lastCancelAtMs;
104
+ const shouldClear = canUseCancelWindow(timestamp, snapshot.composer.lastCancelAtMs) &&
105
+ cancelDelta >= 0 &&
106
+ cancelDelta <= 500;
107
+ return handled([
108
+ ...confirmationEffects,
109
+ ...(shouldClear ? [{ kind: 'clear-composer' }] : []),
110
+ ...(Number.isFinite(timestamp)
111
+ ? [{ kind: 'record-composer-cancel', timestamp }]
112
+ : []),
113
+ ]);
114
+ }
115
+ if (input.action !== undefined)
116
+ return delegated(confirmationEffects);
117
+ const transition = routeComposerKey(snapshot.composer.editor, input.composerKey);
118
+ if (transition.kind === 'edit')
119
+ return handled([
120
+ ...confirmationEffects,
121
+ { kind: 'set-composer-editor', editor: transition.editor },
122
+ ]);
123
+ if (transition.kind === 'cancel')
124
+ return handled([...confirmationEffects, { kind: 'clear-composer' }]);
125
+ return delegated(confirmationEffects);
126
+ }
127
+ //# sourceMappingURL=tui-interaction-router.js.map
@@ -0,0 +1,114 @@
1
+ import type { TranscriptItem } from './transcript-presentation.js';
2
+ import { type TranscriptPresentationEntry } from './transcript-presentation.js';
3
+ import { appendTuiHistory, createTuiHistoryChange, type TuiHistoryChange } from './transcript-window-model.js';
4
+ import type { TuiPresentationEnvironment } from './presentation-environment.js';
5
+ export { appendTuiHistory, createTuiHistoryChange };
6
+ export type { TuiHistoryChange };
7
+ export type TuiRendererMode = 'default' | 'fullscreen';
8
+ /**
9
+ * Inputs for the pure TUI view projection. `history` is the live transcript
10
+ * that grows while the session runs; `initialHistory` is the transcript loaded
11
+ * before rendering and decides the resumed/fresh identity. `resume` is true
12
+ * only when the session was opened through a resume option.
13
+ */
14
+ export interface TuiViewInput {
15
+ readonly initialHistory: readonly TranscriptItem[];
16
+ readonly history: readonly TranscriptItem[];
17
+ readonly resume: boolean;
18
+ readonly fixedViewport: boolean;
19
+ readonly screenReader: boolean;
20
+ readonly rows: number | undefined;
21
+ readonly width: number;
22
+ readonly scrollOffset: number;
23
+ readonly detailedTranscript: boolean;
24
+ readonly historyChange?: TuiHistoryChange;
25
+ }
26
+ export interface TuiViewModel {
27
+ readonly transcriptEntries: readonly TranscriptPresentationEntry[];
28
+ readonly transcriptPageRows: number;
29
+ readonly maxTranscriptScrollOffset: number;
30
+ readonly resumed: boolean;
31
+ readonly freshSession: boolean;
32
+ readonly hasConversationHistory: boolean;
33
+ }
34
+ export declare function projectTuiView(input: TuiViewInput, previous?: TuiViewModel): TuiViewModel;
35
+ export interface TuiScreenSurfaceModels {
36
+ readonly sessionPicker: unknown;
37
+ readonly priority: unknown;
38
+ readonly secondary: unknown;
39
+ readonly overlay: unknown;
40
+ }
41
+ export interface TuiScreenInput<Surfaces extends TuiScreenSurfaceModels = TuiScreenSurfaceModels> {
42
+ readonly presentation: TuiPresentationEnvironment;
43
+ readonly conversation: {
44
+ readonly initialHistory: readonly TranscriptItem[];
45
+ readonly history: readonly TranscriptItem[];
46
+ readonly historyChange?: TuiHistoryChange;
47
+ readonly resumeRequested: boolean;
48
+ readonly scrollOffset: number;
49
+ readonly detailed: boolean;
50
+ readonly activeText: string;
51
+ readonly activeThinking: string;
52
+ };
53
+ readonly sessionId: string | null;
54
+ readonly surfaces: {
55
+ readonly sessionPicker?: Surfaces['sessionPicker'];
56
+ readonly priority?: Surfaces['priority'];
57
+ readonly secondary?: Surfaces['secondary'];
58
+ readonly overlays: readonly Surfaces['overlay'][];
59
+ };
60
+ }
61
+ export interface TuiTranscriptScreenModel {
62
+ readonly entries: readonly TranscriptPresentationEntry[];
63
+ readonly pageRows: number;
64
+ readonly maxScrollOffset: number;
65
+ readonly scrollOffset: number;
66
+ readonly readingMode: 'normal' | 'audit' | 'screen-reader';
67
+ readonly active: {
68
+ readonly text: string;
69
+ readonly thinking: string;
70
+ readonly visible: boolean;
71
+ };
72
+ }
73
+ export type TuiScreenForeground<Surfaces extends TuiScreenSurfaceModels> = {
74
+ readonly kind: 'priority';
75
+ readonly surface: Surfaces['priority'];
76
+ } | {
77
+ readonly kind: 'secondary';
78
+ readonly surface: Surfaces['secondary'];
79
+ } | {
80
+ readonly kind: 'compose';
81
+ readonly overlays: readonly Surfaces['overlay'][];
82
+ };
83
+ export type TuiScreenBody<Surfaces extends TuiScreenSurfaceModels> = {
84
+ readonly kind: 'session-picker';
85
+ readonly surface: Surfaces['sessionPicker'];
86
+ } | {
87
+ readonly kind: 'conversation';
88
+ readonly intro: 'welcome' | 'identity' | 'none';
89
+ readonly sessionLabel?: string;
90
+ readonly resumed: boolean;
91
+ readonly freshSession: boolean;
92
+ readonly hasConversationHistory: boolean;
93
+ readonly transcript: TuiTranscriptScreenModel;
94
+ readonly foreground: TuiScreenForeground<Surfaces>;
95
+ };
96
+ export interface TuiScreenModel<Surfaces extends TuiScreenSurfaceModels = TuiScreenSurfaceModels> {
97
+ readonly presentation: TuiPresentationEnvironment;
98
+ readonly body: TuiScreenBody<Surfaces>;
99
+ }
100
+ export declare function projectTuiScreen<Surfaces extends TuiScreenSurfaceModels>(input: TuiScreenInput<Surfaces>, previous?: TuiScreenModel<Surfaces>): TuiScreenModel<Surfaces>;
101
+ export interface TuiRendererInput {
102
+ configured: TuiRendererMode;
103
+ explicitlyConfigured: boolean;
104
+ interactiveTty: boolean;
105
+ screenReader: boolean;
106
+ }
107
+ /**
108
+ * Resolves the renderer for an interactive session. Fullscreen is the default
109
+ * for interactive TTY execution; classic remains the fallback for screen-reader
110
+ * and non-interactive paths, and an explicit renderer configuration is always
111
+ * honored over the default.
112
+ */
113
+ export declare function resolveTuiRenderer(input: TuiRendererInput): TuiRendererMode;
114
+ //# sourceMappingURL=tui-screen-model.d.ts.map
@@ -0,0 +1,210 @@
1
+ import { FULLSCREEN_TRANSCRIPT_RESERVED_ROWS } from './transcript-viewport.js';
2
+ import { appendTuiHistory, createTuiHistoryChange, projectTranscriptWindow, } from './transcript-window-model.js';
3
+ export { appendTuiHistory, createTuiHistoryChange };
4
+ /**
5
+ * Pure, deterministic TUI projection. It classifies the session identity
6
+ * (fresh/resumed/started) and projects the rendered transcript exactly as the
7
+ * fullscreen renderer needs it, preserving TranscriptItem identity and order
8
+ * and the fullscreen suffix/window behavior. It never writes to transcripts or
9
+ * alters runtime events.
10
+ */
11
+ const transcriptState = Symbol('transcript-window-state');
12
+ const classificationState = Symbol('tui-classification-state');
13
+ const selectionState = Symbol('tui-selection-state');
14
+ export function projectTuiView(input, previous) {
15
+ // Startup diagnostics are useful before the first prompt, but they are not
16
+ // conversation history and must not suppress the new-session welcome panel.
17
+ // Only real user/assistant transcript entries start a conversation; every
18
+ // other kind (thinking, context, tool, shell, notices, results, and so on)
19
+ // is operational bookkeeping that must not hide the fresh-session welcome.
20
+ const isRealConversation = (item) => item.kind === 'user' || item.kind === 'assistant';
21
+ const priorState = previous?.[transcriptState];
22
+ const priorClassification = previous?.[classificationState];
23
+ // Fullscreen projects only the newest transcript tail that fits the fixed
24
+ // viewport, leaving the composer/status chrome intact and keeping the active
25
+ // stream visible. Classic and screen-reader modes always render the full
26
+ // history exactly as before.
27
+ const mode = input.screenReader
28
+ ? 'screen-reader'
29
+ : input.detailedTranscript
30
+ ? 'audit'
31
+ : 'normal';
32
+ const transcriptPageRows = Math.max(2, (input.rows ?? 0) - FULLSCREEN_TRANSCRIPT_RESERVED_ROWS);
33
+ const retained = projectTranscriptWindow({
34
+ history: input.history,
35
+ mode,
36
+ width: input.width,
37
+ pageRows: transcriptPageRows,
38
+ scrollOffset: input.scrollOffset,
39
+ revision: input.historyChange?.revision ?? 0,
40
+ bounded: input.fixedViewport && !input.screenReader,
41
+ }, priorState, input.historyChange);
42
+ const classificationCompatible = previous !== undefined &&
43
+ priorClassification !== undefined &&
44
+ priorState !== undefined &&
45
+ priorClassification.history === priorState.history &&
46
+ priorClassification.revision === priorState.revision &&
47
+ priorClassification.initialHistory === input.initialHistory &&
48
+ priorClassification.resume === input.resume;
49
+ const sameClassification = classificationCompatible && retained.transition === 'same';
50
+ const appendedClassification = classificationCompatible && retained.transition === 'append';
51
+ // The original loaded transcript decides whether the session was resumed,
52
+ // separately from the live history that grows while the session runs.
53
+ const resumed = sameClassification || appendedClassification
54
+ ? priorClassification.resumed
55
+ : input.resume && input.initialHistory.some(isRealConversation);
56
+ const hasConversationHistory = sameClassification
57
+ ? priorClassification.hasConversationHistory
58
+ : appendedClassification
59
+ ? priorClassification.hasConversationHistory ||
60
+ input.history.slice(priorState.history.length).some(isRealConversation)
61
+ : input.history.some(isRealConversation);
62
+ // A session is resumed only when it was opened through `resume` and the
63
+ // original transcript already contained real conversation content. Supplying
64
+ // a session ID alone with an empty transcript keeps the session fresh, so the
65
+ // full welcome panel renders and the compact identity stays hidden until real
66
+ // conversation content appears.
67
+ const freshSession = !resumed && !hasConversationHistory;
68
+ const fullEntries = retained.allEntries;
69
+ const maxTranscriptScrollOffset = retained.maxOffset;
70
+ const projectedEntries = input.fixedViewport && !input.screenReader ? retained.entries : fullEntries;
71
+ const priorSelection = previous?.[selectionState];
72
+ const transcriptEntries = priorSelection &&
73
+ priorSelection.offset === input.scrollOffset &&
74
+ priorSelection.width === input.width &&
75
+ priorSelection.mode === mode &&
76
+ priorSelection.fixedViewport === input.fixedViewport &&
77
+ priorSelection.screenReader === input.screenReader &&
78
+ priorSelection.pageRows === transcriptPageRows &&
79
+ priorState === retained.state
80
+ ? priorSelection.entries
81
+ : projectedEntries;
82
+ const result = {
83
+ transcriptEntries,
84
+ transcriptPageRows,
85
+ maxTranscriptScrollOffset,
86
+ resumed,
87
+ freshSession,
88
+ hasConversationHistory,
89
+ };
90
+ Object.defineProperty(result, transcriptState, {
91
+ value: retained.state,
92
+ enumerable: false,
93
+ });
94
+ Object.defineProperty(result, classificationState, {
95
+ value: {
96
+ initialHistory: input.initialHistory,
97
+ resume: input.resume,
98
+ history: input.history,
99
+ revision: retained.state.revision,
100
+ resumed,
101
+ hasConversationHistory,
102
+ },
103
+ enumerable: false,
104
+ });
105
+ Object.defineProperty(result, selectionState, {
106
+ value: {
107
+ offset: input.scrollOffset,
108
+ width: input.width,
109
+ mode,
110
+ fixedViewport: input.fixedViewport,
111
+ screenReader: input.screenReader,
112
+ pageRows: transcriptPageRows,
113
+ entries: transcriptEntries,
114
+ },
115
+ enumerable: false,
116
+ });
117
+ return result;
118
+ }
119
+ const screenViewState = Symbol('tui-screen-view-state');
120
+ export function projectTuiScreen(input, previous) {
121
+ const conversation = input.conversation;
122
+ const priorView = previous?.[screenViewState];
123
+ const view = projectTuiView({
124
+ initialHistory: conversation.initialHistory,
125
+ history: conversation.history,
126
+ resume: conversation.resumeRequested,
127
+ fixedViewport: input.presentation.fixedViewport,
128
+ screenReader: input.presentation.screenReader,
129
+ rows: input.presentation.viewport.rows,
130
+ width: input.presentation.viewport.columns,
131
+ scrollOffset: conversation.scrollOffset,
132
+ detailedTranscript: conversation.detailed,
133
+ ...(conversation.historyChange === undefined
134
+ ? {}
135
+ : { historyChange: conversation.historyChange }),
136
+ }, priorView);
137
+ const readingMode = input.presentation.screenReader
138
+ ? 'screen-reader'
139
+ : conversation.detailed
140
+ ? 'audit'
141
+ : 'normal';
142
+ const scrollOffset = Math.min(Math.max(0, conversation.scrollOffset), view.maxTranscriptScrollOffset);
143
+ const transcript = {
144
+ entries: view.transcriptEntries,
145
+ pageRows: view.transcriptPageRows,
146
+ maxScrollOffset: view.maxTranscriptScrollOffset,
147
+ scrollOffset,
148
+ readingMode,
149
+ active: {
150
+ text: conversation.activeText,
151
+ thinking: conversation.activeThinking,
152
+ visible: scrollOffset === 0,
153
+ },
154
+ };
155
+ let body;
156
+ if (input.surfaces.sessionPicker !== undefined) {
157
+ body = { kind: 'session-picker', surface: input.surfaces.sessionPicker };
158
+ }
159
+ else {
160
+ const intro = input.presentation.screenReader
161
+ ? 'none'
162
+ : view.freshSession
163
+ ? 'welcome'
164
+ : !view.resumed && view.hasConversationHistory
165
+ ? 'identity'
166
+ : 'none';
167
+ const foreground = input.surfaces.priority !== undefined
168
+ ? { kind: 'priority', surface: input.surfaces.priority }
169
+ : input.surfaces.secondary !== undefined
170
+ ? { kind: 'secondary', surface: input.surfaces.secondary }
171
+ : { kind: 'compose', overlays: input.surfaces.overlays };
172
+ body = {
173
+ kind: 'conversation',
174
+ intro,
175
+ ...(input.sessionId === null
176
+ ? {}
177
+ : { sessionLabel: input.sessionId.slice(0, 8) }),
178
+ resumed: view.resumed,
179
+ freshSession: view.freshSession,
180
+ hasConversationHistory: view.hasConversationHistory,
181
+ transcript,
182
+ foreground,
183
+ };
184
+ }
185
+ const result = {
186
+ presentation: input.presentation,
187
+ body,
188
+ };
189
+ Object.defineProperty(result, screenViewState, {
190
+ value: view,
191
+ enumerable: false,
192
+ });
193
+ return result;
194
+ }
195
+ /**
196
+ * Resolves the renderer for an interactive session. Fullscreen is the default
197
+ * for interactive TTY execution; classic remains the fallback for screen-reader
198
+ * and non-interactive paths, and an explicit renderer configuration is always
199
+ * honored over the default.
200
+ */
201
+ export function resolveTuiRenderer(input) {
202
+ if (input.screenReader)
203
+ return 'default';
204
+ if (!input.interactiveTty)
205
+ return 'default';
206
+ if (input.explicitlyConfigured)
207
+ return input.configured;
208
+ return 'fullscreen';
209
+ }
210
+ //# sourceMappingURL=tui-screen-model.js.map
@@ -1,50 +1,3 @@
1
- import type { TranscriptItem } from './transcript-presentation.js';
2
- import { type TranscriptPresentationEntry } from './transcript-presentation.js';
3
- export type TuiRendererMode = 'default' | 'fullscreen';
4
- /**
5
- * Inputs for the pure TUI view projection. `history` is the live transcript
6
- * that grows while the session runs; `initialHistory` is the transcript loaded
7
- * before rendering and decides the resumed/fresh identity. `resume` is true
8
- * only when the session was opened through a resume option.
9
- */
10
- export interface TuiViewInput {
11
- initialHistory: readonly TranscriptItem[];
12
- history: readonly TranscriptItem[];
13
- resume: boolean;
14
- fixedViewport: boolean;
15
- screenReader: boolean;
16
- rows: number | undefined;
17
- width: number;
18
- scrollOffset: number;
19
- detailedTranscript: boolean;
20
- }
21
- export interface TuiViewModel {
22
- transcriptEntries: readonly TranscriptPresentationEntry[];
23
- transcriptPageRows: number;
24
- maxTranscriptScrollOffset: number;
25
- resumed: boolean;
26
- freshSession: boolean;
27
- hasConversationHistory: boolean;
28
- }
29
- /**
30
- * Pure, deterministic TUI projection. It classifies the session identity
31
- * (fresh/resumed/started) and projects the rendered transcript exactly as the
32
- * fullscreen renderer needs it, preserving TranscriptItem identity and order
33
- * and the fullscreen suffix/window behavior. It never writes to transcripts or
34
- * alters runtime events.
35
- */
36
- export declare function projectTuiView(input: TuiViewInput): TuiViewModel;
37
- export interface TuiRendererInput {
38
- configured: TuiRendererMode;
39
- explicitlyConfigured: boolean;
40
- interactiveTty: boolean;
41
- screenReader: boolean;
42
- }
43
- /**
44
- * Resolves the renderer for an interactive session. Fullscreen is the default
45
- * for interactive TTY execution; classic remains the fallback for screen-reader
46
- * and non-interactive paths, and an explicit renderer configuration is always
47
- * honored over the default.
48
- */
49
- export declare function resolveTuiRenderer(input: TuiRendererInput): TuiRendererMode;
1
+ export { appendTuiHistory, createTuiHistoryChange, projectTuiView, resolveTuiRenderer, } from './tui-screen-model.js';
2
+ export type { TuiHistoryChange, TuiRendererInput, TuiRendererMode, TuiViewInput, TuiViewModel, } from './tui-screen-model.js';
50
3
  //# sourceMappingURL=tui-view-model.d.ts.map
@@ -1,69 +1,2 @@
1
- import { projectTranscriptPresentation, } from './transcript-presentation.js';
2
- import { FULLSCREEN_TRANSCRIPT_RESERVED_ROWS, projectTranscriptPresentationTail, projectTranscriptPresentationWindow, transcriptPresentationLineCount, } from './transcript-viewport.js';
3
- /**
4
- * Pure, deterministic TUI projection. It classifies the session identity
5
- * (fresh/resumed/started) and projects the rendered transcript exactly as the
6
- * fullscreen renderer needs it, preserving TranscriptItem identity and order
7
- * and the fullscreen suffix/window behavior. It never writes to transcripts or
8
- * alters runtime events.
9
- */
10
- export function projectTuiView(input) {
11
- // Startup diagnostics are useful before the first prompt, but they are not
12
- // conversation history and must not suppress the new-session welcome panel.
13
- // Only real user/assistant transcript entries start a conversation; every
14
- // other kind (thinking, context, tool, shell, notices, results, and so on)
15
- // is operational bookkeeping that must not hide the fresh-session welcome.
16
- const isRealConversation = (item) => item.kind === 'user' || item.kind === 'assistant';
17
- // The original loaded transcript decides whether the session was resumed,
18
- // separately from the live history that grows while the session runs.
19
- const resumed = input.resume && input.initialHistory.some(isRealConversation);
20
- const hasConversationHistory = input.history.some(isRealConversation);
21
- // A session is resumed only when it was opened through `resume` and the
22
- // original transcript already contained real conversation content. Supplying
23
- // a session ID alone with an empty transcript keeps the session fresh, so the
24
- // full welcome panel renders and the compact identity stays hidden until real
25
- // conversation content appears.
26
- const freshSession = !resumed && !hasConversationHistory;
27
- // Fullscreen projects only the newest transcript tail that fits the fixed
28
- // viewport, leaving the composer/status chrome intact and keeping the active
29
- // stream visible. Classic and screen-reader modes always render the full
30
- // history exactly as before.
31
- const mode = input.screenReader
32
- ? 'screen-reader'
33
- : input.detailedTranscript
34
- ? 'audit'
35
- : 'normal';
36
- const fullEntries = projectTranscriptPresentation(input.history, mode);
37
- const transcriptPageRows = Math.max(2, (input.rows ?? 0) - FULLSCREEN_TRANSCRIPT_RESERVED_ROWS);
38
- const maxTranscriptScrollOffset = Math.max(0, transcriptPresentationLineCount(fullEntries, input.width, mode) -
39
- transcriptPageRows);
40
- const transcriptEntries = input.fixedViewport && !input.screenReader
41
- ? input.scrollOffset > 0
42
- ? projectTranscriptPresentationWindow(fullEntries, transcriptPageRows, input.width, input.scrollOffset, mode)
43
- : projectTranscriptPresentationTail(fullEntries, transcriptPageRows, input.width, mode)
44
- : fullEntries;
45
- return {
46
- transcriptEntries,
47
- transcriptPageRows,
48
- maxTranscriptScrollOffset,
49
- resumed,
50
- freshSession,
51
- hasConversationHistory,
52
- };
53
- }
54
- /**
55
- * Resolves the renderer for an interactive session. Fullscreen is the default
56
- * for interactive TTY execution; classic remains the fallback for screen-reader
57
- * and non-interactive paths, and an explicit renderer configuration is always
58
- * honored over the default.
59
- */
60
- export function resolveTuiRenderer(input) {
61
- if (input.screenReader)
62
- return 'default';
63
- if (!input.interactiveTty)
64
- return 'default';
65
- if (input.explicitlyConfigured)
66
- return input.configured;
67
- return 'fullscreen';
68
- }
1
+ export { appendTuiHistory, createTuiHistoryChange, projectTuiView, resolveTuiRenderer, } from './tui-screen-model.js';
69
2
  //# sourceMappingURL=tui-view-model.js.map