jeo-code 0.6.19 → 0.6.21
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 +21 -0
- package/README.ja.md +2 -2
- package/README.ko.md +2 -2
- package/README.md +2 -2
- package/README.zh.md +2 -2
- package/package.json +1 -2
- package/src/agent/engine.ts +7 -0
- package/src/agent/loop.ts +5 -0
- package/src/commands/launch/mentions.ts +48 -0
- package/src/commands/launch/slash-handlers.ts +62 -0
- package/src/commands/launch/slash-views.ts +48 -0
- package/src/commands/launch.ts +38 -77
package/CHANGELOG.md
CHANGED
|
@@ -5,8 +5,29 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
5
5
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
7
|
The README mirrors the latest 5 entries — regenerate with `bun run changelog:sync`.
|
|
8
|
+
+
|
|
9
|
+
## [0.6.21] - 2026-06-18
|
|
10
|
+
_Session thinking level now reaches the provider's actual reasoning depth, not just the token ceiling._
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- **`/thinking`, `--thinking`, and `/fast` now change real provider reasoning depth.** Previously a live session thinking change only adjusted the per-step `maxTokens` budget; the provider's reasoning effort (Anthropic `thinking.budget_tokens`, OpenAI `reasoning_effort`, Gemini `thinkingConfig.thinkingBudget`) still came from the global `~/.jeo/config.json` `thinkingLevel`. `reasoningEffort` is now threaded from the session level through `AgentLoopOptions` → `ChatOptions` → the model manager, so the session setting actually controls how deeply the model reasons. When unset it still falls back to the global config.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
- **`thinkingToReasoningEffort` mapping test** locking the session-level → provider-tier contract (minimal/low → low, medium → medium, high/xhigh → high, unset → undefined).
|
|
17
|
+
|
|
18
|
+
## [0.6.20] - 2026-06-18
|
|
19
|
+
_Launch REPL internals decomposed into testable modules: `@mention` path completion, slash-command view renderers, and slash-command handlers extracted from the monolithic `launch.ts` into dedicated files with full unit-test coverage._
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
- **`mentionPaths` / `currentAtLabel` extracted to `src/commands/launch/mentions.ts`.** The `@path` filesystem completion and footer label logic are now pure, `cwd`-parametric functions that can be unit-tested in isolation; `launch.ts` delegates to them via thin wrappers.
|
|
23
|
+
- **Slash-command view renderers extracted to `src/commands/launch/slash-views.ts`.** `hotkeysLines` and `contextUsageLines` are now pure functions (no I/O, no hidden state) returning `string[]`; verified with snapshot-style unit tests.
|
|
24
|
+
- **Slash-command handlers extracted to `src/commands/launch/slash-handlers.ts`.** `/usage`, `/tools`, `/hotkeys`, `/context` handlers are isolated behind a `SlashContext` interface, each returning a typed result — testable without spinning up the full REPL.
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
- **Unit tests for all extracted modules** (`test/launch-mentions.test.ts`, `test/launch-slash-views.test.ts`, `test/slash-handlers.test.ts`): 13 new tests, 79 assertions — mentionPaths directory traversal, case-insensitive filtering, unreadable-dir guard, currentAtLabel edge cases, context token tallies, singular/plural spacing, handler outputs.
|
|
8
28
|
|
|
9
29
|
## [0.6.19] - 2026-06-18
|
|
30
|
+
|
|
10
31
|
_Post-turn hooks run once per batch (not per edit), local hook reads are mtime-cached, tool-result formatting is parallelized, and wrapped colored text keeps its tint._
|
|
11
32
|
|
|
12
33
|
### Changed
|
package/README.ja.md
CHANGED
|
@@ -200,11 +200,11 @@ CI は `.github/workflows/npm-publish.yml` で公開します — GitHub リリ
|
|
|
200
200
|
## 変更履歴 (Changelog)
|
|
201
201
|
|
|
202
202
|
<!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
|
|
203
|
+
- **[0.6.21]** (2026-06-18) — Session thinking level now reaches the provider's actual reasoning depth, not just the token ceiling.
|
|
204
|
+
- **[0.6.20]** (2026-06-18) — Launch REPL internals decomposed into testable modules: `@mention` path completion, slash-command view renderers, and slash-command handlers extracted from the monolithic `launch.ts` into dedicated files with full unit-test coverage.
|
|
203
205
|
- **[0.6.19]** (2026-06-18) — Post-turn hooks run once per batch (not per edit), local hook reads are mtime-cached, tool-result formatting is parallelized, and wrapped colored text keeps its tint.
|
|
204
206
|
- **[0.6.18]** (2026-06-17) — Memory data-flow diagram and a README "Memory flow" section documenting the actual runtime behavior.
|
|
205
207
|
- **[0.6.17]** (2026-06-17) — Legacy MEMORY.md migrates losslessly into the OKF concept bundle, with a one-shot command and a rollback toggle.
|
|
206
|
-
- **[0.6.16]** (2026-06-17) — OKF memory grows a concept cross-link graph: 1-hop search expansion, bundle lint, graphify-optional.
|
|
207
|
-
- **[0.6.15]** (2026-06-17) — Query-aware OKF memory injection with budget-priority selection, and a truthful end-of-turn Todos receipt.
|
|
208
208
|
|
|
209
209
|
See [CHANGELOG.md](CHANGELOG.md) for the full history.
|
|
210
210
|
<!-- CHANGELOG:END -->
|
package/README.ko.md
CHANGED
|
@@ -200,11 +200,11 @@ CI는 `.github/workflows/npm-publish.yml`로 배포합니다 — GitHub 릴리
|
|
|
200
200
|
## 변경 이력 (Changelog)
|
|
201
201
|
|
|
202
202
|
<!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
|
|
203
|
+
- **[0.6.21]** (2026-06-18) — Session thinking level now reaches the provider's actual reasoning depth, not just the token ceiling.
|
|
204
|
+
- **[0.6.20]** (2026-06-18) — Launch REPL internals decomposed into testable modules: `@mention` path completion, slash-command view renderers, and slash-command handlers extracted from the monolithic `launch.ts` into dedicated files with full unit-test coverage.
|
|
203
205
|
- **[0.6.19]** (2026-06-18) — Post-turn hooks run once per batch (not per edit), local hook reads are mtime-cached, tool-result formatting is parallelized, and wrapped colored text keeps its tint.
|
|
204
206
|
- **[0.6.18]** (2026-06-17) — Memory data-flow diagram and a README "Memory flow" section documenting the actual runtime behavior.
|
|
205
207
|
- **[0.6.17]** (2026-06-17) — Legacy MEMORY.md migrates losslessly into the OKF concept bundle, with a one-shot command and a rollback toggle.
|
|
206
|
-
- **[0.6.16]** (2026-06-17) — OKF memory grows a concept cross-link graph: 1-hop search expansion, bundle lint, graphify-optional.
|
|
207
|
-
- **[0.6.15]** (2026-06-17) — Query-aware OKF memory injection with budget-priority selection, and a truthful end-of-turn Todos receipt.
|
|
208
208
|
|
|
209
209
|
See [CHANGELOG.md](CHANGELOG.md) for the full history.
|
|
210
210
|
<!-- CHANGELOG:END -->
|
package/README.md
CHANGED
|
@@ -200,11 +200,11 @@ Required npm token permissions (repository secret `NPM_TOKEN`):
|
|
|
200
200
|
## Changelog
|
|
201
201
|
|
|
202
202
|
<!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
|
|
203
|
+
- **[0.6.21]** (2026-06-18) — Session thinking level now reaches the provider's actual reasoning depth, not just the token ceiling.
|
|
204
|
+
- **[0.6.20]** (2026-06-18) — Launch REPL internals decomposed into testable modules: `@mention` path completion, slash-command view renderers, and slash-command handlers extracted from the monolithic `launch.ts` into dedicated files with full unit-test coverage.
|
|
203
205
|
- **[0.6.19]** (2026-06-18) — Post-turn hooks run once per batch (not per edit), local hook reads are mtime-cached, tool-result formatting is parallelized, and wrapped colored text keeps its tint.
|
|
204
206
|
- **[0.6.18]** (2026-06-17) — Memory data-flow diagram and a README "Memory flow" section documenting the actual runtime behavior.
|
|
205
207
|
- **[0.6.17]** (2026-06-17) — Legacy MEMORY.md migrates losslessly into the OKF concept bundle, with a one-shot command and a rollback toggle.
|
|
206
|
-
- **[0.6.16]** (2026-06-17) — OKF memory grows a concept cross-link graph: 1-hop search expansion, bundle lint, graphify-optional.
|
|
207
|
-
- **[0.6.15]** (2026-06-17) — Query-aware OKF memory injection with budget-priority selection, and a truthful end-of-turn Todos receipt.
|
|
208
208
|
|
|
209
209
|
See [CHANGELOG.md](CHANGELOG.md) for the full history.
|
|
210
210
|
<!-- CHANGELOG:END -->
|
package/README.zh.md
CHANGED
|
@@ -200,11 +200,11 @@ CI 通过 `.github/workflows/npm-publish.yml` 发布 — GitHub 发布 release
|
|
|
200
200
|
## 更新日志 (Changelog)
|
|
201
201
|
|
|
202
202
|
<!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
|
|
203
|
+
- **[0.6.21]** (2026-06-18) — Session thinking level now reaches the provider's actual reasoning depth, not just the token ceiling.
|
|
204
|
+
- **[0.6.20]** (2026-06-18) — Launch REPL internals decomposed into testable modules: `@mention` path completion, slash-command view renderers, and slash-command handlers extracted from the monolithic `launch.ts` into dedicated files with full unit-test coverage.
|
|
203
205
|
- **[0.6.19]** (2026-06-18) — Post-turn hooks run once per batch (not per edit), local hook reads are mtime-cached, tool-result formatting is parallelized, and wrapped colored text keeps its tint.
|
|
204
206
|
- **[0.6.18]** (2026-06-17) — Memory data-flow diagram and a README "Memory flow" section documenting the actual runtime behavior.
|
|
205
207
|
- **[0.6.17]** (2026-06-17) — Legacy MEMORY.md migrates losslessly into the OKF concept bundle, with a one-shot command and a rollback toggle.
|
|
206
|
-
- **[0.6.16]** (2026-06-17) — OKF memory grows a concept cross-link graph: 1-hop search expansion, bundle lint, graphify-optional.
|
|
207
|
-
- **[0.6.15]** (2026-06-17) — Query-aware OKF memory injection with budget-priority selection, and a truthful end-of-turn Todos receipt.
|
|
208
208
|
|
|
209
209
|
See [CHANGELOG.md](CHANGELOG.md) for the full history.
|
|
210
210
|
<!-- CHANGELOG:END -->
|
package/package.json
CHANGED
package/src/agent/engine.ts
CHANGED
|
@@ -28,6 +28,7 @@ async function invokeCallLlm(history: Message[], options: {
|
|
|
28
28
|
jsonMode: boolean;
|
|
29
29
|
model?: string;
|
|
30
30
|
maxTokens?: number;
|
|
31
|
+
reasoningEffort?: import("../ai/types").CallOptions["reasoningEffort"];
|
|
31
32
|
signal?: AbortSignal;
|
|
32
33
|
onUsage?: (u: { inputTokens?: number; outputTokens?: number }) => void;
|
|
33
34
|
onRetry?: (attempt: number, err: unknown, delayMs: number) => void;
|
|
@@ -205,6 +206,11 @@ export interface AgentLoopOptions {
|
|
|
205
206
|
model?: string;
|
|
206
207
|
/** Max generation tokens per step (drives the thinking budget). */
|
|
207
208
|
maxTokens?: number;
|
|
209
|
+
/** Provider reasoning depth (mapped from the live session thinking level). Threaded to
|
|
210
|
+
* callLlm so `/thinking`, `--thinking`, and `/fast` reach the provider's actual reasoning
|
|
211
|
+
* budget (Anthropic budget_tokens / OpenAI reasoning_effort / Gemini thinkingBudget), not
|
|
212
|
+
* just the max-token ceiling. When unset the model-manager falls back to the global config. */
|
|
213
|
+
reasoningEffort?: import("../ai/types").CallOptions["reasoningEffort"];
|
|
208
214
|
tools?: Record<string, ToolHandler>;
|
|
209
215
|
signal?: AbortSignal;
|
|
210
216
|
events?: AgentLoopEvents;
|
|
@@ -499,6 +505,7 @@ export async function runAgentLoop(history: Message[], opts: AgentLoopOptions):
|
|
|
499
505
|
tools: nativeToolSchemasFor(Object.keys(tools)),
|
|
500
506
|
model: opts.model,
|
|
501
507
|
maxTokens: opts.maxTokens,
|
|
508
|
+
reasoningEffort: opts.reasoningEffort,
|
|
502
509
|
signal: opts.signal,
|
|
503
510
|
onUsage: u => { acc.inputTokens += u.inputTokens ?? 0; acc.outputTokens += u.outputTokens ?? 0; sawUsage = true; },
|
|
504
511
|
onToken,
|
package/src/agent/loop.ts
CHANGED
|
@@ -10,6 +10,11 @@ export interface ChatOptions {
|
|
|
10
10
|
systemPrompt?: string;
|
|
11
11
|
temperature?: number;
|
|
12
12
|
maxTokens?: number;
|
|
13
|
+
/** Provider reasoning depth (mapped from the live session thinking level). When set it
|
|
14
|
+
* overrides the global config-derived effort, so `/thinking` and `--thinking` reach the
|
|
15
|
+
* provider's actual reasoning budget (Anthropic budget_tokens / OpenAI reasoning_effort /
|
|
16
|
+
* Gemini thinkingBudget), not just the max-token ceiling. */
|
|
17
|
+
reasoningEffort?: import("../ai/types").CallOptions["reasoningEffort"];
|
|
13
18
|
jsonMode?: boolean;
|
|
14
19
|
signal?: AbortSignal;
|
|
15
20
|
onUsage?: (usage: import("../ai/types").Usage) => void;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { tokenize } from "../../tui/components/autocomplete";
|
|
4
|
+
|
|
5
|
+
/** Filesystem completion for an `@path` mention prefix, relative to `cwd`.
|
|
6
|
+
* Lists the directory the prefix points at, filtering by the basename fragment,
|
|
7
|
+
* directories first, hidden entries dropped, capped at 50 — directories keep a
|
|
8
|
+
* trailing `/` so the next Tab descends. Pure given `cwd`: never throws (an
|
|
9
|
+
* unreadable directory yields no matches). Extracted verbatim from the launch
|
|
10
|
+
* REPL so it can be unit-tested in isolation. */
|
|
11
|
+
export function mentionPaths(cwd: string, prefix: string): string[] {
|
|
12
|
+
const norm = prefix.replace(/\\/g, "/");
|
|
13
|
+
const wantsDirChildren = norm.endsWith("/");
|
|
14
|
+
const dirPart = wantsDirChildren ? norm.slice(0, -1) : path.posix.dirname(norm) === "." ? "" : path.posix.dirname(norm);
|
|
15
|
+
const namePart = wantsDirChildren ? "" : path.posix.basename(norm);
|
|
16
|
+
const absDir = path.resolve(cwd, dirPart || ".");
|
|
17
|
+
let entries: fs.Dirent[] = [];
|
|
18
|
+
try {
|
|
19
|
+
entries = fs.readdirSync(absDir, { withFileTypes: true });
|
|
20
|
+
} catch {
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
return entries
|
|
24
|
+
.filter(entry => !entry.name.startsWith("."))
|
|
25
|
+
.filter(entry => !namePart || entry.name.toLowerCase().startsWith(namePart.toLowerCase()))
|
|
26
|
+
.sort((a, b) => {
|
|
27
|
+
if (a.isDirectory() !== b.isDirectory()) return a.isDirectory() ? -1 : 1;
|
|
28
|
+
return a.name.localeCompare(b.name);
|
|
29
|
+
})
|
|
30
|
+
.slice(0, 50)
|
|
31
|
+
.map(entry => {
|
|
32
|
+
const rel = dirPart ? `${dirPart}/${entry.name}` : entry.name;
|
|
33
|
+
return entry.isDirectory() ? `${rel}/` : rel;
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** The `@ <dir>` label shown in the boxed-input footer for the last `@mention`
|
|
38
|
+
* token on the current line, or undefined when the line has no mention. Pure. */
|
|
39
|
+
export function currentAtLabel(line: string): string | undefined {
|
|
40
|
+
const { tokens } = tokenize(line);
|
|
41
|
+
const token = [...tokens].reverse().find(t => t.startsWith("@"));
|
|
42
|
+
if (!token) return undefined;
|
|
43
|
+
const norm = token.slice(1).replace(/\\/g, "/");
|
|
44
|
+
if (!norm) return "@ .";
|
|
45
|
+
if (norm.endsWith("/")) return `@ ${norm.slice(0, -1) || "."}`;
|
|
46
|
+
const dir = path.posix.dirname(norm);
|
|
47
|
+
return `@ ${dir === "." ? norm : dir}`;
|
|
48
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slash command handlers extracted from launch.ts.
|
|
3
|
+
* Each handler is a pure function taking context and returning a result.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Message } from "../../agent/loop";
|
|
7
|
+
import type { Config } from "../../agent/state";
|
|
8
|
+
import { TOOL_PROTOCOL } from "../../agent/engine";
|
|
9
|
+
import { taskToolProtocolLine } from "../../agent/task-tool";
|
|
10
|
+
import { TODO_TOOL_PROTOCOL_LINE } from "../../agent/todo-tool";
|
|
11
|
+
import { SUBAGENT_TOOL_PROTOCOL_LINE } from "../../agent/subagent-tool";
|
|
12
|
+
import { hotkeysLines, contextUsageLines } from "./slash-views";
|
|
13
|
+
import { describeModel, catalogMetadata } from "../../ai";
|
|
14
|
+
import { readGlobalConfig } from "../../agent/state";
|
|
15
|
+
|
|
16
|
+
/** Shared context passed to all slash handlers. */
|
|
17
|
+
export interface SlashContext {
|
|
18
|
+
history: Message[];
|
|
19
|
+
sessionModel?: string;
|
|
20
|
+
sessionId?: string;
|
|
21
|
+
cwd: string;
|
|
22
|
+
config: Config;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Handler result: lines to print, or undefined if handler didn't match. */
|
|
26
|
+
export type SlashResult = { lines: string[] } | { action: "exit" | "continue" } | undefined;
|
|
27
|
+
|
|
28
|
+
/** Handles /usage command. */
|
|
29
|
+
export function handleUsage(ctx: SlashContext, sessionUsage: { turns: number; inputTokens: number; outputTokens: number }): SlashResult {
|
|
30
|
+
const total = sessionUsage.inputTokens + sessionUsage.outputTokens;
|
|
31
|
+
return {
|
|
32
|
+
lines: [
|
|
33
|
+
"Provider token usage (this REPL):",
|
|
34
|
+
` turns ${sessionUsage.turns}`,
|
|
35
|
+
` input ${sessionUsage.inputTokens}`,
|
|
36
|
+
` output ${sessionUsage.outputTokens}`,
|
|
37
|
+
` total ${total}${total === 0 ? " (providers report usage per turn; run a request first)" : ""}`,
|
|
38
|
+
],
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Handles /tools command. */
|
|
43
|
+
export async function handleTools(ctx: SlashContext): Promise<SlashResult> {
|
|
44
|
+
const lines = ["Tools visible to the agent:"];
|
|
45
|
+
for (const line of TOOL_PROTOCOL.split("\n")) lines.push(` ${line}`);
|
|
46
|
+
lines.push(` ${taskToolProtocolLine(await readGlobalConfig())}`);
|
|
47
|
+
lines.push(` ${TODO_TOOL_PROTOCOL_LINE}`);
|
|
48
|
+
lines.push(` ${SUBAGENT_TOOL_PROTOCOL_LINE}`);
|
|
49
|
+
return { lines };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Handles /hotkeys command. */
|
|
53
|
+
export function handleHotkeys(_ctx: SlashContext): SlashResult {
|
|
54
|
+
return { lines: hotkeysLines() };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Handles /context command. */
|
|
58
|
+
export async function handleContext(ctx: SlashContext): Promise<SlashResult> {
|
|
59
|
+
const { resolved } = await describeModel(ctx.sessionModel || ctx.config.defaultModel);
|
|
60
|
+
const window = catalogMetadata(resolved)?.contextTokens;
|
|
61
|
+
return { lines: contextUsageLines(ctx.history, resolved, window) };
|
|
62
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Pure presenters for read-only REPL slash commands. These build the exact
|
|
2
|
+
// line arrays the inline handlers used to `console.log` directly, extracted so
|
|
3
|
+
// they can be unit-tested without driving the whole launch REPL. They hold NO
|
|
4
|
+
// mutable session state — every input is passed in explicitly.
|
|
5
|
+
import type { Message } from "../../agent/loop";
|
|
6
|
+
|
|
7
|
+
/** Static keyboard-shortcut reference for `/hotkeys` (no inputs, no state). */
|
|
8
|
+
export function hotkeysLines(): string[] {
|
|
9
|
+
return [
|
|
10
|
+
"Keyboard shortcuts:",
|
|
11
|
+
" Tab complete slash commands, models, roles, @paths",
|
|
12
|
+
" ↑ / ↓ navigate the slash-command preview (Enter runs the highlighted one)",
|
|
13
|
+
" Enter submit input / confirm picker selection",
|
|
14
|
+
" Esc cancel an open picker",
|
|
15
|
+
" Ctrl-C cancel the in-flight turn (press again at the prompt to exit)",
|
|
16
|
+
" Ctrl-D exit the REPL",
|
|
17
|
+
" Ctrl-O dump the full last response (untruncated, tables rendered) into scrollback",
|
|
18
|
+
" Ctrl-K / Ctrl-U / Ctrl-W kill to end / start of line / previous word (emacs kill-ring)",
|
|
19
|
+
" Ctrl-Y / Alt-Y yank / yank-pop the killed text",
|
|
20
|
+
" Ctrl-A / Ctrl-E move to start / end of line",
|
|
21
|
+
" / open the slash-command palette",
|
|
22
|
+
" @path mention a file (Tab completes relative paths)",
|
|
23
|
+
];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Per-role token tallies for `/context`, estimated at ~4 chars/token. Pure over
|
|
27
|
+
* the in-memory history so the math is verifiable in isolation. */
|
|
28
|
+
export function contextUsageLines(
|
|
29
|
+
history: Message[],
|
|
30
|
+
resolved: string,
|
|
31
|
+
window: number | undefined,
|
|
32
|
+
): string[] {
|
|
33
|
+
const est = (s: string) => Math.ceil(s.length / 4);
|
|
34
|
+
const byRole: Record<string, { msgs: number; tokens: number }> = {};
|
|
35
|
+
for (const m of history) {
|
|
36
|
+
const slot = (byRole[m.role] ??= { msgs: 0, tokens: 0 });
|
|
37
|
+
slot.msgs++;
|
|
38
|
+
slot.tokens += est(m.content);
|
|
39
|
+
}
|
|
40
|
+
const total = Object.values(byRole).reduce((sum, r) => sum + r.tokens, 0);
|
|
41
|
+
const lines = ["Context usage (estimated, ~4 chars/token):"];
|
|
42
|
+
for (const [role, r] of Object.entries(byRole)) {
|
|
43
|
+
lines.push(` ${role.padEnd(9)} ${String(r.msgs).padStart(3)} msg${r.msgs === 1 ? " " : "s"} ~${r.tokens} tokens`);
|
|
44
|
+
}
|
|
45
|
+
lines.push(` ${"total".padEnd(9)} ${String(history.length).padStart(3)} msgs ~${total} tokens${window ? ` (${Math.round((total / window) * 100)}% of ${resolved}'s ${window}-token window)` : ""}`);
|
|
46
|
+
lines.push(" Free context with /compact or /clear.");
|
|
47
|
+
return lines;
|
|
48
|
+
}
|
package/src/commands/launch.ts
CHANGED
|
@@ -36,7 +36,7 @@ import { callLlm, type Message } from "../agent/loop";
|
|
|
36
36
|
import { friendlyProviderError } from "../util/provider-error";
|
|
37
37
|
import { readGlobalConfig, saveConfigPatch } from "../agent/state";
|
|
38
38
|
import { rememberModelPatch, recentModelsForDisplay } from "../agent/model-recency";
|
|
39
|
-
import { describeModel, describeAllProviders, thinkingMaxTokens, discoverModels, flattenModels, resolveSelection, catalogMetadata, resolveRoleModel, CODEX_MODELS, qualifyModelId } from "../ai";
|
|
39
|
+
import { describeModel, describeAllProviders, thinkingMaxTokens, thinkingToReasoningEffort, discoverModels, flattenModels, resolveSelection, catalogMetadata, resolveRoleModel, CODEX_MODELS, qualifyModelId } from "../ai";
|
|
40
40
|
import type { ProviderModelsResult, PickEntry, ProviderName, ModelRole, ThinkLevel } from "../ai";
|
|
41
41
|
import { readGoalState, writeGoalState, clearGoalState, verifyGoal } from "../agent/goal-verifier";
|
|
42
42
|
|
|
@@ -147,6 +147,21 @@ import {
|
|
|
147
147
|
isWorkflowSkill,
|
|
148
148
|
runWorkflowEngine,
|
|
149
149
|
} from "./launch/workflow";
|
|
150
|
+
import {
|
|
151
|
+
mentionPaths as mentionPathsIn,
|
|
152
|
+
currentAtLabel as currentAtLabelFn,
|
|
153
|
+
} from "./launch/mentions";
|
|
154
|
+
import {
|
|
155
|
+
hotkeysLines,
|
|
156
|
+
contextUsageLines,
|
|
157
|
+
} from "./launch/slash-views";
|
|
158
|
+
import {
|
|
159
|
+
handleUsage,
|
|
160
|
+
handleTools,
|
|
161
|
+
handleHotkeys,
|
|
162
|
+
handleContext,
|
|
163
|
+
type SlashContext,
|
|
164
|
+
} from "./launch/slash-handlers";
|
|
150
165
|
|
|
151
166
|
export {
|
|
152
167
|
type LaunchFlags,
|
|
@@ -192,6 +207,8 @@ export {
|
|
|
192
207
|
WORKFLOW_NAMES,
|
|
193
208
|
isWorkflowSkill,
|
|
194
209
|
runWorkflowEngine,
|
|
210
|
+
mentionPathsIn as mentionPaths,
|
|
211
|
+
currentAtLabelFn as currentAtLabel,
|
|
195
212
|
};
|
|
196
213
|
export function normalizeSlashAlias(input: string): string {
|
|
197
214
|
if (input === "/login" || input.startsWith("/login ")) return `/provider login${input.slice("/login".length)}`;
|
|
@@ -769,6 +786,7 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
|
|
|
769
786
|
maxSteps: flags.maxSteps,
|
|
770
787
|
model: sessionModel,
|
|
771
788
|
maxTokens: sessionThinking ? thinkingMaxTokens(sessionThinking) : undefined,
|
|
789
|
+
reasoningEffort: sessionThinking ? thinkingToReasoningEffort(sessionThinking) : undefined,
|
|
772
790
|
signal: ac.signal,
|
|
773
791
|
steer: drainSteer,
|
|
774
792
|
events: wrapEvents(withStepPersistence({ ...withToolDetailCapture(tui ? tui.events() : streamEvents), onBeforeDone }, persistTurnTail), opik),
|
|
@@ -787,6 +805,7 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
|
|
|
787
805
|
budget: { maxExtensions: 0 },
|
|
788
806
|
model: sessionModel,
|
|
789
807
|
maxTokens: sessionThinking ? thinkingMaxTokens(sessionThinking) : undefined,
|
|
808
|
+
reasoningEffort: sessionThinking ? thinkingToReasoningEffort(sessionThinking) : undefined,
|
|
790
809
|
signal: ac.signal,
|
|
791
810
|
steer: drainSteer,
|
|
792
811
|
events: wrapEvents(withToolDetailCapture(tui ? tui.events() : streamEvents), opik),
|
|
@@ -1158,31 +1177,7 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
|
|
|
1158
1177
|
liveModelsCache ??= r;
|
|
1159
1178
|
})
|
|
1160
1179
|
.catch(() => {});
|
|
1161
|
-
const mentionPaths = (prefix: string): string[] =>
|
|
1162
|
-
const norm = prefix.replace(/\\/g, "/");
|
|
1163
|
-
const wantsDirChildren = norm.endsWith("/");
|
|
1164
|
-
const dirPart = wantsDirChildren ? norm.slice(0, -1) : path.posix.dirname(norm) === "." ? "" : path.posix.dirname(norm);
|
|
1165
|
-
const namePart = wantsDirChildren ? "" : path.posix.basename(norm);
|
|
1166
|
-
const absDir = path.resolve(cwd, dirPart || ".");
|
|
1167
|
-
let entries: fs.Dirent[] = [];
|
|
1168
|
-
try {
|
|
1169
|
-
entries = fs.readdirSync(absDir, { withFileTypes: true });
|
|
1170
|
-
} catch {
|
|
1171
|
-
return [];
|
|
1172
|
-
}
|
|
1173
|
-
return entries
|
|
1174
|
-
.filter(entry => !entry.name.startsWith("."))
|
|
1175
|
-
.filter(entry => !namePart || entry.name.toLowerCase().startsWith(namePart.toLowerCase()))
|
|
1176
|
-
.sort((a, b) => {
|
|
1177
|
-
if (a.isDirectory() !== b.isDirectory()) return a.isDirectory() ? -1 : 1;
|
|
1178
|
-
return a.name.localeCompare(b.name);
|
|
1179
|
-
})
|
|
1180
|
-
.slice(0, 50)
|
|
1181
|
-
.map(entry => {
|
|
1182
|
-
const rel = dirPart ? `${dirPart}/${entry.name}` : entry.name;
|
|
1183
|
-
return entry.isDirectory() ? `${rel}/` : rel;
|
|
1184
|
-
});
|
|
1185
|
-
};
|
|
1180
|
+
const mentionPaths = (prefix: string): string[] => mentionPathsIn(cwd, prefix);
|
|
1186
1181
|
const completionContext = (): CompletionContext => {
|
|
1187
1182
|
const base = staticCompletionContext();
|
|
1188
1183
|
return {
|
|
@@ -1512,16 +1507,7 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
|
|
|
1512
1507
|
// turns/command output so the full-screen turn TUI renders normally. The footer
|
|
1513
1508
|
// is drawn at absolute rows (per-row clear → no scroll, no duplication).
|
|
1514
1509
|
// Opt out with JEO_NO_SLASH_PREVIEW=1; auto-off on short terminals.
|
|
1515
|
-
const currentAtLabel = (line: string): string | undefined =>
|
|
1516
|
-
const { tokens } = tokenize(line);
|
|
1517
|
-
const token = [...tokens].reverse().find(t => t.startsWith("@"));
|
|
1518
|
-
if (!token) return undefined;
|
|
1519
|
-
const norm = token.slice(1).replace(/\\/g, "/");
|
|
1520
|
-
if (!norm) return "@ .";
|
|
1521
|
-
if (norm.endsWith("/")) return `@ ${norm.slice(0, -1) || "."}`;
|
|
1522
|
-
const dir = path.posix.dirname(norm);
|
|
1523
|
-
return `@ ${dir === "." ? norm : dir}`;
|
|
1524
|
-
};
|
|
1510
|
+
const currentAtLabel = (line: string): string | undefined => currentAtLabelFn(line);
|
|
1525
1511
|
// Boxed-input footer height — ADAPTIVE so short terminals/panes still get the single
|
|
1526
1512
|
// boxed input instead of silently falling back to the raw `jeo>` prompt (previously
|
|
1527
1513
|
// any terminal under 17 rows lost the box entirely and showed bare CLI input).
|
|
@@ -2893,56 +2879,31 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
|
|
|
2893
2879
|
}
|
|
2894
2880
|
// ---- gjc-parity inspection commands ------------------------------------
|
|
2895
2881
|
if (input === "/usage") {
|
|
2896
|
-
const
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
console.log(
|
|
2900
|
-
console.log(` output ${sessionUsage.outputTokens}`);
|
|
2901
|
-
console.log(` total ${total}${total === 0 ? " (providers report usage per turn; run a request first)" : ""}`);
|
|
2882
|
+
const cfg = await readGlobalConfig();
|
|
2883
|
+
const ctx: SlashContext = { history, sessionModel, sessionId, cwd, config: cfg };
|
|
2884
|
+
const result = handleUsage(ctx, sessionUsage);
|
|
2885
|
+
if (result && "lines" in result) for (const line of result.lines) console.log(line);
|
|
2902
2886
|
continue;
|
|
2903
2887
|
}
|
|
2904
2888
|
if (input === "/context") {
|
|
2905
|
-
|
|
2906
|
-
const
|
|
2907
|
-
const
|
|
2908
|
-
for (const
|
|
2909
|
-
const slot = (byRole[m.role] ??= { msgs: 0, tokens: 0 });
|
|
2910
|
-
slot.msgs++;
|
|
2911
|
-
slot.tokens += est(m.content);
|
|
2912
|
-
}
|
|
2913
|
-
const total = Object.values(byRole).reduce((sum, r) => sum + r.tokens, 0);
|
|
2914
|
-
const { resolved } = await describeModel(sessionModel || (await readGlobalConfig()).defaultModel);
|
|
2915
|
-
const window = catalogMetadata(resolved)?.contextTokens;
|
|
2916
|
-
console.log("Context usage (estimated, ~4 chars/token):");
|
|
2917
|
-
for (const [role, r] of Object.entries(byRole)) {
|
|
2918
|
-
console.log(` ${role.padEnd(9)} ${String(r.msgs).padStart(3)} msg${r.msgs === 1 ? " " : "s"} ~${r.tokens} tokens`);
|
|
2919
|
-
}
|
|
2920
|
-
console.log(` ${"total".padEnd(9)} ${String(history.length).padStart(3)} msgs ~${total} tokens${window ? ` (${Math.round((total / window) * 100)}% of ${resolved}'s ${window}-token window)` : ""}`);
|
|
2921
|
-
console.log(" Free context with /compact or /clear.");
|
|
2889
|
+
const cfg = await readGlobalConfig();
|
|
2890
|
+
const ctx: SlashContext = { history, sessionModel, sessionId, cwd, config: cfg };
|
|
2891
|
+
const result = await handleContext(ctx);
|
|
2892
|
+
if (result && "lines" in result) for (const line of result.lines) console.log(line);
|
|
2922
2893
|
continue;
|
|
2923
2894
|
}
|
|
2924
2895
|
if (input === "/tools") {
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
console.log(
|
|
2929
|
-
console.log(` ${SUBAGENT_TOOL_PROTOCOL_LINE}`);
|
|
2896
|
+
const cfg = await readGlobalConfig();
|
|
2897
|
+
const ctx: SlashContext = { history, sessionModel, sessionId, cwd, config: cfg };
|
|
2898
|
+
const result = await handleTools(ctx);
|
|
2899
|
+
if (result && "lines" in result) for (const line of result.lines) console.log(line);
|
|
2930
2900
|
continue;
|
|
2931
2901
|
}
|
|
2932
2902
|
if (input === "/hotkeys") {
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
console.log(" Esc cancel an open picker");
|
|
2938
|
-
console.log(" Ctrl-C cancel the in-flight turn (press again at the prompt to exit)");
|
|
2939
|
-
console.log(" Ctrl-D exit the REPL");
|
|
2940
|
-
console.log(" Ctrl-O dump the full last response (untruncated, tables rendered) into scrollback");
|
|
2941
|
-
console.log(" Ctrl-K / Ctrl-U / Ctrl-W kill to end / start of line / previous word (emacs kill-ring)");
|
|
2942
|
-
console.log(" Ctrl-Y / Alt-Y yank / yank-pop the killed text");
|
|
2943
|
-
console.log(" Ctrl-A / Ctrl-E move to start / end of line");
|
|
2944
|
-
console.log(" / open the slash-command palette");
|
|
2945
|
-
console.log(" @path mention a file (Tab completes relative paths)");
|
|
2903
|
+
const cfg = await readGlobalConfig();
|
|
2904
|
+
const ctx: SlashContext = { history, sessionModel, sessionId, cwd, config: cfg };
|
|
2905
|
+
const result = handleHotkeys(ctx);
|
|
2906
|
+
if (result && "lines" in result) for (const line of result.lines) console.log(line);
|
|
2946
2907
|
continue;
|
|
2947
2908
|
}
|
|
2948
2909
|
if (input === "/theme" || input.startsWith("/theme ")) {
|