@giovannijecha/jecode 0.8.6 → 0.8.7
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 +18 -8
- package/assets/tokenizers/LICENSE +21 -0
- package/assets/tokenizers/o200k-base.tiktoken.gz +0 -0
- package/dist/cli-info.js +8 -13
- package/dist/commands.js +7 -3
- package/dist/config.js +33 -47
- package/dist/context/automatic.js +16 -1
- package/dist/context/budget.js +5 -2
- package/dist/context/compactor.js +91 -37
- package/dist/context/diagnostics.js +108 -0
- package/dist/context/lifetime.js +18 -0
- package/dist/context/manager.js +67 -0
- package/dist/context/manual.js +11 -10
- package/dist/context/measurement.js +75 -0
- package/dist/context/policy.js +13 -10
- package/dist/context/request-observation.js +46 -0
- package/dist/context/request-projection.js +8 -34
- package/dist/context/request.js +22 -0
- package/dist/context/tokenizer/bpe.js +68 -0
- package/dist/context/tokenizer/o200k.js +54 -0
- package/dist/context/tokenizer/vocabulary.js +34 -0
- package/dist/controller-request.js +47 -45
- package/dist/controller.js +13 -2
- package/dist/input-boundary.js +0 -62
- package/dist/launch.js +13 -9
- package/dist/model-command.js +7 -17
- package/dist/oauth-result-page.js +68 -0
- package/dist/openai-oauth-callback.js +2 -50
- package/dist/permission-command.js +9 -24
- package/dist/permissions.js +10 -8
- package/dist/providers/anthropic-wire.js +1 -1
- package/dist/providers/anthropic.js +3 -1
- package/dist/providers/input-measurement.js +85 -0
- package/dist/providers/ollama-wire.js +1 -1
- package/dist/providers/ollama.js +2 -0
- package/dist/providers/openai-codex.js +3 -0
- package/dist/providers/openai-stream.js +20 -8
- package/dist/providers/openai-summary.js +37 -0
- package/dist/providers/openai-wire.js +1 -1
- package/dist/providers/openai.js +3 -0
- package/dist/settings-command.js +5 -10
- package/dist/settings.js +1 -1
- package/dist/start.js +35 -51
- package/dist/timeline.js +2 -0
- package/dist/tui/activity.js +1 -1
- package/dist/tui/app-input.js +34 -17
- package/dist/tui/app-workflows.js +7 -2
- package/dist/tui/app.js +55 -28
- package/dist/tui/approve.js +3 -4
- package/dist/tui/blocks.js +5 -7
- package/dist/tui/command-workflow.js +14 -8
- package/dist/tui/components/command-menu.js +1 -1
- package/dist/tui/components/menu.js +19 -11
- package/dist/tui/components/status.js +1 -1
- package/dist/tui/frame.js +8 -3
- package/dist/tui/help.js +2 -1
- package/dist/tui/picker-layout.js +5 -1
- package/dist/tui/screen.js +7 -0
- package/dist/tui/session-view.js +0 -1
- package/dist/tui/transcript-grammar.js +1 -8
- package/dist/tui/transcript-view.js +4 -0
- package/dist/tui/turn-workflow.js +47 -76
- package/dist/tui/view.js +4 -4
- package/dist/ui/render.js +0 -4
- package/package.json +5 -1
- package/dist/batch-view.js +0 -42
- package/dist/batch.js +0 -269
package/dist/tui/app.js
CHANGED
|
@@ -34,7 +34,7 @@ export async function runApp(session, transcriptRoot, environment = {}) {
|
|
|
34
34
|
const state = appState();
|
|
35
35
|
state.blocks.push(...session.conversation.transcript);
|
|
36
36
|
state.committedNodeId = session.conversation.activeNodeId;
|
|
37
|
-
const permissions = sessionPermissions(session.tools
|
|
37
|
+
const permissions = sessionPermissions(session.tools);
|
|
38
38
|
let closed;
|
|
39
39
|
let frameTimer;
|
|
40
40
|
let motionTimer;
|
|
@@ -42,9 +42,10 @@ export async function runApp(session, transcriptRoot, environment = {}) {
|
|
|
42
42
|
let escapeTimer;
|
|
43
43
|
let stopResize = () => { };
|
|
44
44
|
let stopInput = () => { };
|
|
45
|
+
let stopOutput = () => { };
|
|
45
46
|
let stopShutdown = () => { };
|
|
46
47
|
let failure;
|
|
47
|
-
|
|
48
|
+
const activeWorkflows = new Set();
|
|
48
49
|
// Timers outlive the teardown they were scheduled before. Painting after the
|
|
49
50
|
// terminal has been handed back would write escapes into the user's shell.
|
|
50
51
|
let live = true;
|
|
@@ -61,22 +62,25 @@ export async function runApp(session, transcriptRoot, environment = {}) {
|
|
|
61
62
|
};
|
|
62
63
|
const view = () => {
|
|
63
64
|
const now = Date.now();
|
|
65
|
+
const activity = state.command !== undefined && state.open === undefined && state.approval === undefined
|
|
66
|
+
? state.command : state.activity ?? state.command;
|
|
64
67
|
return {
|
|
65
68
|
blocks: state.blocks,
|
|
66
69
|
editor: state.editor,
|
|
67
70
|
scroll: state.scroll,
|
|
68
71
|
unseen: state.unseen,
|
|
69
72
|
pal: session.palette,
|
|
70
|
-
footer: footerInfo(session, workspace),
|
|
71
|
-
status:
|
|
73
|
+
footer: state.turnFooter === undefined ? footerInfo(session, workspace) : { ...state.turnFooter, workspace },
|
|
74
|
+
status: activity === undefined
|
|
72
75
|
? undefined
|
|
73
|
-
: activityStatus(
|
|
74
|
-
|
|
76
|
+
: activityStatus(activity, now),
|
|
77
|
+
turnActive: state.activity !== undefined,
|
|
78
|
+
steering: state.command === undefined ? state.steering : undefined,
|
|
75
79
|
feedback: state.feedback,
|
|
76
80
|
readiness: turnBlocker(session),
|
|
77
81
|
now,
|
|
78
82
|
reducedMotion: session.config.reducedMotion,
|
|
79
|
-
modal: overlay.shown(state.open),
|
|
83
|
+
modal: overlay.shown(state.approval ?? state.open),
|
|
80
84
|
menu: completionOptions(state.completing),
|
|
81
85
|
menuIndex: state.completing?.index,
|
|
82
86
|
};
|
|
@@ -143,8 +147,8 @@ export async function runApp(session, transcriptRoot, environment = {}) {
|
|
|
143
147
|
if (state.follow)
|
|
144
148
|
state.unseen = 0;
|
|
145
149
|
};
|
|
146
|
-
const replaceTranscript = () => {
|
|
147
|
-
state.blocks.splice(0, state.blocks.length, ...
|
|
150
|
+
const replaceTranscript = (blocks = session.conversation.transcript) => {
|
|
151
|
+
state.blocks.splice(0, state.blocks.length, ...blocks);
|
|
148
152
|
state.scroll = 0;
|
|
149
153
|
state.follow = true;
|
|
150
154
|
state.unseen = 0;
|
|
@@ -158,6 +162,7 @@ export async function runApp(session, transcriptRoot, environment = {}) {
|
|
|
158
162
|
return;
|
|
159
163
|
live = false;
|
|
160
164
|
safely(stopInput);
|
|
165
|
+
safely(stopOutput);
|
|
161
166
|
safely(stopResize);
|
|
162
167
|
safely(stopShutdown);
|
|
163
168
|
if (activityTimer !== undefined)
|
|
@@ -183,35 +188,42 @@ export async function runApp(session, transcriptRoot, environment = {}) {
|
|
|
183
188
|
function fail(error) {
|
|
184
189
|
failure ??= { error };
|
|
185
190
|
state.open = overlay.cancel(state.open);
|
|
191
|
+
state.approval = overlay.cancel(state.approval);
|
|
186
192
|
state.activity?.control.abort(error);
|
|
193
|
+
state.command?.control.abort(error);
|
|
187
194
|
quit();
|
|
188
195
|
}
|
|
189
196
|
function track(work) {
|
|
190
197
|
const tracked = work
|
|
191
198
|
.catch((error) => fail(error))
|
|
192
199
|
.finally(() => {
|
|
193
|
-
|
|
194
|
-
activeWorkflow = undefined;
|
|
200
|
+
activeWorkflows.delete(tracked);
|
|
195
201
|
});
|
|
196
|
-
|
|
202
|
+
activeWorkflows.add(tracked);
|
|
197
203
|
return tracked;
|
|
198
204
|
}
|
|
199
205
|
function requestQuit(reason = new Error("interrupted")) {
|
|
200
206
|
state.open = overlay.cancel(state.open);
|
|
201
|
-
|
|
202
|
-
if (activity === undefined) {
|
|
207
|
+
state.approval = overlay.cancel(state.approval);
|
|
208
|
+
if (state.activity === undefined && state.command === undefined) {
|
|
203
209
|
quit();
|
|
204
210
|
return;
|
|
205
211
|
}
|
|
206
212
|
state.closeWhenIdle = true;
|
|
207
|
-
activity
|
|
213
|
+
state.activity?.control.abort(reason);
|
|
214
|
+
state.command?.control.abort(reason);
|
|
208
215
|
}
|
|
209
216
|
function startActivity(kind, label) {
|
|
210
|
-
if (state.
|
|
217
|
+
if (state.closeWhenIdle)
|
|
218
|
+
return undefined;
|
|
219
|
+
if (state.command !== undefined || (kind === "turn" && state.activity !== undefined))
|
|
211
220
|
return undefined;
|
|
212
221
|
const activity = begin(kind, label);
|
|
213
|
-
|
|
214
|
-
|
|
222
|
+
if (kind === "turn")
|
|
223
|
+
state.activity = activity;
|
|
224
|
+
else
|
|
225
|
+
state.command = activity;
|
|
226
|
+
activityTimer ??= setInterval(() => guard(() => {
|
|
215
227
|
let activeTool;
|
|
216
228
|
for (let index = state.blocks.length - 1; index >= 0; index--) {
|
|
217
229
|
const block = state.blocks[index];
|
|
@@ -226,14 +238,23 @@ export async function runApp(session, transcriptRoot, environment = {}) {
|
|
|
226
238
|
return activity;
|
|
227
239
|
}
|
|
228
240
|
function finishActivity(activity) {
|
|
229
|
-
if (state.activity
|
|
241
|
+
if (state.activity === activity) {
|
|
242
|
+
state.activity = undefined;
|
|
243
|
+
state.turnFooter = undefined;
|
|
244
|
+
state.approval = overlay.cancel(state.approval);
|
|
245
|
+
}
|
|
246
|
+
else if (state.command === activity) {
|
|
247
|
+
state.command = undefined;
|
|
248
|
+
state.open = overlay.cancel(state.open);
|
|
249
|
+
}
|
|
250
|
+
else
|
|
230
251
|
return;
|
|
231
|
-
|
|
252
|
+
const idle = state.activity === undefined && state.command === undefined;
|
|
253
|
+
if (idle && activityTimer !== undefined) {
|
|
232
254
|
clearInterval(activityTimer);
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
state.
|
|
236
|
-
if (state.closeWhenIdle)
|
|
255
|
+
activityTimer = undefined;
|
|
256
|
+
}
|
|
257
|
+
if (state.closeWhenIdle && idle)
|
|
237
258
|
quit();
|
|
238
259
|
else
|
|
239
260
|
render();
|
|
@@ -273,9 +294,6 @@ export async function runApp(session, transcriptRoot, environment = {}) {
|
|
|
273
294
|
invalidate: () => paint.invalidate(),
|
|
274
295
|
transcriptChanged: (block) => transcript.invalidate(block),
|
|
275
296
|
});
|
|
276
|
-
const resumeAtLaunch = session.resume === undefined
|
|
277
|
-
? undefined
|
|
278
|
-
: track(openResumedSession(session.resume));
|
|
279
297
|
async function openResumedSession(launch) {
|
|
280
298
|
while (live) {
|
|
281
299
|
const index = await new Promise((resolve) => {
|
|
@@ -314,6 +332,7 @@ export async function runApp(session, transcriptRoot, environment = {}) {
|
|
|
314
332
|
shutdownSignal?.addEventListener("abort", onShutdown, { once: true });
|
|
315
333
|
stopShutdown = () => shutdownSignal?.removeEventListener("abort", onShutdown);
|
|
316
334
|
terminal.enter(session.config.reducedMotion);
|
|
335
|
+
stopOutput = paint.onReady?.(() => guard(() => render())) ?? (() => { });
|
|
317
336
|
stopResize = terminal.onResize(() => guard(() => {
|
|
318
337
|
paint.invalidate();
|
|
319
338
|
render();
|
|
@@ -339,6 +358,9 @@ export async function runApp(session, transcriptRoot, environment = {}) {
|
|
|
339
358
|
}), ESCAPE_MS);
|
|
340
359
|
render();
|
|
341
360
|
}));
|
|
361
|
+
const resumeAtLaunch = session.resume === undefined
|
|
362
|
+
? undefined
|
|
363
|
+
: track(openResumedSession(session.resume));
|
|
342
364
|
draw();
|
|
343
365
|
if (resumeAtLaunch !== undefined)
|
|
344
366
|
await Promise.race([resumeAtLaunch, done]);
|
|
@@ -347,10 +369,15 @@ export async function runApp(session, transcriptRoot, environment = {}) {
|
|
|
347
369
|
if (failure !== undefined)
|
|
348
370
|
throw failure.error;
|
|
349
371
|
}
|
|
372
|
+
catch (error) {
|
|
373
|
+
// Startup failures must settle pending menus before teardown waits for work.
|
|
374
|
+
fail(error);
|
|
375
|
+
throw error;
|
|
376
|
+
}
|
|
350
377
|
finally {
|
|
351
378
|
try {
|
|
352
379
|
quit();
|
|
353
|
-
await
|
|
380
|
+
await Promise.allSettled([...activeWorkflows]);
|
|
354
381
|
}
|
|
355
382
|
finally {
|
|
356
383
|
await session.persistence?.close();
|
package/dist/tui/approve.js
CHANGED
|
@@ -16,10 +16,9 @@ export { scopeFor };
|
|
|
16
16
|
export function promptFor(call, target, pal) {
|
|
17
17
|
const scope = scopeFor(call);
|
|
18
18
|
const options = [
|
|
19
|
-
{ label: "Yes, once", hint: "y", key: "y"
|
|
20
|
-
{ label: `Yes, ${scopeNoun(scope)} for the session`, hint: "a", key: "a",
|
|
21
|
-
|
|
22
|
-
{ label: "No, and say why", hint: "n", key: "n", description: "Do not run this call. Return feedback to the model." },
|
|
19
|
+
{ label: "Yes, once", hint: "y", key: "y" },
|
|
20
|
+
{ label: `Yes, ${scopeNoun(scope)} for the session`, hint: "a", key: "a" },
|
|
21
|
+
{ label: "No, and say why", hint: "n", key: "n" },
|
|
23
22
|
];
|
|
24
23
|
return {
|
|
25
24
|
title: [{ text: question(call.name), fg: pal.ink.attention, bold: true }],
|
package/dist/tui/blocks.js
CHANGED
|
@@ -2,24 +2,22 @@
|
|
|
2
2
|
import { renderAnswer, renderReasoning, renderUser } from "./components/messages.js";
|
|
3
3
|
import { renderNotice } from "./components/misc.js";
|
|
4
4
|
import { renderTool } from "./components/tool.js";
|
|
5
|
-
import { insetTranscript, modelTranscriptWidth } from "./transcript-grammar.js";
|
|
6
5
|
export function render(block, width, pal, context = {}) {
|
|
7
|
-
const inner = modelTranscriptWidth(width);
|
|
8
6
|
switch (block.kind) {
|
|
9
7
|
case "user":
|
|
10
8
|
return renderUser(block, width, pal);
|
|
11
9
|
case "answer":
|
|
12
|
-
return
|
|
10
|
+
return renderAnswer(block, width, pal);
|
|
13
11
|
case "reasoning":
|
|
14
|
-
return
|
|
12
|
+
return renderReasoning(block, width, pal, {
|
|
15
13
|
continues: context.previous?.kind === "reasoning",
|
|
16
|
-
})
|
|
14
|
+
});
|
|
17
15
|
case "tool":
|
|
18
|
-
return
|
|
16
|
+
return renderTool(block, width, pal, {
|
|
19
17
|
continues: context.previous?.kind === "reasoning",
|
|
20
18
|
now: context.now,
|
|
21
19
|
reducedMotion: context.reducedMotion,
|
|
22
|
-
})
|
|
20
|
+
});
|
|
23
21
|
case "notice":
|
|
24
22
|
return renderNotice(block, width, pal);
|
|
25
23
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Foreground slash-command interactions and their session updates.
|
|
2
|
-
import { handleCommand } from "../commands.js";
|
|
2
|
+
import { commandNeedsIdle, handleCommand } from "../commands.js";
|
|
3
3
|
import { compactSession } from "../context/manual.js";
|
|
4
4
|
import { resetRequestIdentity } from "../request-identity.js";
|
|
5
5
|
import { updateSettings } from "../settings.js";
|
|
@@ -7,13 +7,16 @@ import { selectTimeline } from "../timeline.js";
|
|
|
7
7
|
import { saveTranscript } from "../transcript-export.js";
|
|
8
8
|
import { transition } from "./activity.js";
|
|
9
9
|
import { cancel as cancelOpen } from "./overlay.js";
|
|
10
|
-
export function commandWorkflow(options, resetCompaction) {
|
|
10
|
+
export function commandWorkflow(options, resetCompaction, resetInput) {
|
|
11
11
|
const { session, state, permissions, feedback } = options;
|
|
12
12
|
const choose = (picker) => new Promise((resolve) => {
|
|
13
|
+
state.command?.control.signal.throwIfAborted();
|
|
13
14
|
state.open = { picker, settle: resolve };
|
|
14
15
|
options.render();
|
|
15
16
|
});
|
|
16
17
|
async function command(text) {
|
|
18
|
+
if (state.activity !== undefined && commandNeedsIdle(text))
|
|
19
|
+
return;
|
|
17
20
|
const activity = options.startActivity("command", `Running ${text.split(/\s+/)[0]}`);
|
|
18
21
|
if (activity === undefined)
|
|
19
22
|
return;
|
|
@@ -23,6 +26,7 @@ export function commandWorkflow(options, resetCompaction) {
|
|
|
23
26
|
emit: options.commandNotice,
|
|
24
27
|
signal: activity.control.signal,
|
|
25
28
|
showHelp: () => new Promise((resolve) => {
|
|
29
|
+
activity.control.signal.throwIfAborted();
|
|
26
30
|
state.open = { help: true, settle: resolve };
|
|
27
31
|
options.render();
|
|
28
32
|
}),
|
|
@@ -32,6 +36,7 @@ export function commandWorkflow(options, resetCompaction) {
|
|
|
32
36
|
options.render();
|
|
33
37
|
},
|
|
34
38
|
type: (field) => new Promise((resolve) => {
|
|
39
|
+
activity.control.signal.throwIfAborted();
|
|
35
40
|
state.open = { field, settle: resolve };
|
|
36
41
|
options.render();
|
|
37
42
|
}),
|
|
@@ -43,17 +48,13 @@ export function commandWorkflow(options, resetCompaction) {
|
|
|
43
48
|
await session.persistence?.reset();
|
|
44
49
|
resetRequestIdentity(session);
|
|
45
50
|
resetCompaction();
|
|
46
|
-
state.blocks.splice(0);
|
|
47
51
|
state.past.length = 0;
|
|
48
52
|
permissions.reset();
|
|
49
|
-
|
|
50
|
-
state.follow = true;
|
|
51
|
-
state.unseen = 0;
|
|
52
|
-
state.lastMaxScroll = 0;
|
|
53
|
+
options.replaceTranscript([]);
|
|
53
54
|
state.committedNodeId = 0;
|
|
54
55
|
},
|
|
55
56
|
permissions,
|
|
56
|
-
exportTranscript: () => saveTranscript(options.transcriptRoot, state.blocks),
|
|
57
|
+
exportTranscript: () => saveTranscript(options.transcriptRoot, structuredClone(state.blocks)),
|
|
57
58
|
saveSettings: async (patch) => {
|
|
58
59
|
await updateSettings(patch);
|
|
59
60
|
},
|
|
@@ -62,6 +63,7 @@ export function commandWorkflow(options, resetCompaction) {
|
|
|
62
63
|
const selected = await selectTimeline(session, choose);
|
|
63
64
|
if (!selected)
|
|
64
65
|
return "unchanged";
|
|
66
|
+
resetCompaction();
|
|
65
67
|
options.replaceTranscript();
|
|
66
68
|
return session.conversation.activeNodeId === state.committedNodeId
|
|
67
69
|
? "unchanged"
|
|
@@ -99,6 +101,10 @@ export function commandWorkflow(options, resetCompaction) {
|
|
|
99
101
|
}
|
|
100
102
|
}
|
|
101
103
|
finally {
|
|
104
|
+
// Access can change while a turn is running. Discard even an observation
|
|
105
|
+
// from an in-flight response that used the previous connection.
|
|
106
|
+
if (text.trim().split(/\s+/)[0] === "/providers")
|
|
107
|
+
resetInput();
|
|
102
108
|
options.finishActivity(activity);
|
|
103
109
|
}
|
|
104
110
|
}
|
|
@@ -5,7 +5,7 @@ export function renderCommandMenu(commands, selected, width, pal) {
|
|
|
5
5
|
if (commands.length === 0)
|
|
6
6
|
return { rows: [], right: "" };
|
|
7
7
|
const menu = renderMenu(commands.map((command, index) => ({
|
|
8
|
-
label: `/${command.name}`,
|
|
8
|
+
label: `/${command.name}`, selected: selectedIndex === index,
|
|
9
9
|
})), width, pal, { maxRows: MAX_ROWS + 2, visible: MAX_ROWS });
|
|
10
10
|
return {
|
|
11
11
|
rows: menu.rows,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Ribbon rows and
|
|
1
|
+
// Ribbon rows and bounded overflow recovery shared by completion and selectors.
|
|
2
2
|
import { row } from "../../ui/render.js";
|
|
3
3
|
import { terminalText } from "../../ui/terminal-text.js";
|
|
4
4
|
import { elide, textWidth, wrapText } from "../../ui/width.js";
|
|
@@ -16,10 +16,10 @@ export function renderMenu(entries, width, pal, options) {
|
|
|
16
16
|
const room = Math.max(0, options.maxRows);
|
|
17
17
|
if (room === 0)
|
|
18
18
|
return { rows: [], first: 0, last: 0 };
|
|
19
|
-
const detailLimit = Math.min(2, room - 1);
|
|
19
|
+
const detailLimit = options.overflow === "truncate" ? 0 : Math.min(2, room - 1);
|
|
20
20
|
let detailRows = 0;
|
|
21
|
-
// Measure every possible selection
|
|
22
|
-
//
|
|
21
|
+
// Measure every possible selection so revealing clipped identity and values
|
|
22
|
+
// does not move the dock when the selected row changes.
|
|
23
23
|
for (const entry of entries) {
|
|
24
24
|
detailRows = Math.max(detailRows, detailLines(entry, width, detailLimit).length);
|
|
25
25
|
if (detailRows === detailLimit)
|
|
@@ -47,11 +47,8 @@ export function renderMenu(entries, width, pal, options) {
|
|
|
47
47
|
function detailLines(entry, width, maxRows) {
|
|
48
48
|
if (entry === undefined || maxRows <= 0)
|
|
49
49
|
return [];
|
|
50
|
-
// Complete identity and values take priority over explanatory copy.
|
|
51
50
|
const room = Math.max(1, width - 2);
|
|
52
|
-
|
|
53
|
-
lines.push(...menuText(entry.description ?? "", room, maxRows - lines.length));
|
|
54
|
-
return lines;
|
|
51
|
+
return menuText(clippedParts(entry, width).join(" · "), room, maxRows);
|
|
55
52
|
}
|
|
56
53
|
export function menuWindow(length, selected, visible) {
|
|
57
54
|
const count = Math.max(0, length);
|
|
@@ -68,11 +65,22 @@ export function menuText(text, width, maxRows) {
|
|
|
68
65
|
return lines.slice(0, maxRows).map((line, index) => index === maxRows - 1 && lines.length > maxRows ? elide(line + " …", width) : line);
|
|
69
66
|
}
|
|
70
67
|
function fullSummary(entry) {
|
|
71
|
-
|
|
72
|
-
return [entry.hint, value].filter(Boolean).join(" · ");
|
|
68
|
+
return [entry.hint, displayedValue(entry)].filter(Boolean).join(" · ");
|
|
73
69
|
}
|
|
74
70
|
function summary(entry, width) {
|
|
75
|
-
|
|
71
|
+
const room = Math.max(1, Math.floor(width * 0.42));
|
|
72
|
+
const value = terminalText(displayedValue(entry));
|
|
73
|
+
const hint = terminalText(entry.hint ?? "");
|
|
74
|
+
if (value === "" || hint === "")
|
|
75
|
+
return elide(value || hint, room);
|
|
76
|
+
// Keep the current policy visible even when its remembered-scope hint is long.
|
|
77
|
+
const hintRoom = room - textWidth(value) - 3;
|
|
78
|
+
return hintRoom <= 0 ? elide(value, room) : `${elide(hint, hintRoom)} · ${value}`;
|
|
79
|
+
}
|
|
80
|
+
function displayedValue(entry) {
|
|
81
|
+
if (entry.value === undefined)
|
|
82
|
+
return "";
|
|
83
|
+
return entry.selected && entry.adjustable ? `‹ ${entry.value} ›` : entry.value;
|
|
76
84
|
}
|
|
77
85
|
function clippedParts(entry, width) {
|
|
78
86
|
// Reserve the stepper's width for every peer, so moving selection cannot
|
|
@@ -19,7 +19,7 @@ export function renderStatus(info, pal) {
|
|
|
19
19
|
];
|
|
20
20
|
return withUnseen([
|
|
21
21
|
...active,
|
|
22
|
-
{ text: " · esc to interrupt", fg: pal.ink.dim, optional: true },
|
|
22
|
+
...(info.interruptible === false ? [] : [{ text: " · esc to interrupt", fg: pal.ink.dim, optional: true }]),
|
|
23
23
|
], info.unseen, pal);
|
|
24
24
|
}
|
|
25
25
|
if (info.feedback !== undefined) {
|
package/dist/tui/frame.js
CHANGED
|
@@ -5,11 +5,15 @@
|
|
|
5
5
|
// writing it: a full repaint at streaming speed flickers and floods the pipe.
|
|
6
6
|
// So the painter keeps the last frame and emits the difference.
|
|
7
7
|
import { CSI } from "../ui/render.js";
|
|
8
|
-
import { CURSOR, SYNC, write } from "./screen.js";
|
|
9
|
-
export function painter() {
|
|
8
|
+
import { CURSOR, SYNC, write, outputReady, onDrain } from "./screen.js";
|
|
9
|
+
export function painter(output = { write, ready: outputReady, onReady: onDrain }) {
|
|
10
10
|
let previous = [];
|
|
11
11
|
return {
|
|
12
12
|
paint(rows, cursor) {
|
|
13
|
+
// The last accepted frame remains the diff base. The host redraws current
|
|
14
|
+
// state on drain, so stale intermediate frames never form another queue.
|
|
15
|
+
if (!output.ready())
|
|
16
|
+
return;
|
|
13
17
|
let out = SYNC.begin + CURSOR.hide;
|
|
14
18
|
const height = Math.max(rows.length, previous.length);
|
|
15
19
|
for (let i = 0; i < height; i++) {
|
|
@@ -24,10 +28,11 @@ export function painter() {
|
|
|
24
28
|
previous = rows.slice();
|
|
25
29
|
// One write, so a terminal that ignores the synchronization hint still
|
|
26
30
|
// gets the frame as a single arrival rather than a row at a time.
|
|
27
|
-
write(out + SYNC.end);
|
|
31
|
+
output.write(out + SYNC.end);
|
|
28
32
|
},
|
|
29
33
|
invalidate() {
|
|
30
34
|
previous = [];
|
|
31
35
|
},
|
|
36
|
+
onReady: (handler) => output.onReady(handler),
|
|
32
37
|
};
|
|
33
38
|
}
|
package/dist/tui/help.js
CHANGED
|
@@ -3,6 +3,7 @@ import { row } from "../ui/render.js";
|
|
|
3
3
|
import { textWidth } from "../ui/width.js";
|
|
4
4
|
const CONTROL_WIDTH = 18;
|
|
5
5
|
const CONTROLS = [
|
|
6
|
+
{ key: "/", description: "open commands, also while working" },
|
|
6
7
|
{ key: "up / down", description: "move through menus or history" },
|
|
7
8
|
{ key: "left / right", description: "move cursor or change a value" },
|
|
8
9
|
{ key: "ctrl+left / right", description: "move cursor by word" },
|
|
@@ -10,7 +11,7 @@ const CONTROLS = [
|
|
|
10
11
|
{ key: "enter / tab", description: "select, send/steer · complete" },
|
|
11
12
|
{ key: "alt+enter", description: "insert a new line" },
|
|
12
13
|
{ key: "esc", description: "close UI or interrupt work" },
|
|
13
|
-
{ key: "ctrl+c", description: "interrupt work or exit" },
|
|
14
|
+
{ key: "ctrl+c", description: "cancel menu, interrupt work or exit" },
|
|
14
15
|
{ key: "ctrl+o", description: "expand/collapse reasoning or evidence" },
|
|
15
16
|
{ key: "wheel / pgup/dn", description: "scroll the transcript" },
|
|
16
17
|
{ key: "ctrl+l", description: "redraw the screen" },
|
|
@@ -12,7 +12,11 @@ export function layoutPicker(picker, found, width, pal, maxRows) {
|
|
|
12
12
|
contextRoom -= target.length;
|
|
13
13
|
const note = menuText(picker.description ?? picker.footer ?? "", width, Math.min(2, contextRoom));
|
|
14
14
|
const prefix = titleRows + target.length + note.length + queryRows;
|
|
15
|
-
const menu = renderMenu(found.map(({ option, index }) => ({ ...option, selected: index === picker.index })), width, pal, {
|
|
15
|
+
const menu = renderMenu(found.map(({ option, index }) => ({ ...option, selected: index === picker.index })), width, pal, {
|
|
16
|
+
maxRows: budget - prefix - controlRows,
|
|
17
|
+
visible: picker.visible ?? 6,
|
|
18
|
+
overflow: picker.overflow,
|
|
19
|
+
});
|
|
16
20
|
const progress = found.length > menu.last - menu.first || picker.searchable
|
|
17
21
|
? `${found.length === 0 ? "0" : `${menu.first + 1}–${menu.last}`} / ${found.length}` +
|
|
18
22
|
(found.length === picker.options.length ? "" : ` · ${picker.options.length} total`)
|
package/dist/tui/screen.js
CHANGED
|
@@ -50,6 +50,13 @@ export function size() {
|
|
|
50
50
|
export function write(text) {
|
|
51
51
|
process.stdout.write(text);
|
|
52
52
|
}
|
|
53
|
+
export function outputReady() {
|
|
54
|
+
return !process.stdout.writableNeedDrain;
|
|
55
|
+
}
|
|
56
|
+
export function onDrain(handler) {
|
|
57
|
+
process.stdout.on("drain", handler);
|
|
58
|
+
return () => { process.stdout.off("drain", handler); };
|
|
59
|
+
}
|
|
53
60
|
export function enter(reducedMotion = false) {
|
|
54
61
|
if (active)
|
|
55
62
|
return;
|
package/dist/tui/session-view.js
CHANGED
|
@@ -14,7 +14,6 @@ export function controllerOptions(session, contextPolicy, tools = session.tools,
|
|
|
14
14
|
contextPolicy,
|
|
15
15
|
effort: session.config.effort,
|
|
16
16
|
requestIdentity: requestIdentityForSession(session),
|
|
17
|
-
maxModelRequests: session.config.maxModelRequests,
|
|
18
17
|
toolContext: { root: session.config.root },
|
|
19
18
|
...(steering === undefined ? {} : { steering }),
|
|
20
19
|
};
|
|
@@ -1,12 +1,5 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Content padding for user surfaces, notices, and tool evidence markers.
|
|
2
2
|
const GUTTER = 2;
|
|
3
|
-
const MODEL_MEASURE = 86;
|
|
4
|
-
export function modelTranscriptWidth(width) {
|
|
5
|
-
return Math.max(8, Math.min(width - GUTTER * 2, MODEL_MEASURE));
|
|
6
|
-
}
|
|
7
|
-
export function insetTranscript(rows) {
|
|
8
|
-
return rows.map((line) => line === "" ? "" : `${" ".repeat(GUTTER)}${line}`);
|
|
9
|
-
}
|
|
10
3
|
export function transcriptGutter(_width) {
|
|
11
4
|
return GUTTER;
|
|
12
5
|
}
|
|
@@ -17,6 +17,10 @@ export function transcriptRenderer(draw = render, clock = Date.now) {
|
|
|
17
17
|
const activeTools = new Set();
|
|
18
18
|
return {
|
|
19
19
|
viewport(blocks, width, height, scroll, palette, state = {}) {
|
|
20
|
+
if (blocks.length === 0 && layouts.some((layout) => layout.entries.length > 0)) {
|
|
21
|
+
layouts = [];
|
|
22
|
+
activeTools.clear();
|
|
23
|
+
}
|
|
20
24
|
const layout = layoutFor(blocks, width, palette);
|
|
21
25
|
state = { ...state, now: state.now ?? clock() };
|
|
22
26
|
if (scroll === 0)
|