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.
- package/CHANGELOG.md +40 -0
- package/README.ja.md +31 -38
- package/README.ko.md +31 -38
- package/README.md +32 -38
- package/README.zh.md +31 -38
- package/package.json +4 -2
- package/src/agent/compaction.ts +2 -1
- package/src/agent/computer-supervisor.ts +59 -0
- package/src/agent/config-schema.ts +5 -0
- package/src/agent/engine.ts +9 -4
- package/src/agent/json.ts +75 -8
- package/src/agent/loop-guards.ts +16 -5
- package/src/agent/memory-okf.ts +2 -0
- package/src/agent/memory.ts +25 -8
- package/src/agent/state.ts +38 -7
- package/src/agent/task-tool.ts +12 -3
- package/src/agent/tool-schemas.ts +19 -0
- package/src/ai/model-catalog.ts +22 -0
- package/src/ai/model-discovery.ts +27 -9
- package/src/ai/providers/anthropic.ts +10 -6
- package/src/ai/providers/openai-compatible-catalog.ts +2 -0
- package/src/ai/types.ts +2 -1
- package/src/auth/index.ts +1 -1
- package/src/auth/refresh.ts +47 -2
- package/src/auth/storage.ts +11 -5
- package/src/cli/runner.ts +9 -0
- package/src/cli.ts +16 -0
- package/src/commands/approve.ts +17 -1
- package/src/commands/computer.ts +334 -0
- package/src/commands/launch/input.ts +49 -6
- package/src/commands/launch.ts +41 -18
- package/src/commands/ralplan.ts +4 -0
- package/src/commands/team.ts +13 -0
- package/src/index.ts +1 -0
- package/src/prompts/agents/executor.md +6 -4
- package/src/prompts/skills/ralplan/SKILL.md +5 -0
- package/src/prompts/skills/team/SKILL.md +5 -0
- package/src/tui/app.ts +7 -4
- package/src/tui/components/ascii-art.ts +6 -2
- package/src/tui/components/slash.ts +1 -0
- package/src/tui/components/themes.ts +14 -0
- package/src/util/terminal-restore.ts +41 -0
package/src/commands/ralplan.ts
CHANGED
|
@@ -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);
|
package/src/commands/team.ts
CHANGED
|
@@ -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
|
@@ -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.
|
|
20
|
-
3.
|
|
21
|
-
4.
|
|
22
|
-
5.
|
|
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
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
+
}
|