@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.
Files changed (104) hide show
  1. package/README.md +36 -9
  2. package/assets/tokenizers/LICENSE +21 -0
  3. package/assets/tokenizers/o200k-base.tiktoken.gz +0 -0
  4. package/dist/cli-info.js +8 -14
  5. package/dist/commands.js +7 -3
  6. package/dist/config.js +47 -53
  7. package/dist/context/automatic.js +16 -1
  8. package/dist/context/budget.js +5 -2
  9. package/dist/context/compactor.js +91 -37
  10. package/dist/context/diagnostics.js +108 -0
  11. package/dist/context/lifetime.js +18 -0
  12. package/dist/context/manager.js +67 -0
  13. package/dist/context/manual.js +11 -10
  14. package/dist/context/measurement.js +75 -0
  15. package/dist/context/policy.js +13 -10
  16. package/dist/context/request-observation.js +46 -0
  17. package/dist/context/request-projection.js +8 -34
  18. package/dist/context/request.js +22 -0
  19. package/dist/context/tokenizer/bpe.js +68 -0
  20. package/dist/context/tokenizer/o200k.js +54 -0
  21. package/dist/context/tokenizer/vocabulary.js +34 -0
  22. package/dist/controller-request.js +47 -45
  23. package/dist/controller.js +13 -2
  24. package/dist/credential-commands.js +3 -3
  25. package/dist/input-boundary.js +0 -62
  26. package/dist/launch.js +13 -9
  27. package/dist/model-command.js +7 -17
  28. package/dist/oauth-result-page.js +68 -0
  29. package/dist/openai-account-command.js +14 -12
  30. package/dist/openai-account.js +7 -5
  31. package/dist/openai-oauth-callback.js +15 -54
  32. package/dist/openai-oauth-tokens.js +9 -7
  33. package/dist/openai-oauth.js +8 -6
  34. package/dist/permission-command.js +9 -24
  35. package/dist/permissions.js +10 -8
  36. package/dist/provider-commands.js +1 -33
  37. package/dist/provider-errors.js +1 -10
  38. package/dist/provider-label.js +3 -10
  39. package/dist/providers/anthropic-wire.js +1 -1
  40. package/dist/providers/anthropic.js +3 -2
  41. package/dist/providers/index.js +1 -5
  42. package/dist/providers/input-measurement.js +85 -0
  43. package/dist/providers/ollama-context.js +42 -0
  44. package/dist/providers/ollama-endpoint.js +7 -34
  45. package/dist/providers/ollama-wire.js +1 -1
  46. package/dist/providers/ollama.js +15 -147
  47. package/dist/providers/openai-codex.js +7 -4
  48. package/dist/providers/openai-stream.js +20 -8
  49. package/dist/providers/openai-summary.js +37 -0
  50. package/dist/providers/openai-wire.js +1 -1
  51. package/dist/providers/openai.js +3 -1
  52. package/dist/sessions/bucket.js +55 -0
  53. package/dist/sessions/catalog-io.js +162 -0
  54. package/dist/sessions/catalog.js +3 -1
  55. package/dist/sessions/codec-messages.js +122 -0
  56. package/dist/sessions/codec-transcript.js +93 -0
  57. package/dist/sessions/codec-values.js +52 -0
  58. package/dist/sessions/codec.js +4 -257
  59. package/dist/sessions/files.js +158 -0
  60. package/dist/sessions/load.js +90 -0
  61. package/dist/sessions/snapshot.js +33 -0
  62. package/dist/sessions/store.js +42 -461
  63. package/dist/settings.js +17 -16
  64. package/dist/start.js +36 -53
  65. package/dist/timeline.js +2 -0
  66. package/dist/tools/file-read.js +192 -0
  67. package/dist/tools/file-summary.js +9 -0
  68. package/dist/tools/{fs.js → file-write.js} +5 -193
  69. package/dist/tools/glob.js +107 -0
  70. package/dist/tools/index.js +2 -1
  71. package/dist/tools/search.js +1 -105
  72. package/dist/tui/activity.js +1 -1
  73. package/dist/tui/app-input.js +34 -17
  74. package/dist/tui/app-workflows.js +13 -361
  75. package/dist/tui/app.js +55 -28
  76. package/dist/tui/approve.js +3 -2
  77. package/dist/tui/blocks.js +1 -2
  78. package/dist/tui/command-workflow.js +112 -0
  79. package/dist/tui/components/command-menu.js +7 -10
  80. package/dist/tui/components/menu.js +82 -43
  81. package/dist/tui/components/messages.js +16 -9
  82. package/dist/tui/components/status.js +1 -1
  83. package/dist/tui/components/tool-evidence.js +107 -0
  84. package/dist/tui/components/tool-motion.js +32 -0
  85. package/dist/tui/components/tool.js +48 -202
  86. package/dist/tui/frame.js +8 -3
  87. package/dist/tui/help.js +3 -2
  88. package/dist/tui/picker-layout.js +44 -0
  89. package/dist/tui/picker.js +7 -71
  90. package/dist/tui/screen.js +7 -0
  91. package/dist/tui/session-view.js +0 -1
  92. package/dist/tui/tool-details.js +135 -0
  93. package/dist/tui/transcript-grammar.js +1 -1
  94. package/dist/tui/transcript-view.js +30 -112
  95. package/dist/tui/turn-workflow.js +235 -0
  96. package/dist/tui/turn.js +7 -140
  97. package/dist/tui/view.js +4 -4
  98. package/dist/tui/workflow-types.js +2 -0
  99. package/dist/ui/render.js +0 -4
  100. package/package.json +16 -12
  101. package/dist/batch-view.js +0 -17
  102. package/dist/batch.js +0 -267
  103. package/dist/ollama-settings-command.js +0 -74
  104. 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
+ }
@@ -1,5 +1,6 @@
1
1
  // The tool registry, and the one place a tool actually gets run.
2
- import { editFile, listDir, readFile, writeFile } from "./fs.js";
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() {
@@ -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)
@@ -1,4 +1,4 @@
1
- // One foreground operation owns cancellation and elapsed time.
1
+ // Each turn or command owns its cancellation and elapsed time.
2
2
  export function begin(kind, label, now = Date.now()) {
3
3
  return {
4
4
  kind,
@@ -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
- if (state.open !== undefined) {
42
- const outcome = overlay.handle(state.open, key);
43
- state.open = outcome.open;
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
- if (state.activity !== undefined)
54
- state.activity.control.abort(new Error("interrupted"));
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.activity !== undefined)
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.activity === undefined ? activateCompletion(edited.text) : undefined;
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 (state.activity !== undefined) {
167
- if (isCommand) {
168
- keep("Slash commands run after the active work finishes");
169
- return;
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"