@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
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// Bounded workspace glob matching without regular-expression backtracking.
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
const MAX_GLOB_CHARS = 512;
|
|
4
|
+
export function glob(pattern) {
|
|
5
|
+
const normalized = pattern.replace(/\\/g, "/");
|
|
6
|
+
if (normalized.length > MAX_GLOB_CHARS) {
|
|
7
|
+
throw new Error(`"pattern" must be at most ${MAX_GLOB_CHARS} characters`);
|
|
8
|
+
}
|
|
9
|
+
const tokens = tokenizeGlob(normalized.toLowerCase());
|
|
10
|
+
const basenameOnly = !normalized.includes("/");
|
|
11
|
+
return (relative) => {
|
|
12
|
+
const candidate = relative.replace(/\\/g, "/");
|
|
13
|
+
const target = (basenameOnly ? path.posix.basename(candidate) : candidate).toLowerCase();
|
|
14
|
+
return matchGlob(tokens, Array.from(target));
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function tokenizeGlob(pattern) {
|
|
18
|
+
const chars = Array.from(pattern);
|
|
19
|
+
const tokens = [];
|
|
20
|
+
let index = 0;
|
|
21
|
+
while (index < chars.length) {
|
|
22
|
+
const char = chars[index];
|
|
23
|
+
if (char === "*") {
|
|
24
|
+
let end = index + 1;
|
|
25
|
+
while (chars[end] === "*")
|
|
26
|
+
end++;
|
|
27
|
+
if (end - index >= 2) {
|
|
28
|
+
if (chars[end] === "/") {
|
|
29
|
+
tokens.push({ kind: "globdir-start" }, { kind: "globdir-body" });
|
|
30
|
+
index = end + 1;
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
tokens.push({ kind: "globstar" });
|
|
34
|
+
index = end;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
tokens.push({ kind: "star" });
|
|
39
|
+
index = end;
|
|
40
|
+
}
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
tokens.push(char === "?" ? { kind: "one" } : { kind: "literal", value: char });
|
|
44
|
+
index++;
|
|
45
|
+
}
|
|
46
|
+
return tokens;
|
|
47
|
+
}
|
|
48
|
+
/** Thompson-style wildcard matching: O(pattern × path), with no regex backtracking. */
|
|
49
|
+
function matchGlob(tokens, text) {
|
|
50
|
+
let states = epsilonClosure(new Set([0]), tokens);
|
|
51
|
+
for (const char of text) {
|
|
52
|
+
const next = new Set();
|
|
53
|
+
for (const state of states) {
|
|
54
|
+
const token = tokens[state];
|
|
55
|
+
if (token === undefined)
|
|
56
|
+
continue;
|
|
57
|
+
switch (token.kind) {
|
|
58
|
+
case "literal":
|
|
59
|
+
if (token.value === char)
|
|
60
|
+
next.add(state + 1);
|
|
61
|
+
break;
|
|
62
|
+
case "one":
|
|
63
|
+
if (char !== "/")
|
|
64
|
+
next.add(state + 1);
|
|
65
|
+
break;
|
|
66
|
+
case "star":
|
|
67
|
+
if (char !== "/")
|
|
68
|
+
next.add(state);
|
|
69
|
+
break;
|
|
70
|
+
case "globstar":
|
|
71
|
+
next.add(state);
|
|
72
|
+
break;
|
|
73
|
+
case "globdir-body":
|
|
74
|
+
next.add(state);
|
|
75
|
+
if (char === "/")
|
|
76
|
+
next.add(state + 1);
|
|
77
|
+
break;
|
|
78
|
+
case "globdir-start":
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
states = epsilonClosure(next, tokens);
|
|
83
|
+
if (states.size === 0)
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
return epsilonClosure(states, tokens).has(tokens.length);
|
|
87
|
+
}
|
|
88
|
+
function epsilonClosure(seed, tokens) {
|
|
89
|
+
const states = new Set(seed);
|
|
90
|
+
const pending = [...seed];
|
|
91
|
+
while (pending.length > 0) {
|
|
92
|
+
const state = pending.pop();
|
|
93
|
+
const token = tokens[state];
|
|
94
|
+
const targets = token?.kind === "globdir-start"
|
|
95
|
+
? [state + 1, state + 2]
|
|
96
|
+
: token?.kind === "star" || token?.kind === "globstar"
|
|
97
|
+
? [state + 1]
|
|
98
|
+
: [];
|
|
99
|
+
for (const target of targets) {
|
|
100
|
+
if (states.has(target))
|
|
101
|
+
continue;
|
|
102
|
+
states.add(target);
|
|
103
|
+
pending.push(target);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return states;
|
|
107
|
+
}
|
package/dist/tools/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// The tool registry, and the one place a tool actually gets run.
|
|
2
|
-
import {
|
|
2
|
+
import { listDir, readFile } from "./file-read.js";
|
|
3
|
+
import { editFile, writeFile } from "./file-write.js";
|
|
3
4
|
import { runCommand } from "./shell.js";
|
|
4
5
|
import { findFiles, searchText } from "./search.js";
|
|
5
6
|
export function builtinTools() {
|
package/dist/tools/search.js
CHANGED
|
@@ -6,12 +6,12 @@ import { readStableDirectory, StableDirectoryError, } from "../stable-directory.
|
|
|
6
6
|
import { optionalBool, optionalInt, optionalString, requireString } from "./args.js";
|
|
7
7
|
import { displayPath, resolveExistingInRoot } from "./paths.js";
|
|
8
8
|
import { leadingText } from "./text-boundary.js";
|
|
9
|
+
import { glob } from "./glob.js";
|
|
9
10
|
const DEFAULT_RESULTS = 100;
|
|
10
11
|
const MAX_RESULTS = 500;
|
|
11
12
|
const MAX_VISITED = 20_000;
|
|
12
13
|
const MAX_FILE_BYTES = 1_000_000;
|
|
13
14
|
const MAX_MATCH_LINE = 500;
|
|
14
|
-
const MAX_GLOB_CHARS = 512;
|
|
15
15
|
const PORTABLE_SEARCH_CONCURRENCY = 8;
|
|
16
16
|
const SKIP = new Set([".git", ".hg", ".svn", "node_modules"]);
|
|
17
17
|
export const findFiles = {
|
|
@@ -255,110 +255,6 @@ function resultLimit(args) {
|
|
|
255
255
|
throw new Error('"max_results" must be a positive integer');
|
|
256
256
|
return Math.min(requested, MAX_RESULTS);
|
|
257
257
|
}
|
|
258
|
-
function glob(pattern) {
|
|
259
|
-
const normalized = pattern.replace(/\\/g, "/");
|
|
260
|
-
if (normalized.length > MAX_GLOB_CHARS) {
|
|
261
|
-
throw new Error(`"pattern" must be at most ${MAX_GLOB_CHARS} characters`);
|
|
262
|
-
}
|
|
263
|
-
const tokens = tokenizeGlob(normalized.toLowerCase());
|
|
264
|
-
const basenameOnly = !normalized.includes("/");
|
|
265
|
-
return (relative) => {
|
|
266
|
-
const candidate = relative.replace(/\\/g, "/");
|
|
267
|
-
const target = (basenameOnly ? path.posix.basename(candidate) : candidate).toLowerCase();
|
|
268
|
-
return matchGlob(tokens, Array.from(target));
|
|
269
|
-
};
|
|
270
|
-
}
|
|
271
|
-
function tokenizeGlob(pattern) {
|
|
272
|
-
const chars = Array.from(pattern);
|
|
273
|
-
const tokens = [];
|
|
274
|
-
let index = 0;
|
|
275
|
-
while (index < chars.length) {
|
|
276
|
-
const char = chars[index];
|
|
277
|
-
if (char === "*") {
|
|
278
|
-
let end = index + 1;
|
|
279
|
-
while (chars[end] === "*")
|
|
280
|
-
end++;
|
|
281
|
-
if (end - index >= 2) {
|
|
282
|
-
if (chars[end] === "/") {
|
|
283
|
-
tokens.push({ kind: "globdir-start" }, { kind: "globdir-body" });
|
|
284
|
-
index = end + 1;
|
|
285
|
-
}
|
|
286
|
-
else {
|
|
287
|
-
tokens.push({ kind: "globstar" });
|
|
288
|
-
index = end;
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
else {
|
|
292
|
-
tokens.push({ kind: "star" });
|
|
293
|
-
index = end;
|
|
294
|
-
}
|
|
295
|
-
continue;
|
|
296
|
-
}
|
|
297
|
-
tokens.push(char === "?" ? { kind: "one" } : { kind: "literal", value: char });
|
|
298
|
-
index++;
|
|
299
|
-
}
|
|
300
|
-
return tokens;
|
|
301
|
-
}
|
|
302
|
-
/** Thompson-style wildcard matching: O(pattern × path), with no regex backtracking. */
|
|
303
|
-
function matchGlob(tokens, text) {
|
|
304
|
-
let states = epsilonClosure(new Set([0]), tokens);
|
|
305
|
-
for (const char of text) {
|
|
306
|
-
const next = new Set();
|
|
307
|
-
for (const state of states) {
|
|
308
|
-
const token = tokens[state];
|
|
309
|
-
if (token === undefined)
|
|
310
|
-
continue;
|
|
311
|
-
switch (token.kind) {
|
|
312
|
-
case "literal":
|
|
313
|
-
if (token.value === char)
|
|
314
|
-
next.add(state + 1);
|
|
315
|
-
break;
|
|
316
|
-
case "one":
|
|
317
|
-
if (char !== "/")
|
|
318
|
-
next.add(state + 1);
|
|
319
|
-
break;
|
|
320
|
-
case "star":
|
|
321
|
-
if (char !== "/")
|
|
322
|
-
next.add(state);
|
|
323
|
-
break;
|
|
324
|
-
case "globstar":
|
|
325
|
-
next.add(state);
|
|
326
|
-
break;
|
|
327
|
-
case "globdir-body":
|
|
328
|
-
next.add(state);
|
|
329
|
-
if (char === "/")
|
|
330
|
-
next.add(state + 1);
|
|
331
|
-
break;
|
|
332
|
-
case "globdir-start":
|
|
333
|
-
break;
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
states = epsilonClosure(next, tokens);
|
|
337
|
-
if (states.size === 0)
|
|
338
|
-
return false;
|
|
339
|
-
}
|
|
340
|
-
return epsilonClosure(states, tokens).has(tokens.length);
|
|
341
|
-
}
|
|
342
|
-
function epsilonClosure(seed, tokens) {
|
|
343
|
-
const states = new Set(seed);
|
|
344
|
-
const pending = [...seed];
|
|
345
|
-
while (pending.length > 0) {
|
|
346
|
-
const state = pending.pop();
|
|
347
|
-
const token = tokens[state];
|
|
348
|
-
const targets = token?.kind === "globdir-start"
|
|
349
|
-
? [state + 1, state + 2]
|
|
350
|
-
: token?.kind === "star" || token?.kind === "globstar"
|
|
351
|
-
? [state + 1]
|
|
352
|
-
: [];
|
|
353
|
-
for (const target of targets) {
|
|
354
|
-
if (states.has(target))
|
|
355
|
-
continue;
|
|
356
|
-
states.add(target);
|
|
357
|
-
pending.push(target);
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
return states;
|
|
361
|
-
}
|
|
362
258
|
function summary(count, limit, capped, one, many) {
|
|
363
259
|
const noun = count === 1 ? one : many;
|
|
364
260
|
if (count >= limit)
|
package/dist/tui/activity.js
CHANGED
package/dist/tui/app-input.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// Keyboard and pointer intent for the TUI shell.
|
|
2
|
+
import { commandNeedsIdle } from "../commands.js";
|
|
2
3
|
import { PROMPT_LIMIT_MESSAGE, PromptLimitError } from "../input-boundary.js";
|
|
3
4
|
import { activate as activateCompletion, move as moveCompletion, selected as selectedCompletion, } from "./complete.js";
|
|
4
5
|
import * as edit from "./editor.js";
|
|
@@ -11,7 +12,7 @@ const WHEEL_STEP = 3;
|
|
|
11
12
|
export function appInput(options) {
|
|
12
13
|
const { session, state, feedback, actions } = options;
|
|
13
14
|
function handle(key) {
|
|
14
|
-
if (!options.live())
|
|
15
|
+
if (!options.live() || state.closeWhenIdle)
|
|
15
16
|
return;
|
|
16
17
|
// The wheel only changes what is visible. It never answers an open prompt.
|
|
17
18
|
if (key.name === "pointer") {
|
|
@@ -23,7 +24,7 @@ export function appInput(options) {
|
|
|
23
24
|
if (state.feedback !== undefined)
|
|
24
25
|
feedback.dismiss();
|
|
25
26
|
if (key.name === "input_limit") {
|
|
26
|
-
if (state.open === undefined)
|
|
27
|
+
if (state.open === undefined && state.approval === undefined)
|
|
27
28
|
rejectPrompt();
|
|
28
29
|
else
|
|
29
30
|
showInputLimit();
|
|
@@ -38,20 +39,27 @@ export function appInput(options) {
|
|
|
38
39
|
options.transcriptChanged(changed);
|
|
39
40
|
return;
|
|
40
41
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
state.
|
|
42
|
+
const open = state.approval ?? state.open;
|
|
43
|
+
if (open !== undefined) {
|
|
44
|
+
const approving = state.approval !== undefined;
|
|
45
|
+
const outcome = overlay.handle(open, key);
|
|
46
|
+
if (approving)
|
|
47
|
+
state.approval = outcome.open;
|
|
48
|
+
else
|
|
49
|
+
state.open = outcome.open;
|
|
44
50
|
if (outcome.inputLimit === true)
|
|
45
51
|
showInputLimit();
|
|
46
|
-
if (outcome.abort === true)
|
|
47
|
-
state.activity?.control.abort(new Error("interrupted"));
|
|
52
|
+
if (outcome.abort === true) {
|
|
53
|
+
(approving ? state.activity : state.command ?? state.activity)?.control.abort(new Error("interrupted"));
|
|
54
|
+
}
|
|
48
55
|
if (outcome.quit === true)
|
|
49
56
|
options.requestQuit();
|
|
50
57
|
return;
|
|
51
58
|
}
|
|
52
59
|
if (key.ctrl && key.name === "c") {
|
|
53
|
-
|
|
54
|
-
|
|
60
|
+
const activity = state.command ?? state.activity;
|
|
61
|
+
if (activity !== undefined)
|
|
62
|
+
activity.control.abort(new Error("interrupted"));
|
|
55
63
|
else
|
|
56
64
|
options.quit();
|
|
57
65
|
return;
|
|
@@ -66,7 +74,7 @@ export function appInput(options) {
|
|
|
66
74
|
state.completing = undefined;
|
|
67
75
|
return;
|
|
68
76
|
}
|
|
69
|
-
state.activity?.control.abort(new Error("interrupted"));
|
|
77
|
+
(state.command ?? state.activity)?.control.abort(new Error("interrupted"));
|
|
70
78
|
return;
|
|
71
79
|
case "enter": {
|
|
72
80
|
if (state.completing !== undefined) {
|
|
@@ -81,7 +89,7 @@ export function appInput(options) {
|
|
|
81
89
|
return;
|
|
82
90
|
}
|
|
83
91
|
case "tab": {
|
|
84
|
-
if (state.
|
|
92
|
+
if (state.command !== undefined)
|
|
85
93
|
return;
|
|
86
94
|
const completion = state.completing ?? activateCompletion(state.editor.text);
|
|
87
95
|
const completed = completion === undefined ? undefined : selectedCompletion(completion);
|
|
@@ -125,7 +133,7 @@ export function appInput(options) {
|
|
|
125
133
|
if (edited.text !== state.editor.text)
|
|
126
134
|
state.promptRejected = false;
|
|
127
135
|
state.editor = edited;
|
|
128
|
-
state.completing = state.
|
|
136
|
+
state.completing = state.command === undefined ? activateCompletion(edited.text) : undefined;
|
|
129
137
|
}
|
|
130
138
|
}
|
|
131
139
|
catch (error) {
|
|
@@ -163,11 +171,20 @@ export function appInput(options) {
|
|
|
163
171
|
if (text === "")
|
|
164
172
|
return;
|
|
165
173
|
const isCommand = text.startsWith("/");
|
|
166
|
-
if (
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
174
|
+
if (isCommand && text.slice(1).trim().split(/\s+/)[0] === "exit") {
|
|
175
|
+
accept(text);
|
|
176
|
+
options.requestQuit();
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (state.command !== undefined) {
|
|
180
|
+
keep("Wait for the active command to finish");
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
if (isCommand && state.activity !== undefined && commandNeedsIdle(text)) {
|
|
184
|
+
keep(`Stop the active turn before ${text.split(/\s+/)[0]}`);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if (state.activity !== undefined && !isCommand) {
|
|
171
188
|
const result = actions.steer(text);
|
|
172
189
|
if (result !== "queued") {
|
|
173
190
|
keep(result === "full"
|