praxis-agent 0.43.0 → 0.44.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.
- package/README.md +48 -47
- package/dist/application/native-session-projection.d.ts +4 -0
- package/dist/application/native-session-projection.js +135 -0
- package/dist/application/session-service.d.ts +0 -1
- package/dist/application/session-service.js +9 -170
- package/dist/application/top-level-agent-manager.d.ts +7 -0
- package/dist/application/top-level-agent-manager.js +21 -3
- package/dist/cli/controls.js +4 -0
- package/dist/cli/interactive.js +112 -156
- package/dist/cli/protocol.d.ts +5 -0
- package/dist/cli/protocol.js +45 -0
- package/dist/cli/provider-auth-command.d.ts +32 -0
- package/dist/cli/provider-auth-command.js +249 -0
- package/dist/cli/tui/ansi-frame-renderer.d.ts +2 -0
- package/dist/cli/tui/ansi-frame-renderer.js +109 -21
- package/dist/cli/tui/ansi-surface.d.ts +6 -22
- package/dist/cli/tui/ansi-surface.js +6 -126
- package/dist/cli/tui/ansi-theme.d.ts +3 -2
- package/dist/cli/tui/ansi-theme.js +22 -1
- package/dist/cli/tui/quiet-choice-rows.d.ts +26 -0
- package/dist/cli/tui/quiet-choice-rows.js +302 -0
- package/dist/cli/tui/quiet-frame-adapter.d.ts +18 -0
- package/dist/cli/tui/quiet-frame-adapter.js +90 -0
- package/dist/cli/tui/quiet-frame.d.ts +43 -0
- package/dist/cli/tui/quiet-frame.js +346 -0
- package/dist/cli/tui/quiet-operational-rows.d.ts +17 -0
- package/dist/cli/tui/quiet-operational-rows.js +413 -0
- package/dist/cli/tui/quiet-screen-projector.d.ts +25 -0
- package/dist/cli/tui/quiet-screen-projector.js +110 -0
- package/dist/cli/tui/quiet-settings-rows.d.ts +17 -0
- package/dist/cli/tui/quiet-settings-rows.js +490 -0
- package/dist/cli/tui/status-line.d.ts +15 -9
- package/dist/cli/tui/status-line.js +15 -6
- package/dist/cli/tui/theme.js +20 -20
- package/dist/cli/tui/transcript-viewport.d.ts +5 -0
- package/dist/cli/tui/transcript-viewport.js +213 -78
- package/dist/cli/tui/tui-row-ir.js +4 -4
- package/dist/cli/workspace-trust-prompt.js +2 -2
- package/dist/cli-runtime.d.ts +34 -1
- package/dist/cli-runtime.js +587 -184
- package/dist/maintenance/doctor.js +100 -22
- package/dist/persistence/in-memory-transcript-store.js +1 -7
- package/dist/persistence/native-transcript-store.d.ts +0 -9
- package/dist/persistence/native-transcript-store.js +4 -38
- package/dist/persistence/provider-credential-vault.d.ts +72 -0
- package/dist/persistence/provider-credential-vault.js +498 -0
- package/dist/providers/codex-oauth.d.ts +104 -0
- package/dist/providers/codex-oauth.js +770 -0
- package/dist/providers/codex-subscription.d.ts +33 -0
- package/dist/providers/codex-subscription.js +849 -0
- package/dist/providers/provider-auth.d.ts +71 -0
- package/dist/providers/provider-auth.js +149 -0
- package/dist/providers/provider-registry.d.ts +52 -0
- package/dist/providers/provider-registry.js +187 -0
- package/dist/providers/provider-settings.d.ts +38 -0
- package/dist/providers/provider-settings.js +334 -0
- package/dist/security/workspace-trust.d.ts +4 -3
- package/dist/security/workspace-trust.js +35 -1
- package/package.json +8 -4
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
import { createQuietFrameRow, } from './quiet-frame.js';
|
|
2
|
+
import { tuiMcpServerActions } from './mcp-panel-projector.js';
|
|
3
|
+
const full = (d) => d === 'full' || d === 'standard';
|
|
4
|
+
const text = (v, fallback = '') => typeof v === 'string' ? v.replace(/\s+/gu, ' ').trim() : fallback;
|
|
5
|
+
const limit = (o) => Number.isInteger(o.maxItems) && o.maxItems > 0
|
|
6
|
+
? o.maxItems
|
|
7
|
+
: 20;
|
|
8
|
+
const marker = (selected, label, sr) => sr
|
|
9
|
+
? `${selected ? 'Selected' : 'Option'}: ${label}`
|
|
10
|
+
: `${selected ? '❯ ' : ' '}${label}`;
|
|
11
|
+
const row = (key, value, role = 'body', sr) => createQuietFrameRow(key, value, 'focus', role, sr);
|
|
12
|
+
const footer = (visual, accessible, sr) => row('quiet:operational:footer', sr ? accessible : visual, 'muted', accessible);
|
|
13
|
+
const statusRole = (s) => /fail|error|denied/iu.test(s)
|
|
14
|
+
? 'error'
|
|
15
|
+
: /running|answer|fork|auth|warn|retry/iu.test(s)
|
|
16
|
+
? 'warning'
|
|
17
|
+
: /complete|connected|success|pass/iu.test(s)
|
|
18
|
+
? 'success'
|
|
19
|
+
: 'muted';
|
|
20
|
+
const statusInfo = (s) => ({
|
|
21
|
+
marker: /fail|error/iu.test(s)
|
|
22
|
+
? '!'
|
|
23
|
+
: /running|answer|fork|auth|warn|retry/iu.test(s)
|
|
24
|
+
? '…'
|
|
25
|
+
: /complete|connected|success|pass/iu.test(s)
|
|
26
|
+
? '✓'
|
|
27
|
+
: '○',
|
|
28
|
+
role: statusRole(s),
|
|
29
|
+
label: /fail|error/iu.test(s)
|
|
30
|
+
? 'failed'
|
|
31
|
+
: /running|answer|fork/iu.test(s)
|
|
32
|
+
? 'in progress'
|
|
33
|
+
: /auth|warn|retry/iu.test(s)
|
|
34
|
+
? 'needs attention'
|
|
35
|
+
: /complete|connected|success|pass/iu.test(s)
|
|
36
|
+
? 'complete'
|
|
37
|
+
: /disabled|stopped|interrupted/iu.test(s)
|
|
38
|
+
? 'stopped'
|
|
39
|
+
: 'neutral',
|
|
40
|
+
});
|
|
41
|
+
const normalizedIndex = (value, fallback = 0) => Number.isFinite(value) ? Math.max(0, Math.trunc(value)) : fallback;
|
|
42
|
+
const selectionWindow = (count, selected, size, preferred = 0) => {
|
|
43
|
+
if (!count)
|
|
44
|
+
return { start: 0, end: 0 };
|
|
45
|
+
const n = Math.max(1, Math.trunc(size));
|
|
46
|
+
const index = Math.min(count - 1, normalizedIndex(selected));
|
|
47
|
+
const lastStart = Math.max(0, count - n);
|
|
48
|
+
let start = Math.min(lastStart, normalizedIndex(preferred));
|
|
49
|
+
if (index < start)
|
|
50
|
+
start = index;
|
|
51
|
+
if (index >= start + n)
|
|
52
|
+
start = index - n + 1;
|
|
53
|
+
start = Math.max(0, Math.min(lastStart, start));
|
|
54
|
+
return { start, end: Math.min(count, start + n) };
|
|
55
|
+
};
|
|
56
|
+
const excerpt = (v, n = 180) => text(v).slice(0, n);
|
|
57
|
+
const boundedLines = (v, max, start = 0) => {
|
|
58
|
+
if (typeof v !== 'string')
|
|
59
|
+
return {
|
|
60
|
+
lines: [],
|
|
61
|
+
truncated: false,
|
|
62
|
+
};
|
|
63
|
+
const source = v.slice(0, 32768);
|
|
64
|
+
const lines = [];
|
|
65
|
+
const requestedStart = normalizedIndex(start);
|
|
66
|
+
const requestedCount = Math.max(1, Math.trunc(max));
|
|
67
|
+
let sourceIndex = 0;
|
|
68
|
+
let cursor = 0;
|
|
69
|
+
let moreSource = false;
|
|
70
|
+
while (cursor <= source.length) {
|
|
71
|
+
const newline = source.indexOf('\n', cursor);
|
|
72
|
+
const end = newline === -1 ? source.length : newline;
|
|
73
|
+
const value = source.slice(cursor, end).replace(/\r$/u, '');
|
|
74
|
+
if (sourceIndex >= requestedStart) {
|
|
75
|
+
lines.push({ sourceIndex, text: value });
|
|
76
|
+
if (lines.length >= requestedCount) {
|
|
77
|
+
moreSource = newline !== -1;
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (newline === -1)
|
|
82
|
+
break;
|
|
83
|
+
cursor = newline + 1;
|
|
84
|
+
sourceIndex += 1;
|
|
85
|
+
}
|
|
86
|
+
return { lines, truncated: v.length > source.length || moreSource };
|
|
87
|
+
};
|
|
88
|
+
const boundedRows = (result, max) => result.truncated && result.lines.length >= max
|
|
89
|
+
? result.lines.slice(0, Math.max(0, max - 1))
|
|
90
|
+
: result.lines;
|
|
91
|
+
const actionFooter = (actions, cancellation) => ({
|
|
92
|
+
visual: [
|
|
93
|
+
...actions.flatMap((action) => action.visualLabel ? [action.visualLabel] : []),
|
|
94
|
+
cancellation.visualLabel,
|
|
95
|
+
].join(' '),
|
|
96
|
+
accessible: [
|
|
97
|
+
...actions.map((action) => action.screenReaderLabel),
|
|
98
|
+
cancellation.screenReaderLabel,
|
|
99
|
+
].join('. '),
|
|
100
|
+
});
|
|
101
|
+
const mcpActionLabel = (action) => action.type === 'view-tools'
|
|
102
|
+
? 'View tools'
|
|
103
|
+
: action.type === 'reconnect'
|
|
104
|
+
? 'Reconnect'
|
|
105
|
+
: action.type === 'authenticate'
|
|
106
|
+
? 'Authenticate'
|
|
107
|
+
: action.type === 'set-enabled'
|
|
108
|
+
? action.enabled
|
|
109
|
+
? 'Enable'
|
|
110
|
+
: 'Disable'
|
|
111
|
+
: 'Close';
|
|
112
|
+
const unsupported = (v) => {
|
|
113
|
+
throw new Error(`Unsupported quiet operational surface: ${String(v)}`);
|
|
114
|
+
};
|
|
115
|
+
function help(s, o) {
|
|
116
|
+
const sr = !!o.screenReader;
|
|
117
|
+
const out = [
|
|
118
|
+
row('quiet:help:heading', `Help · ${s.activeTab.label}`, 'heading', `Help, ${s.activeTab.label} tab`),
|
|
119
|
+
];
|
|
120
|
+
if (s.activeContent.kind === 'general') {
|
|
121
|
+
out.push(...(full(o.density)
|
|
122
|
+
? [
|
|
123
|
+
row('quiet:help:description', excerpt(s.activeContent.description), 'muted'),
|
|
124
|
+
]
|
|
125
|
+
: []));
|
|
126
|
+
const shortcuts = s.activeContent.shortcutGroups.flatMap((group) => group.shortcuts);
|
|
127
|
+
for (const x of shortcuts.slice(0, limit(o)))
|
|
128
|
+
out.push(row(`quiet:help:shortcut:${x.id}`, marker(false, `${x.key} ${x.description}`, sr), 'body', sr ? `Option: ${x.key} ${x.description}` : undefined));
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
out.push(row('quiet:help:content-heading', s.activeContent.heading, 'muted'));
|
|
132
|
+
const window = selectionWindow(s.activeContent.commands.length, s.activeContent.focusedIndex ?? 0, limit(o));
|
|
133
|
+
const items = s.activeContent.commands.slice(window.start, window.end);
|
|
134
|
+
const selected = Math.max(0, Math.min(s.activeContent.commands.length - 1, s.activeContent.focusedIndex ?? 0));
|
|
135
|
+
for (const [i, x] of items.entries()) {
|
|
136
|
+
const yes = i + window.start === selected;
|
|
137
|
+
out.push(row(`quiet:help:command:${x.id}`, marker(yes, full(o.density)
|
|
138
|
+
? `${x.invocation} — ${excerpt(x.description)}`
|
|
139
|
+
: x.invocation, sr), yes ? 'selection' : 'body', sr
|
|
140
|
+
? `${yes ? 'Selected' : 'Option'}: ${x.invocation} ${x.description}`
|
|
141
|
+
: undefined));
|
|
142
|
+
}
|
|
143
|
+
if (!items.length)
|
|
144
|
+
out.push(row('quiet:help:empty', s.activeContent.emptyText, 'muted'));
|
|
145
|
+
}
|
|
146
|
+
if (full(o.density))
|
|
147
|
+
out.push(row('quiet:help:documentation', `${s.documentation.label}: ${s.documentation.url}`, 'muted'));
|
|
148
|
+
out.push(footer('←/→ tabs ↑/↓ browse Esc close', 'Use left and right arrows to switch tabs. Use up and down arrows to browse. Press Escape to close.', sr));
|
|
149
|
+
return out;
|
|
150
|
+
}
|
|
151
|
+
function diff(s, o) {
|
|
152
|
+
const out = [row('quiet:diff:heading', s.title, 'heading')];
|
|
153
|
+
const sr = !!o.screenReader;
|
|
154
|
+
const selectedSource = Math.max(0, s.sourceTabs.findIndex((source) => source.selected));
|
|
155
|
+
const sourceWindow = selectionWindow(s.sourceTabs.length, selectedSource, limit(o));
|
|
156
|
+
for (const t of s.sourceTabs.slice(sourceWindow.start, sourceWindow.end))
|
|
157
|
+
out.push(row(`quiet:diff:source:${t.id}`, marker(t.selected, t.label, sr), t.selected ? 'selection' : 'body', sr
|
|
158
|
+
? `${t.selected ? 'Selected' : 'Option'}: source ${t.label}`
|
|
159
|
+
: undefined));
|
|
160
|
+
if (s.view.kind === 'summary') {
|
|
161
|
+
out.push(row('quiet:diff:totals', `Totals: +${s.view.totals.additions} −${s.view.totals.deletions}`, 'muted'));
|
|
162
|
+
const window = selectionWindow(s.view.files.length, s.view.selectedIndex ?? 0, limit(o));
|
|
163
|
+
for (const [i, f] of s.view.files
|
|
164
|
+
.slice(window.start, window.end)
|
|
165
|
+
.entries()) {
|
|
166
|
+
const yes = i + window.start === s.view.selectedIndex;
|
|
167
|
+
out.push(row(`quiet:diff:file:${f.id}`, marker(yes, `${f.path} (+${f.additions}/−${f.deletions})`, sr), yes ? 'selection' : 'body', sr
|
|
168
|
+
? `${yes ? 'Selected' : 'Option'}: ${f.path}, plus ${f.additions} additions and ${f.deletions} deletions`
|
|
169
|
+
: undefined));
|
|
170
|
+
}
|
|
171
|
+
if (!s.view.files.length)
|
|
172
|
+
out.push(row('quiet:diff:empty', s.view.emptyText, 'muted'));
|
|
173
|
+
const labels = actionFooter(s.view.actions, s.view.cancellation);
|
|
174
|
+
out.push(footer(labels.visual, labels.accessible, sr));
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
out.push(row(`quiet:diff:file:${s.view.file.id}`, `${s.view.file.path} (+${s.view.file.additions}/−${s.view.file.deletions})`, 'selection'));
|
|
178
|
+
for (const p of s.view.patchRows.slice(0, limit(o)))
|
|
179
|
+
out.push(row(`quiet:diff:patch:${p.id}`, p.text, p.kind === 'added'
|
|
180
|
+
? 'diffAdded'
|
|
181
|
+
: p.kind === 'removed'
|
|
182
|
+
? 'diffRemoved'
|
|
183
|
+
: 'body'));
|
|
184
|
+
if (!s.view.patchRows.length)
|
|
185
|
+
out.push(row('quiet:diff:empty', s.view.emptyPatchText, 'muted'));
|
|
186
|
+
const labels = actionFooter(s.view.actions, s.view.cancellation);
|
|
187
|
+
out.push(footer(labels.visual, labels.accessible, sr));
|
|
188
|
+
}
|
|
189
|
+
return out;
|
|
190
|
+
}
|
|
191
|
+
function mcp(s, o) {
|
|
192
|
+
const { model, state } = s;
|
|
193
|
+
const out = [row('quiet:mcp:heading', 'MCP servers', 'heading')];
|
|
194
|
+
const server = model.servers[state.serverIndex];
|
|
195
|
+
const sr = !!o.screenReader;
|
|
196
|
+
if (state.depth === 'list') {
|
|
197
|
+
const window = selectionWindow(model.servers.length, state.serverIndex, limit(o));
|
|
198
|
+
for (const [i, x] of model.servers
|
|
199
|
+
.slice(window.start, window.end)
|
|
200
|
+
.entries()) {
|
|
201
|
+
const yes = i + window.start === state.serverIndex;
|
|
202
|
+
const info = statusInfo(x.status);
|
|
203
|
+
out.push(row(`quiet:mcp:server:${x.name}`, marker(yes, `${info.marker} ${x.name} · ${x.status}`, sr), yes ? info.role : info.role, sr
|
|
204
|
+
? `${yes ? 'Selected' : 'Option'}: ${x.name}, ${info.label}`
|
|
205
|
+
: undefined));
|
|
206
|
+
}
|
|
207
|
+
if (!model.servers.length)
|
|
208
|
+
out.push(row('quiet:mcp:empty', 'No MCP servers configured.', 'muted'));
|
|
209
|
+
out.push(footer('↑/↓ select Enter open Esc close', 'Use up and down arrows to select. Press Enter to open. Press Escape to close.', sr));
|
|
210
|
+
return out;
|
|
211
|
+
}
|
|
212
|
+
if (!server)
|
|
213
|
+
return [
|
|
214
|
+
...out,
|
|
215
|
+
row('quiet:mcp:empty', 'No MCP server selected.', 'muted'),
|
|
216
|
+
footer('Esc back', 'Press Escape to go back.', sr),
|
|
217
|
+
];
|
|
218
|
+
const serverInfo = statusInfo(server.status);
|
|
219
|
+
out.push(row(`quiet:mcp:server:${server.name}`, `${serverInfo.marker} ${server.name} · ${server.status}`, serverInfo.role, sr ? `Selected: ${server.name}, ${serverInfo.label}` : undefined));
|
|
220
|
+
if (state.depth === 'detail') {
|
|
221
|
+
if (full(o.density))
|
|
222
|
+
out.push(row(`quiet:mcp:meta:${server.name}`, `${server.transport} · ${server.scope} · ${server.location}`, 'muted'));
|
|
223
|
+
const acts = tuiMcpServerActions(server);
|
|
224
|
+
const window = selectionWindow(acts.length, state.selectedIndex, limit(o));
|
|
225
|
+
acts.slice(window.start, window.end).forEach((a, visibleIndex) => {
|
|
226
|
+
const actionIndex = visibleIndex + window.start;
|
|
227
|
+
const selected = actionIndex === state.selectedIndex;
|
|
228
|
+
const label = mcpActionLabel(a);
|
|
229
|
+
out.push(row(`quiet:mcp:action:${server.name}:${a.type}`, marker(selected, label, sr), selected ? 'selection' : 'body', sr ? `${selected ? 'Selected' : 'Option'}: ${label}` : undefined));
|
|
230
|
+
});
|
|
231
|
+
out.push(footer('↑/↓ select Enter choose Esc back', 'Use up and down arrows to select. Press Enter to choose. Press Escape to go back.', sr));
|
|
232
|
+
return out;
|
|
233
|
+
}
|
|
234
|
+
const tools = server.tools ?? [];
|
|
235
|
+
if (state.depth === 'tools') {
|
|
236
|
+
const window = selectionWindow(tools.length, state.selectedIndex, limit(o));
|
|
237
|
+
for (const [i, t] of tools.slice(window.start, window.end).entries())
|
|
238
|
+
out.push(row(`quiet:mcp:tool:${t.fullName}`, marker(i + window.start === state.selectedIndex, t.name, sr), i + window.start === state.selectedIndex ? 'selection' : 'body', sr
|
|
239
|
+
? `${i + window.start === state.selectedIndex ? 'Selected' : 'Option'}: ${t.name}`
|
|
240
|
+
: undefined));
|
|
241
|
+
if (!tools.length)
|
|
242
|
+
out.push(row('quiet:mcp:tools-empty', 'No tools available.', 'muted'));
|
|
243
|
+
out.push(footer('↑/↓ select Enter view Esc back', 'Use up and down arrows to select. Press Enter to view. Press Escape to go back.', sr));
|
|
244
|
+
return out;
|
|
245
|
+
}
|
|
246
|
+
const tool = tools[state.selectedIndex];
|
|
247
|
+
if (tool) {
|
|
248
|
+
out.push(row(`quiet:mcp:tool:${tool.fullName}`, tool.name, 'selection'));
|
|
249
|
+
if (full(o.density))
|
|
250
|
+
out.push(row(`quiet:mcp:tool-description:${tool.fullName}`, excerpt(tool.description, 240), 'muted'));
|
|
251
|
+
}
|
|
252
|
+
else
|
|
253
|
+
out.push(row('quiet:mcp:tool-empty', 'No tool selected.', 'muted'));
|
|
254
|
+
out.push(footer('Esc back', 'Press Escape to go back.', sr));
|
|
255
|
+
return out;
|
|
256
|
+
}
|
|
257
|
+
function tasks(s, o) {
|
|
258
|
+
const out = [row('quiet:tasks:heading', 'Tasks', 'heading')];
|
|
259
|
+
const sr = !!o.screenReader;
|
|
260
|
+
const list = s.tasks;
|
|
261
|
+
if (s.state.depth === 'list') {
|
|
262
|
+
const window = selectionWindow(list.length, s.state.selectedIndex, limit(o));
|
|
263
|
+
for (const [i, t] of list.slice(window.start, window.end).entries()) {
|
|
264
|
+
const yes = i + window.start === s.state.selectedIndex;
|
|
265
|
+
const info = statusInfo(t.status);
|
|
266
|
+
out.push(row(`quiet:task:${t.id}`, marker(yes, `${info.marker} ${t.kind} · ${t.status} · ${excerpt(t.label, 100)}`, sr), info.role, sr
|
|
267
|
+
? `${yes ? 'Selected' : 'Option'}: ${t.kind}, ${info.label}, ${t.label}`
|
|
268
|
+
: undefined));
|
|
269
|
+
}
|
|
270
|
+
if (!list.length)
|
|
271
|
+
out.push(row('quiet:tasks:empty', 'No tasks.', 'muted'));
|
|
272
|
+
out.push(footer('↑/↓ select Enter open Esc close', 'Use up and down arrows to select. Press Enter to open. Press Escape to close.', sr));
|
|
273
|
+
return out;
|
|
274
|
+
}
|
|
275
|
+
const t = list[s.state.selectedIndex];
|
|
276
|
+
if (!t)
|
|
277
|
+
return [...out, row('quiet:tasks:empty', 'No task selected.', 'muted')];
|
|
278
|
+
const info = statusInfo(t.status);
|
|
279
|
+
out.push(row(`quiet:task:${t.id}`, `${info.marker} ${t.kind} · ${t.status} · ${t.command ?? t.label}`, info.role, sr
|
|
280
|
+
? `Selected: ${t.kind}, ${info.label}, ${t.command ?? t.label}`
|
|
281
|
+
: undefined));
|
|
282
|
+
if (o.nowMs !== undefined &&
|
|
283
|
+
Number.isFinite(o.nowMs) &&
|
|
284
|
+
o.nowMs >= 0 &&
|
|
285
|
+
t.status === 'running' &&
|
|
286
|
+
t.startedAtMs !== undefined)
|
|
287
|
+
out.push(row(`quiet:task:${t.id}:duration`, `Duration: ${Math.max(0, Math.floor((o.nowMs - t.startedAtMs) / 1000))} seconds`, 'muted'));
|
|
288
|
+
const output = boundedLines(t.output, limit(o), normalizedIndex(s.state.scrollOffset));
|
|
289
|
+
for (const line of boundedRows(output, limit(o)))
|
|
290
|
+
out.push(row(`quiet:task:${t.id}:output:${line.sourceIndex}`, line.text, t.status === 'failed' ? 'error' : 'body'));
|
|
291
|
+
if (output.truncated)
|
|
292
|
+
out.push(row(`quiet:task:${t.id}:truncated`, 'Output truncated.', 'muted'));
|
|
293
|
+
else if (output.lines.length === 0)
|
|
294
|
+
out.push(row(`quiet:task:${t.id}:output:empty`, 'No output available.', 'muted'));
|
|
295
|
+
out.push(footer(t.status === 'running' ? 'Esc back Stop task available' : 'Esc back', 'Press Escape to go back.', sr));
|
|
296
|
+
return out;
|
|
297
|
+
}
|
|
298
|
+
function list(s, o) {
|
|
299
|
+
const out = [row('quiet:list:heading', s.title, 'heading')];
|
|
300
|
+
const sr = !!o.screenReader;
|
|
301
|
+
const occurrences = new Map();
|
|
302
|
+
const keys = s.rows.map((x) => {
|
|
303
|
+
const occurrence = occurrences.get(x.label) ?? 0;
|
|
304
|
+
occurrences.set(x.label, occurrence + 1);
|
|
305
|
+
return occurrence;
|
|
306
|
+
});
|
|
307
|
+
const window = selectionWindow(s.rows.length, s.selectedIndex, limit(o));
|
|
308
|
+
for (const [i, x] of s.rows.slice(window.start, window.end).entries()) {
|
|
309
|
+
const actual = i + window.start;
|
|
310
|
+
const yes = actual === s.selectedIndex;
|
|
311
|
+
out.push(row(`quiet:list:${JSON.stringify([s.title, x.label, keys[actual]])}`, marker(yes, full(o.density) && x.description
|
|
312
|
+
? `${x.label} — ${excerpt(x.description)}`
|
|
313
|
+
: x.label, sr), yes ? 'selection' : 'body', sr ? `${yes ? 'Selected' : 'Option'}: ${x.label}` : undefined));
|
|
314
|
+
}
|
|
315
|
+
if (!s.rows.length)
|
|
316
|
+
out.push(row('quiet:list:empty', s.emptyText, 'muted'));
|
|
317
|
+
out.push(footer('↑/↓ select Enter open Esc close', 'Use up and down arrows to select. Press Enter to open. Press Escape to close.', sr));
|
|
318
|
+
return out;
|
|
319
|
+
}
|
|
320
|
+
function btw(s, o) {
|
|
321
|
+
const out = [row('quiet:btw:heading', 'BTW', 'heading')];
|
|
322
|
+
const sr = !!o.screenReader;
|
|
323
|
+
const window = selectionWindow(s.entries.length, s.selectedIndex, limit(o), normalizedIndex(s.scrollOffset));
|
|
324
|
+
for (const [i, e] of s.entries.slice(window.start, window.end).entries()) {
|
|
325
|
+
const actual = i + window.start;
|
|
326
|
+
const info = statusInfo(e.status);
|
|
327
|
+
const yes = actual === s.selectedIndex;
|
|
328
|
+
out.push(row(`quiet:btw:${e.id}`, marker(yes, `${info.marker} ${e.status} · ${excerpt(e.question, 120)}`, sr), info.role, sr
|
|
329
|
+
? `${yes ? 'Selected' : 'Option'}: ${e.question}, ${info.label}`
|
|
330
|
+
: undefined));
|
|
331
|
+
if (yes && (e.error || (full(o.density) && e.answer))) {
|
|
332
|
+
const detail = boundedLines(e.error ? `Error: ${e.error}` : e.answer, limit(o));
|
|
333
|
+
for (const line of boundedRows(detail, limit(o)))
|
|
334
|
+
out.push(row(`quiet:btw:${e.id}:detail:${line.sourceIndex}`, line.text, e.error ? 'error' : 'body'));
|
|
335
|
+
if (detail.truncated)
|
|
336
|
+
out.push(row(`quiet:btw:${e.id}:truncated`, 'Answer truncated.', 'muted'));
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
if (!s.entries.length)
|
|
340
|
+
out.push(row('quiet:btw:empty', 'No side questions.', 'muted'));
|
|
341
|
+
if (s.copied)
|
|
342
|
+
out.push(row('quiet:btw:copied', 'Copied.', 'success'));
|
|
343
|
+
out.push(footer('↑/↓ select Enter open Esc close', 'Use up and down arrows to select. Press Enter to open. Press Escape to close.', sr));
|
|
344
|
+
return out;
|
|
345
|
+
}
|
|
346
|
+
function rewind(s, o) {
|
|
347
|
+
const out = [row('quiet:rewind:heading', 'Rewind', 'heading')];
|
|
348
|
+
const sr = !!o.screenReader;
|
|
349
|
+
if (s.view === 'points') {
|
|
350
|
+
const start = Math.min(s.points.length, normalizedIndex(s.window.start));
|
|
351
|
+
const end = Math.max(start, Math.min(s.points.length, normalizedIndex(s.window.end, start)));
|
|
352
|
+
const window = selectionWindow(end - start, s.selectedIndex - start, limit(o));
|
|
353
|
+
for (const [i, p] of s.points
|
|
354
|
+
.slice(start + window.start, start + window.end)
|
|
355
|
+
.entries()) {
|
|
356
|
+
const actual = i + start + window.start;
|
|
357
|
+
const yes = actual === s.selectedIndex;
|
|
358
|
+
out.push(row(`quiet:rewind:${p.messageId}`, marker(yes, excerpt(p.prompt, 140), sr), yes ? 'selection' : 'body'));
|
|
359
|
+
}
|
|
360
|
+
if (!s.points.length)
|
|
361
|
+
out.push(row('quiet:rewind:empty', 'No rewind points.', 'muted'));
|
|
362
|
+
out.push(footer('↑/↓ select Enter choose Esc close', 'Use up and down arrows to select. Press Enter to choose. Press Escape to close.', sr));
|
|
363
|
+
}
|
|
364
|
+
else if (s.view === 'confirm') {
|
|
365
|
+
out.push(row(`quiet:rewind:${s.point.messageId}`, excerpt(s.point.prompt), 'muted'));
|
|
366
|
+
const window = selectionWindow(s.actions.length, s.selectedIndex, limit(o));
|
|
367
|
+
for (const [visibleIndex, a] of s.actions
|
|
368
|
+
.slice(window.start, window.end)
|
|
369
|
+
.entries()) {
|
|
370
|
+
const actionIndex = visibleIndex + window.start;
|
|
371
|
+
const selected = actionIndex === s.selectedIndex;
|
|
372
|
+
out.push(row(`quiet:rewind:action:${a.action}`, marker(selected, a.label, sr), a.action === 'cancel' ? (selected ? 'selection' : 'body') : 'warning', sr
|
|
373
|
+
? `${selected ? 'Selected' : 'Option'}: ${a.label}${a.action === 'cancel' ? '' : ', restores prior state'}`
|
|
374
|
+
: undefined));
|
|
375
|
+
}
|
|
376
|
+
if (!s.actions.length)
|
|
377
|
+
out.push(row('quiet:rewind:actions-empty', 'No rewind actions.', 'muted'));
|
|
378
|
+
out.push(footer('↑/↓ select Enter confirm Esc back', 'Use up and down arrows to select. Press Enter to confirm. Press Escape to go back.', sr));
|
|
379
|
+
}
|
|
380
|
+
else {
|
|
381
|
+
out.push(row(`quiet:rewind:${s.point.messageId}`, `${s.direction === 'from' ? 'From' : 'To'}: ${excerpt(s.point.prompt)}`, 'muted'));
|
|
382
|
+
const context = boundedLines(s.context, limit(o));
|
|
383
|
+
for (const line of boundedRows(context, limit(o)))
|
|
384
|
+
out.push(row(`quiet:rewind:context:${line.sourceIndex}`, line.text, 'body'));
|
|
385
|
+
if (context.truncated)
|
|
386
|
+
out.push(row('quiet:rewind:context:truncated', 'Context truncated.', 'muted'));
|
|
387
|
+
else if (context.lines.length === 0)
|
|
388
|
+
out.push(row('quiet:rewind:context:empty', 'No context.', 'muted'));
|
|
389
|
+
out.push(footer('Esc back', 'Press Escape to go back.', sr));
|
|
390
|
+
}
|
|
391
|
+
return out;
|
|
392
|
+
}
|
|
393
|
+
export function projectQuietOperationalRows(surface, options) {
|
|
394
|
+
switch (surface.kind) {
|
|
395
|
+
case 'help':
|
|
396
|
+
return help(surface, options);
|
|
397
|
+
case 'diff':
|
|
398
|
+
return diff(surface, options);
|
|
399
|
+
case 'mcp-panel':
|
|
400
|
+
return mcp(surface, options);
|
|
401
|
+
case 'tasks-panel':
|
|
402
|
+
return tasks(surface, options);
|
|
403
|
+
case 'list-panel':
|
|
404
|
+
return list(surface, options);
|
|
405
|
+
case 'btw-panel':
|
|
406
|
+
return btw(surface, options);
|
|
407
|
+
case 'rewind-panel':
|
|
408
|
+
return rewind(surface, options);
|
|
409
|
+
default:
|
|
410
|
+
return unsupported(surface);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
//# sourceMappingURL=quiet-operational-rows.js.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type QuietOverlaySurface, type QuietPrioritySurface } from './quiet-choice-rows.js';
|
|
2
|
+
import { type QuietOperationalSurface } from './quiet-operational-rows.js';
|
|
3
|
+
import { type QuietSettingsSurface } from './quiet-settings-rows.js';
|
|
4
|
+
import { type QuietFrame, type QuietFrameInput } from './quiet-frame.js';
|
|
5
|
+
import type { TuiScreenModel, TuiScreenSurfaceModels } from './tui-screen-model.js';
|
|
6
|
+
import type { TuiPermissionSurfaceModel } from './permission-surface-model.js';
|
|
7
|
+
import type { TuiSessionPickerModel } from './session-picker-model.js';
|
|
8
|
+
export type QuietSecondarySurface = TuiPermissionSurfaceModel | QuietOperationalSurface | QuietSettingsSurface;
|
|
9
|
+
export interface QuietScreenSurfaces extends TuiScreenSurfaceModels {
|
|
10
|
+
readonly sessionPicker: TuiSessionPickerModel;
|
|
11
|
+
readonly priority: QuietPrioritySurface;
|
|
12
|
+
readonly secondary: QuietSecondarySurface;
|
|
13
|
+
readonly overlay: QuietOverlaySurface;
|
|
14
|
+
}
|
|
15
|
+
export interface QuietScreenProjectionInput {
|
|
16
|
+
readonly screen: TuiScreenModel<QuietScreenSurfaces>;
|
|
17
|
+
readonly composerText: string;
|
|
18
|
+
readonly composerCursor: number;
|
|
19
|
+
readonly shellMode: boolean;
|
|
20
|
+
readonly busy: boolean;
|
|
21
|
+
readonly status: string;
|
|
22
|
+
readonly display?: QuietFrameInput['display'];
|
|
23
|
+
}
|
|
24
|
+
export declare function projectQuietScreenFrame(input: QuietScreenProjectionInput): QuietFrame;
|
|
25
|
+
//# sourceMappingURL=quiet-screen-projector.d.ts.map
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { projectQuietChoiceRows, } from './quiet-choice-rows.js';
|
|
2
|
+
import { projectQuietOperationalRows, } from './quiet-operational-rows.js';
|
|
3
|
+
import { projectQuietSettingsRows, } from './quiet-settings-rows.js';
|
|
4
|
+
import { projectQuietFrame, createQuietFrameRow, resolveQuietFrameDensity, } from './quiet-frame.js';
|
|
5
|
+
function unsupported(value) {
|
|
6
|
+
throw new Error(`Unsupported quiet secondary surface: ${String(value)}`);
|
|
7
|
+
}
|
|
8
|
+
function maxItems(rows) {
|
|
9
|
+
if (rows === undefined || !Number.isFinite(rows) || rows <= 0)
|
|
10
|
+
return 20;
|
|
11
|
+
return Math.max(1, Math.floor(rows) - 6);
|
|
12
|
+
}
|
|
13
|
+
function overlay(surface) {
|
|
14
|
+
return (surface.find((item) => item.kind === 'exit-confirmation') ??
|
|
15
|
+
surface.find((item) => item.kind === 'mention-picker') ??
|
|
16
|
+
surface.find((item) => item.kind === 'command-palette'));
|
|
17
|
+
}
|
|
18
|
+
function projectFocusRows(input, density, screenReader) {
|
|
19
|
+
const body = input.screen.body;
|
|
20
|
+
const options = { density, screenReader };
|
|
21
|
+
if (body.kind === 'session-picker')
|
|
22
|
+
return projectQuietChoiceRows(body.surface, options);
|
|
23
|
+
const foreground = body.foreground;
|
|
24
|
+
if (foreground.kind === 'priority')
|
|
25
|
+
return projectQuietChoiceRows(foreground.surface, options);
|
|
26
|
+
if (foreground.kind === 'secondary') {
|
|
27
|
+
const surface = foreground.surface;
|
|
28
|
+
switch (surface.kind) {
|
|
29
|
+
case 'tool-request':
|
|
30
|
+
case 'recovery-request':
|
|
31
|
+
case 'permission-dashboard':
|
|
32
|
+
case 'permission-rule-input':
|
|
33
|
+
case 'permission-scope':
|
|
34
|
+
case 'permission-delete':
|
|
35
|
+
case 'workspace-directory-input':
|
|
36
|
+
case 'workspace-directory-delete':
|
|
37
|
+
return projectQuietChoiceRows(surface, options);
|
|
38
|
+
case 'help':
|
|
39
|
+
case 'diff':
|
|
40
|
+
case 'mcp-panel':
|
|
41
|
+
case 'tasks-panel':
|
|
42
|
+
case 'list-panel':
|
|
43
|
+
case 'btw-panel':
|
|
44
|
+
case 'rewind-panel':
|
|
45
|
+
return projectQuietOperationalRows(surface, {
|
|
46
|
+
...options,
|
|
47
|
+
maxItems: maxItems(input.screen.presentation.viewport.rows),
|
|
48
|
+
});
|
|
49
|
+
case 'doctor-panel':
|
|
50
|
+
case 'memory-panel':
|
|
51
|
+
case 'hooks-panel':
|
|
52
|
+
case 'config-panel':
|
|
53
|
+
case 'sandbox-panel':
|
|
54
|
+
case 'model-panel':
|
|
55
|
+
case 'effort-panel':
|
|
56
|
+
case 'theme-panel':
|
|
57
|
+
case 'custom-theme-create':
|
|
58
|
+
case 'custom-theme-editor':
|
|
59
|
+
case 'custom-theme-token':
|
|
60
|
+
case 'custom-theme-delete':
|
|
61
|
+
case 'model-input':
|
|
62
|
+
case 'export':
|
|
63
|
+
case 'copy':
|
|
64
|
+
case 'export-filename':
|
|
65
|
+
case 'compact-progress':
|
|
66
|
+
return projectQuietSettingsRows(surface, {
|
|
67
|
+
...options,
|
|
68
|
+
maxItems: maxItems(input.screen.presentation.viewport.rows),
|
|
69
|
+
});
|
|
70
|
+
default:
|
|
71
|
+
return unsupported(surface);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const selected = overlay(foreground.overlays);
|
|
75
|
+
return selected === undefined ? [] : projectQuietChoiceRows(selected, options);
|
|
76
|
+
}
|
|
77
|
+
export function projectQuietScreenFrame(input) {
|
|
78
|
+
const viewport = input.screen.presentation.viewport;
|
|
79
|
+
const width = Number.isFinite(viewport.columns) && viewport.columns > 0
|
|
80
|
+
? Math.floor(viewport.columns)
|
|
81
|
+
: 1;
|
|
82
|
+
const rows = viewport.rows === undefined ||
|
|
83
|
+
!Number.isFinite(viewport.rows) ||
|
|
84
|
+
viewport.rows <= 0
|
|
85
|
+
? undefined
|
|
86
|
+
: Math.floor(viewport.rows);
|
|
87
|
+
const density = resolveQuietFrameDensity(width);
|
|
88
|
+
let focusRows;
|
|
89
|
+
try {
|
|
90
|
+
focusRows = projectFocusRows(input, density, input.screen.presentation.screenReader);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
focusRows = [
|
|
94
|
+
createQuietFrameRow('quiet:projection-error', 'Unable to render this view. Press Esc to return.', 'focus', 'error', 'Unable to render this view. Press Esc to return.'),
|
|
95
|
+
];
|
|
96
|
+
}
|
|
97
|
+
return projectQuietFrame({
|
|
98
|
+
screen: input.screen,
|
|
99
|
+
width,
|
|
100
|
+
rows,
|
|
101
|
+
composerText: input.composerText,
|
|
102
|
+
composerCursor: input.composerCursor,
|
|
103
|
+
shellMode: input.shellMode,
|
|
104
|
+
busy: input.busy,
|
|
105
|
+
status: input.status,
|
|
106
|
+
...(input.display === undefined ? {} : { display: input.display }),
|
|
107
|
+
focusRows,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=quiet-screen-projector.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type QuietFrameDensity, type QuietFrameRow } from './quiet-frame.js';
|
|
2
|
+
import type { TuiDoctorSurfaceModel } from './doctor-surface-model.js';
|
|
3
|
+
import type { TuiMemorySurfaceModel } from './memory-surface-model.js';
|
|
4
|
+
import type { TuiHooksSurfaceModel } from './hooks-surface-model.js';
|
|
5
|
+
import type { TuiConfigSurfaceModel } from './config-surface-model.js';
|
|
6
|
+
import type { TuiSandboxSurfaceModel } from './sandbox-surface-model.js';
|
|
7
|
+
import type { TuiModelSurfaceModel, TuiEffortSurfaceModel } from './model-effort-surface-model.js';
|
|
8
|
+
import type { TuiThemeSurfaceModel } from './theme-surface-model.js';
|
|
9
|
+
import type { TuiLeafSurfaceModel } from './leaf-surface-model.js';
|
|
10
|
+
export type QuietSettingsSurface = TuiDoctorSurfaceModel | TuiMemorySurfaceModel | TuiHooksSurfaceModel | TuiConfigSurfaceModel | TuiSandboxSurfaceModel | TuiModelSurfaceModel | TuiEffortSurfaceModel | TuiThemeSurfaceModel | TuiLeafSurfaceModel;
|
|
11
|
+
export interface QuietSettingsProjectionOptions {
|
|
12
|
+
readonly density: QuietFrameDensity;
|
|
13
|
+
readonly screenReader?: boolean;
|
|
14
|
+
readonly maxItems?: number;
|
|
15
|
+
}
|
|
16
|
+
export declare function projectQuietSettingsRows(surface: QuietSettingsSurface, o: QuietSettingsProjectionOptions): readonly QuietFrameRow[];
|
|
17
|
+
//# sourceMappingURL=quiet-settings-rows.d.ts.map
|