jeo-code 0.6.41 → 0.7.1

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 (42) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.ja.md +31 -38
  3. package/README.ko.md +31 -38
  4. package/README.md +32 -38
  5. package/README.zh.md +31 -38
  6. package/package.json +4 -2
  7. package/src/agent/compaction.ts +2 -1
  8. package/src/agent/computer-supervisor.ts +59 -0
  9. package/src/agent/config-schema.ts +5 -0
  10. package/src/agent/engine.ts +9 -4
  11. package/src/agent/json.ts +75 -8
  12. package/src/agent/loop-guards.ts +16 -5
  13. package/src/agent/memory-okf.ts +2 -0
  14. package/src/agent/memory.ts +25 -8
  15. package/src/agent/state.ts +38 -7
  16. package/src/agent/task-tool.ts +12 -3
  17. package/src/agent/tool-schemas.ts +19 -0
  18. package/src/ai/model-catalog.ts +22 -0
  19. package/src/ai/model-discovery.ts +27 -9
  20. package/src/ai/providers/anthropic.ts +10 -6
  21. package/src/ai/providers/openai-compatible-catalog.ts +2 -0
  22. package/src/ai/types.ts +2 -1
  23. package/src/auth/index.ts +1 -1
  24. package/src/auth/refresh.ts +47 -2
  25. package/src/auth/storage.ts +11 -5
  26. package/src/cli/runner.ts +9 -0
  27. package/src/cli.ts +16 -0
  28. package/src/commands/approve.ts +17 -1
  29. package/src/commands/computer.ts +334 -0
  30. package/src/commands/launch/input.ts +49 -6
  31. package/src/commands/launch.ts +41 -18
  32. package/src/commands/ralplan.ts +4 -0
  33. package/src/commands/team.ts +13 -0
  34. package/src/index.ts +1 -0
  35. package/src/prompts/agents/executor.md +6 -4
  36. package/src/prompts/skills/ralplan/SKILL.md +5 -0
  37. package/src/prompts/skills/team/SKILL.md +5 -0
  38. package/src/tui/app.ts +7 -4
  39. package/src/tui/components/ascii-art.ts +6 -2
  40. package/src/tui/components/slash.ts +1 -0
  41. package/src/tui/components/themes.ts +14 -0
  42. package/src/util/terminal-restore.ts +41 -0
@@ -1,5 +1,6 @@
1
1
  import * as fs from "node:fs/promises";
2
2
  import * as path from "node:path";
3
+ import { createHash } from "node:crypto";
3
4
  import { callLlm, type Message } from "../agent/loop";
4
5
  import {
5
6
  readWorkflowState,
@@ -268,6 +269,9 @@ export async function runRalplanEngine(opts: RalplanEngineOptions = {}): Promise
268
269
  ralplanState.plan_path = planPath;
269
270
  ralplanState.consensus = gate.verdict;
270
271
  ralplanState.consensus_detail = gate.detail.slice(0, 600);
272
+ if (gate.verdict === "okay") {
273
+ ralplanState.consensus_hash = createHash("sha256").update(cleanPlan).digest("hex");
274
+ }
271
275
  if (gate.verdict !== "okay") {
272
276
  ralplanState.approved = false;
273
277
  await writeWorkflowState("ralplan", ralplanState, cwd);
@@ -1,4 +1,5 @@
1
1
  import * as fs from "node:fs/promises";
2
+ import { createHash } from "node:crypto";
2
3
  import chalk from "chalk";
3
4
  import { PlanSchema, normalizePlanShape, parseYaml } from "../agent/plan";
4
5
  import {
@@ -221,6 +222,18 @@ export async function runTeamEngine(opts: TeamEngineOptions = {}): Promise<{ ok:
221
222
  return { ok: false, reason: err.message };
222
223
  }
223
224
 
225
+ // Round-13: verify the plan's hash matches the consensus hash to prevent silent edits after approval
226
+ if (planState.consensus_hash) {
227
+ const currentHash = createHash("sha256").update(planContent).digest("hex");
228
+ if (currentHash !== planState.consensus_hash) {
229
+ log(
230
+ `[ERROR] Plan file has been modified since it was reviewed by the consensus critic.\n` +
231
+ ` Re-run 'jeo ralplan' to let the critic review the updated plan, then approve and execute again.`
232
+ );
233
+ return { ok: false, reason: "Plan file modified since consensus review" };
234
+ }
235
+ }
236
+
224
237
  let rawPlan: any;
225
238
  try {
226
239
  rawPlan = parseYaml(planContent);
package/src/index.ts CHANGED
@@ -29,3 +29,4 @@ export * from "./commands/deep-interview";
29
29
  export * from "./commands/ralplan";
30
30
  export * from "./commands/team";
31
31
  export * from "./commands/ultragoal";
32
+ export * from "./commands/computer";
@@ -16,10 +16,12 @@ Turn a bounded task into a working, verified outcome with the smallest correct c
16
16
 
17
17
  <execution_loop>
18
18
  1. Inspect the relevant files and conventions.
19
- 2. Make the minimum change that satisfies the assigned task.
20
- 3. Run focused verification with the available tools.
21
- 4. Remove debug leftovers.
22
- 5. Call `done` only after verification evidence is available.
19
+ 2. Decompose a large task into ordered subgoals; finish and verify one before starting the next.
20
+ 3. Make the minimum change that satisfies the current subgoal.
21
+ 4. Run focused verification with the available tools.
22
+ 5. When a step fails, extract the lesson, feed it into the next attempt, and split a stuck subgoal into a smaller one rather than retrying unchanged.
23
+ 6. Remove debug leftovers.
24
+ 7. Call `done` only after verification evidence is available.
23
25
  </execution_loop>
24
26
 
25
27
  <tool_protocol>
@@ -9,3 +9,8 @@ when: When requirements are clear (e.g. from deep-interview) and you need a robu
9
9
  Executes a multi-agent critique and planning process to generate a structured implementation plan.
10
10
  Combines views from a Planner, an Architect, and a Critic to identify risks, define tasks, and specify files.
11
11
  Saves the blueprint for execution.
12
+
13
+ When the roles disagree, do not collapse the split to a single verdict: record each
14
+ contested decision with the competing options and their rationale, and flag the points
15
+ that need a human judgment call rather than auto-resolving them.
16
+
@@ -9,3 +9,8 @@ when: When you have a blueprint/plan and need to execute the concrete implementa
9
9
  Coordinates execution of individual tasks defined in the blueprint.
10
10
  Spawns per-task executor subagents or loops to implement code changes.
11
11
  Ensures task-level isolation and tracks implementation status.
12
+
13
+ Each task is a bounded subgoal: decompose large work into ordered subgoals, verify
14
+ one before starting the next, and feed the facts a failed task exposes into the
15
+ inputs of the following task instead of retrying the same approach unchanged.
16
+
package/src/tui/app.ts CHANGED
@@ -16,13 +16,13 @@ import { Spinner } from "./components/spinner";
16
16
  import { ToolList } from "./components/tool-list";
17
17
  import { StreamRegion } from "./components/stream";
18
18
  import { renderFooter, type FooterData } from "./components/footer";
19
- import { renderForgeMark, forgeMarkHeight, forgeMarkFrameCount, forgeBeat, FORGE_FLOW_PALETTE } from "./components/ascii-art";
19
+ import { renderForgeMark, forgeMarkHeight, forgeMarkFrameCount, forgeBeat } from "./components/ascii-art";
20
20
  import { evolutionTrack, createStageProgress, type StageProgress, transitionMessage } from "./components/evolution";
21
21
  import type { TaskSubEvent } from "../agent/task-tool";
22
22
  import { supportsUnicode } from "./components/capability";
23
23
  import { centerBlock, padLineTo, boxBlock, BOX_ASCII, BOX_UNICODE } from "./components/layout";
24
24
  import { SECTION_GAP, sectionLabel, stackSections } from "./components/section";
25
- import { resolveTheme, themeGradient, accentPaint, accentShadowPaint, diffPaint, mutedPaint, cardFillPaint } from "./components/themes";
25
+ import { resolveTheme, themeGradient, accentPaint, accentShadowPaint, diffPaint, mutedPaint, cardFillPaint, themeFlowPalette } from "./components/themes";
26
26
  import { detectColorLevel, animatedGradientText, ColorLevel } from "./components/color";
27
27
  import { formatForgeBox, summarizeForgeInvocation, summarizeForgeResult, fitForgeBoxes, webSearchCardLines, scaleForgeWidth, type ForgeSummary } from "./components/forge";
28
28
  import { renderStatusBar, renderStatusBox, type StatusBoxData } from "./components/status";
@@ -314,6 +314,9 @@ export class LaunchTui {
314
314
  private readonly unicode: boolean = supportsUnicode();
315
315
  // Active color theme (JEO_TUI_THEME), default cosmic; `mono` disables color.
316
316
  private readonly theme = resolveTheme(process.env);
317
+ // Neon flow palette derived from the active theme (forge-card border + forge mark
318
+ // animation), so the live glow tracks the theme instead of a fixed brand palette.
319
+ private readonly themeFlow = themeFlowPalette(this.theme);
317
320
  // Whether the live turn may use the alternate screen buffer (real TTY only).
318
321
  private readonly tty: boolean;
319
322
  // gjc-style inline rendering (default on a TTY): the live frame repaints in place in
@@ -968,7 +971,6 @@ export class LaunchTui {
968
971
  this.subagentLive = null; // fresh turn: no nested subagent in flight
969
972
  this.activityLog.length = 0; // per-turn ring: timestamps are turn-relative
970
973
  this.spinner.updateStep(0, this.footer.maxSteps);
971
- // On a real TTY the live turn renders gjc-style in the MAIN buffer by default:
972
974
  // completed ledger lines are flushed into normal scrollback as they happen, so a
973
975
  // tmux / terminal mouse-wheel scroll can review earlier progress mid-turn. The
974
976
  // differential renderer reserves frame rows with real newlines, keeping the
@@ -1278,7 +1280,7 @@ export class LaunchTui {
1278
1280
  // the card border and the prompt beat marks the title. Flushed/final cards
1279
1281
  // stay static. Suppressed while `dim` (in-flight shading takes precedence).
1280
1282
  ...(anim && !dim
1281
- ? { flow: { palette: FORGE_FLOW_PALETTE, phase: anim.phase, colorLevel: anim.colorLevel }, titleMark: anim.beat }
1283
+ ? { flow: { palette: this.themeFlow, phase: anim.phase, colorLevel: anim.colorLevel }, titleMark: anim.beat }
1282
1284
  : {}),
1283
1285
  }));
1284
1286
  }
@@ -1570,6 +1572,7 @@ export class LaunchTui {
1570
1572
  unicode: this.unicode,
1571
1573
  color: this.theme.color,
1572
1574
  colorLevel: detectColorLevel(process.env, isTTY()),
1575
+ palette: this.themeFlow,
1573
1576
  });
1574
1577
  this.cachedArt = fit ? centerBlock(art, innerWidth) : art;
1575
1578
  const track = evolutionTrack(idx, { unicode: this.unicode, color: this.theme.color });
@@ -528,8 +528,12 @@ export function renderForgeMark(opts: {
528
528
  * flowing gradient `phase` this animates the forge identity without any
529
529
  * frame-count growth. */
530
530
  frame?: number;
531
+ /** Gradient palette for the flowing glow. Defaults to the brand `FORGE_FLOW_PALETTE`;
532
+ * pass a theme-derived palette (see `themeFlowPalette`) so the mark tracks the theme. */
533
+ palette?: readonly string[];
531
534
  }): string[] {
532
- const memoKey = `${opts.grand ? "g" : "c"}|${opts.unicode !== false ? 1 : 0}|${opts.cols ?? -1}|${opts.color !== false ? 1 : 0}|${opts.colorLevel ?? ColorLevel.TrueColor}|${opts.phase ?? 0}|${opts.frame ?? 0}`;
535
+ const palette = opts.palette ?? FORGE_FLOW_PALETTE;
536
+ const memoKey = `${opts.grand ? "g" : "c"}|${opts.unicode !== false ? 1 : 0}|${opts.cols ?? -1}|${opts.color !== false ? 1 : 0}|${opts.colorLevel ?? ColorLevel.TrueColor}|${opts.phase ?? 0}|${opts.frame ?? 0}|${palette.join(",")}`;
533
537
  const memoHit = forgeMarkMemo.get(memoKey);
534
538
  if (memoHit) return memoHit;
535
539
  const useUnicode = opts.unicode !== false;
@@ -551,7 +555,7 @@ export function renderForgeMark(opts: {
551
555
  const phase = opts.phase ?? 0;
552
556
  const useColor = opts.color !== false;
553
557
  const colorLevel = opts.colorLevel ?? ColorLevel.TrueColor;
554
- const palette = FORGE_FLOW_PALETTE;
558
+
555
559
 
556
560
  const result = source.map((line, idx) => {
557
561
  const padded = line.length < width ? line + " ".repeat(width - line.length) : line;
@@ -29,6 +29,7 @@ export const SLASH_COMMAND_DETAILS: readonly SlashCommandInfo[] = [
29
29
  { command: "/rename", usage: "/rename <title>", description: "Rename the current session", group: "session" },
30
30
  { command: "/resume", usage: "/resume [id]", description: "Resume a saved session in this REPL", group: "session" },
31
31
  { command: "/retry", usage: "/retry", description: "Retry the last request", group: "session" },
32
+ { command: "/undo", usage: "/undo", description: "Undo the last jeo auto-commit and restore the working tree", group: "session" },
32
33
  { command: "/history", usage: "/history [n|all]", description: "Re-print the worked turn history (prompts, tool steps, replies) into scrollback", group: "session" },
33
34
  { command: "/export", usage: "/export [path] [json]", description: "Export the session transcript to a file", group: "session" },
34
35
  { command: "/dump", usage: "/dump", description: "Copy the session transcript to the clipboard", group: "session" },
@@ -285,6 +285,20 @@ export function accentShadowPaint(theme: EvolutionTheme): (s: string) => string
285
285
  return (s: string) => chalk.dim(chalk.hex(hex)(s));
286
286
  }
287
287
 
288
+
289
+ /** A 3-stop neon "flow" palette derived from the active theme, for the animated
290
+ * forge-card border and the forge mark so the live flow matches the THEME instead
291
+ * of a fixed brand palette. Sweeps `accentShadow` → `accent` → the brightest stage
292
+ * gradient end (a dark → accent → light shimmer that reads as the theme's own glow).
293
+ * Colorless themes collapse to a single accent stop — callers gate the animation on
294
+ * color/TrueColor, so the value is only a harmless fallback there. */
295
+ export function themeFlowPalette(theme: EvolutionTheme): readonly string[] {
296
+ if (!theme.color) return [theme.accent];
297
+ const bright = theme.gradients[theme.gradients.length - 1]?.to ?? theme.accent;
298
+ const dark = theme.accentShadow ?? theme.accent;
299
+ return [dark, theme.accent, bright];
300
+ }
301
+
288
302
  /** Default muted foreground when a theme defines no `muted` — a readable mid-gray
289
303
  * that holds up on dark terminals (unlike ANSI `dim`, which collapses toward the
290
304
  * background). */
@@ -0,0 +1,41 @@
1
+ import { writeSync } from "node:fs";
2
+
3
+ // Restoring the terminal on an ABNORMAL exit (uncaught exception, unhandled
4
+ // rejection, fatal error) is what keeps the user's shell usable afterwards. The
5
+ // failure mode this guards against: a background `fetch()` stream rejects after a
6
+ // turn ends, Bun reports it as an unhandled rejection and tears the process down
7
+ // WITHOUT running the readline cleanup — leaving stdin in raw mode and bracketed
8
+ // paste enabled, so the shell echoes nothing and never submits a line (the
9
+ // "prints an error and then input is dead" bug).
10
+ //
11
+ // `process.stdout.write` is async/buffered and is NOT guaranteed to flush from an
12
+ // `exit` handler or a crashing tick, so the escape sequences are emitted with the
13
+ // SYNCHRONOUS `writeSync(1, …)` — the only reliable way to hand the terminal back
14
+ // in a sane line-discipline before the process disappears. `setRawMode(false)` is
15
+ // a synchronous ioctl and is safe to call in the same context.
16
+
17
+ type RawCapableStdin = NodeJS.ReadStream & { isRaw?: boolean; setRawMode?(raw: boolean): void };
18
+
19
+ let restored = false;
20
+
21
+ /**
22
+ * Synchronously return the terminal to cooked input, a visible cursor, and
23
+ * bracketed-paste OFF. Idempotent and safe to call from crash/exit paths.
24
+ */
25
+ export function restoreTerminalState(): void {
26
+ if (restored) return;
27
+ restored = true;
28
+ const stdin = process.stdin as RawCapableStdin;
29
+ try {
30
+ if (stdin.isTTY && stdin.isRaw && typeof stdin.setRawMode === "function") stdin.setRawMode(false);
31
+ } catch { /* terminal already gone */ }
32
+ try {
33
+ // \x1b[?2004l = disable bracketed paste, \x1b[?25h = show cursor.
34
+ if (process.stdout.isTTY) writeSync(1, "\x1b[?2004l\x1b[?25h");
35
+ } catch { /* terminal already gone */ }
36
+ }
37
+
38
+ /** Test-only: reset the idempotency latch so a fresh restore can be observed. */
39
+ export function resetTerminalRestoreLatch(): void {
40
+ restored = false;
41
+ }