@giovannijecha/jecode 0.7.4 → 0.8.0

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/dist/tui/turn.js CHANGED
@@ -10,7 +10,7 @@ import { promptFor } from "./approve.js";
10
10
  // reasoning and tools keep the detailed work visible in the transcript.
11
11
  const WAITING = "Waiting";
12
12
  const THINKING = "Thinking";
13
- const WRITING = "Writing";
13
+ const RESPONDING = "Responding";
14
14
  const ASKING = "Waiting for you";
15
15
  /** Unchanged rows kept either side of a change. */
16
16
  const CONTEXT = 2;
@@ -19,10 +19,7 @@ export function transcribe(stage) {
19
19
  // one, which is what keeps reasoning and answer from running together.
20
20
  let open;
21
21
  const tools = new Map();
22
- let step = 1;
23
- let steps = 1;
24
- let tool = 1;
25
- let toolTotal = 1;
22
+ const progress = new Map();
26
23
  const close = () => {
27
24
  const block = open?.block;
28
25
  const changed = block?.kind === "reasoning" && block.live === true;
@@ -41,19 +38,28 @@ export function transcribe(stage) {
41
38
  continue;
42
39
  block.tone = "fail";
43
40
  block.right = reason ?? "failed";
44
- block.startedAt = undefined;
41
+ settleDuration(block);
45
42
  stage.render(block);
46
43
  }
47
44
  },
48
- onStep(current, total) {
49
- step = current;
50
- steps = total;
51
- stage.status(waiting(step, steps));
45
+ onToolPreparing(call, current, total) {
46
+ progress.set(call.id, { current, total });
47
+ const changed = close();
48
+ if (changed !== undefined)
49
+ stage.render(changed);
50
+ stage.status(toolStatus("Preparing", call, progress));
52
51
  stage.render();
53
52
  },
54
- onToolProgress(current, total) {
55
- tool = current;
56
- toolTotal = total;
53
+ onToolStart(call, current, total) {
54
+ progress.set(call.id, { current, total });
55
+ stage.status(toolStatus("Running", call, progress));
56
+ const block = tools.get(call.id);
57
+ if (block !== undefined && block.kind === "tool") {
58
+ block.right = "running";
59
+ block.startedAt = Date.now();
60
+ block.durationMs = undefined;
61
+ }
62
+ stage.render(block);
57
63
  },
58
64
  onUsage(usage) {
59
65
  stage.usage?.(usage);
@@ -66,8 +72,16 @@ export function transcribe(stage) {
66
72
  stage.render();
67
73
  },
68
74
  onStream(event) {
75
+ if (event.kind === "tool") {
76
+ const changed = close();
77
+ if (changed !== undefined)
78
+ stage.render(changed);
79
+ stage.status(`Preparing ${event.name ?? "tool"}`);
80
+ stage.render();
81
+ return;
82
+ }
69
83
  const kind = event.kind === "thinking" ? "reasoning" : "answer";
70
- stage.status(kind === "reasoning" ? THINKING : WRITING);
84
+ stage.status(kind === "reasoning" ? THINKING : RESPONDING);
71
85
  if (open === undefined || open.kind !== kind) {
72
86
  const changed = close();
73
87
  if (changed !== undefined)
@@ -87,15 +101,13 @@ export function transcribe(stage) {
87
101
  const changed = close();
88
102
  if (changed !== undefined)
89
103
  stage.render(changed);
90
- stage.status(`Running ${call.name}${toolTotal > 1 ? ` · tool ${tool}/${toolTotal}` : ""}${step > 1 ? ` · step ${step}/${steps}` : ""}`);
91
104
  const block = {
92
105
  kind: "tool",
93
106
  name: call.name,
94
107
  target: target(call.input),
95
- right: "running",
108
+ right: "ready",
96
109
  tone: "pending",
97
110
  body: preview(call, look),
98
- startedAt: Date.now(),
99
111
  };
100
112
  tools.set(call.id, block);
101
113
  stage.emit(block);
@@ -109,7 +121,7 @@ export function transcribe(stage) {
109
121
  stage.render(block);
110
122
  },
111
123
  onToolResult(call, result, summary) {
112
- stage.status(waiting(step, steps));
124
+ stage.status(WAITING);
113
125
  const block = tools.get(call.id);
114
126
  if (block === undefined || block.kind !== "tool")
115
127
  return;
@@ -122,7 +134,7 @@ export function transcribe(stage) {
122
134
  }
123
135
  block.tone = result.isError ? "fail" : "ok";
124
136
  block.right = summary ?? "";
125
- block.startedAt = undefined;
137
+ settleDuration(block);
126
138
  // A failure replaces the preview: what the call was going to do stops
127
139
  // being the interesting part the moment it did not do it.
128
140
  const outcome = result.isError
@@ -134,14 +146,8 @@ export function transcribe(stage) {
134
146
  },
135
147
  approve(call) {
136
148
  const block = tools.get(call.id);
137
- if (stage.approved(call)) {
138
- if (block !== undefined && block.kind === "tool") {
139
- block.right = "running";
140
- block.startedAt ??= Date.now();
141
- stage.render(block);
142
- }
149
+ if (stage.approved(call))
143
150
  return Promise.resolve(true);
144
- }
145
151
  const changed = close();
146
152
  if (changed !== undefined)
147
153
  stage.render(changed);
@@ -160,8 +166,8 @@ export function transcribe(stage) {
160
166
  if (settled !== undefined && settled.kind === "tool") {
161
167
  if (approved) {
162
168
  settled.tone = "pending";
163
- settled.right = "running";
164
- settled.startedAt = Date.now();
169
+ settled.right = "ready";
170
+ settled.startedAt = undefined;
165
171
  }
166
172
  else {
167
173
  settled.tone = "deny";
@@ -169,7 +175,9 @@ export function transcribe(stage) {
169
175
  settled.startedAt = undefined;
170
176
  }
171
177
  }
172
- stage.status(approved ? `Running ${call.name}` : waiting(step, steps));
178
+ stage.status(approved
179
+ ? toolStatus("Preparing", call, progress)
180
+ : WAITING);
173
181
  stage.render(settled);
174
182
  resolve(approved);
175
183
  });
@@ -177,14 +185,24 @@ export function transcribe(stage) {
177
185
  },
178
186
  };
179
187
  }
188
+ function toolStatus(phase, call, progress) {
189
+ const position = progress.get(call.id);
190
+ const tool = position !== undefined && position.total > 1
191
+ ? ` · tool ${position.current}/${position.total}`
192
+ : "";
193
+ return `${phase} ${call.name}${tool}`;
194
+ }
180
195
  function pendingApproval(body) {
181
196
  const added = body?.filter((detail) => detail.kind === "add").length ?? 0;
182
197
  const removed = body?.filter((detail) => detail.kind === "del").length ?? 0;
183
198
  const summary = added + removed === 0 ? "" : `+${added} −${removed} · `;
184
199
  return `${summary}pending approval`;
185
200
  }
186
- function waiting(step, total) {
187
- return step === 1 ? WAITING : `${WAITING} · step ${step}/${total}`;
201
+ function settleDuration(block) {
202
+ if (block.startedAt !== undefined) {
203
+ block.durationMs = Math.max(0, Date.now() - block.startedAt);
204
+ }
205
+ block.startedAt = undefined;
188
206
  }
189
207
  /** The argument worth showing: the thing the call acts on. */
190
208
  function target(input) {
package/dist/tui/view.js CHANGED
@@ -25,7 +25,7 @@ export function compose(view, size, transcript = transcriptRenderer()) {
25
25
  // Any spare height belongs above the conversation, so short sessions grow
26
26
  // upward from the fixed dock rhythm instead of leaving a changing hole
27
27
  // beneath the latest reply.
28
- const viewport = transcript.viewport(view.blocks, width, transcriptHeight, view.scroll, view.pal, { now: view.now });
28
+ const viewport = transcript.viewport(view.blocks, width, transcriptHeight, view.scroll, view.pal, { now: view.now, reducedMotion: view.reducedMotion });
29
29
  const cursor = dock.cursor === undefined
30
30
  ? undefined
31
31
  : { row: transcriptHeight + dock.cursor.row, col: dock.cursor.col };
@@ -34,6 +34,7 @@ export function compose(view, size, transcript = transcriptRenderer()) {
34
34
  cursor,
35
35
  maxScroll: viewport.maxScroll,
36
36
  transcriptPending: viewport.pending,
37
+ transcriptAnimating: viewport.animating,
37
38
  };
38
39
  }
39
40
  function dockRows(view, width, height) {
@@ -42,6 +43,7 @@ function dockRows(view, width, height) {
42
43
  // use the footer while that interaction is open instead of repeating a
43
44
  // generic "Running /…" label beside it.
44
45
  status: view.modal === undefined ? view.status : undefined,
46
+ steering: view.modal === undefined ? view.steering : undefined,
45
47
  feedback: view.feedback,
46
48
  readiness: view.readiness,
47
49
  unseen: view.unseen ?? 0,
@@ -87,5 +89,5 @@ function tooSmall(height, width, view) {
87
89
  { text: elide(`need ${MIN_COLS}×${MIN_ROWS}`, Math.max(1, width)), fg: view.pal.ink.dim },
88
90
  ]);
89
91
  }
90
- return { rows, maxScroll: 0, transcriptPending: false };
92
+ return { rows, maxScroll: 0, transcriptPending: false, transcriptAnimating: false };
91
93
  }
package/dist/ui/render.js CHANGED
@@ -125,9 +125,7 @@ export function blank(width, ground) {
125
125
  /**
126
126
  * A full-width divider.
127
127
  *
128
- * Edge to edge, because the other thing that spans a whole row is the ground
129
- * behind a user's message: two elements that stop at different columns read as
130
- * two grids, and the eye finds the discrepancy before it finds the text.
128
+ * Edge to edge so the composer and dock keep one exact terminal boundary.
131
129
  */
132
130
  export function rule(width, color) {
133
131
  return paint({ text: "─".repeat(Math.max(0, width)), fg: color });
package/dist/ui/theme.js CHANGED
@@ -1,26 +1,27 @@
1
1
  // Jecode's fixed terminal identity. Components depend on semantic tokens, not
2
- // literal colours, while the product exposes one deliberate dark Steel look.
3
- // Jecode's fixed dark Steel baseline. Structural blues, semantic outcomes,
4
- // and slate surfaces keep the transcript vivid without turning it decorative.
2
+ // literal colours, while the product exposes one deliberate dark Slate look.
3
+ // Jecode's fixed dark Slate baseline. The exported name stays stable while
4
+ // cooler, quieter values leave the transcript bright enough to read and let
5
+ // live state carry the colour.
5
6
  // Components depend on these roles rather than embedding presentation values.
6
7
  export const STEEL = {
7
- accent: [102, 155, 210],
8
- technical: [78, 201, 232],
9
- focus: [102, 155, 210],
10
- rule: [53, 80, 110],
8
+ accent: [124, 164, 222],
9
+ technical: [126, 186, 208],
10
+ focus: [124, 164, 222],
11
+ rule: [44, 60, 78],
11
12
  ink: {
12
- fg: [220, 224, 229],
13
- bright: [235, 239, 244],
14
- muted: [156, 169, 183],
15
- dim: [112, 124, 137],
16
- attention: [230, 191, 95],
17
- added: [134, 203, 146],
18
- removed: [232, 112, 112],
13
+ fg: [214, 219, 226],
14
+ bright: [241, 244, 248],
15
+ muted: [149, 160, 174],
16
+ dim: [99, 111, 125],
17
+ attention: [226, 188, 112],
18
+ added: [138, 190, 150],
19
+ removed: [223, 120, 120],
19
20
  },
20
21
  surface: {
21
- subtle: [31, 38, 47],
22
- added: [22, 55, 34],
23
- removed: [62, 24, 27],
24
- attention: [62, 50, 19],
22
+ subtle: [23, 29, 37],
23
+ added: [21, 52, 33],
24
+ removed: [58, 24, 27],
25
+ attention: [58, 47, 20],
25
26
  },
26
27
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@giovannijecha/jecode",
3
- "version": "0.7.4",
3
+ "version": "0.8.0",
4
4
  "description": "An owned coding agent with zero external runtime dependencies.",
5
5
  "license": "MIT",
6
6
  "repository": {