@giovannijecha/jecode 0.7.0 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +244 -265
- package/dist/config.js +46 -21
- package/dist/context/manual.js +1 -0
- package/dist/conversation.js +41 -4
- package/dist/credential-safety.js +9 -5
- package/dist/providers/anthropic-wire.js +5 -4
- package/dist/providers/ollama-wire.js +3 -2
- package/dist/providers/openai-wire.js +6 -5
- package/dist/providers/wire-usage.js +6 -0
- package/dist/sessions/codec.js +54 -8
- package/dist/sessions/runtime.js +2 -2
- package/dist/sessions/store.js +54 -32
- package/dist/timeline.js +12 -6
- package/dist/tui/app-workflows.js +54 -5
- package/dist/tui/app.js +85 -38
- package/dist/tui/picker.js +2 -2
- package/dist/ui/terminal-text.js +4 -1
- package/package.json +1 -1
package/dist/timeline.js
CHANGED
|
@@ -7,7 +7,7 @@ import { usageFromHistory } from "./usage.js";
|
|
|
7
7
|
import { heading } from "./tui/picker.js";
|
|
8
8
|
export function timelinePicker(conversation, palette) {
|
|
9
9
|
const entries = timelineEntries(conversation);
|
|
10
|
-
const selectedId = conversation.
|
|
10
|
+
const selectedId = conversation.latestResumable()?.activeNodeId ?? 0;
|
|
11
11
|
const index = Math.max(0, entries.findIndex((entry) => entry.node.id === selectedId));
|
|
12
12
|
return Object.freeze({
|
|
13
13
|
picker: {
|
|
@@ -18,7 +18,7 @@ export function timelinePicker(conversation, palette) {
|
|
|
18
18
|
options: entries.map((entry) => ({
|
|
19
19
|
label: `${entry.prefix}${preview(entry.node)}`,
|
|
20
20
|
hint: stamp(entry.node.createdAt),
|
|
21
|
-
...(entry.node
|
|
21
|
+
...timelineValue(entry.node, selectedId),
|
|
22
22
|
})),
|
|
23
23
|
index,
|
|
24
24
|
},
|
|
@@ -38,12 +38,12 @@ export async function selectTimeline(session, choose) {
|
|
|
38
38
|
return true;
|
|
39
39
|
}
|
|
40
40
|
function timelineEntries(conversation) {
|
|
41
|
-
const
|
|
42
|
-
const
|
|
41
|
+
const resumable = conversation.nodes.filter((node) => node.settlement !== "checkpointed");
|
|
42
|
+
const resumableIds = new Set(resumable.map((node) => node.id));
|
|
43
43
|
const children = new Map();
|
|
44
|
-
for (const node of
|
|
44
|
+
for (const node of resumable) {
|
|
45
45
|
let parentId = node.parentId;
|
|
46
|
-
while (parentId !== 0 && !
|
|
46
|
+
while (parentId !== 0 && !resumableIds.has(parentId)) {
|
|
47
47
|
parentId = conversation.node(parentId)?.parentId ?? 0;
|
|
48
48
|
}
|
|
49
49
|
const siblings = children.get(parentId) ?? [];
|
|
@@ -70,6 +70,12 @@ function timelineEntries(conversation) {
|
|
|
70
70
|
visit(0, []);
|
|
71
71
|
return entries;
|
|
72
72
|
}
|
|
73
|
+
function timelineValue(node, selectedId) {
|
|
74
|
+
const state = node.settlement === "completed" ? undefined : node.settlement;
|
|
75
|
+
const active = node.id === selectedId ? "active" : undefined;
|
|
76
|
+
const value = [state, active].filter((part) => part !== undefined).join(" · ");
|
|
77
|
+
return value === "" ? {} : { value };
|
|
78
|
+
}
|
|
73
79
|
function preview(node) {
|
|
74
80
|
for (const message of node.messages) {
|
|
75
81
|
if (message.role !== "user")
|
|
@@ -10,6 +10,7 @@ import { saveTranscript } from "../transcript-export.js";
|
|
|
10
10
|
import { recordAuxiliaryUsage, recordUsage } from "../usage.js";
|
|
11
11
|
import { selectTimeline } from "../timeline.js";
|
|
12
12
|
import { answerAt } from "./approve.js";
|
|
13
|
+
import * as edit from "./editor.js";
|
|
13
14
|
import { cancel as cancelOpen } from "./overlay.js";
|
|
14
15
|
import { controllerOptions, turnFailure } from "./session-view.js";
|
|
15
16
|
import { transcribe } from "./turn.js";
|
|
@@ -136,7 +137,7 @@ export function appWorkflows(options) {
|
|
|
136
137
|
},
|
|
137
138
|
usage: (usage) => recordUsage(session.usage, usage),
|
|
138
139
|
});
|
|
139
|
-
const persist = async (checkpoint, settlement) => {
|
|
140
|
+
const persist = async (checkpoint, settlement, failure) => {
|
|
140
141
|
const next = session.conversation.commit({
|
|
141
142
|
...(nodeId === undefined ? {} : { nodeId }),
|
|
142
143
|
parentId,
|
|
@@ -149,6 +150,7 @@ export function appWorkflows(options) {
|
|
|
149
150
|
messages: checkpoint.slice(historyStart),
|
|
150
151
|
blocks: state.blocks.slice(blockStart),
|
|
151
152
|
...(context === undefined ? {} : { context }),
|
|
153
|
+
...(failure === undefined ? {} : { failure }),
|
|
152
154
|
}, settlement);
|
|
153
155
|
await session.persistence?.checkpoint(next);
|
|
154
156
|
session.conversation = next;
|
|
@@ -217,18 +219,65 @@ export function appWorkflows(options) {
|
|
|
217
219
|
return compacted;
|
|
218
220
|
};
|
|
219
221
|
let finishReason;
|
|
222
|
+
let failed;
|
|
220
223
|
try {
|
|
221
224
|
await runTurn(history, controllerOptions(session, permissions.availableTools()), events, activity.control.signal, modelHistory);
|
|
222
225
|
}
|
|
223
226
|
catch (error) {
|
|
224
227
|
const interrupted = activity.control.signal.aborted;
|
|
225
|
-
|
|
226
|
-
|
|
228
|
+
const completed = nodeId !== undefined && session.conversation.activeNodeId === nodeId &&
|
|
229
|
+
session.conversation.activeNode?.settlement === "completed";
|
|
230
|
+
if (completed) {
|
|
231
|
+
const notice = turnFailure(session, error, interrupted);
|
|
232
|
+
feedback.show({ text: notice.text, tone: notice.tone, timeoutMs: 6_000 });
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
finishReason = interrupted ? "interrupted" : "failed";
|
|
236
|
+
failed = { error: error, interrupted };
|
|
237
|
+
}
|
|
227
238
|
}
|
|
228
239
|
finally {
|
|
229
|
-
|
|
230
|
-
|
|
240
|
+
try {
|
|
241
|
+
events.finish(finishReason);
|
|
242
|
+
if (failed !== undefined) {
|
|
243
|
+
const notice = turnFailure(session, failed.error, failed.interrupted);
|
|
244
|
+
const settlement = failed.interrupted ? "interrupted" : "failed";
|
|
245
|
+
const failure = {
|
|
246
|
+
text: notice.text,
|
|
247
|
+
tone: failed.interrupted ? "warn" : "error",
|
|
248
|
+
};
|
|
249
|
+
options.emit(notice);
|
|
250
|
+
try {
|
|
251
|
+
await persist(closeFailedTurn(history, settlement), settlement, failure);
|
|
252
|
+
}
|
|
253
|
+
catch (error) {
|
|
254
|
+
// A failed persistence boundary cannot remain visible as if it had
|
|
255
|
+
// been saved. Revert to the last durable path and return the input
|
|
256
|
+
// to the composer so the user can retry without losing it.
|
|
257
|
+
options.replaceTranscript();
|
|
258
|
+
state.editor = edit.of(text);
|
|
259
|
+
feedback.show({
|
|
260
|
+
text: error.message,
|
|
261
|
+
tone: "error",
|
|
262
|
+
timeoutMs: 6_000,
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
finally {
|
|
268
|
+
options.finishActivity(activity);
|
|
269
|
+
}
|
|
231
270
|
}
|
|
232
271
|
}
|
|
233
272
|
return { command, turn };
|
|
234
273
|
}
|
|
274
|
+
function closeFailedTurn(history, settlement) {
|
|
275
|
+
const closed = [...history];
|
|
276
|
+
if (closed.at(-1)?.role === "assistant")
|
|
277
|
+
return closed;
|
|
278
|
+
const text = settlement === "interrupted"
|
|
279
|
+
? "The previous attempt was interrupted by the user before completion."
|
|
280
|
+
: "The previous attempt failed before completion.";
|
|
281
|
+
closed.push({ role: "assistant", content: [{ kind: "text", text }] });
|
|
282
|
+
return closed;
|
|
283
|
+
}
|
package/dist/tui/app.js
CHANGED
|
@@ -40,12 +40,22 @@ export async function runApp(session, transcriptRoot, environment = {}) {
|
|
|
40
40
|
let escapeTimer;
|
|
41
41
|
let stopResize = () => { };
|
|
42
42
|
let stopInput = () => { };
|
|
43
|
+
let failure;
|
|
44
|
+
let activeWorkflow;
|
|
43
45
|
// Timers outlive the teardown they were scheduled before. Painting after the
|
|
44
46
|
// terminal has been handed back would write escapes into the user's shell.
|
|
45
47
|
let live = true;
|
|
46
48
|
const done = new Promise((resolve) => {
|
|
47
49
|
closed = resolve;
|
|
48
50
|
});
|
|
51
|
+
const guard = (action) => {
|
|
52
|
+
try {
|
|
53
|
+
action();
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
fail(error);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
49
59
|
const view = () => {
|
|
50
60
|
const now = Date.now();
|
|
51
61
|
return {
|
|
@@ -94,8 +104,9 @@ export async function runApp(session, transcriptRoot, environment = {}) {
|
|
|
94
104
|
const render = (block) => {
|
|
95
105
|
if (block !== undefined)
|
|
96
106
|
transcript.invalidate(block);
|
|
97
|
-
if (live && frameTimer === undefined)
|
|
98
|
-
frameTimer = setTimeout(draw, FRAME_MS);
|
|
107
|
+
if (live && frameTimer === undefined) {
|
|
108
|
+
frameTimer = setTimeout(() => guard(draw), FRAME_MS);
|
|
109
|
+
}
|
|
99
110
|
};
|
|
100
111
|
const feedback = feedbackController((next) => {
|
|
101
112
|
state.feedback = next;
|
|
@@ -131,18 +142,42 @@ export async function runApp(session, transcriptRoot, environment = {}) {
|
|
|
131
142
|
if (!live)
|
|
132
143
|
return;
|
|
133
144
|
live = false;
|
|
134
|
-
stopInput
|
|
135
|
-
stopResize
|
|
145
|
+
safely(stopInput);
|
|
146
|
+
safely(stopResize);
|
|
136
147
|
if (spinTimer !== undefined)
|
|
137
148
|
clearInterval(spinTimer);
|
|
138
149
|
if (frameTimer !== undefined)
|
|
139
150
|
clearTimeout(frameTimer);
|
|
140
151
|
if (escapeTimer !== undefined)
|
|
141
152
|
clearTimeout(escapeTimer);
|
|
142
|
-
feedback.close();
|
|
143
|
-
terminal.leave();
|
|
153
|
+
safely(() => feedback.close());
|
|
154
|
+
safely(() => terminal.leave());
|
|
144
155
|
closed?.();
|
|
145
156
|
}
|
|
157
|
+
function safely(action) {
|
|
158
|
+
try {
|
|
159
|
+
action();
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
failure ??= { error };
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
function fail(error) {
|
|
166
|
+
failure ??= { error };
|
|
167
|
+
state.open = overlay.cancel(state.open);
|
|
168
|
+
state.activity?.control.abort(error);
|
|
169
|
+
quit();
|
|
170
|
+
}
|
|
171
|
+
function track(work) {
|
|
172
|
+
const tracked = work
|
|
173
|
+
.catch((error) => fail(error))
|
|
174
|
+
.finally(() => {
|
|
175
|
+
if (activeWorkflow === tracked)
|
|
176
|
+
activeWorkflow = undefined;
|
|
177
|
+
});
|
|
178
|
+
activeWorkflow = tracked;
|
|
179
|
+
return tracked;
|
|
180
|
+
}
|
|
146
181
|
function requestQuit() {
|
|
147
182
|
const activity = state.activity;
|
|
148
183
|
if (activity === undefined) {
|
|
@@ -159,7 +194,7 @@ export async function runApp(session, transcriptRoot, environment = {}) {
|
|
|
159
194
|
const activity = begin(kind, label);
|
|
160
195
|
state.activity = activity;
|
|
161
196
|
state.status = label;
|
|
162
|
-
spinTimer = setInterval(() => {
|
|
197
|
+
spinTimer = setInterval(() => guard(() => {
|
|
163
198
|
if (!session.config.reducedMotion)
|
|
164
199
|
state.spin++;
|
|
165
200
|
let activeTool;
|
|
@@ -171,7 +206,7 @@ export async function runApp(session, transcriptRoot, environment = {}) {
|
|
|
171
206
|
break;
|
|
172
207
|
}
|
|
173
208
|
render(activeTool);
|
|
174
|
-
}, session.config.reducedMotion ? 1_000 : SPIN_MS);
|
|
209
|
+
}), session.config.reducedMotion ? 1_000 : SPIN_MS);
|
|
175
210
|
render();
|
|
176
211
|
return activity;
|
|
177
212
|
}
|
|
@@ -212,7 +247,10 @@ export async function runApp(session, transcriptRoot, environment = {}) {
|
|
|
212
247
|
session,
|
|
213
248
|
state,
|
|
214
249
|
feedback,
|
|
215
|
-
actions
|
|
250
|
+
actions: {
|
|
251
|
+
command: (text) => track(actions.command(text)),
|
|
252
|
+
turn: (text) => track(actions.turn(text)),
|
|
253
|
+
},
|
|
216
254
|
live: () => live,
|
|
217
255
|
quit,
|
|
218
256
|
requestQuit,
|
|
@@ -222,7 +260,7 @@ export async function runApp(session, transcriptRoot, environment = {}) {
|
|
|
222
260
|
});
|
|
223
261
|
const resumeAtLaunch = session.resume === undefined
|
|
224
262
|
? undefined
|
|
225
|
-
: openResumedSession(session.resume);
|
|
263
|
+
: track(openResumedSession(session.resume));
|
|
226
264
|
async function openResumedSession(launch) {
|
|
227
265
|
while (live) {
|
|
228
266
|
const index = await new Promise((resolve) => {
|
|
@@ -253,39 +291,48 @@ export async function runApp(session, transcriptRoot, environment = {}) {
|
|
|
253
291
|
}
|
|
254
292
|
}
|
|
255
293
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
return;
|
|
271
|
-
escapeTimer = setTimeout(() => {
|
|
272
|
-
escapeTimer = undefined;
|
|
294
|
+
try {
|
|
295
|
+
terminal.enter(session.config.reducedMotion);
|
|
296
|
+
stopResize = terminal.onResize(() => guard(() => {
|
|
297
|
+
paint.invalidate();
|
|
298
|
+
draw();
|
|
299
|
+
}));
|
|
300
|
+
stopInput = terminal.onInput((chunk) => guard(() => {
|
|
301
|
+
if (escapeTimer !== undefined)
|
|
302
|
+
clearTimeout(escapeTimer);
|
|
303
|
+
for (const key of keys.push(chunk)) {
|
|
304
|
+
if (!live)
|
|
305
|
+
break;
|
|
306
|
+
input.handle(key);
|
|
307
|
+
}
|
|
273
308
|
if (!live)
|
|
274
309
|
return;
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
310
|
+
escapeTimer = setTimeout(() => guard(() => {
|
|
311
|
+
escapeTimer = undefined;
|
|
312
|
+
if (!live)
|
|
313
|
+
return;
|
|
314
|
+
for (const key of keys.flush())
|
|
315
|
+
input.handle(key);
|
|
316
|
+
if (live)
|
|
317
|
+
render();
|
|
318
|
+
}), ESCAPE_MS);
|
|
319
|
+
render();
|
|
320
|
+
}));
|
|
321
|
+
draw();
|
|
322
|
+
if (resumeAtLaunch !== undefined)
|
|
323
|
+
await Promise.race([resumeAtLaunch, done]);
|
|
285
324
|
if (live)
|
|
286
325
|
await done;
|
|
326
|
+
if (failure !== undefined)
|
|
327
|
+
throw failure.error;
|
|
287
328
|
}
|
|
288
329
|
finally {
|
|
289
|
-
|
|
330
|
+
try {
|
|
331
|
+
quit();
|
|
332
|
+
await activeWorkflow;
|
|
333
|
+
}
|
|
334
|
+
finally {
|
|
335
|
+
await session.persistence?.close();
|
|
336
|
+
}
|
|
290
337
|
}
|
|
291
338
|
}
|
package/dist/tui/picker.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// One interaction model for every terminal selector.
|
|
2
2
|
import { row } from "../ui/render.js";
|
|
3
|
-
import { elide } from "../ui/width.js";
|
|
3
|
+
import { elide, graphemes } from "../ui/width.js";
|
|
4
4
|
import { menuWindow, renderMenuRows } from "./components/menu.js";
|
|
5
5
|
import { promptCursor, promptLine } from "./components/prompt.js";
|
|
6
6
|
const WINDOW = 6;
|
|
@@ -33,7 +33,7 @@ export function type(picker, text) {
|
|
|
33
33
|
export function backspace(picker) {
|
|
34
34
|
if (picker.searchable !== true || (picker.query ?? "") === "")
|
|
35
35
|
return picker;
|
|
36
|
-
return withQuery(picker,
|
|
36
|
+
return withQuery(picker, graphemes(picker.query ?? "").slice(0, -1).join(""));
|
|
37
37
|
}
|
|
38
38
|
export function clear(picker) {
|
|
39
39
|
return withQuery(picker, "");
|
package/dist/ui/terminal-text.js
CHANGED
|
@@ -38,5 +38,8 @@ export function terminalText(text, options = {}) {
|
|
|
38
38
|
return safe;
|
|
39
39
|
}
|
|
40
40
|
function isBidiControl(code) {
|
|
41
|
-
return
|
|
41
|
+
return code === 0x061c ||
|
|
42
|
+
(code >= 0x200e && code <= 0x200f) ||
|
|
43
|
+
(code >= 0x202a && code <= 0x202e) ||
|
|
44
|
+
(code >= 0x2066 && code <= 0x2069);
|
|
42
45
|
}
|