jeopi-cli 16.4.2 → 16.4.4
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 +74 -0
- package/dist/cli.js +3033 -2991
- package/dist/types/advisor/advise-tool.d.ts +5 -1
- package/dist/types/advisor/config.d.ts +4 -3
- package/dist/types/advisor/runtime.d.ts +15 -0
- package/dist/types/cli/args.d.ts +2 -0
- package/dist/types/cli/update-cli.d.ts +4 -1
- package/dist/types/cli/usage-error.d.ts +4 -0
- package/dist/types/commit/agentic/agent.d.ts +1 -0
- package/dist/types/config/model-registry.d.ts +3 -3
- package/dist/types/config/model-resolver.d.ts +7 -1
- package/dist/types/config/models-config.d.ts +1 -1
- package/dist/types/config/settings-schema.d.ts +3 -13
- package/dist/types/eval/py/tool-bridge.d.ts +1 -0
- package/dist/types/extensibility/custom-tools/types.d.ts +3 -0
- package/dist/types/extensibility/extensions/runner.d.ts +3 -1
- package/dist/types/extensibility/extensions/types.d.ts +3 -0
- package/dist/types/internal-urls/memory-protocol.d.ts +6 -6
- package/dist/types/internal-urls/types.d.ts +7 -0
- package/dist/types/irc/bus.d.ts +4 -0
- package/dist/types/lsp/deferred-diagnostics.d.ts +11 -0
- package/dist/types/modes/components/move-overlay.d.ts +1 -1
- package/dist/types/modes/components/plan-review-overlay.d.ts +2 -0
- package/dist/types/modes/components/welcome.d.ts +4 -0
- package/dist/types/modes/magic-keyword-boundary.d.ts +9 -0
- package/dist/types/modes/orchestrate.d.ts +1 -1
- package/dist/types/modes/rpc/host-tools.d.ts +2 -0
- package/dist/types/modes/rpc/rpc-mode.d.ts +27 -7
- package/dist/types/modes/ultrathink.d.ts +1 -1
- package/dist/types/modes/utils/transcript-render-helpers.d.ts +9 -0
- package/dist/types/modes/workflow.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +6 -0
- package/dist/types/session/auth-broker-config.d.ts +2 -2
- package/dist/types/tools/bash-skill-urls.d.ts +3 -2
- package/dist/types/tools/browser/cmux/cmux-tab.d.ts +3 -1
- package/dist/types/tools/browser/cmux/rpc.d.ts +20 -0
- package/dist/types/tools/browser/launch.d.ts +1 -0
- package/dist/types/tools/browser/run-cancellation.d.ts +17 -2
- package/dist/types/tools/browser/run-output.d.ts +25 -0
- package/dist/types/tools/browser/tab-worker.d.ts +22 -0
- package/dist/types/utils/changelog.d.ts +35 -1
- package/package.json +12 -12
- package/src/advisor/__tests__/advisor.test.ts +13 -0
- package/src/advisor/__tests__/config.test.ts +36 -0
- package/src/advisor/advise-tool.ts +6 -1
- package/src/advisor/config.ts +11 -8
- package/src/advisor/runtime.ts +127 -0
- package/src/autolearn/controller.ts +15 -3
- package/src/cli/args.ts +12 -0
- package/src/cli/flag-tables.ts +21 -7
- package/src/cli/update-cli.ts +63 -13
- package/src/cli/usage-error.ts +7 -0
- package/src/commands/acp.ts +11 -2
- package/src/commands/launch.ts +12 -3
- package/src/commit/agentic/agent.ts +4 -0
- package/src/commit/agentic/index.ts +46 -21
- package/src/config/config-file.ts +30 -12
- package/src/config/model-registry.ts +7 -7
- package/src/config/model-resolver.ts +24 -6
- package/src/config/models-config.ts +1 -1
- package/src/config/settings-schema.ts +3 -15
- package/src/edit/index.ts +10 -76
- package/src/edit/renderer.ts +13 -10
- package/src/eval/__tests__/agent-bridge.test.ts +19 -14
- package/src/eval/__tests__/bridge-timeout.test.ts +60 -0
- package/src/eval/executor-base.ts +84 -7
- package/src/eval/js/shared/rewrite-imports.ts +31 -13
- package/src/eval/py/tool-bridge.ts +13 -14
- package/src/exec/non-interactive-env.ts +0 -1
- package/src/export/ttsr.ts +0 -3
- package/src/extensibility/custom-tools/loader.ts +70 -37
- package/src/extensibility/custom-tools/types.ts +3 -0
- package/src/extensibility/extensions/runner.ts +3 -0
- package/src/extensibility/extensions/types.ts +3 -0
- package/src/extensibility/plugins/legacy-pi-bundled-keys.ts +9 -1
- package/src/extensibility/plugins/legacy-pi-bundled-registry.ts +30 -3
- package/src/extensibility/plugins/manager.ts +21 -0
- package/src/internal-urls/docs-index.generated.txt +1 -1
- package/src/internal-urls/memory-protocol.ts +13 -8
- package/src/internal-urls/skill-protocol.ts +1 -1
- package/src/internal-urls/types.ts +7 -0
- package/src/irc/bus.ts +48 -18
- package/src/lsp/client.ts +7 -1
- package/src/lsp/deferred-diagnostics.ts +66 -0
- package/src/main.ts +13 -13
- package/src/mcp/tool-bridge.ts +57 -6
- package/src/mcp/transports/stdio.test.ts +45 -1
- package/src/mcp/transports/stdio.ts +6 -3
- package/src/modes/acp/acp-agent.ts +73 -2
- package/src/modes/acp/acp-event-mapper.ts +5 -0
- package/src/modes/acp/acp-mode.ts +11 -0
- package/src/modes/components/__tests__/move-overlay.test.ts +16 -1
- package/src/modes/components/advisor-config.ts +15 -7
- package/src/modes/components/chat-transcript-builder.ts +31 -11
- package/src/modes/components/move-overlay.ts +2 -3
- package/src/modes/components/plan-review-overlay.ts +7 -0
- package/src/modes/components/tips.txt +1 -1
- package/src/modes/components/welcome.ts +13 -14
- package/src/modes/controllers/command-controller.ts +8 -10
- package/src/modes/controllers/event-controller.ts +58 -42
- package/src/modes/controllers/input-controller.ts +9 -6
- package/src/modes/controllers/selector-controller.ts +3 -2
- package/src/modes/controllers/tan-command-controller.ts +40 -1
- package/src/modes/interactive-mode.ts +13 -0
- package/src/modes/magic-keyword-boundary.ts +23 -0
- package/src/modes/orchestrate.ts +6 -5
- package/src/modes/print-mode.ts +9 -0
- package/src/modes/rpc/host-tools.ts +13 -0
- package/src/modes/rpc/rpc-mode.ts +130 -56
- package/src/modes/ultrathink.ts +6 -5
- package/src/modes/utils/transcript-render-helpers.ts +44 -0
- package/src/modes/utils/ui-helpers.ts +16 -1
- package/src/modes/workflow.ts +5 -4
- package/src/prompts/system/eager-task.md +2 -2
- package/src/prompts/system/tan-context-switch.md +17 -0
- package/src/prompts/tools/browser.md +5 -5
- package/src/sdk.ts +3 -0
- package/src/session/agent-session.ts +267 -36
- package/src/session/auth-broker-config.ts +4 -4
- package/src/session/snapcompact-inline.ts +3 -19
- package/src/session/streaming-output.ts +40 -12
- package/src/slash-commands/builtin-registry.ts +8 -8
- package/src/task/executor.ts +8 -0
- package/src/task/render.ts +51 -44
- package/src/tools/bash-skill-urls.ts +7 -4
- package/src/tools/bash.ts +30 -33
- package/src/tools/browser/cmux/cmux-tab.ts +49 -50
- package/src/tools/browser/cmux/rpc.ts +50 -0
- package/src/tools/browser/launch.ts +8 -4
- package/src/tools/browser/run-cancellation.ts +44 -5
- package/src/tools/browser/run-output.ts +76 -0
- package/src/tools/browser/tab-worker.ts +280 -133
- package/src/tools/eval-render.ts +16 -14
- package/src/tools/glob.ts +20 -6
- package/src/tools/grep.ts +1 -0
- package/src/tools/image-gen.ts +3 -4
- package/src/tools/irc.ts +27 -13
- package/src/tools/path-utils.ts +6 -1
- package/src/tools/read.ts +14 -7
- package/src/tools/write.ts +22 -5
- package/src/tools/yield.ts +29 -1
- package/src/utils/changelog.ts +106 -9
- package/src/utils/git.ts +0 -1
- package/src/utils/title-generator.ts +72 -7
- package/src/web/search/index.ts +14 -6
- package/src/web/search/providers/perplexity.ts +18 -2
- package/dist/types/tools/bash-command-fixup.d.ts +0 -11
- package/src/tools/bash-command-fixup.ts +0 -37
|
@@ -3,12 +3,14 @@ import * as fs from "node:fs";
|
|
|
3
3
|
import * as fsp from "node:fs/promises";
|
|
4
4
|
import * as os from "node:os";
|
|
5
5
|
import * as path from "node:path";
|
|
6
|
+
import { visibleWidth } from "jeopi-tui";
|
|
6
7
|
import { Settings } from "../../../config/settings";
|
|
7
8
|
import { getThemeByName, setThemeInstance, type Theme } from "../../theme/theme";
|
|
8
9
|
import { MoveOverlay, type MoveOverlayResult, resolveExistingDirectory, resolveMovePath } from "../move-overlay";
|
|
9
10
|
|
|
10
11
|
// Strip SGR colors so assertions see visible text only.
|
|
11
|
-
const
|
|
12
|
+
const stripAnsi = (text: string): string => text.replace(/\x1b\[[0-9;]*m/g, "");
|
|
13
|
+
const strip = (lines: readonly string[]): string => lines.map(stripAnsi).join("\n");
|
|
12
14
|
|
|
13
15
|
describe("resolveMovePath", () => {
|
|
14
16
|
it("expands ~ to homedir", () => {
|
|
@@ -82,6 +84,19 @@ describe("MoveOverlay", () => {
|
|
|
82
84
|
expect(text).toContain("Path:");
|
|
83
85
|
});
|
|
84
86
|
|
|
87
|
+
it("renders every frame row at the assigned overlay width", () => {
|
|
88
|
+
const overlay = new MoveOverlay(cwd, () => {});
|
|
89
|
+
const lines = overlay.render(72);
|
|
90
|
+
const plainLines = lines.map(stripAnsi);
|
|
91
|
+
|
|
92
|
+
expect(lines.map(line => visibleWidth(line))).toEqual(Array(lines.length).fill(72));
|
|
93
|
+
expect(plainLines[0]!.endsWith(uiTheme.boxRound.topRight)).toBe(true);
|
|
94
|
+
expect(plainLines.at(-1)!.endsWith(uiTheme.boxRound.bottomRight)).toBe(true);
|
|
95
|
+
for (const line of plainLines.slice(1, -1)) {
|
|
96
|
+
expect(line.endsWith(uiTheme.boxRound.vertical)).toBe(true);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
85
100
|
it("lists child directories (excluding hidden and files) on empty input", () => {
|
|
86
101
|
const overlay = new MoveOverlay(cwd, () => {});
|
|
87
102
|
const text = strip(overlay.render(80));
|
|
@@ -82,9 +82,9 @@ function previewLine(text: string | undefined): string {
|
|
|
82
82
|
return first.length > PREVIEW_WIDTH ? `${first.slice(0, PREVIEW_WIDTH - 1)}…` : first;
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
/**
|
|
85
|
+
/** Omitted means default read/grep/glob; an explicit empty set means no tools. */
|
|
86
86
|
function commitTools(selected: ReadonlySet<string>, all: readonly string[]): string[] | undefined {
|
|
87
|
-
if (selected.size === 0) return
|
|
87
|
+
if (selected.size === 0) return [];
|
|
88
88
|
if (selected.size === ADVISOR_DEFAULT_TOOL_NAMES.size) {
|
|
89
89
|
let matchesDefault = true;
|
|
90
90
|
for (const name of ADVISOR_DEFAULT_TOOL_NAMES) {
|
|
@@ -98,6 +98,11 @@ function commitTools(selected: ReadonlySet<string>, all: readonly string[]): str
|
|
|
98
98
|
return all.filter(name => selected.has(name));
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
function formatAdvisorTools(tools: readonly string[] | undefined, emptyLabel: string): string {
|
|
102
|
+
if (tools === undefined) return "read, grep, glob (default)";
|
|
103
|
+
return tools.length > 0 ? tools.join(", ") : emptyLabel;
|
|
104
|
+
}
|
|
105
|
+
|
|
101
106
|
/** Soft-wrap plain text to `width`, returning at least one (possibly empty) line. */
|
|
102
107
|
function wrap(text: string, width: number): string[] {
|
|
103
108
|
if (!text) return [""];
|
|
@@ -266,7 +271,7 @@ export class AdvisorConfigOverlayComponent implements Component {
|
|
|
266
271
|
|
|
267
272
|
#advisorPreview(advisor: AdvisorConfig, bodyWidth: number): string[] {
|
|
268
273
|
const model = advisor.model?.trim() || this.#defaultModelLabel || "advisor role default";
|
|
269
|
-
const tools = advisor.tools
|
|
274
|
+
const tools = formatAdvisorTools(advisor.tools, "no tools");
|
|
270
275
|
const lines = [
|
|
271
276
|
theme.bold(advisor.name || "(unnamed)"),
|
|
272
277
|
"",
|
|
@@ -303,13 +308,16 @@ export class AdvisorConfigOverlayComponent implements Component {
|
|
|
303
308
|
const advisor = doc.advisors[0];
|
|
304
309
|
if (!advisor) return false;
|
|
305
310
|
return (
|
|
306
|
-
advisor.name === "default" &&
|
|
311
|
+
advisor.name === "default" &&
|
|
312
|
+
!advisor.model?.trim() &&
|
|
313
|
+
advisor.tools === undefined &&
|
|
314
|
+
!advisor.instructions?.trim()
|
|
307
315
|
);
|
|
308
316
|
}
|
|
309
317
|
|
|
310
318
|
#advisorSummary(advisor: AdvisorConfig): string {
|
|
311
319
|
const model = advisor.model?.trim() || this.#defaultModelLabel || "advisor role default";
|
|
312
|
-
const tools = advisor.tools
|
|
320
|
+
const tools = formatAdvisorTools(advisor.tools, "no tools");
|
|
313
321
|
return `${model} · ${tools}`;
|
|
314
322
|
}
|
|
315
323
|
|
|
@@ -384,7 +392,7 @@ export class AdvisorConfigOverlayComponent implements Component {
|
|
|
384
392
|
return;
|
|
385
393
|
}
|
|
386
394
|
const modelDescription = advisor.model?.trim() || this.#defaultModelLabel || "advisor role default";
|
|
387
|
-
const toolsDescription = advisor.tools
|
|
395
|
+
const toolsDescription = formatAdvisorTools(advisor.tools, "no tools");
|
|
388
396
|
const items: SelectItem[] = [
|
|
389
397
|
{ value: "name", label: "Name", description: advisor.name },
|
|
390
398
|
{ value: "model", label: "Model", description: modelDescription },
|
|
@@ -524,7 +532,7 @@ export class AdvisorConfigOverlayComponent implements Component {
|
|
|
524
532
|
this.#setScreen(
|
|
525
533
|
"tools",
|
|
526
534
|
list,
|
|
527
|
-
"Enter / click toggle · select Done or Esc to apply (empty
|
|
535
|
+
"Enter / click toggle · select Done or Esc to apply (empty = no tools; read/grep/glob = default)",
|
|
528
536
|
);
|
|
529
537
|
}
|
|
530
538
|
|
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
buildIrcMessageCard,
|
|
35
35
|
normalizeToolArgs,
|
|
36
36
|
resolveAssistantErrorMessage,
|
|
37
|
+
splitAssistantMessageToolTimeline,
|
|
37
38
|
} from "../utils/transcript-render-helpers";
|
|
38
39
|
import { createAdvisorMessageCard } from "./advisor-message";
|
|
39
40
|
import { AssistantMessageComponent } from "./assistant-message";
|
|
@@ -268,13 +269,16 @@ export class ChatTranscriptBuilder {
|
|
|
268
269
|
}
|
|
269
270
|
|
|
270
271
|
#appendAssistantMessage(message: Extract<AgentMessage, { role: "assistant" }>): void {
|
|
272
|
+
const hideThinkingBlock = this.deps.hideThinkingBlock?.() ?? false;
|
|
273
|
+
const proseOnlyThinking = this.deps.proseOnlyThinking ? this.deps.proseOnlyThinking() : true;
|
|
274
|
+
const timeline = splitAssistantMessageToolTimeline(message);
|
|
271
275
|
const assistantComponent = new AssistantMessageComponent(
|
|
272
|
-
|
|
273
|
-
|
|
276
|
+
timeline.beforeTools,
|
|
277
|
+
hideThinkingBlock,
|
|
274
278
|
() => this.deps.requestRender(),
|
|
275
|
-
this.deps.getMessageRenderer ? undefined : [],
|
|
276
|
-
undefined,
|
|
277
|
-
|
|
279
|
+
this.deps.getMessageRenderer ? undefined : [],
|
|
280
|
+
undefined,
|
|
281
|
+
proseOnlyThinking,
|
|
278
282
|
);
|
|
279
283
|
this.container.addChild(assistantComponent);
|
|
280
284
|
|
|
@@ -286,18 +290,30 @@ export class ChatTranscriptBuilder {
|
|
|
286
290
|
this.#lastAssistantUsage = message.usage;
|
|
287
291
|
}
|
|
288
292
|
|
|
289
|
-
|
|
290
|
-
if (hasVisibleAssistantContent) {
|
|
291
|
-
// New visible turn content closes the current read run (mirrors rebuild).
|
|
293
|
+
if (assistantHasVisibleContent(message)) {
|
|
292
294
|
this.#readGroup?.seal();
|
|
293
295
|
this.#readGroup = null;
|
|
294
296
|
}
|
|
295
297
|
|
|
296
298
|
const { hasErrorStop, errorMessage } = resolveAssistantErrorMessage(message);
|
|
299
|
+
const appendAssistantSegment = (segment: Extract<AgentMessage, { role: "assistant" }> | undefined) => {
|
|
300
|
+
if (!segment || !assistantHasVisibleContent(segment)) return;
|
|
301
|
+
this.container.addChild(
|
|
302
|
+
new AssistantMessageComponent(
|
|
303
|
+
segment,
|
|
304
|
+
hideThinkingBlock,
|
|
305
|
+
() => this.deps.requestRender(),
|
|
306
|
+
this.deps.getMessageRenderer ? undefined : [],
|
|
307
|
+
undefined,
|
|
308
|
+
proseOnlyThinking,
|
|
309
|
+
),
|
|
310
|
+
);
|
|
311
|
+
};
|
|
297
312
|
|
|
298
313
|
for (const content of message.content) {
|
|
299
314
|
if (content.type !== "toolCall") continue;
|
|
300
315
|
this.#resolveWaitingPoll(content.name);
|
|
316
|
+
const afterToolSegment = timeline.afterToolCalls.get(content.id);
|
|
301
317
|
|
|
302
318
|
if (
|
|
303
319
|
content.name === "read" &&
|
|
@@ -312,10 +328,14 @@ export class ChatTranscriptBuilder {
|
|
|
312
328
|
false,
|
|
313
329
|
content.id,
|
|
314
330
|
);
|
|
331
|
+
} else if (afterToolSegment) {
|
|
332
|
+
const group = this.#ensureReadGroup();
|
|
333
|
+
group.updateArgs(content.arguments, content.id);
|
|
334
|
+
this.#pendingTools.set(content.id, group);
|
|
315
335
|
} else {
|
|
316
|
-
|
|
317
|
-
this.#readArgs.set(content.id, normalizedArgs);
|
|
336
|
+
this.#readArgs.set(content.id, normalizeToolArgs(content.arguments));
|
|
318
337
|
}
|
|
338
|
+
appendAssistantSegment(afterToolSegment);
|
|
319
339
|
continue;
|
|
320
340
|
}
|
|
321
341
|
|
|
@@ -325,7 +345,6 @@ export class ChatTranscriptBuilder {
|
|
|
325
345
|
content.name,
|
|
326
346
|
content.arguments,
|
|
327
347
|
{
|
|
328
|
-
// Images can't be sliced through the scroll viewport; keep them off.
|
|
329
348
|
showImages: false,
|
|
330
349
|
editFuzzyThreshold: settings.get("edit.fuzzyThreshold"),
|
|
331
350
|
editAllowFuzzy: settings.get("edit.fuzzyMatch"),
|
|
@@ -348,6 +367,7 @@ export class ChatTranscriptBuilder {
|
|
|
348
367
|
} else {
|
|
349
368
|
this.#pendingTools.set(content.id, component);
|
|
350
369
|
}
|
|
370
|
+
appendAssistantSegment(afterToolSegment);
|
|
351
371
|
}
|
|
352
372
|
|
|
353
373
|
this.#pendingUsage = settings.get("display.showTokenUsage") ? message.usage : undefined;
|
|
@@ -25,7 +25,6 @@ interface DirEntry {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
const MAX_RESULTS = 15;
|
|
28
|
-
const OVERLAY_WIDTH = 68;
|
|
29
28
|
|
|
30
29
|
/** TTL for the directory listing cache (ms). */
|
|
31
30
|
const DIR_CACHE_TTL = 500;
|
|
@@ -218,8 +217,8 @@ export class MoveOverlay implements Component, Focusable {
|
|
|
218
217
|
}
|
|
219
218
|
}
|
|
220
219
|
|
|
221
|
-
render(
|
|
222
|
-
const w =
|
|
220
|
+
render(width: number): readonly string[] {
|
|
221
|
+
const w = width;
|
|
223
222
|
const lines: string[] = [];
|
|
224
223
|
|
|
225
224
|
lines.push(topBorder(w, "Move to directory"));
|
|
@@ -84,6 +84,8 @@ export interface PlanReviewOverlayCallbacks {
|
|
|
84
84
|
onCancel: () => void;
|
|
85
85
|
/** Invoked when the external-editor key is pressed (overlay stays open). */
|
|
86
86
|
onExternalEditor?: () => void;
|
|
87
|
+
/** Invoked with the current full plan text when the copy hotkey is pressed. */
|
|
88
|
+
onCopyPlan?: (content: string) => void | Promise<void>;
|
|
87
89
|
/** Invoked when the external-editor key edits the active annotation draft. */
|
|
88
90
|
onAnnotationExternalEditor?: (draft: string, commit: (text: string | null) => void) => void;
|
|
89
91
|
/** Invoked with the new full plan text after an in-overlay delete/undo. */
|
|
@@ -302,6 +304,10 @@ export class PlanReviewOverlay implements Component {
|
|
|
302
304
|
this.callbacks.onExternalEditor();
|
|
303
305
|
return;
|
|
304
306
|
}
|
|
307
|
+
if (this.callbacks.onCopyPlan && keyData === "c") {
|
|
308
|
+
void this.callbacks.onCopyPlan(joinPlanSections(this.#sections));
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
305
311
|
if (matchesKey(keyData, "tab") || keyData === "\t") {
|
|
306
312
|
this.#cycleRegion(1);
|
|
307
313
|
return;
|
|
@@ -677,6 +683,7 @@ export class PlanReviewOverlay implements Component {
|
|
|
677
683
|
parts.push("↑↓ scroll", "⇧ faster", "pgup/pgdn", "g/G ends");
|
|
678
684
|
break;
|
|
679
685
|
}
|
|
686
|
+
if (this.callbacks.onCopyPlan) parts.push("c copy");
|
|
680
687
|
parts.push("tab regions");
|
|
681
688
|
if (this.#externalEditorLabel && this.#focus !== "toc") parts.push(`${this.#externalEditorLabel} editor`);
|
|
682
689
|
parts.push(this.#helpSuffix);
|
|
@@ -21,4 +21,4 @@ Pair up live: `/collab` shares your session through an end-to-end encrypted rela
|
|
|
21
21
|
Press ← ← to drill into a running or finished agent and inspect its tool calls and transcript
|
|
22
22
|
Hit a Codex rate limit? `/usage reset` spends a saved reset credit to immediately restore your quota
|
|
23
23
|
No native tool_calling? Inference provider botches parsing them? `PI_DIALECT=glm|kimi|anthropic…` rolls it locally for them!
|
|
24
|
-
Turn on `/advisor` to attach a second model that reviews every turn and quietly injects advice
|
|
24
|
+
Turn on `/advisor` to attach a second model that reviews every turn and quietly injects advice
|
|
@@ -44,20 +44,19 @@ const NEW_GLOW_PERIOD_MS = 1500;
|
|
|
44
44
|
* affordance surfaces this many times as often. */
|
|
45
45
|
const NEW_TIP_WEIGHT = 4;
|
|
46
46
|
|
|
47
|
-
/**
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
let
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
if (r < 0) return TIPS[i] ?? "";
|
|
47
|
+
/** Pick a tip from `tips`, biased toward "[NEW]" tips by {@link NEW_TIP_WEIGHT};
|
|
48
|
+
* `r` is a uniform sample in [0, 1). Returns "" when `tips` is empty.
|
|
49
|
+
* Exported for tests. */
|
|
50
|
+
export function pickWeightedTip(tips: readonly string[], r: number): string {
|
|
51
|
+
if (tips.length === 0) return "";
|
|
52
|
+
const weights = tips.map(tip => (NEW_TIP_MARKER.test(tip) ? NEW_TIP_WEIGHT : 1));
|
|
53
|
+
const total = weights.reduce((sum, weight) => sum + weight, 0);
|
|
54
|
+
let acc = r * total;
|
|
55
|
+
for (let i = 0; i < tips.length; i++) {
|
|
56
|
+
acc -= weights[i] ?? 1;
|
|
57
|
+
if (acc < 0) return tips[i] ?? "";
|
|
59
58
|
}
|
|
60
|
-
return
|
|
59
|
+
return tips[tips.length - 1] ?? "";
|
|
61
60
|
}
|
|
62
61
|
|
|
63
62
|
type ColorEncoding = "ansi-16m" | "ansi-256";
|
|
@@ -161,7 +160,7 @@ export class WelcomeComponent implements Component {
|
|
|
161
160
|
if (theme.getSymbolPreset() === "unicode" && Math.random() < 0.1) {
|
|
162
161
|
this.#selectedTip = "Please use nerdfont 😭.";
|
|
163
162
|
} else {
|
|
164
|
-
this.#selectedTip = pickWeightedTip();
|
|
163
|
+
this.#selectedTip = pickWeightedTip(TIPS, Math.random());
|
|
165
164
|
}
|
|
166
165
|
}
|
|
167
166
|
return this.#selectedTip || undefined;
|
|
@@ -47,7 +47,12 @@ import { limitMatchesActiveAccount } from "../../slash-commands/helpers/active-o
|
|
|
47
47
|
import { outputMeta } from "../../tools/output-meta";
|
|
48
48
|
import { resolveToCwd, stripOuterDoubleQuotes } from "../../tools/path-utils";
|
|
49
49
|
import { replaceTabs, truncateToWidth } from "../../tools/render-utils";
|
|
50
|
-
import {
|
|
50
|
+
import {
|
|
51
|
+
getChangelogPath,
|
|
52
|
+
parseChangelog,
|
|
53
|
+
RECENT_CHANGELOG_ENTRY_LIMIT,
|
|
54
|
+
renderChangelogEntries,
|
|
55
|
+
} from "../../utils/changelog";
|
|
51
56
|
import { copyToClipboard } from "../../utils/clipboard";
|
|
52
57
|
import { openPath } from "../../utils/open";
|
|
53
58
|
import { setSessionTerminalTitle } from "../../utils/title-generator";
|
|
@@ -487,16 +492,9 @@ export class CommandController {
|
|
|
487
492
|
async handleChangelogCommand(showFull = false): Promise<void> {
|
|
488
493
|
const changelogPath = getChangelogPath();
|
|
489
494
|
const allEntries = await parseChangelog(changelogPath);
|
|
490
|
-
|
|
491
|
-
// allEntries comes from parseChangelog with newest first, reverse to show oldest->newest
|
|
492
|
-
const entriesToShow = showFull ? allEntries : allEntries.slice(0, 3);
|
|
495
|
+
const entriesToShow = showFull ? allEntries : allEntries.slice(0, RECENT_CHANGELOG_ENTRY_LIMIT);
|
|
493
496
|
const changelogMarkdown =
|
|
494
|
-
entriesToShow.length > 0
|
|
495
|
-
? [...entriesToShow]
|
|
496
|
-
.reverse()
|
|
497
|
-
.map(e => e.content)
|
|
498
|
-
.join("\n\n")
|
|
499
|
-
: "No changelog entries found.";
|
|
497
|
+
entriesToShow.length > 0 ? renderChangelogEntries(entriesToShow).markdown : "No changelog entries found.";
|
|
500
498
|
const title = showFull ? "Full Changelog" : "Recent Changes";
|
|
501
499
|
const hint = showFull
|
|
502
500
|
? ""
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ImageContent } from "jeopi-ai";
|
|
1
|
+
import type { AssistantMessage, ImageContent } from "jeopi-ai";
|
|
2
2
|
import * as AIError from "jeopi-ai/error";
|
|
3
3
|
import { getStreamingPartialJson } from "jeopi-ai/utils/block-symbols";
|
|
4
4
|
import { type Component, Loader, TERMINAL } from "jeopi-tui";
|
|
@@ -31,6 +31,7 @@ import { vocalizer } from "../../tts/vocalizer";
|
|
|
31
31
|
import { canonicalizeMessage } from "../../utils/thinking-display";
|
|
32
32
|
import { interruptHint } from "../shared";
|
|
33
33
|
import { createAssistantMessageComponent } from "../utils/interactive-context-helpers";
|
|
34
|
+
import { assistantHasVisibleContent, splitAssistantMessageToolTimeline } from "../utils/transcript-render-helpers";
|
|
34
35
|
import { StreamingRevealController } from "./streaming-reveal";
|
|
35
36
|
import { streamingStringKeysForTool, ToolArgsRevealController } from "./tool-args-reveal";
|
|
36
37
|
|
|
@@ -74,8 +75,9 @@ export class EventController {
|
|
|
74
75
|
#lastVisibleBlockCount = 0;
|
|
75
76
|
#renderedCustomMessages = new Set<string>();
|
|
76
77
|
#lastIntent: string | undefined = undefined;
|
|
77
|
-
#
|
|
78
|
+
#backgroundTaskCallIds = new Set<string>();
|
|
78
79
|
#readToolCallArgs = new Map<string, Record<string, unknown>>();
|
|
80
|
+
#toolTimelineComponents = new Map<string, Component>();
|
|
79
81
|
#readToolCallAssistantComponents = new Map<string, AssistantMessageComponent>();
|
|
80
82
|
#lastAssistantComponent: AssistantMessageComponent | undefined = undefined;
|
|
81
83
|
// Assistant component whose turn-ending error is currently mirrored in the
|
|
@@ -235,6 +237,24 @@ export class EventController {
|
|
|
235
237
|
assistantComponent.setToolResultImages(toolCallId, images);
|
|
236
238
|
return true;
|
|
237
239
|
}
|
|
240
|
+
|
|
241
|
+
#insertAfterTranscriptComponent(anchor: Component | undefined, component: Component): void {
|
|
242
|
+
const children = this.ctx.chatContainer.children;
|
|
243
|
+
const anchorIndex = anchor ? children.indexOf(anchor) : -1;
|
|
244
|
+
this.ctx.chatContainer.addChild(component);
|
|
245
|
+
if (anchorIndex < 0) return;
|
|
246
|
+
children.splice(children.length - 1, 1);
|
|
247
|
+
children.splice(anchorIndex + 1, 0, component);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
#appendPostToolAssistantSegment(toolCallId: string, segment: AssistantMessage | undefined) {
|
|
251
|
+
if (!segment || !assistantHasVisibleContent(segment)) return undefined;
|
|
252
|
+
const component = createAssistantMessageComponent(this.ctx);
|
|
253
|
+
component.updateContent(segment);
|
|
254
|
+
component.markTranscriptBlockFinalized();
|
|
255
|
+
this.#insertAfterTranscriptComponent(this.#toolTimelineComponents.get(toolCallId), component);
|
|
256
|
+
return component;
|
|
257
|
+
}
|
|
238
258
|
#updateWorkingMessageFromIntent(intent: unknown): void {
|
|
239
259
|
if (this.ctx.session.isAborting) return;
|
|
240
260
|
// Streamed JSON can deliver non-string `i` (object, number, boolean) before
|
|
@@ -262,7 +282,8 @@ export class EventController {
|
|
|
262
282
|
this.#lastVisibleBlockCount = 0;
|
|
263
283
|
this.#renderedCustomMessages.clear();
|
|
264
284
|
this.#lastIntent = undefined;
|
|
265
|
-
this.#
|
|
285
|
+
this.#toolTimelineComponents.clear();
|
|
286
|
+
this.#backgroundTaskCallIds.clear();
|
|
266
287
|
this.#readToolCallArgs.clear();
|
|
267
288
|
this.#readToolCallAssistantComponents.clear();
|
|
268
289
|
this.#lastAssistantComponent = undefined;
|
|
@@ -306,6 +327,7 @@ export class EventController {
|
|
|
306
327
|
}
|
|
307
328
|
|
|
308
329
|
async #handleAgentStart(_event: Extract<AgentSessionEvent, { type: "agent_start" }>): Promise<void> {
|
|
330
|
+
this.#toolTimelineComponents.clear();
|
|
309
331
|
this.#lastIntent = undefined;
|
|
310
332
|
this.#readToolCallArgs.clear();
|
|
311
333
|
this.#readToolCallAssistantComponents.clear();
|
|
@@ -626,6 +648,7 @@ export class EventController {
|
|
|
626
648
|
const group = this.#getReadGroup();
|
|
627
649
|
group.updateArgs(content.arguments, content.id);
|
|
628
650
|
this.ctx.pendingTools.set(content.id, group);
|
|
651
|
+
this.#toolTimelineComponents.set(content.id, group);
|
|
629
652
|
}
|
|
630
653
|
continue;
|
|
631
654
|
}
|
|
@@ -672,6 +695,7 @@ export class EventController {
|
|
|
672
695
|
component.setExpanded(this.ctx.toolOutputExpanded);
|
|
673
696
|
this.ctx.chatContainer.addChild(component);
|
|
674
697
|
this.ctx.pendingTools.set(content.id, component);
|
|
698
|
+
this.#toolTimelineComponents.set(content.id, component);
|
|
675
699
|
this.#toolArgsReveal.bind(content.id, component);
|
|
676
700
|
} else {
|
|
677
701
|
const component = this.ctx.pendingTools.get(content.id);
|
|
@@ -745,15 +769,12 @@ export class EventController {
|
|
|
745
769
|
errorMessage = resolveAbortLabel(this.ctx.streamingMessage, this.ctx.viewSession.retryAttempt);
|
|
746
770
|
this.ctx.streamingMessage.errorMessage = errorMessage;
|
|
747
771
|
}
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
} else {
|
|
755
|
-
this.ctx.streamingComponent.updateContent(this.ctx.streamingMessage);
|
|
756
|
-
}
|
|
772
|
+
const displayMessage: AssistantMessage =
|
|
773
|
+
silentlyAborted || ttsrSilenced
|
|
774
|
+
? { ...this.ctx.streamingMessage, stopReason: "stop" as const }
|
|
775
|
+
: this.ctx.streamingMessage;
|
|
776
|
+
const displayTimeline = splitAssistantMessageToolTimeline(displayMessage);
|
|
777
|
+
this.ctx.streamingComponent.updateContent(displayTimeline.beforeTools);
|
|
757
778
|
|
|
758
779
|
if (this.ctx.streamingMessage.stopReason !== "aborted" && this.ctx.streamingMessage.stopReason !== "error") {
|
|
759
780
|
for (const [toolCallId, component] of this.ctx.pendingTools.entries()) {
|
|
@@ -763,9 +784,9 @@ export class EventController {
|
|
|
763
784
|
// The turn ended without running these calls (abort/error/TTSR rewind),
|
|
764
785
|
// so they will never produce a result. Seal them so they stop animating
|
|
765
786
|
// and freeze instead of pinning the transcript live region while a retry
|
|
766
|
-
// streams fresh blocks below them. Background
|
|
787
|
+
// streams fresh blocks below them. Background task calls keep updating.
|
|
767
788
|
for (const [toolCallId, component] of this.ctx.pendingTools.entries()) {
|
|
768
|
-
if (!this.#
|
|
789
|
+
if (!this.#backgroundTaskCallIds.has(toolCallId) && component instanceof ToolExecutionComponent) {
|
|
769
790
|
component.seal();
|
|
770
791
|
}
|
|
771
792
|
}
|
|
@@ -783,8 +804,13 @@ export class EventController {
|
|
|
783
804
|
}
|
|
784
805
|
this.ctx.lastAssistantUsage = usage;
|
|
785
806
|
}
|
|
786
|
-
this
|
|
787
|
-
|
|
807
|
+
this.ctx.streamingComponent.markTranscriptBlockFinalized();
|
|
808
|
+
let lastPostToolAssistantComponent: AssistantMessageComponent | undefined;
|
|
809
|
+
for (const [toolCallId, segment] of displayTimeline.afterToolCalls) {
|
|
810
|
+
const component = this.#appendPostToolAssistantSegment(toolCallId, segment);
|
|
811
|
+
if (component) lastPostToolAssistantComponent = component;
|
|
812
|
+
}
|
|
813
|
+
this.#lastAssistantComponent = lastPostToolAssistantComponent ?? this.ctx.streamingComponent;
|
|
788
814
|
if (settings.get("display.showTokenUsage")) {
|
|
789
815
|
this.ctx.chatContainer.addChild(
|
|
790
816
|
createUsageRowBlock(event.message.usage, event.message.duration, event.message.ttft),
|
|
@@ -821,6 +847,7 @@ export class EventController {
|
|
|
821
847
|
const group = this.#getReadGroup();
|
|
822
848
|
group.updateArgs(event.args, event.toolCallId);
|
|
823
849
|
this.ctx.pendingTools.set(event.toolCallId, group);
|
|
850
|
+
this.#toolTimelineComponents.set(event.toolCallId, group);
|
|
824
851
|
}
|
|
825
852
|
this.ctx.ui.requestRender();
|
|
826
853
|
return;
|
|
@@ -846,6 +873,7 @@ export class EventController {
|
|
|
846
873
|
component.setExpanded(this.ctx.toolOutputExpanded);
|
|
847
874
|
this.ctx.chatContainer.addChild(component);
|
|
848
875
|
this.ctx.pendingTools.set(event.toolCallId, component);
|
|
876
|
+
this.#toolTimelineComponents.set(event.toolCallId, component);
|
|
849
877
|
this.ctx.ui.requestRender();
|
|
850
878
|
} else {
|
|
851
879
|
// The tool is about to run, so its arguments are final and validated.
|
|
@@ -885,7 +913,7 @@ export class EventController {
|
|
|
885
913
|
);
|
|
886
914
|
if (isFinalAsyncState) {
|
|
887
915
|
this.ctx.pendingTools.delete(event.toolCallId);
|
|
888
|
-
this.#
|
|
916
|
+
this.#backgroundTaskCallIds.delete(event.toolCallId);
|
|
889
917
|
}
|
|
890
918
|
this.ctx.ui.requestRender();
|
|
891
919
|
}
|
|
@@ -906,13 +934,7 @@ export class EventController {
|
|
|
906
934
|
component.updateResult({ ...event.result, isError: event.isError }, false, event.toolCallId);
|
|
907
935
|
this.ctx.pendingTools.delete(event.toolCallId);
|
|
908
936
|
}
|
|
909
|
-
|
|
910
|
-
if (asyncState === "running") {
|
|
911
|
-
this.#backgroundToolCallIds.add(event.toolCallId);
|
|
912
|
-
} else {
|
|
913
|
-
this.#backgroundToolCallIds.delete(event.toolCallId);
|
|
914
|
-
this.#clearReadToolCall(event.toolCallId);
|
|
915
|
-
}
|
|
937
|
+
this.#clearReadToolCall(event.toolCallId);
|
|
916
938
|
this.ctx.ui.requestRender();
|
|
917
939
|
} else {
|
|
918
940
|
let component = this.ctx.pendingTools.get(event.toolCallId);
|
|
@@ -924,30 +946,24 @@ export class EventController {
|
|
|
924
946
|
}
|
|
925
947
|
component = group;
|
|
926
948
|
this.ctx.pendingTools.set(event.toolCallId, group);
|
|
949
|
+
this.#toolTimelineComponents.set(event.toolCallId, group);
|
|
927
950
|
}
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
if (isBackgroundRunning) {
|
|
932
|
-
this.#backgroundToolCallIds.add(event.toolCallId);
|
|
933
|
-
} else {
|
|
934
|
-
this.ctx.pendingTools.delete(event.toolCallId);
|
|
935
|
-
this.#backgroundToolCallIds.delete(event.toolCallId);
|
|
936
|
-
this.#clearReadToolCall(event.toolCallId);
|
|
937
|
-
}
|
|
951
|
+
component.updateResult({ ...event.result, isError: event.isError }, false, event.toolCallId);
|
|
952
|
+
this.ctx.pendingTools.delete(event.toolCallId);
|
|
953
|
+
this.#clearReadToolCall(event.toolCallId);
|
|
938
954
|
this.ctx.ui.requestRender();
|
|
939
955
|
}
|
|
940
956
|
} else {
|
|
941
957
|
const component = this.ctx.pendingTools.get(event.toolCallId);
|
|
942
958
|
if (component) {
|
|
943
959
|
const asyncState = (event.result.details as { async?: { state?: string } } | undefined)?.async?.state;
|
|
944
|
-
const
|
|
945
|
-
component.updateResult({ ...event.result, isError: event.isError },
|
|
946
|
-
if (
|
|
947
|
-
this.#
|
|
960
|
+
const isBackgroundTask = event.toolName === "task" && asyncState === "running";
|
|
961
|
+
component.updateResult({ ...event.result, isError: event.isError }, isBackgroundTask, event.toolCallId);
|
|
962
|
+
if (isBackgroundTask) {
|
|
963
|
+
this.#backgroundTaskCallIds.add(event.toolCallId);
|
|
948
964
|
} else {
|
|
949
965
|
this.ctx.pendingTools.delete(event.toolCallId);
|
|
950
|
-
this.#
|
|
966
|
+
this.#backgroundTaskCallIds.delete(event.toolCallId);
|
|
951
967
|
}
|
|
952
968
|
if (component instanceof ToolExecutionComponent && component.isDisplaceableBlock()) {
|
|
953
969
|
if (event.toolName === "job" && component.canBeDisplacedBy("job")) {
|
|
@@ -1025,7 +1041,7 @@ export class EventController {
|
|
|
1025
1041
|
}
|
|
1026
1042
|
await this.ctx.flushPendingModelSwitch();
|
|
1027
1043
|
for (const toolCallId of Array.from(this.ctx.pendingTools.keys())) {
|
|
1028
|
-
if (!this.#
|
|
1044
|
+
if (!this.#backgroundTaskCallIds.has(toolCallId)) {
|
|
1029
1045
|
// A foreground tool still pending at turn end never delivered a result;
|
|
1030
1046
|
// seal it so it freezes (and stops animating) rather than lingering in
|
|
1031
1047
|
// the transcript live region as a streaming preview until the next thaw.
|
|
@@ -1039,8 +1055,8 @@ export class EventController {
|
|
|
1039
1055
|
this.ctx.pendingTools.delete(toolCallId);
|
|
1040
1056
|
}
|
|
1041
1057
|
}
|
|
1042
|
-
this.#
|
|
1043
|
-
Array.from(this.#
|
|
1058
|
+
this.#backgroundTaskCallIds = new Set(
|
|
1059
|
+
Array.from(this.#backgroundTaskCallIds).filter(toolCallId => this.ctx.pendingTools.has(toolCallId)),
|
|
1044
1060
|
);
|
|
1045
1061
|
this.#readToolCallArgs.clear();
|
|
1046
1062
|
this.#readToolCallAssistantComponents.clear();
|
|
@@ -298,6 +298,8 @@ export class InputController {
|
|
|
298
298
|
});
|
|
299
299
|
}
|
|
300
300
|
this.ctx.editor.onEscape = () => {
|
|
301
|
+
// Side-channel panels are the topmost view. Esc dismisses them before
|
|
302
|
+
// touching loop mode, maintenance, or the underlying main turn.
|
|
301
303
|
// Active context maintenance owns Esc: auto/manual compaction,
|
|
302
304
|
// handoff generation, and auto-retry backoff all advertise
|
|
303
305
|
// "(esc to cancel)". Dispatch on live session state instead of
|
|
@@ -313,6 +315,13 @@ export class InputController {
|
|
|
313
315
|
// (see EventController). Main-session maintenance still owns Esc and
|
|
314
316
|
// stays cancellable from the main view (focused submit gates /compact
|
|
315
317
|
// and handoff, so manual maintenance is main-only anyway).
|
|
318
|
+
if (this.ctx.hasActiveBtw() && this.ctx.handleBtwEscape()) {
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
if (this.ctx.hasActiveOmfg() && this.ctx.handleOmfgEscape()) {
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
|
|
316
325
|
if (!this.ctx.focusedAgentId) {
|
|
317
326
|
const viewSession = this.ctx.viewSession;
|
|
318
327
|
let aborted = false;
|
|
@@ -340,12 +349,6 @@ export class InputController {
|
|
|
340
349
|
}
|
|
341
350
|
return;
|
|
342
351
|
}
|
|
343
|
-
if (this.ctx.hasActiveBtw() && this.ctx.handleBtwEscape()) {
|
|
344
|
-
return;
|
|
345
|
-
}
|
|
346
|
-
if (this.ctx.hasActiveOmfg() && this.ctx.handleOmfgEscape()) {
|
|
347
|
-
return;
|
|
348
|
-
}
|
|
349
352
|
if (this.ctx.focusedAgentId) {
|
|
350
353
|
// Esc never interrupts the focused agent's turn: clear typed text,
|
|
351
354
|
// else return the view to the main session. Interrupt via empty
|
|
@@ -601,8 +601,9 @@ export class SelectorController {
|
|
|
601
601
|
const concreteThinking = isAuto ? undefined : thinkingLevel;
|
|
602
602
|
try {
|
|
603
603
|
if (role === null) {
|
|
604
|
-
// Temporary: update agent state but don't persist the model to settings
|
|
605
|
-
|
|
604
|
+
// Temporary: update agent state but don't persist the model to settings.
|
|
605
|
+
const roleThinkingLevel = this.ctx.session.resolveTemporaryModelThinkingLevel(model);
|
|
606
|
+
await this.ctx.session.setModelTemporary(model, roleThinkingLevel);
|
|
606
607
|
if (isAuto) {
|
|
607
608
|
this.ctx.session.setThinkingLevel(AUTO_THINKING, true);
|
|
608
609
|
}
|