@giovannijecha/jecode 0.8.5 → 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 +36 -9
- package/assets/tokenizers/LICENSE +21 -0
- package/assets/tokenizers/o200k-base.tiktoken.gz +0 -0
- package/dist/cli-info.js +8 -14
- package/dist/commands.js +7 -3
- package/dist/config.js +47 -53
- 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/credential-commands.js +3 -3
- 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-account-command.js +14 -12
- package/dist/openai-account.js +7 -5
- package/dist/openai-oauth-callback.js +15 -54
- package/dist/openai-oauth-tokens.js +9 -7
- package/dist/openai-oauth.js +8 -6
- package/dist/permission-command.js +9 -24
- package/dist/permissions.js +10 -8
- package/dist/provider-commands.js +1 -33
- package/dist/provider-errors.js +1 -10
- package/dist/provider-label.js +3 -10
- package/dist/providers/anthropic-wire.js +1 -1
- package/dist/providers/anthropic.js +3 -2
- package/dist/providers/index.js +1 -5
- package/dist/providers/input-measurement.js +85 -0
- package/dist/providers/ollama-context.js +42 -0
- package/dist/providers/ollama-endpoint.js +7 -34
- package/dist/providers/ollama-wire.js +1 -1
- package/dist/providers/ollama.js +15 -147
- package/dist/providers/openai-codex.js +7 -4
- 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 -1
- package/dist/sessions/bucket.js +55 -0
- package/dist/sessions/catalog-io.js +162 -0
- package/dist/sessions/catalog.js +3 -1
- package/dist/sessions/codec-messages.js +122 -0
- package/dist/sessions/codec-transcript.js +93 -0
- package/dist/sessions/codec-values.js +52 -0
- package/dist/sessions/codec.js +4 -257
- package/dist/sessions/files.js +158 -0
- package/dist/sessions/load.js +90 -0
- package/dist/sessions/snapshot.js +33 -0
- package/dist/sessions/store.js +42 -461
- package/dist/settings.js +17 -16
- package/dist/start.js +36 -53
- package/dist/timeline.js +2 -0
- package/dist/tools/file-read.js +192 -0
- package/dist/tools/file-summary.js +9 -0
- package/dist/tools/{fs.js → file-write.js} +5 -193
- package/dist/tools/glob.js +107 -0
- package/dist/tools/index.js +2 -1
- package/dist/tools/search.js +1 -105
- package/dist/tui/activity.js +1 -1
- package/dist/tui/app-input.js +34 -17
- package/dist/tui/app-workflows.js +13 -361
- package/dist/tui/app.js +55 -28
- package/dist/tui/approve.js +3 -2
- package/dist/tui/blocks.js +1 -2
- package/dist/tui/command-workflow.js +112 -0
- package/dist/tui/components/command-menu.js +7 -10
- package/dist/tui/components/menu.js +82 -43
- package/dist/tui/components/messages.js +16 -9
- package/dist/tui/components/status.js +1 -1
- package/dist/tui/components/tool-evidence.js +107 -0
- package/dist/tui/components/tool-motion.js +32 -0
- package/dist/tui/components/tool.js +48 -202
- package/dist/tui/frame.js +8 -3
- package/dist/tui/help.js +3 -2
- package/dist/tui/picker-layout.js +44 -0
- package/dist/tui/picker.js +7 -71
- package/dist/tui/screen.js +7 -0
- package/dist/tui/session-view.js +0 -1
- package/dist/tui/tool-details.js +135 -0
- package/dist/tui/transcript-grammar.js +1 -1
- package/dist/tui/transcript-view.js +30 -112
- package/dist/tui/turn-workflow.js +235 -0
- package/dist/tui/turn.js +7 -140
- package/dist/tui/view.js +4 -4
- package/dist/tui/workflow-types.js +2 -0
- package/dist/ui/render.js +0 -4
- package/package.json +16 -12
- package/dist/batch-view.js +0 -17
- package/dist/batch.js +0 -267
- package/dist/ollama-settings-command.js +0 -74
- package/dist/tui/motion.js +0 -32
|
@@ -1,364 +1,16 @@
|
|
|
1
|
-
//
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { compactContext } from "../context/compactor.js";
|
|
7
|
-
import { compactSession } from "../context/manual.js";
|
|
8
|
-
import { isContextOverflow } from "../context/policy.js";
|
|
9
|
-
import { updateSettings } from "../settings.js";
|
|
10
|
-
import { steeringInbox } from "../steering.js";
|
|
11
|
-
import { saveTranscript } from "../transcript-export.js";
|
|
12
|
-
import { recordAuxiliaryUsage, recordRequestInput, recordUsage } from "../usage.js";
|
|
13
|
-
import { selectTimeline } from "../timeline.js";
|
|
14
|
-
import { toolSpecs } from "../tools/index.js";
|
|
15
|
-
import { resetRequestIdentity, requestIdentityForSession } from "../request-identity.js";
|
|
16
|
-
import { transition } from "./activity.js";
|
|
17
|
-
import { answerAt } from "./approve.js";
|
|
18
|
-
import * as edit from "./editor.js";
|
|
19
|
-
import { cancel as cancelOpen } from "./overlay.js";
|
|
20
|
-
import { controllerOptions, turnFailure } from "./session-view.js";
|
|
21
|
-
import { transcribe } from "./turn.js";
|
|
22
|
-
const WAITING = "Waiting";
|
|
1
|
+
// Wire foreground workflows to the shell and their shared compaction lifetime.
|
|
2
|
+
import { automaticCompactionGate } from "../context/automatic.js";
|
|
3
|
+
import { inputLifetime } from "../context/lifetime.js";
|
|
4
|
+
import { commandWorkflow } from "./command-workflow.js";
|
|
5
|
+
import { turnWorkflow } from "./turn-workflow.js";
|
|
23
6
|
export function appWorkflows(options) {
|
|
24
|
-
const { session, state, permissions, feedback } = options;
|
|
25
|
-
let activeSteering;
|
|
26
7
|
const automaticCompaction = automaticCompactionGate();
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
options
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const status = (label) => transition(activity, label);
|
|
36
|
-
try {
|
|
37
|
-
const outcome = await handleCommand(text, session, {
|
|
38
|
-
emit: options.commandNotice,
|
|
39
|
-
signal: activity.control.signal,
|
|
40
|
-
showHelp: () => new Promise((resolve) => {
|
|
41
|
-
state.open = { help: true, settle: resolve };
|
|
42
|
-
options.render();
|
|
43
|
-
}),
|
|
44
|
-
choose,
|
|
45
|
-
dismiss: () => {
|
|
46
|
-
state.open = state.open === undefined ? undefined : cancelOpen(state.open);
|
|
47
|
-
options.render();
|
|
48
|
-
},
|
|
49
|
-
type: (field) => new Promise((resolve) => {
|
|
50
|
-
state.open = { field, settle: resolve };
|
|
51
|
-
options.render();
|
|
52
|
-
}),
|
|
53
|
-
status: (said) => {
|
|
54
|
-
status(said ?? activity.label);
|
|
55
|
-
options.render();
|
|
56
|
-
},
|
|
57
|
-
reset: async () => {
|
|
58
|
-
await session.persistence?.reset();
|
|
59
|
-
resetRequestIdentity(session);
|
|
60
|
-
automaticCompaction.reset();
|
|
61
|
-
state.blocks.splice(0);
|
|
62
|
-
state.past.length = 0;
|
|
63
|
-
permissions.reset();
|
|
64
|
-
state.scroll = 0;
|
|
65
|
-
state.follow = true;
|
|
66
|
-
state.unseen = 0;
|
|
67
|
-
state.lastMaxScroll = 0;
|
|
68
|
-
state.committedNodeId = 0;
|
|
69
|
-
},
|
|
70
|
-
permissions,
|
|
71
|
-
exportTranscript: () => saveTranscript(options.transcriptRoot, state.blocks),
|
|
72
|
-
saveSettings: async (patch) => {
|
|
73
|
-
await updateSettings(patch);
|
|
74
|
-
},
|
|
75
|
-
refreshSettings: options.refreshSettings,
|
|
76
|
-
timeline: async () => {
|
|
77
|
-
const selected = await selectTimeline(session, choose);
|
|
78
|
-
if (!selected)
|
|
79
|
-
return "unchanged";
|
|
80
|
-
options.replaceTranscript();
|
|
81
|
-
return session.conversation.activeNodeId === state.committedNodeId
|
|
82
|
-
? "unchanged"
|
|
83
|
-
: "selected";
|
|
84
|
-
},
|
|
85
|
-
compact: async () => {
|
|
86
|
-
if (session.conversation.activeNodeId !== state.committedNodeId) {
|
|
87
|
-
return "branch-pending";
|
|
88
|
-
}
|
|
89
|
-
const result = await compactSession(session, {
|
|
90
|
-
signal: activity.control.signal,
|
|
91
|
-
onStatus: (said) => {
|
|
92
|
-
status(said ?? activity.label);
|
|
93
|
-
options.render();
|
|
94
|
-
},
|
|
95
|
-
});
|
|
96
|
-
if (result === "compacted")
|
|
97
|
-
automaticCompaction.reset();
|
|
98
|
-
return result;
|
|
99
|
-
},
|
|
100
|
-
});
|
|
101
|
-
if (outcome === "exit")
|
|
102
|
-
state.closeWhenIdle = true;
|
|
103
|
-
}
|
|
104
|
-
catch (error) {
|
|
105
|
-
// Command cancellation is already visible through the dock closing and
|
|
106
|
-
// the activity ending. Keep it silent instead of replacing the footer
|
|
107
|
-
// with a redundant warning.
|
|
108
|
-
if (!activity.control.signal.aborted) {
|
|
109
|
-
feedback.show({
|
|
110
|
-
text: error.message,
|
|
111
|
-
tone: "error",
|
|
112
|
-
timeoutMs: 6_000,
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
finally {
|
|
117
|
-
options.finishActivity(activity);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
async function turn(text) {
|
|
121
|
-
const activity = options.startActivity("turn", WAITING);
|
|
122
|
-
if (activity === undefined)
|
|
123
|
-
return;
|
|
124
|
-
const inbox = steeringInbox((pending, accepting) => {
|
|
125
|
-
if (activeSteering !== inbox)
|
|
126
|
-
return;
|
|
127
|
-
state.steering = accepting ? pending : undefined;
|
|
128
|
-
options.render();
|
|
129
|
-
});
|
|
130
|
-
activeSteering = inbox;
|
|
131
|
-
state.steering = 0;
|
|
132
|
-
const status = (label) => transition(activity, label);
|
|
133
|
-
const parentId = session.conversation.activeNodeId;
|
|
134
|
-
const history = session.conversation.history;
|
|
135
|
-
const modelHistory = session.conversation.contextHistory;
|
|
136
|
-
const historyStart = history.length;
|
|
137
|
-
const blockStart = state.blocks.length;
|
|
138
|
-
const createdAt = new Date().toISOString();
|
|
139
|
-
const prospectiveNodeId = session.conversation.nodes.length + 1;
|
|
140
|
-
let nodeId;
|
|
141
|
-
let context;
|
|
142
|
-
const unpersistedSteering = [];
|
|
143
|
-
const turnTools = permissions.availableTools();
|
|
144
|
-
const specs = toolSpecs(turnTools);
|
|
145
|
-
let firstPolicy = true;
|
|
146
|
-
const policy = () => {
|
|
147
|
-
let visible = firstPolicy;
|
|
148
|
-
firstPolicy = false;
|
|
149
|
-
if (visible) {
|
|
150
|
-
status("Checking context");
|
|
151
|
-
options.render();
|
|
152
|
-
}
|
|
153
|
-
return resolveContextPolicy({
|
|
154
|
-
provider: session.provider,
|
|
155
|
-
model: session.model,
|
|
156
|
-
compactionPercent: session.config.compactionPercent,
|
|
157
|
-
signal: activity.control.signal,
|
|
158
|
-
onStatus: (said) => {
|
|
159
|
-
visible = true;
|
|
160
|
-
status(said);
|
|
161
|
-
options.render();
|
|
162
|
-
},
|
|
163
|
-
}).finally(() => {
|
|
164
|
-
if (visible) {
|
|
165
|
-
status(WAITING);
|
|
166
|
-
options.render();
|
|
167
|
-
}
|
|
168
|
-
});
|
|
169
|
-
};
|
|
170
|
-
options.emit({ kind: "user", text });
|
|
171
|
-
const user = { role: "user", content: [{ kind: "text", text }] };
|
|
172
|
-
history.push(user);
|
|
173
|
-
modelHistory.push(structuredClone(user));
|
|
174
|
-
const events = transcribe({
|
|
175
|
-
emit: options.emit,
|
|
176
|
-
render: options.render,
|
|
177
|
-
palette: session.palette,
|
|
178
|
-
approved: (call) => permissions.approved(call),
|
|
179
|
-
remember: (call) => permissions.remember(call),
|
|
180
|
-
ask: (prompt, settle) => {
|
|
181
|
-
state.open = { picker: prompt, settle: (index) => settle(answerAt(index)) };
|
|
182
|
-
options.render();
|
|
183
|
-
},
|
|
184
|
-
status: (text) => {
|
|
185
|
-
status(text);
|
|
186
|
-
},
|
|
187
|
-
usage: (usage) => recordUsage(session.usage, usage),
|
|
188
|
-
requestInput: (inputTokens) => recordRequestInput(session.usage, inputTokens),
|
|
189
|
-
});
|
|
190
|
-
const persist = async (checkpoint, settlement, failure) => {
|
|
191
|
-
const next = session.conversation.commit({
|
|
192
|
-
...(nodeId === undefined ? {} : { nodeId }),
|
|
193
|
-
parentId,
|
|
194
|
-
createdAt,
|
|
195
|
-
identity: {
|
|
196
|
-
providerId: session.provider.id,
|
|
197
|
-
model: session.model,
|
|
198
|
-
effort: session.config.effort,
|
|
199
|
-
},
|
|
200
|
-
messages: checkpoint.slice(historyStart),
|
|
201
|
-
blocks: state.blocks.slice(blockStart),
|
|
202
|
-
...(context === undefined ? {} : { context }),
|
|
203
|
-
...(failure === undefined ? {} : { failure }),
|
|
204
|
-
}, settlement);
|
|
205
|
-
await session.persistence?.checkpoint(next);
|
|
206
|
-
session.conversation = next;
|
|
207
|
-
nodeId = next.activeNodeId;
|
|
208
|
-
state.committedNodeId = next.activeNodeId;
|
|
209
|
-
unpersistedSteering.length = 0;
|
|
210
|
-
};
|
|
211
|
-
const compact = async (checkpoint, projected, request) => {
|
|
212
|
-
if (request.reason === "overflow" &&
|
|
213
|
-
(request.error === undefined || !isContextOverflow(request.error))) {
|
|
214
|
-
return undefined;
|
|
215
|
-
}
|
|
216
|
-
const force = request.reason === "overflow" || request.projectionSaturated;
|
|
217
|
-
const key = automaticCompactionKey(session.provider.id, session.model, nodeId ?? prospectiveNodeId, checkpoint.length);
|
|
218
|
-
const attempt = { key, reason: request.reason };
|
|
219
|
-
if (!automaticCompaction.allows(attempt))
|
|
220
|
-
return undefined;
|
|
221
|
-
let attempted = false;
|
|
222
|
-
const result = await compactContext({
|
|
223
|
-
provider: session.provider,
|
|
224
|
-
model: session.model,
|
|
225
|
-
effort: session.config.effort,
|
|
226
|
-
context: projected,
|
|
227
|
-
turn: checkpoint.slice(historyStart),
|
|
228
|
-
nodeId: nodeId ?? prospectiveNodeId,
|
|
229
|
-
coveredMessages: context?.messageCount ?? 0,
|
|
230
|
-
lastInputTokens: Math.max(session.usage.lastInputTokens, request.inputTokens),
|
|
231
|
-
estimatedInputTokens: request.inputTokens,
|
|
232
|
-
signal: activity.control.signal,
|
|
233
|
-
force,
|
|
234
|
-
policy: request.policy,
|
|
235
|
-
requestEnvelope: {
|
|
236
|
-
system: session.system,
|
|
237
|
-
tools: specs,
|
|
238
|
-
maxOutputTokens: session.config.maxTokens,
|
|
239
|
-
},
|
|
240
|
-
requestIdentity: requestIdentityForSession(session),
|
|
241
|
-
onBegin: () => {
|
|
242
|
-
attempted = true;
|
|
243
|
-
status("Compacting");
|
|
244
|
-
options.render();
|
|
245
|
-
},
|
|
246
|
-
onEnd: () => {
|
|
247
|
-
status(WAITING);
|
|
248
|
-
options.render();
|
|
249
|
-
},
|
|
250
|
-
});
|
|
251
|
-
if (result === undefined) {
|
|
252
|
-
if (attempted && !activity.control.signal.aborted)
|
|
253
|
-
automaticCompaction.failed(attempt);
|
|
254
|
-
return undefined;
|
|
255
|
-
}
|
|
256
|
-
automaticCompaction.succeeded(attempt);
|
|
257
|
-
context = result.anchor;
|
|
258
|
-
if (result.usage !== undefined)
|
|
259
|
-
recordAuxiliaryUsage(session.usage, result.usage);
|
|
260
|
-
return result.messages;
|
|
261
|
-
};
|
|
262
|
-
events.onContext = compact;
|
|
263
|
-
events.onSteering = (guidance) => {
|
|
264
|
-
unpersistedSteering.push(guidance);
|
|
265
|
-
options.emit({ kind: "user", text: guidance });
|
|
266
|
-
options.render();
|
|
267
|
-
};
|
|
268
|
-
events.onCheckpoint = async (checkpoint, settlement, projected) => {
|
|
269
|
-
await persist(checkpoint, settlement);
|
|
270
|
-
const compacted = await compact(checkpoint, projected, {
|
|
271
|
-
reason: "budget",
|
|
272
|
-
policy: await policy(),
|
|
273
|
-
inputTokens: session.usage.lastInputTokens,
|
|
274
|
-
projectionSaturated: false,
|
|
275
|
-
});
|
|
276
|
-
if (compacted !== undefined)
|
|
277
|
-
await persist(checkpoint, settlement);
|
|
278
|
-
return compacted;
|
|
279
|
-
};
|
|
280
|
-
let finishReason;
|
|
281
|
-
let failed;
|
|
282
|
-
try {
|
|
283
|
-
await runTurn(history, controllerOptions(session, policy, turnTools, inbox), events, activity.control.signal, modelHistory);
|
|
284
|
-
}
|
|
285
|
-
catch (error) {
|
|
286
|
-
const interrupted = activity.control.signal.aborted;
|
|
287
|
-
const completed = nodeId !== undefined && session.conversation.activeNodeId === nodeId &&
|
|
288
|
-
session.conversation.activeNode?.settlement === "completed";
|
|
289
|
-
if (completed) {
|
|
290
|
-
const notice = turnFailure(session, error, interrupted);
|
|
291
|
-
feedback.show({ text: notice.text, tone: notice.tone, timeoutMs: 6_000 });
|
|
292
|
-
}
|
|
293
|
-
else {
|
|
294
|
-
finishReason = interrupted ? "interrupted" : "failed";
|
|
295
|
-
failed = { error: error, interrupted };
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
finally {
|
|
299
|
-
let pendingSteering = inbox.close();
|
|
300
|
-
if (activeSteering === inbox)
|
|
301
|
-
activeSteering = undefined;
|
|
302
|
-
state.steering = undefined;
|
|
303
|
-
try {
|
|
304
|
-
events.finish(finishReason);
|
|
305
|
-
if (failed !== undefined) {
|
|
306
|
-
const notice = turnFailure(session, failed.error, failed.interrupted);
|
|
307
|
-
const settlement = failed.interrupted ? "interrupted" : "failed";
|
|
308
|
-
const failure = {
|
|
309
|
-
text: notice.text,
|
|
310
|
-
tone: failed.interrupted ? "warn" : "error",
|
|
311
|
-
};
|
|
312
|
-
options.emit(notice);
|
|
313
|
-
try {
|
|
314
|
-
await persist(closeFailedTurn(history, settlement), settlement, failure);
|
|
315
|
-
}
|
|
316
|
-
catch (error) {
|
|
317
|
-
// A failed persistence boundary cannot remain visible as if it had
|
|
318
|
-
// been saved. Revert to the last durable path and return the input
|
|
319
|
-
// to the composer so the user can retry without losing it.
|
|
320
|
-
options.replaceTranscript();
|
|
321
|
-
state.editor = edit.of([
|
|
322
|
-
...(nodeId === undefined ? [text] : []),
|
|
323
|
-
...unpersistedSteering,
|
|
324
|
-
...pendingSteering,
|
|
325
|
-
state.editor.text,
|
|
326
|
-
].filter((part) => part !== "").join("\n\n"));
|
|
327
|
-
state.completing = undefined;
|
|
328
|
-
pendingSteering = [];
|
|
329
|
-
feedback.show({
|
|
330
|
-
text: error.message,
|
|
331
|
-
tone: "error",
|
|
332
|
-
timeoutMs: 6_000,
|
|
333
|
-
});
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
finally {
|
|
338
|
-
restorePendingSteering(state, pendingSteering);
|
|
339
|
-
options.finishActivity(activity);
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
function steer(text) {
|
|
344
|
-
return activeSteering?.offer(text) ?? "unavailable";
|
|
345
|
-
}
|
|
346
|
-
return { command, turn, steer };
|
|
347
|
-
}
|
|
348
|
-
function restorePendingSteering(state, messages) {
|
|
349
|
-
if (messages.length === 0)
|
|
350
|
-
return;
|
|
351
|
-
const pending = messages.join("\n\n");
|
|
352
|
-
state.editor = edit.of(state.editor.text === "" ? pending : `${pending}\n\n${state.editor.text}`);
|
|
353
|
-
state.completing = undefined;
|
|
354
|
-
}
|
|
355
|
-
function closeFailedTurn(history, settlement) {
|
|
356
|
-
const closed = [...history];
|
|
357
|
-
if (closed.at(-1)?.role === "assistant")
|
|
358
|
-
return closed;
|
|
359
|
-
const text = settlement === "interrupted"
|
|
360
|
-
? "The previous attempt was interrupted by the user before completion."
|
|
361
|
-
: "The previous attempt failed before completion.";
|
|
362
|
-
closed.push({ role: "assistant", content: [{ kind: "text", text }] });
|
|
363
|
-
return closed;
|
|
8
|
+
const inputs = inputLifetime();
|
|
9
|
+
return {
|
|
10
|
+
command: commandWorkflow(options, () => {
|
|
11
|
+
automaticCompaction.reset();
|
|
12
|
+
inputs.reset();
|
|
13
|
+
}, () => inputs.reset()),
|
|
14
|
+
...turnWorkflow(options, automaticCompaction, inputs),
|
|
15
|
+
};
|
|
364
16
|
}
|
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,13 +16,14 @@ 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: "
|
|
19
|
+
{ label: "Yes, once", hint: "y", key: "y" },
|
|
20
20
|
{ label: `Yes, ${scopeNoun(scope)} for the session`, hint: "a", key: "a" },
|
|
21
|
-
{ label: "No, and say why", hint: "
|
|
21
|
+
{ label: "No, and say why", hint: "n", key: "n" },
|
|
22
22
|
];
|
|
23
23
|
return {
|
|
24
24
|
title: [{ text: question(call.name), fg: pal.ink.attention, bold: true }],
|
|
25
25
|
right: `${call.name}${target === "" ? "" : ` · ${target}`}`,
|
|
26
|
+
controls: "↑↓ choose · enter confirm · esc deny",
|
|
26
27
|
options,
|
|
27
28
|
index: 0,
|
|
28
29
|
};
|
package/dist/tui/blocks.js
CHANGED
|
@@ -14,9 +14,8 @@ export function render(block, width, pal, context = {}) {
|
|
|
14
14
|
});
|
|
15
15
|
case "tool":
|
|
16
16
|
return renderTool(block, width, pal, {
|
|
17
|
-
continues: context.previous?.kind === "
|
|
17
|
+
continues: context.previous?.kind === "reasoning",
|
|
18
18
|
now: context.now,
|
|
19
|
-
motion: context.motion,
|
|
20
19
|
reducedMotion: context.reducedMotion,
|
|
21
20
|
});
|
|
22
21
|
case "notice":
|