@oh-my-pi/pi-coding-agent 13.9.2 → 13.9.3
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 +53 -0
- package/examples/sdk/02-custom-model.ts +2 -1
- package/package.json +7 -7
- package/src/cli/args.ts +6 -5
- package/src/cli/list-models.ts +2 -2
- package/src/commands/launch.ts +3 -3
- package/src/config/model-registry.ts +85 -39
- package/src/config/model-resolver.ts +47 -21
- package/src/config/settings-schema.ts +56 -2
- package/src/discovery/helpers.ts +2 -2
- package/src/extensibility/custom-tools/types.ts +2 -0
- package/src/extensibility/extensions/loader.ts +3 -2
- package/src/extensibility/extensions/types.ts +10 -7
- package/src/extensibility/hooks/types.ts +2 -0
- package/src/main.ts +5 -22
- package/src/memories/index.ts +7 -3
- package/src/modes/components/footer.ts +10 -8
- package/src/modes/components/model-selector.ts +33 -38
- package/src/modes/components/settings-defs.ts +31 -2
- package/src/modes/components/settings-selector.ts +16 -5
- package/src/modes/components/status-line/context-thresholds.ts +68 -0
- package/src/modes/components/status-line/segments.ts +11 -12
- package/src/modes/components/thinking-selector.ts +7 -7
- package/src/modes/components/tree-selector.ts +3 -2
- package/src/modes/controllers/command-controller.ts +11 -26
- package/src/modes/controllers/event-controller.ts +16 -3
- package/src/modes/controllers/input-controller.ts +4 -2
- package/src/modes/controllers/selector-controller.ts +5 -4
- package/src/modes/interactive-mode.ts +2 -2
- package/src/modes/rpc/rpc-client.ts +5 -10
- package/src/modes/rpc/rpc-types.ts +5 -5
- package/src/modes/theme/theme.ts +8 -3
- package/src/priority.json +1 -0
- package/src/prompts/system/auto-handoff-threshold-focus.md +1 -0
- package/src/prompts/system/system-prompt.md +18 -2
- package/src/prompts/tools/hashline.md +139 -83
- package/src/sdk.ts +22 -14
- package/src/session/agent-session.ts +259 -117
- package/src/session/agent-storage.ts +14 -14
- package/src/session/compaction/compaction.ts +500 -13
- package/src/session/messages.ts +12 -1
- package/src/session/session-manager.ts +77 -19
- package/src/slash-commands/builtin-registry.ts +48 -0
- package/src/task/agents.ts +3 -2
- package/src/task/executor.ts +2 -2
- package/src/task/types.ts +2 -1
- package/src/thinking.ts +87 -0
- package/src/tools/browser.ts +15 -6
- package/src/tools/fetch.ts +118 -100
- package/src/web/search/providers/exa.ts +74 -3
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { ThinkingLevel } from "@oh-my-pi/pi-
|
|
1
|
+
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
2
|
+
import type { Effort } from "@oh-my-pi/pi-ai";
|
|
2
3
|
import {
|
|
3
4
|
Container,
|
|
4
5
|
matchesKey,
|
|
@@ -134,9 +135,9 @@ function getSettingsTabs(): Tab[] {
|
|
|
134
135
|
*/
|
|
135
136
|
export interface SettingsRuntimeContext {
|
|
136
137
|
/** Available thinking levels (from session) */
|
|
137
|
-
availableThinkingLevels:
|
|
138
|
+
availableThinkingLevels: Effort[];
|
|
138
139
|
/** Current thinking level (from session) */
|
|
139
|
-
thinkingLevel: ThinkingLevel;
|
|
140
|
+
thinkingLevel: ThinkingLevel | undefined;
|
|
140
141
|
/** Available themes */
|
|
141
142
|
availableThemes: string[];
|
|
142
143
|
/** Working directory for plugins tab */
|
|
@@ -272,7 +273,7 @@ export class SettingsSelectorComponent extends Container {
|
|
|
272
273
|
id: def.path,
|
|
273
274
|
label: def.label,
|
|
274
275
|
description: def.description,
|
|
275
|
-
currentValue:
|
|
276
|
+
currentValue: this.#getSubmenuCurrentValue(def.path, currentValue),
|
|
276
277
|
submenu: (cv, done) => this.#createSubmenu(def, cv, done),
|
|
277
278
|
};
|
|
278
279
|
}
|
|
@@ -285,6 +286,14 @@ export class SettingsSelectorComponent extends Container {
|
|
|
285
286
|
return settings.get(def.path);
|
|
286
287
|
}
|
|
287
288
|
|
|
289
|
+
#getSubmenuCurrentValue(path: SettingPath, value: unknown): string {
|
|
290
|
+
const rawValue = String(value ?? "");
|
|
291
|
+
if (path === "compaction.thresholdPercent" && (rawValue === "-1" || rawValue === "")) {
|
|
292
|
+
return "default";
|
|
293
|
+
}
|
|
294
|
+
return rawValue;
|
|
295
|
+
}
|
|
296
|
+
|
|
288
297
|
/**
|
|
289
298
|
* Create a submenu for a submenu-type setting.
|
|
290
299
|
*/
|
|
@@ -382,7 +391,9 @@ export class SettingsSelectorComponent extends Container {
|
|
|
382
391
|
#setSettingValue(path: SettingPath, value: string): void {
|
|
383
392
|
// Handle number conversions
|
|
384
393
|
const currentValue = settings.get(path);
|
|
385
|
-
if (
|
|
394
|
+
if (path === "compaction.thresholdPercent" && value === "default") {
|
|
395
|
+
settings.set(path, -1 as never);
|
|
396
|
+
} else if (typeof currentValue === "number") {
|
|
386
397
|
settings.set(path, Number(value) as never);
|
|
387
398
|
} else if (typeof currentValue === "boolean") {
|
|
388
399
|
settings.set(path, (value === "true") as never);
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { ThemeColor } from "../../../modes/theme/theme";
|
|
2
|
+
|
|
3
|
+
export type ContextUsageLevel = "normal" | "warning" | "purple" | "error";
|
|
4
|
+
|
|
5
|
+
const CONTEXT_WARNING_PERCENT_THRESHOLD = 50;
|
|
6
|
+
const CONTEXT_WARNING_TOKEN_THRESHOLD = 150_000;
|
|
7
|
+
const CONTEXT_PURPLE_PERCENT_THRESHOLD = 70;
|
|
8
|
+
const CONTEXT_PURPLE_TOKEN_THRESHOLD = 270_000;
|
|
9
|
+
const CONTEXT_ERROR_PERCENT_THRESHOLD = 90;
|
|
10
|
+
const CONTEXT_ERROR_TOKEN_THRESHOLD = 500_000;
|
|
11
|
+
|
|
12
|
+
function reachesThreshold(
|
|
13
|
+
contextPercent: number,
|
|
14
|
+
contextWindow: number,
|
|
15
|
+
percentThreshold: number,
|
|
16
|
+
tokenThreshold: number,
|
|
17
|
+
): boolean {
|
|
18
|
+
if (!Number.isFinite(contextPercent) || contextPercent <= 0) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!Number.isFinite(contextWindow) || contextWindow <= 0) {
|
|
23
|
+
return contextPercent >= percentThreshold;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const tokenPercentThreshold = (tokenThreshold / contextWindow) * 100;
|
|
27
|
+
return contextPercent >= Math.min(percentThreshold, tokenPercentThreshold);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function getContextUsageLevel(contextPercent: number, contextWindow: number): ContextUsageLevel {
|
|
31
|
+
if (
|
|
32
|
+
reachesThreshold(contextPercent, contextWindow, CONTEXT_ERROR_PERCENT_THRESHOLD, CONTEXT_ERROR_TOKEN_THRESHOLD)
|
|
33
|
+
) {
|
|
34
|
+
return "error";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (
|
|
38
|
+
reachesThreshold(contextPercent, contextWindow, CONTEXT_PURPLE_PERCENT_THRESHOLD, CONTEXT_PURPLE_TOKEN_THRESHOLD)
|
|
39
|
+
) {
|
|
40
|
+
return "purple";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (
|
|
44
|
+
reachesThreshold(
|
|
45
|
+
contextPercent,
|
|
46
|
+
contextWindow,
|
|
47
|
+
CONTEXT_WARNING_PERCENT_THRESHOLD,
|
|
48
|
+
CONTEXT_WARNING_TOKEN_THRESHOLD,
|
|
49
|
+
)
|
|
50
|
+
) {
|
|
51
|
+
return "warning";
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return "normal";
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function getContextUsageThemeColor(level: ContextUsageLevel): ThemeColor {
|
|
58
|
+
switch (level) {
|
|
59
|
+
case "error":
|
|
60
|
+
return "error";
|
|
61
|
+
case "purple":
|
|
62
|
+
return "thinkingHigh";
|
|
63
|
+
case "warning":
|
|
64
|
+
return "warning";
|
|
65
|
+
case "normal":
|
|
66
|
+
return "statusLineContext";
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import * as os from "node:os";
|
|
2
|
+
import { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
2
3
|
import { TERMINAL } from "@oh-my-pi/pi-tui";
|
|
3
4
|
import { formatDuration, formatNumber, getProjectDir } from "@oh-my-pi/pi-utils";
|
|
4
5
|
import { theme } from "../../../modes/theme/theme";
|
|
5
6
|
import { shortenPath } from "../../../tools/render-utils";
|
|
7
|
+
import { getContextUsageLevel, getContextUsageThemeColor } from "./context-thresholds";
|
|
6
8
|
import type { RenderedSegment, SegmentContext, StatusLineSegment, StatusLineSegmentId } from "./types";
|
|
7
9
|
|
|
8
10
|
export type { SegmentContext } from "./types";
|
|
@@ -44,10 +46,14 @@ const modelSegment: StatusLineSegment = {
|
|
|
44
46
|
|
|
45
47
|
let content = withIcon(theme.icon.model, modelName);
|
|
46
48
|
|
|
49
|
+
if (ctx.session.isFastModeEnabled() && theme.icon.fast) {
|
|
50
|
+
content += ` ${theme.icon.fast}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
47
53
|
// Add thinking level with dot separator
|
|
48
|
-
if (opts.showThinkingLevel !== false && state.model?.
|
|
49
|
-
const level = state.thinkingLevel
|
|
50
|
-
if (level !==
|
|
54
|
+
if (opts.showThinkingLevel !== false && state.model?.thinking) {
|
|
55
|
+
const level = state.thinkingLevel ?? ThinkingLevel.Off;
|
|
56
|
+
if (level !== ThinkingLevel.Off) {
|
|
51
57
|
const thinkingText = theme.thinking[level as keyof typeof theme.thinking];
|
|
52
58
|
if (thinkingText) {
|
|
53
59
|
content += `${theme.sep.dot}${thinkingText}`;
|
|
@@ -244,15 +250,8 @@ const contextPctSegment: StatusLineSegment = {
|
|
|
244
250
|
const autoIcon = ctx.autoCompactEnabled && theme.icon.auto ? ` ${theme.icon.auto}` : "";
|
|
245
251
|
const text = `${pct.toFixed(1)}%/${formatNumber(window)}${autoIcon}`;
|
|
246
252
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
content = withIcon(theme.icon.context, theme.fg("error", text));
|
|
250
|
-
} else if (pct > 70) {
|
|
251
|
-
content = withIcon(theme.icon.context, theme.fg("warning", text));
|
|
252
|
-
} else {
|
|
253
|
-
const colored = theme.fg("statusLineContext", text);
|
|
254
|
-
content = withIcon(theme.icon.context, colored);
|
|
255
|
-
}
|
|
253
|
+
const color = getContextUsageThemeColor(getContextUsageLevel(pct, window));
|
|
254
|
+
const content = withIcon(theme.icon.context, theme.fg(color, text));
|
|
256
255
|
|
|
257
256
|
return { content, visible: true };
|
|
258
257
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import type { Effort } from "@oh-my-pi/pi-ai";
|
|
3
2
|
import { Container, type SelectItem, SelectList } from "@oh-my-pi/pi-tui";
|
|
4
3
|
import { getSelectListTheme } from "../../modes/theme/theme";
|
|
4
|
+
import { getThinkingLevelMetadata } from "../../thinking";
|
|
5
5
|
import { DynamicBorder } from "./dynamic-border";
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -11,14 +11,14 @@ export class ThinkingSelectorComponent extends Container {
|
|
|
11
11
|
#selectList: SelectList;
|
|
12
12
|
|
|
13
13
|
constructor(
|
|
14
|
-
currentLevel:
|
|
15
|
-
availableLevels:
|
|
16
|
-
onSelect: (level:
|
|
14
|
+
currentLevel: Effort,
|
|
15
|
+
availableLevels: Effort[],
|
|
16
|
+
onSelect: (level: Effort) => void,
|
|
17
17
|
onCancel: () => void,
|
|
18
18
|
) {
|
|
19
19
|
super();
|
|
20
20
|
|
|
21
|
-
const thinkingLevels: SelectItem[] = availableLevels.map(
|
|
21
|
+
const thinkingLevels: SelectItem[] = availableLevels.map(getThinkingLevelMetadata);
|
|
22
22
|
|
|
23
23
|
// Add top border
|
|
24
24
|
this.addChild(new DynamicBorder());
|
|
@@ -33,7 +33,7 @@ export class ThinkingSelectorComponent extends Container {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
this.#selectList.onSelect = item => {
|
|
36
|
-
onSelect(item.value as
|
|
36
|
+
onSelect(item.value as Effort);
|
|
37
37
|
};
|
|
38
38
|
|
|
39
39
|
this.#selectList.onCancel = () => {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
1
2
|
import {
|
|
2
3
|
type Component,
|
|
3
4
|
Container,
|
|
@@ -382,7 +383,7 @@ class TreeList implements Component {
|
|
|
382
383
|
parts.push("model", entry.model);
|
|
383
384
|
break;
|
|
384
385
|
case "thinking_level_change":
|
|
385
|
-
parts.push("thinking", entry.thinkingLevel);
|
|
386
|
+
parts.push("thinking", entry.thinkingLevel ?? ThinkingLevel.Off);
|
|
386
387
|
break;
|
|
387
388
|
case "custom":
|
|
388
389
|
parts.push("custom", entry.customType);
|
|
@@ -585,7 +586,7 @@ class TreeList implements Component {
|
|
|
585
586
|
result = theme.fg("dim", `[model: ${entry.model}]`);
|
|
586
587
|
break;
|
|
587
588
|
case "thinking_level_change":
|
|
588
|
-
result = theme.fg("dim", `[thinking: ${entry.thinkingLevel}]`);
|
|
589
|
+
result = theme.fg("dim", `[thinking: ${entry.thinkingLevel ?? ThinkingLevel.Off}]`);
|
|
589
590
|
break;
|
|
590
591
|
case "custom":
|
|
591
592
|
result = theme.fg("dim", `[custom: ${entry.customType}]`);
|
|
@@ -25,7 +25,6 @@ import { getMarkdownTheme, getSymbolTheme, theme } from "../../modes/theme/theme
|
|
|
25
25
|
import type { InteractiveModeContext } from "../../modes/types";
|
|
26
26
|
import type { AsyncJobSnapshotItem } from "../../session/agent-session";
|
|
27
27
|
import type { AuthStorage } from "../../session/auth-storage";
|
|
28
|
-
import { createCompactionSummaryMessage } from "../../session/messages";
|
|
29
28
|
import { outputMeta } from "../../tools/output-meta";
|
|
30
29
|
import { resolveToCwd } from "../../tools/path-utils";
|
|
31
30
|
import { replaceTabs } from "../../tools/render-utils";
|
|
@@ -776,18 +775,10 @@ export class CommandController {
|
|
|
776
775
|
customInstructionsOrOptions && typeof customInstructionsOrOptions === "object"
|
|
777
776
|
? customInstructionsOrOptions
|
|
778
777
|
: undefined;
|
|
779
|
-
|
|
778
|
+
await this.ctx.session.compact(instructions, options);
|
|
780
779
|
|
|
781
780
|
this.ctx.rebuildChatFromMessages();
|
|
782
781
|
|
|
783
|
-
const msg = createCompactionSummaryMessage(
|
|
784
|
-
result.summary,
|
|
785
|
-
result.tokensBefore,
|
|
786
|
-
new Date().toISOString(),
|
|
787
|
-
result.shortSummary,
|
|
788
|
-
);
|
|
789
|
-
this.ctx.addMessageToChat(msg);
|
|
790
|
-
|
|
791
782
|
this.ctx.statusLine.invalidate();
|
|
792
783
|
this.ctx.updateEditorTopBorder();
|
|
793
784
|
} catch (error) {
|
|
@@ -834,6 +825,9 @@ export class CommandController {
|
|
|
834
825
|
this.ctx.chatContainer.addChild(
|
|
835
826
|
new Text(`${theme.fg("accent", `${theme.status.success} New session started with handoff context`)}`, 1, 1),
|
|
836
827
|
);
|
|
828
|
+
if (result.savedPath) {
|
|
829
|
+
this.ctx.showStatus(`Handoff document saved to: ${result.savedPath}`);
|
|
830
|
+
}
|
|
837
831
|
} catch (error) {
|
|
838
832
|
const message = error instanceof Error ? error.message : String(error);
|
|
839
833
|
if (message === "Handoff cancelled" || (error instanceof Error && error.name === "AbortError")) {
|
|
@@ -959,9 +953,6 @@ function formatAccountLabel(limit: UsageLimit, report: UsageReport, index: numbe
|
|
|
959
953
|
}
|
|
960
954
|
|
|
961
955
|
function formatResetShort(limit: UsageLimit, nowMs: number): string | undefined {
|
|
962
|
-
if (limit.window?.resetInMs !== undefined) {
|
|
963
|
-
return formatDuration(limit.window.resetInMs);
|
|
964
|
-
}
|
|
965
956
|
if (limit.window?.resetsAt !== undefined) {
|
|
966
957
|
return formatDuration(limit.window.resetsAt - nowMs);
|
|
967
958
|
}
|
|
@@ -1021,19 +1012,13 @@ function formatAggregateAmount(limits: UsageLimit[]): string {
|
|
|
1021
1012
|
}
|
|
1022
1013
|
|
|
1023
1014
|
function resolveResetRange(limits: UsageLimit[], nowMs: number): string | null {
|
|
1024
|
-
const
|
|
1025
|
-
.map(limit => limit.window?.
|
|
1026
|
-
.filter((value): value is number => value !== undefined && Number.isFinite(value) && value >
|
|
1027
|
-
if (
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
if (absolute.length === 0) return null;
|
|
1032
|
-
const earliest = Math.min(...absolute);
|
|
1033
|
-
return `resets at ${new Date(earliest).toLocaleString()}`;
|
|
1034
|
-
}
|
|
1035
|
-
const minReset = Math.min(...resets);
|
|
1036
|
-
const maxReset = Math.max(...resets);
|
|
1015
|
+
const absolute = limits
|
|
1016
|
+
.map(limit => limit.window?.resetsAt)
|
|
1017
|
+
.filter((value): value is number => value !== undefined && Number.isFinite(value) && value > nowMs);
|
|
1018
|
+
if (absolute.length === 0) return null;
|
|
1019
|
+
const offsets = absolute.map(value => value - nowMs);
|
|
1020
|
+
const minReset = Math.min(...offsets);
|
|
1021
|
+
const maxReset = Math.max(...offsets);
|
|
1037
1022
|
if (maxReset - minReset > 60_000) {
|
|
1038
1023
|
return `resets in ${formatDuration(minReset)}–${formatDuration(maxReset)}`;
|
|
1039
1024
|
}
|
|
@@ -438,11 +438,12 @@ export class EventController {
|
|
|
438
438
|
};
|
|
439
439
|
this.ctx.statusContainer.clear();
|
|
440
440
|
const reasonText = event.reason === "overflow" ? "Context overflow detected, " : "";
|
|
441
|
+
const actionLabel = event.action === "handoff" ? "Auto-handoff" : "Auto context-full maintenance";
|
|
441
442
|
this.ctx.autoCompactionLoader = new Loader(
|
|
442
443
|
this.ctx.ui,
|
|
443
444
|
spinner => theme.fg("accent", spinner),
|
|
444
445
|
text => theme.fg("muted", text),
|
|
445
|
-
`${reasonText}
|
|
446
|
+
`${reasonText}${actionLabel}… (esc to cancel)`,
|
|
446
447
|
getSymbolTheme().spinnerFrames,
|
|
447
448
|
);
|
|
448
449
|
this.ctx.statusContainer.addChild(this.ctx.autoCompactionLoader);
|
|
@@ -460,8 +461,11 @@ export class EventController {
|
|
|
460
461
|
this.ctx.autoCompactionLoader = undefined;
|
|
461
462
|
this.ctx.statusContainer.clear();
|
|
462
463
|
}
|
|
464
|
+
const isHandoffAction = event.action === "handoff";
|
|
463
465
|
if (event.aborted) {
|
|
464
|
-
this.ctx.showStatus(
|
|
466
|
+
this.ctx.showStatus(
|
|
467
|
+
isHandoffAction ? "Auto-handoff cancelled" : "Auto context-full maintenance cancelled",
|
|
468
|
+
);
|
|
465
469
|
} else if (event.result) {
|
|
466
470
|
this.ctx.chatContainer.clear();
|
|
467
471
|
this.ctx.rebuildChatFromMessages();
|
|
@@ -474,8 +478,17 @@ export class EventController {
|
|
|
474
478
|
});
|
|
475
479
|
this.ctx.statusLine.invalidate();
|
|
476
480
|
this.ctx.updateEditorTopBorder();
|
|
481
|
+
} else if (event.errorMessage) {
|
|
482
|
+
this.ctx.showWarning(event.errorMessage);
|
|
483
|
+
} else if (isHandoffAction) {
|
|
484
|
+
this.ctx.chatContainer.clear();
|
|
485
|
+
this.ctx.rebuildChatFromMessages();
|
|
486
|
+
this.ctx.statusLine.invalidate();
|
|
487
|
+
this.ctx.updateEditorTopBorder();
|
|
488
|
+
await this.ctx.reloadTodos();
|
|
489
|
+
this.ctx.showStatus("Auto-handoff completed");
|
|
477
490
|
} else {
|
|
478
|
-
this.ctx.showWarning("Auto-
|
|
491
|
+
this.ctx.showWarning("Auto context-full maintenance failed; continuing without maintenance");
|
|
479
492
|
}
|
|
480
493
|
await this.ctx.flushCompactionQueue({ willRetry: event.willRetry });
|
|
481
494
|
this.ctx.ui.requestRender();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
|
-
import type
|
|
2
|
+
import { type AgentMessage, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
3
3
|
import { copyToClipboard, readImageFromClipboard, sanitizeText } from "@oh-my-pi/pi-natives";
|
|
4
4
|
import { $env } from "@oh-my-pi/pi-utils";
|
|
5
5
|
import { settings } from "../../config/settings";
|
|
@@ -544,7 +544,9 @@ export class InputController {
|
|
|
544
544
|
const roleLabel = result.role === "default" ? "default" : result.role;
|
|
545
545
|
const roleLabelStyled = theme.bold(theme.fg("accent", roleLabel));
|
|
546
546
|
const thinkingStr =
|
|
547
|
-
result.model.
|
|
547
|
+
result.model.thinking && result.thinkingLevel !== ThinkingLevel.Off
|
|
548
|
+
? ` (thinking: ${result.thinkingLevel})`
|
|
549
|
+
: "";
|
|
548
550
|
const tempLabel = options?.temporary ? " (temporary)" : "";
|
|
549
551
|
const cycleSeparator = theme.fg("dim", " > ");
|
|
550
552
|
const cycleLabel = roleOrder
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
2
|
+
import { getOAuthProviders, type OAuthProvider } from "@oh-my-pi/pi-ai";
|
|
2
3
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
3
4
|
import { Input, Loader, Spacer, Text } from "@oh-my-pi/pi-tui";
|
|
4
5
|
import { getAgentDbPath, getProjectDir } from "@oh-my-pi/pi-utils";
|
|
@@ -380,7 +381,7 @@ export class SelectorController {
|
|
|
380
381
|
this.ctx.settings,
|
|
381
382
|
this.ctx.session.modelRegistry,
|
|
382
383
|
this.ctx.session.scopedModels,
|
|
383
|
-
async (model, role,
|
|
384
|
+
async (model, role, thinkingLevel) => {
|
|
384
385
|
try {
|
|
385
386
|
if (role === null) {
|
|
386
387
|
// Temporary: update agent state but don't persist to settings
|
|
@@ -393,8 +394,8 @@ export class SelectorController {
|
|
|
393
394
|
} else if (role === "default") {
|
|
394
395
|
// Default: update agent state and persist
|
|
395
396
|
await this.ctx.session.setModel(model, role);
|
|
396
|
-
if (
|
|
397
|
-
this.ctx.session.setThinkingLevel(
|
|
397
|
+
if (thinkingLevel && thinkingLevel !== ThinkingLevel.Inherit) {
|
|
398
|
+
this.ctx.session.setThinkingLevel(thinkingLevel);
|
|
398
399
|
}
|
|
399
400
|
this.ctx.statusLine.invalidate();
|
|
400
401
|
this.ctx.updateEditorBorderColor();
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Handles TUI rendering and user interaction, delegating business logic to AgentSession.
|
|
4
4
|
*/
|
|
5
5
|
import * as path from "node:path";
|
|
6
|
-
import type
|
|
6
|
+
import { type Agent, type AgentMessage, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
7
7
|
import type { AssistantMessage, ImageContent, Message, Model, UsageReport } from "@oh-my-pi/pi-ai";
|
|
8
8
|
import type { Component, Loader, SlashCommand } from "@oh-my-pi/pi-tui";
|
|
9
9
|
import {
|
|
@@ -423,7 +423,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
423
423
|
} else if (this.isPythonMode) {
|
|
424
424
|
this.editor.borderColor = theme.getPythonModeBorderColor();
|
|
425
425
|
} else {
|
|
426
|
-
const level = this.session.thinkingLevel
|
|
426
|
+
const level = this.session.thinkingLevel ?? ThinkingLevel.Off;
|
|
427
427
|
this.editor.borderColor = theme.getThinkingBorderColor(level);
|
|
428
428
|
}
|
|
429
429
|
this.updateEditorTopBorder();
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Spawns the agent in RPC mode and provides a typed API for all operations.
|
|
5
5
|
*/
|
|
6
|
-
import type { AgentEvent, AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
7
|
-
import type { ImageContent,
|
|
6
|
+
import type { AgentEvent, AgentMessage, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
7
|
+
import type { Effort, ImageContent, Model } from "@oh-my-pi/pi-ai";
|
|
8
8
|
import { isRecord, ptree, readJsonl } from "@oh-my-pi/pi-utils";
|
|
9
9
|
import type { BashResult } from "../../exec/bash-executor";
|
|
10
10
|
import type { SessionStats } from "../../session/agent-session";
|
|
@@ -34,12 +34,7 @@ export interface RpcClientOptions {
|
|
|
34
34
|
args?: string[];
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
export
|
|
38
|
-
provider: string;
|
|
39
|
-
id: string;
|
|
40
|
-
contextWindow: number;
|
|
41
|
-
reasoning: boolean;
|
|
42
|
-
}
|
|
37
|
+
export type ModelInfo = Pick<Model, "provider" | "id" | "contextWindow" | "reasoning" | "thinking">;
|
|
43
38
|
|
|
44
39
|
export type RpcEventListener = (event: AgentEvent) => void;
|
|
45
40
|
|
|
@@ -284,7 +279,7 @@ export class RpcClient {
|
|
|
284
279
|
*/
|
|
285
280
|
async cycleModel(): Promise<{
|
|
286
281
|
model: { provider: string; id: string };
|
|
287
|
-
thinkingLevel: ThinkingLevel;
|
|
282
|
+
thinkingLevel: ThinkingLevel | undefined;
|
|
288
283
|
isScoped: boolean;
|
|
289
284
|
} | null> {
|
|
290
285
|
const response = await this.#send({ type: "cycle_model" });
|
|
@@ -309,7 +304,7 @@ export class RpcClient {
|
|
|
309
304
|
/**
|
|
310
305
|
* Cycle thinking level.
|
|
311
306
|
*/
|
|
312
|
-
async cycleThinkingLevel(): Promise<{ level:
|
|
307
|
+
async cycleThinkingLevel(): Promise<{ level: Effort } | null> {
|
|
313
308
|
const response = await this.#send({ type: "cycle_thinking_level" });
|
|
314
309
|
return this.#getData(response);
|
|
315
310
|
}
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* Commands are sent as JSON lines on stdin.
|
|
5
5
|
* Responses and events are emitted as JSON lines on stdout.
|
|
6
6
|
*/
|
|
7
|
-
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
8
|
-
import type { ImageContent, Model
|
|
7
|
+
import type { AgentMessage, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
8
|
+
import type { Effort, ImageContent, Model } from "@oh-my-pi/pi-ai";
|
|
9
9
|
import type { BashResult } from "../../exec/bash-executor";
|
|
10
10
|
import type { SessionStats } from "../../session/agent-session";
|
|
11
11
|
import type { CompactionResult } from "../../session/compaction";
|
|
@@ -70,7 +70,7 @@ export type RpcCommand =
|
|
|
70
70
|
|
|
71
71
|
export interface RpcSessionState {
|
|
72
72
|
model?: Model;
|
|
73
|
-
thinkingLevel: ThinkingLevel;
|
|
73
|
+
thinkingLevel: ThinkingLevel | undefined;
|
|
74
74
|
isStreaming: boolean;
|
|
75
75
|
isCompacting: boolean;
|
|
76
76
|
steeringMode: "all" | "one-at-a-time";
|
|
@@ -114,7 +114,7 @@ export type RpcResponse =
|
|
|
114
114
|
type: "response";
|
|
115
115
|
command: "cycle_model";
|
|
116
116
|
success: true;
|
|
117
|
-
data: { model: Model; thinkingLevel: ThinkingLevel; isScoped: boolean } | null;
|
|
117
|
+
data: { model: Model; thinkingLevel: ThinkingLevel | undefined; isScoped: boolean } | null;
|
|
118
118
|
}
|
|
119
119
|
| {
|
|
120
120
|
id?: string;
|
|
@@ -131,7 +131,7 @@ export type RpcResponse =
|
|
|
131
131
|
type: "response";
|
|
132
132
|
command: "cycle_thinking_level";
|
|
133
133
|
success: true;
|
|
134
|
-
data: { level:
|
|
134
|
+
data: { level: Effort } | null;
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
// Queue modes
|
package/src/modes/theme/theme.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import type { ThinkingLevel } from "@oh-my-pi/pi-
|
|
3
|
+
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
4
|
+
import type { Effort } from "@oh-my-pi/pi-ai";
|
|
4
5
|
import {
|
|
5
6
|
detectMacOSAppearance,
|
|
6
7
|
type HighlightColors as NativeHighlightColors,
|
|
@@ -108,6 +109,7 @@ export type SymbolKey =
|
|
|
108
109
|
| "icon.warning"
|
|
109
110
|
| "icon.rewind"
|
|
110
111
|
| "icon.auto"
|
|
112
|
+
| "icon.fast"
|
|
111
113
|
| "icon.extensionSkill"
|
|
112
114
|
| "icon.extensionTool"
|
|
113
115
|
| "icon.extensionSlashCommand"
|
|
@@ -268,6 +270,7 @@ const UNICODE_SYMBOLS: SymbolMap = {
|
|
|
268
270
|
"icon.warning": "⚠",
|
|
269
271
|
"icon.rewind": "↶",
|
|
270
272
|
"icon.auto": "⟲",
|
|
273
|
+
"icon.fast": "⚡",
|
|
271
274
|
"icon.extensionSkill": "✦",
|
|
272
275
|
"icon.extensionTool": "🛠",
|
|
273
276
|
"icon.extensionSlashCommand": "⌘",
|
|
@@ -499,7 +502,7 @@ const NERD_SYMBOLS: SymbolMap = {
|
|
|
499
502
|
"icon.rewind": "\uf0e2",
|
|
500
503
|
// pick: | alt:
|
|
501
504
|
"icon.auto": "\u{f0068}",
|
|
502
|
-
|
|
505
|
+
"icon.fast": "\uf0e7",
|
|
503
506
|
"icon.extensionSkill": "\uf0eb",
|
|
504
507
|
// pick: | alt:
|
|
505
508
|
"icon.extensionTool": "\uf0ad",
|
|
@@ -680,6 +683,7 @@ const ASCII_SYMBOLS: SymbolMap = {
|
|
|
680
683
|
"icon.warning": "[!]",
|
|
681
684
|
"icon.rewind": "<-",
|
|
682
685
|
"icon.auto": "[A]",
|
|
686
|
+
"icon.fast": ">>",
|
|
683
687
|
"icon.extensionSkill": "SK",
|
|
684
688
|
"icon.extensionTool": "TL",
|
|
685
689
|
"icon.extensionSlashCommand": "/",
|
|
@@ -1220,7 +1224,7 @@ export class Theme {
|
|
|
1220
1224
|
return this.mode;
|
|
1221
1225
|
}
|
|
1222
1226
|
|
|
1223
|
-
getThinkingBorderColor(level: ThinkingLevel): (str: string) => string {
|
|
1227
|
+
getThinkingBorderColor(level: ThinkingLevel | Effort): (str: string) => string {
|
|
1224
1228
|
// Map thinking levels to dedicated theme colors
|
|
1225
1229
|
switch (level) {
|
|
1226
1230
|
case "off":
|
|
@@ -1381,6 +1385,7 @@ export class Theme {
|
|
|
1381
1385
|
warning: this.#symbols["icon.warning"],
|
|
1382
1386
|
rewind: this.#symbols["icon.rewind"],
|
|
1383
1387
|
auto: this.#symbols["icon.auto"],
|
|
1388
|
+
fast: this.#symbols["icon.fast"],
|
|
1384
1389
|
extensionSkill: this.#symbols["icon.extensionSkill"],
|
|
1385
1390
|
extensionTool: this.#symbols["icon.extensionTool"],
|
|
1386
1391
|
extensionSlashCommand: this.#symbols["icon.extensionSlashCommand"],
|
package/src/priority.json
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Threshold-triggered maintenance: preserve critical implementation state and immediate next actions.
|
|
@@ -186,7 +186,7 @@ Use the Task tool unless the change is:
|
|
|
186
186
|
- A direct answer or explanation with no code changes
|
|
187
187
|
- A command the user asked you to run yourself
|
|
188
188
|
|
|
189
|
-
For everything else — multi-file changes, refactors, new features, test additions, investigations — break the work into tasks and delegate. Err on the side of delegating
|
|
189
|
+
For everything else — multi-file changes, refactors, new features, test additions, investigations — break the work into tasks and delegate once the target design is settled. Err on the side of delegating after the architectural direction is fixed.
|
|
190
190
|
</eager-tasks>
|
|
191
191
|
{{/if}}
|
|
192
192
|
|
|
@@ -218,6 +218,18 @@ These are inviolable. Violation is system failure.
|
|
|
218
218
|
6. You **MUST NOT** ask for information obtainable from tools, repo context, or files. File referenced → you **MUST** locate and read it. Path implied → you **MUST** resolve it.
|
|
219
219
|
7. Full CUTOVER is **REQUIRED**. You **MUST** replace old usage everywhere you touch — no backwards-compat shims, no gradual migration, no "keeping both for now." The old way is dead; lingering instances **MUST** be treated as bugs.
|
|
220
220
|
|
|
221
|
+
# Design Integrity
|
|
222
|
+
- You **MUST** prefer a coherent final design over a minimally invasive patch.
|
|
223
|
+
- You **MUST NOT** preserve obsolete abstractions to reduce edit scope.
|
|
224
|
+
- Temporary bridges are **PROHIBITED** unless the user explicitly asks for a migration path.
|
|
225
|
+
- If a refactor introduces a new canonical abstraction, you **MUST** migrate consumers to it instead of wrapping it in compatibility helpers.
|
|
226
|
+
- Parallel APIs that express the same concept are a bug, not a convenience.
|
|
227
|
+
- Boolean compatibility helpers that collapse richer capability models are **PROHIBITED**.
|
|
228
|
+
- You **MUST NOT** collapse structured capability data into lossy booleans or convenience wrappers unless the domain is truly boolean.
|
|
229
|
+
- If a change removes a field, type, or API, all fixtures, tests, docs, and callsites using it **MUST** be updated in the same change.
|
|
230
|
+
- You **MUST** optimize for the next maintainer's edit, not for minimizing the current diff.
|
|
231
|
+
- "Works" is insufficient. The result **MUST** also be singular, obvious, and maintainable.
|
|
232
|
+
|
|
221
233
|
# Procedure
|
|
222
234
|
## 1. Scope
|
|
223
235
|
{{#if skills.length}}- If a skill matches the domain, you **MUST** read it before starting.{{/if}}
|
|
@@ -245,6 +257,8 @@ Justify sequential work; default parallel. Cannot articulate why B depends on A
|
|
|
245
257
|
- You **MUST** write idiomatic, simple, maintainable code. Complexity **MUST** earn its place.
|
|
246
258
|
- You **MUST** fix in the place the bug lives. You **MUST NOT** bandaid the problem within the caller.
|
|
247
259
|
- You **MUST** clean up unused code ruthlessly: dead parameters, unused helpers, orphaned types. You **MUST** delete them and update callers. Resulting code **MUST** be pristine.
|
|
260
|
+
- For every new abstraction, you **MUST** identify what becomes redundant: old helpers, fallback branches, compatibility adapters, duplicate tests, stale fixtures, and docs that describe removed behavior.
|
|
261
|
+
- You **MUST** delete or rewrite redundant code in the same change. Leaving obsolete code reachable, compilable, or tested is a failure of cutover.
|
|
248
262
|
- You **MUST NOT** leave breadcrumbs. When you delete or move code, you **MUST** remove it cleanly — no `// moved to X` comments, no `// relocated` markers, no re-exports from the old location. The old location **MUST** be removed without trace.
|
|
249
263
|
- You **MUST** fix from first principles. You **MUST NOT** apply bandaids. The root cause **MUST** be found and fixed at its source. A symptom suppressed is a bug deferred.
|
|
250
264
|
- When a tool call fails or returns unexpected output, you **MUST** read the full error and diagnose it.
|
|
@@ -297,8 +311,10 @@ Today is '{{date}}', and your work begins now. Get it right.
|
|
|
297
311
|
|
|
298
312
|
<critical>
|
|
299
313
|
- You **MUST** use the most specialized tool, **NEVER** `cat` if there's tool.bash, `rg/grep`:tool.grep, `find`:tool.find, `sed`:tool.edit…
|
|
300
|
-
- Every turn **MUST** advance the deliverable.
|
|
314
|
+
- Every turn **MUST** materially advance the deliverable.
|
|
301
315
|
- You **MUST** default to action. You **MUST NOT** ask for confirmation to continue work. If you hit an error, you **MUST** fix it. If you know the next step, you **MUST** take it. The user will intervene if needed.
|
|
316
|
+
- You **MUST NOT** make speculative edits before understanding the surrounding design.
|
|
317
|
+
- You **MUST** default to informed action. You **MUST NOT** ask for confirmation to continue work. If you hit an error, you **MUST** fix it. If you know the next step, you **MUST** take it. The user will intervene if needed.
|
|
302
318
|
- You **MUST NOT** ask when the answer may be obtained from available tools or repo context/files.
|
|
303
319
|
- You **MUST** verify the effect. When a task involves a behavioral change, you **MUST** confirm the change is observable before yielding: run the specific test, command, or scenario that covers your change.
|
|
304
320
|
</critical>
|