@oh-my-pi/pi-coding-agent 15.11.3 → 15.11.6
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 +107 -0
- package/dist/cli.js +692 -607
- package/dist/types/cli/usage-cli.d.ts +10 -1
- package/dist/types/commands/usage.d.ts +9 -0
- package/dist/types/config/api-key-resolver.d.ts +9 -3
- package/dist/types/config/keybindings.d.ts +1 -1
- package/dist/types/config/model-discovery.d.ts +6 -4
- package/dist/types/config/model-registry.d.ts +7 -4
- package/dist/types/config/settings-schema.d.ts +508 -155
- package/dist/types/export/html/template.generated.d.ts +1 -1
- package/dist/types/mnemopi/config.d.ts +3 -1
- package/dist/types/modes/components/reset-usage-selector.d.ts +12 -0
- package/dist/types/modes/components/session-selector.d.ts +1 -1
- package/dist/types/modes/components/settings-defs.d.ts +9 -2
- package/dist/types/modes/components/settings-selector.d.ts +9 -4
- package/dist/types/modes/components/tool-execution.d.ts +26 -1
- package/dist/types/modes/components/transcript-container.d.ts +12 -0
- package/dist/types/modes/controllers/input-controller.d.ts +9 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +10 -0
- package/dist/types/modes/session-observer-registry.d.ts +2 -0
- package/dist/types/modes/theme/theme.d.ts +23 -3
- package/dist/types/modes/types.d.ts +2 -0
- package/dist/types/modes/utils/context-usage.d.ts +6 -1
- package/dist/types/session/agent-session.d.ts +28 -8
- package/dist/types/session/auth-storage.d.ts +1 -1
- package/dist/types/session/codex-auto-reset.d.ts +107 -0
- package/dist/types/session/snapcompact-inline.d.ts +129 -0
- package/dist/types/slash-commands/helpers/active-oauth-account.d.ts +14 -0
- package/dist/types/slash-commands/helpers/reset-usage.d.ts +27 -0
- package/dist/types/system-prompt.d.ts +3 -1
- package/dist/types/task/render.d.ts +17 -6
- package/dist/types/tools/gh.d.ts +3 -0
- package/dist/types/tools/render-utils.d.ts +8 -16
- package/dist/types/tools/todo.d.ts +0 -11
- package/dist/types/utils/session-color.d.ts +15 -3
- package/dist/types/web/kagi.d.ts +1 -2
- package/dist/types/web/search/providers/codex.d.ts +1 -1
- package/dist/types/web/search/providers/gemini.d.ts +9 -6
- package/package.json +11 -11
- package/src/auto-thinking/classifier.ts +1 -5
- package/src/cli/usage-cli.ts +187 -16
- package/src/commands/usage.ts +8 -0
- package/src/commit/model-selection.ts +3 -6
- package/src/config/api-key-resolver.ts +10 -3
- package/src/config/keybindings.ts +1 -1
- package/src/config/model-discovery.ts +60 -46
- package/src/config/model-registry.ts +21 -8
- package/src/config/model-resolver.ts +57 -3
- package/src/config/settings-schema.ts +654 -153
- package/src/config/settings.ts +9 -0
- package/src/eval/completion-bridge.ts +1 -5
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +13 -6
- package/src/internal-urls/docs-index.generated.ts +6 -6
- package/src/internal-urls/issue-pr-protocol.ts +10 -4
- package/src/memories/index.ts +2 -10
- package/src/mnemopi/backend.ts +30 -8
- package/src/mnemopi/config.ts +6 -1
- package/src/mnemopi/state.ts +6 -0
- package/src/modes/components/extensions/inspector-panel.ts +6 -2
- package/src/modes/components/plan-review-overlay.ts +15 -17
- package/src/modes/components/plugin-settings.ts +22 -5
- package/src/modes/components/reset-usage-selector.ts +161 -0
- package/src/modes/components/session-selector.ts +8 -2
- package/src/modes/components/settings-defs.ts +19 -4
- package/src/modes/components/settings-selector.ts +510 -95
- package/src/modes/components/status-line/component.ts +3 -1
- package/src/modes/components/status-line/segments.ts +3 -1
- package/src/modes/components/tool-execution.ts +87 -12
- package/src/modes/components/transcript-container.ts +49 -1
- package/src/modes/components/tree-selector.ts +16 -6
- package/src/modes/controllers/command-controller.ts +61 -8
- package/src/modes/controllers/event-controller.ts +1 -0
- package/src/modes/controllers/input-controller.ts +68 -6
- package/src/modes/controllers/selector-controller.ts +149 -61
- package/src/modes/interactive-mode.ts +63 -2
- package/src/modes/rpc/rpc-mode.ts +2 -1
- package/src/modes/session-observer-registry.ts +61 -3
- package/src/modes/shared.ts +2 -0
- package/src/modes/theme/theme.ts +102 -9
- package/src/modes/types.ts +2 -0
- package/src/modes/utils/context-usage.ts +78 -2
- package/src/modes/utils/hotkeys-markdown.ts +1 -1
- package/src/modes/utils/ui-helpers.ts +9 -5
- package/src/prompts/system/personalities/default.md +26 -0
- package/src/prompts/system/personalities/friendly.md +17 -0
- package/src/prompts/system/personalities/pragmatic.md +15 -0
- package/src/prompts/system/snapcompact-context-frames-note.md +1 -0
- package/src/prompts/system/snapcompact-context-stub.md +1 -0
- package/src/prompts/system/snapcompact-system-frames-note.md +1 -0
- package/src/prompts/system/snapcompact-system-stub.md +1 -0
- package/src/prompts/system/snapcompact-toolresult-note.md +1 -0
- package/src/prompts/system/system-prompt.md +5 -22
- package/src/prompts/tools/browser.md +33 -43
- package/src/prompts/tools/eval.md +27 -50
- package/src/prompts/tools/irc.md +29 -31
- package/src/prompts/tools/read.md +31 -37
- package/src/prompts/tools/task.md +3 -3
- package/src/prompts/tools/todo.md +1 -2
- package/src/sdk.ts +23 -1
- package/src/session/agent-session.ts +221 -29
- package/src/session/auth-storage.ts +4 -0
- package/src/session/codex-auto-reset.ts +190 -0
- package/src/session/session-dump-format.ts +8 -1
- package/src/session/session-manager.ts +5 -5
- package/src/session/snapcompact-inline.ts +524 -0
- package/src/slash-commands/builtin-registry.ts +145 -8
- package/src/slash-commands/helpers/active-oauth-account.ts +44 -0
- package/src/slash-commands/helpers/context-report.ts +28 -1
- package/src/slash-commands/helpers/reset-usage.ts +66 -0
- package/src/slash-commands/helpers/usage-report.ts +36 -3
- package/src/system-prompt.ts +15 -1
- package/src/task/index.ts +30 -7
- package/src/task/render.ts +57 -32
- package/src/tool-discovery/tool-index.ts +2 -0
- package/src/tools/bash.ts +10 -3
- package/src/tools/eval-render.ts +13 -8
- package/src/tools/gh.ts +39 -1
- package/src/tools/image-gen.ts +114 -78
- package/src/tools/inspect-image.ts +1 -5
- package/src/tools/job.ts +25 -5
- package/src/tools/read.ts +1 -57
- package/src/tools/render-utils.ts +29 -31
- package/src/tools/ssh.ts +3 -3
- package/src/tools/todo.ts +8 -128
- package/src/tools/tts.ts +40 -20
- package/src/utils/clipboard.ts +56 -4
- package/src/utils/commit-message-generator.ts +1 -5
- package/src/utils/session-color.ts +83 -9
- package/src/utils/title-generator.ts +1 -1
- package/src/web/kagi.ts +26 -27
- package/src/web/search/providers/codex.ts +42 -40
- package/src/web/search/providers/gemini.ts +42 -22
- package/src/web/search/providers/perplexity.ts +22 -10
|
@@ -27,8 +27,14 @@ import {
|
|
|
27
27
|
theme,
|
|
28
28
|
} from "../../modes/theme/theme";
|
|
29
29
|
import type { InteractiveModeContext } from "../../modes/types";
|
|
30
|
+
import type { ResetCreditRedeemOutcome } from "../../session/auth-storage";
|
|
30
31
|
import { type SessionInfo, SessionManager } from "../../session/session-manager";
|
|
31
32
|
import { FileSessionStorage } from "../../session/session-storage";
|
|
33
|
+
import {
|
|
34
|
+
describeRedeemOutcome,
|
|
35
|
+
type ResetUsageAccount,
|
|
36
|
+
toResetUsageAccounts,
|
|
37
|
+
} from "../../slash-commands/helpers/reset-usage";
|
|
32
38
|
import { AUTO_THINKING, type ConfiguredThinkingLevel } from "../../thinking";
|
|
33
39
|
import {
|
|
34
40
|
isImageProviderPreference,
|
|
@@ -48,6 +54,7 @@ import { HistorySearchComponent } from "../components/history-search";
|
|
|
48
54
|
import { ModelSelectorComponent } from "../components/model-selector";
|
|
49
55
|
import { OAuthSelectorComponent } from "../components/oauth-selector";
|
|
50
56
|
import { PluginSelectorComponent } from "../components/plugin-selector";
|
|
57
|
+
import { ResetUsageSelectorComponent } from "../components/reset-usage-selector";
|
|
51
58
|
import { SessionSelectorComponent } from "../components/session-selector";
|
|
52
59
|
import { SettingsSelectorComponent } from "../components/settings-selector";
|
|
53
60
|
import { ToolExecutionComponent } from "../components/tool-execution";
|
|
@@ -92,71 +99,86 @@ export class SelectorController {
|
|
|
92
99
|
|
|
93
100
|
showSettingsSelector(): void {
|
|
94
101
|
getAvailableThemes().then(availableThemes => {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
this.ctx.statusLine.
|
|
117
|
-
preset: settings.get("statusLine.preset"),
|
|
118
|
-
leftSegments: settings.get("statusLine.leftSegments"),
|
|
119
|
-
rightSegments: settings.get("statusLine.rightSegments"),
|
|
120
|
-
separator: settings.get("statusLine.separator"),
|
|
121
|
-
showHookStatus: settings.get("statusLine.showHookStatus"),
|
|
122
|
-
sessionAccent: settings.get("statusLine.sessionAccent"),
|
|
123
|
-
transparent: settings.get("statusLine.transparent"),
|
|
124
|
-
...previewSettings,
|
|
125
|
-
});
|
|
126
|
-
this.ctx.updateEditorTopBorder();
|
|
127
|
-
this.ctx.ui.requestRender();
|
|
128
|
-
},
|
|
129
|
-
getStatusLinePreview: () => {
|
|
130
|
-
// Return the rendered status line for inline preview
|
|
131
|
-
const availableWidth = this.ctx.editor.getTopBorderAvailableWidth(this.ctx.ui.terminal.columns);
|
|
132
|
-
return this.ctx.statusLine.getTopBorder(availableWidth).content;
|
|
133
|
-
},
|
|
134
|
-
onPluginsChanged: async () => {
|
|
135
|
-
const projectPath = await resolveActiveProjectRegistryPath(this.ctx.sessionManager.getCwd());
|
|
136
|
-
clearPluginRootsAndCaches(projectPath ? [projectPath] : undefined);
|
|
137
|
-
await this.ctx.refreshSlashCommandState();
|
|
138
|
-
await this.ctx.session.refreshSshTool({ activateIfAvailable: true });
|
|
139
|
-
this.ctx.ui.requestRender();
|
|
140
|
-
},
|
|
141
|
-
onCancel: () => {
|
|
142
|
-
done();
|
|
143
|
-
// Restore status line to saved settings
|
|
144
|
-
this.ctx.statusLine.updateSettings({
|
|
145
|
-
preset: settings.get("statusLine.preset"),
|
|
146
|
-
leftSegments: settings.get("statusLine.leftSegments"),
|
|
147
|
-
rightSegments: settings.get("statusLine.rightSegments"),
|
|
148
|
-
separator: settings.get("statusLine.separator"),
|
|
149
|
-
showHookStatus: settings.get("statusLine.showHookStatus"),
|
|
150
|
-
sessionAccent: settings.get("statusLine.sessionAccent"),
|
|
151
|
-
transparent: settings.get("statusLine.transparent"),
|
|
152
|
-
});
|
|
102
|
+
// Fullscreen settings editor on the alternate screen: the overlay
|
|
103
|
+
// enables mouse tracking (click/hover/wheel) for its lifetime and
|
|
104
|
+
// the transcript stays untouched underneath.
|
|
105
|
+
let overlayHandle: OverlayHandle | undefined;
|
|
106
|
+
const done = () => {
|
|
107
|
+
overlayHandle?.hide();
|
|
108
|
+
this.ctx.ui.setFocus(this.ctx.editor);
|
|
109
|
+
this.ctx.ui.requestRender();
|
|
110
|
+
};
|
|
111
|
+
const selector = new SettingsSelectorComponent(
|
|
112
|
+
{
|
|
113
|
+
availableThinkingLevels: [...this.ctx.session.getAvailableThinkingLevels()],
|
|
114
|
+
thinkingLevel: this.ctx.session.thinkingLevel,
|
|
115
|
+
availableThemes,
|
|
116
|
+
cwd: getProjectDir(),
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
onChange: (id, value) => this.handleSettingChange(id, value),
|
|
120
|
+
onThemePreview: async themeName => {
|
|
121
|
+
const result = await previewTheme(themeName);
|
|
122
|
+
if (result.success) {
|
|
123
|
+
this.ctx.statusLine.invalidate();
|
|
153
124
|
this.ctx.updateEditorTopBorder();
|
|
125
|
+
this.ctx.ui.invalidate();
|
|
154
126
|
this.ctx.ui.requestRender();
|
|
155
|
-
}
|
|
127
|
+
}
|
|
156
128
|
},
|
|
157
|
-
|
|
158
|
-
|
|
129
|
+
onStatusLinePreview: previewSettings => {
|
|
130
|
+
// Update status line with preview settings
|
|
131
|
+
this.ctx.statusLine.updateSettings({
|
|
132
|
+
preset: settings.get("statusLine.preset"),
|
|
133
|
+
leftSegments: settings.get("statusLine.leftSegments"),
|
|
134
|
+
rightSegments: settings.get("statusLine.rightSegments"),
|
|
135
|
+
separator: settings.get("statusLine.separator"),
|
|
136
|
+
showHookStatus: settings.get("statusLine.showHookStatus"),
|
|
137
|
+
sessionAccent: settings.get("statusLine.sessionAccent"),
|
|
138
|
+
transparent: settings.get("statusLine.transparent"),
|
|
139
|
+
...previewSettings,
|
|
140
|
+
});
|
|
141
|
+
this.ctx.updateEditorTopBorder();
|
|
142
|
+
this.ctx.ui.requestRender();
|
|
143
|
+
},
|
|
144
|
+
getStatusLinePreview: () => {
|
|
145
|
+
// Return the rendered status line for inline preview
|
|
146
|
+
const availableWidth = this.ctx.editor.getTopBorderAvailableWidth(this.ctx.ui.terminal.columns);
|
|
147
|
+
return this.ctx.statusLine.getTopBorder(availableWidth).content;
|
|
148
|
+
},
|
|
149
|
+
onPluginsChanged: async () => {
|
|
150
|
+
const projectPath = await resolveActiveProjectRegistryPath(this.ctx.sessionManager.getCwd());
|
|
151
|
+
clearPluginRootsAndCaches(projectPath ? [projectPath] : undefined);
|
|
152
|
+
await this.ctx.refreshSlashCommandState();
|
|
153
|
+
await this.ctx.session.refreshSshTool({ activateIfAvailable: true });
|
|
154
|
+
this.ctx.ui.requestRender();
|
|
155
|
+
},
|
|
156
|
+
onCancel: () => {
|
|
157
|
+
done();
|
|
158
|
+
// Restore status line to saved settings
|
|
159
|
+
this.ctx.statusLine.updateSettings({
|
|
160
|
+
preset: settings.get("statusLine.preset"),
|
|
161
|
+
leftSegments: settings.get("statusLine.leftSegments"),
|
|
162
|
+
rightSegments: settings.get("statusLine.rightSegments"),
|
|
163
|
+
separator: settings.get("statusLine.separator"),
|
|
164
|
+
showHookStatus: settings.get("statusLine.showHookStatus"),
|
|
165
|
+
sessionAccent: settings.get("statusLine.sessionAccent"),
|
|
166
|
+
transparent: settings.get("statusLine.transparent"),
|
|
167
|
+
});
|
|
168
|
+
this.ctx.updateEditorTopBorder();
|
|
169
|
+
this.ctx.ui.requestRender();
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
);
|
|
173
|
+
overlayHandle = this.ctx.ui.showOverlay(selector, {
|
|
174
|
+
anchor: "bottom-center",
|
|
175
|
+
width: "100%",
|
|
176
|
+
maxHeight: "100%",
|
|
177
|
+
margin: 0,
|
|
178
|
+
fullscreen: true,
|
|
159
179
|
});
|
|
180
|
+
this.ctx.ui.setFocus(selector);
|
|
181
|
+
this.ctx.ui.requestRender();
|
|
160
182
|
});
|
|
161
183
|
}
|
|
162
184
|
|
|
@@ -267,6 +289,11 @@ export class SelectorController {
|
|
|
267
289
|
this.ctx.statusLine.invalidate();
|
|
268
290
|
this.ctx.updateEditorBorderColor();
|
|
269
291
|
break;
|
|
292
|
+
case "personality":
|
|
293
|
+
void this.ctx.session.refreshBaseSystemPrompt().catch(err => {
|
|
294
|
+
this.ctx.showError(`Failed to apply personality: ${err}`);
|
|
295
|
+
});
|
|
296
|
+
break;
|
|
270
297
|
|
|
271
298
|
case "autocompleteMaxVisible":
|
|
272
299
|
this.ctx.editor.setAutocompleteMaxVisible(typeof value === "number" ? value : Number(value));
|
|
@@ -1071,6 +1098,67 @@ export class SelectorController {
|
|
|
1071
1098
|
});
|
|
1072
1099
|
}
|
|
1073
1100
|
|
|
1101
|
+
async showResetUsageSelector(): Promise<void> {
|
|
1102
|
+
const session = this.ctx.session;
|
|
1103
|
+
this.ctx.showStatus("Checking saved rate-limit resets…", { dim: true });
|
|
1104
|
+
let statuses: Awaited<ReturnType<typeof session.listResetCredits>>;
|
|
1105
|
+
try {
|
|
1106
|
+
statuses = await session.listResetCredits();
|
|
1107
|
+
} catch (error) {
|
|
1108
|
+
this.ctx.showError(`Could not load saved resets: ${error instanceof Error ? error.message : String(error)}`);
|
|
1109
|
+
return;
|
|
1110
|
+
}
|
|
1111
|
+
const accounts = toResetUsageAccounts(statuses);
|
|
1112
|
+
if (accounts.length === 0) {
|
|
1113
|
+
this.ctx.showStatus("No Codex accounts found. Use /login to add one.");
|
|
1114
|
+
return;
|
|
1115
|
+
}
|
|
1116
|
+
if (!accounts.some(account => account.availableCount > 0)) {
|
|
1117
|
+
this.ctx.showStatus(
|
|
1118
|
+
accounts.some(account => account.error)
|
|
1119
|
+
? "No saved resets available — some accounts couldn't be reached (try /login)."
|
|
1120
|
+
: "No saved rate-limit resets available to spend right now.",
|
|
1121
|
+
);
|
|
1122
|
+
return;
|
|
1123
|
+
}
|
|
1124
|
+
this.showSelector(done => {
|
|
1125
|
+
const selector = new ResetUsageSelectorComponent(
|
|
1126
|
+
accounts,
|
|
1127
|
+
account => {
|
|
1128
|
+
done();
|
|
1129
|
+
void this.#redeemReset(account);
|
|
1130
|
+
},
|
|
1131
|
+
() => {
|
|
1132
|
+
done();
|
|
1133
|
+
this.ctx.ui.requestRender();
|
|
1134
|
+
},
|
|
1135
|
+
);
|
|
1136
|
+
return { component: selector, focus: selector };
|
|
1137
|
+
});
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
async #redeemReset(account: ResetUsageAccount): Promise<void> {
|
|
1141
|
+
this.ctx.showStatus(`Spending 1 saved reset for ${account.label}…`, { dim: true });
|
|
1142
|
+
let outcome: ResetCreditRedeemOutcome;
|
|
1143
|
+
try {
|
|
1144
|
+
outcome = await this.ctx.session.redeemResetCredit(account.target);
|
|
1145
|
+
} catch (error) {
|
|
1146
|
+
this.ctx.showError(
|
|
1147
|
+
`Reset failed for ${account.label}: ${error instanceof Error ? error.message : String(error)}`,
|
|
1148
|
+
);
|
|
1149
|
+
return;
|
|
1150
|
+
}
|
|
1151
|
+
const message = describeRedeemOutcome(outcome, account.label);
|
|
1152
|
+
if (outcome.ok) {
|
|
1153
|
+
this.ctx.showStatus(message);
|
|
1154
|
+
// Refresh the status-line usage so the freshly-reset window shows.
|
|
1155
|
+
this.ctx.statusLine.invalidate();
|
|
1156
|
+
this.ctx.ui.requestRender();
|
|
1157
|
+
} else {
|
|
1158
|
+
this.ctx.showWarning(message);
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1074
1162
|
async showDebugSelector(): Promise<void> {
|
|
1075
1163
|
const { DebugSelectorComponent } = await import("../../debug");
|
|
1076
1164
|
this.showSelector(done => {
|
|
@@ -86,8 +86,10 @@ import { BUILTIN_SLASH_COMMAND_RESERVED_NAMES } from "../slash-commands/builtin-
|
|
|
86
86
|
import { formatDuration } from "../slash-commands/helpers/format";
|
|
87
87
|
import { STTController, type SttState } from "../stt";
|
|
88
88
|
import { discoverTitleSystemPromptFile, resolvePromptInput } from "../system-prompt";
|
|
89
|
+
import { formatTaskId } from "../task/render";
|
|
89
90
|
import type { LspStartupServerInfo } from "../tools";
|
|
90
91
|
import { normalizeLocalScheme } from "../tools/path-utils";
|
|
92
|
+
import { replaceTabs, TRUNCATE_LENGTHS, truncateToWidth } from "../tools/render-utils";
|
|
91
93
|
import { setAutoQaConsentHandler } from "../tools/report-tool-issue";
|
|
92
94
|
import { type ResolveToolDetails, runResolveInvocation } from "../tools/resolve";
|
|
93
95
|
import { formatPhaseDisplayName, selectStickyTodoWindow, todoMatchesAnyDescription } from "../tools/todo";
|
|
@@ -132,6 +134,7 @@ import {
|
|
|
132
134
|
parseLoopLimitArgs,
|
|
133
135
|
} from "./loop-limit";
|
|
134
136
|
import { OAuthManualInputManager } from "./oauth-manual-input";
|
|
137
|
+
import type { ObservableSession } from "./session-observer-registry";
|
|
135
138
|
import { SessionObserverRegistry } from "./session-observer-registry";
|
|
136
139
|
import { runProviderSetupWizard } from "./setup-wizard/lazy";
|
|
137
140
|
import { interruptHint } from "./shared";
|
|
@@ -277,6 +280,41 @@ class StatusContainer extends Container implements NativeScrollbackLiveRegion {
|
|
|
277
280
|
}
|
|
278
281
|
}
|
|
279
282
|
|
|
283
|
+
/**
|
|
284
|
+
* Build the anchored subagent HUD block: a bold accent "Subagents" header plus
|
|
285
|
+
* one hooked row per running agent in the same `Id: description` shape the
|
|
286
|
+
* inline task rows use (muted task preview when no description was given).
|
|
287
|
+
* Returns an empty array when nothing is running so the container can clear.
|
|
288
|
+
*/
|
|
289
|
+
export function renderSubagentHudLines(sessions: ObservableSession[], columns: number): string[] {
|
|
290
|
+
const running = sessions.filter(session => session.kind === "subagent" && session.status === "active");
|
|
291
|
+
if (running.length === 0) return [];
|
|
292
|
+
|
|
293
|
+
const indent = " ";
|
|
294
|
+
const hook = theme.tree.hook;
|
|
295
|
+
const dot = theme.styledSymbol("status.done", "accent");
|
|
296
|
+
const lines = ["", indent + theme.bold(theme.fg("accent", "Subagents"))];
|
|
297
|
+
running.forEach((session, index) => {
|
|
298
|
+
const prefix = `${indent}${index === 0 ? hook : " "} `;
|
|
299
|
+
const displayId = formatTaskId(session.id);
|
|
300
|
+
let line = `${prefix}${dot} ${theme.fg("accent", theme.bold(displayId))}`;
|
|
301
|
+
const description = session.description?.trim() || session.progress?.description?.trim();
|
|
302
|
+
if (description) {
|
|
303
|
+
const budget = Math.max(TRUNCATE_LENGTHS.SHORT, columns - visibleWidth(prefix) - visibleWidth(displayId) - 6);
|
|
304
|
+
line += `${theme.fg("accent", ":")} ${theme.fg("accent", truncateToWidth(replaceTabs(description), budget))}`;
|
|
305
|
+
} else {
|
|
306
|
+
// No spawn description: fall back to a muted task preview, same as
|
|
307
|
+
// the inline task rows when a row has no label.
|
|
308
|
+
const taskPreview = session.progress?.task?.trim();
|
|
309
|
+
if (taskPreview) {
|
|
310
|
+
line += ` ${theme.fg("muted", truncateToWidth(replaceTabs(taskPreview), TRUNCATE_LENGTHS.SHORT))}`;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
lines.push(line);
|
|
314
|
+
});
|
|
315
|
+
return lines;
|
|
316
|
+
}
|
|
317
|
+
|
|
280
318
|
export class InteractiveMode implements InteractiveModeContext {
|
|
281
319
|
session: AgentSession;
|
|
282
320
|
sessionManager: SessionManager;
|
|
@@ -291,6 +329,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
291
329
|
pendingMessagesContainer: Container;
|
|
292
330
|
statusContainer: Container;
|
|
293
331
|
todoContainer: Container;
|
|
332
|
+
subagentContainer: Container;
|
|
294
333
|
btwContainer: Container;
|
|
295
334
|
omfgContainer: Container;
|
|
296
335
|
errorBannerContainer: Container;
|
|
@@ -440,6 +479,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
440
479
|
this.pendingMessagesContainer = new Container();
|
|
441
480
|
this.statusContainer = new StatusContainer();
|
|
442
481
|
this.todoContainer = new Container();
|
|
482
|
+
this.subagentContainer = new Container();
|
|
443
483
|
this.btwContainer = new Container();
|
|
444
484
|
this.omfgContainer = new Container();
|
|
445
485
|
this.errorBannerContainer = new Container();
|
|
@@ -606,6 +646,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
606
646
|
this.ui.addChild(this.pendingMessagesContainer);
|
|
607
647
|
this.ui.addChild(this.statusContainer);
|
|
608
648
|
this.ui.addChild(this.todoContainer);
|
|
649
|
+
this.ui.addChild(this.subagentContainer);
|
|
609
650
|
this.ui.addChild(this.btwContainer);
|
|
610
651
|
this.ui.addChild(this.omfgContainer);
|
|
611
652
|
this.ui.addChild(this.errorBannerContainer);
|
|
@@ -632,6 +673,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
632
673
|
this.#reconcileTodosWithSubagents();
|
|
633
674
|
this.#syncTodoAutoClearTimer();
|
|
634
675
|
this.#renderTodoList();
|
|
676
|
+
this.#renderSubagentList();
|
|
635
677
|
this.ui.requestRender();
|
|
636
678
|
});
|
|
637
679
|
|
|
@@ -1093,7 +1135,9 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1093
1135
|
} else {
|
|
1094
1136
|
const accentEnabled = !isSettingsInitialized() || settings.get("statusLine.sessionAccent") !== false;
|
|
1095
1137
|
const sessionName = accentEnabled ? this.sessionManager.getSessionName() : undefined;
|
|
1096
|
-
const hex = sessionName
|
|
1138
|
+
const hex = sessionName
|
|
1139
|
+
? getSessionAccentHex(sessionName, theme.getMajorThemeColorHexes(), theme.accentSurfaceLuminance)
|
|
1140
|
+
: undefined;
|
|
1097
1141
|
const ansi = getSessionAccentAnsi(hex);
|
|
1098
1142
|
if (ansi) {
|
|
1099
1143
|
this.editor.borderColor = (str: string) => `${ansi}${str}\x1b[39m`;
|
|
@@ -1280,6 +1324,19 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1280
1324
|
this.todoContainer.addChild(new Text(lines.join("\n"), 1, 0));
|
|
1281
1325
|
}
|
|
1282
1326
|
|
|
1327
|
+
/**
|
|
1328
|
+
* Anchored HUD of in-flight subagents, mirroring the Todos block above the
|
|
1329
|
+
* editor. Driven entirely by observer-registry change events, so rows appear
|
|
1330
|
+
* on spawn and the whole block clears itself once the last subagent leaves
|
|
1331
|
+
* the "active" state.
|
|
1332
|
+
*/
|
|
1333
|
+
#renderSubagentList(): void {
|
|
1334
|
+
this.subagentContainer.clear();
|
|
1335
|
+
const lines = renderSubagentHudLines(this.#observerRegistry.getSessions(), this.ui.terminal.columns);
|
|
1336
|
+
if (lines.length === 0) return;
|
|
1337
|
+
this.subagentContainer.addChild(new Text(lines.join("\n"), 1, 0));
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1283
1340
|
async #loadTodoList(): Promise<void> {
|
|
1284
1341
|
this.todoPhases = this.session.getTodoPhases();
|
|
1285
1342
|
this.#syncTodoAutoClearTimer();
|
|
@@ -2823,7 +2880,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2823
2880
|
if (!key.sessionAccentEnabled || !key.sessionName) {
|
|
2824
2881
|
return this.#cacheWorkingMessageAccent(key, undefined);
|
|
2825
2882
|
}
|
|
2826
|
-
const hex = getSessionAccentHex(key.sessionName, key.accentSurfaceLuminance);
|
|
2883
|
+
const hex = getSessionAccentHex(key.sessionName, theme.getMajorThemeColorHexes(), key.accentSurfaceLuminance);
|
|
2827
2884
|
const main = getSessionAccentAnsi(hex);
|
|
2828
2885
|
const dim = getSessionAccentAnsi(adjustHsv(hex, { s: 0.55, v: 0.65 }));
|
|
2829
2886
|
return this.#cacheWorkingMessageAccent(key, main && dim ? { main, dim } : undefined);
|
|
@@ -3233,6 +3290,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
3233
3290
|
return this.#selectorController.showOAuthSelector(mode, providerId);
|
|
3234
3291
|
}
|
|
3235
3292
|
|
|
3293
|
+
showResetUsageSelector(): Promise<void> {
|
|
3294
|
+
return this.#selectorController.showResetUsageSelector();
|
|
3295
|
+
}
|
|
3296
|
+
|
|
3236
3297
|
showProviderSetup(): Promise<void> {
|
|
3237
3298
|
return runProviderSetupWizard(this);
|
|
3238
3299
|
}
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* - Extension UI: Extension UI requests are emitted, client responds with extension_ui_response
|
|
12
12
|
*/
|
|
13
13
|
import { getOAuthProviders } from "@oh-my-pi/pi-ai/oauth";
|
|
14
|
+
import { isZodSchema, zodToWireSchema } from "@oh-my-pi/pi-ai/utils/schema";
|
|
14
15
|
import { $env, readJsonl, Snowflake } from "@oh-my-pi/pi-utils";
|
|
15
16
|
import { reset as resetCapabilities } from "../../capability";
|
|
16
17
|
import { clearPluginRootsAndCaches, resolveActiveProjectRegistryPath } from "../../discovery/helpers";
|
|
@@ -662,7 +663,7 @@ export async function runRpcMode(
|
|
|
662
663
|
dumpTools: session.agent.state.tools.map(tool => ({
|
|
663
664
|
name: tool.name,
|
|
664
665
|
description: tool.description,
|
|
665
|
-
parameters: tool.parameters,
|
|
666
|
+
parameters: isZodSchema(tool.parameters) ? zodToWireSchema(tool.parameters) : tool.parameters,
|
|
666
667
|
})),
|
|
667
668
|
contextUsage: session.getContextUsage(),
|
|
668
669
|
};
|
|
@@ -10,6 +10,8 @@ export interface ObservableSession {
|
|
|
10
10
|
description?: string;
|
|
11
11
|
status: "active" | "completed" | "failed" | "aborted";
|
|
12
12
|
sessionFile?: string;
|
|
13
|
+
parentToolCallId?: string;
|
|
14
|
+
index?: number;
|
|
13
15
|
lastUpdate: number;
|
|
14
16
|
/** Latest progress snapshot from the subagent executor */
|
|
15
17
|
progress?: AgentProgress;
|
|
@@ -26,6 +28,9 @@ export class SessionObserverRegistry {
|
|
|
26
28
|
#sessions = new Map<string, ObservableSession>();
|
|
27
29
|
#listeners = new Set<() => void>();
|
|
28
30
|
#eventBusUnsubscribers: Array<() => void> = [];
|
|
31
|
+
#sortOrderById = new Map<string, number>();
|
|
32
|
+
#parentSortOrderById = new Map<string, number>();
|
|
33
|
+
#nextSortOrder = 0;
|
|
29
34
|
|
|
30
35
|
/** Add a change listener. Returns unsubscribe function. */
|
|
31
36
|
onChange(cb: () => void): () => void {
|
|
@@ -37,8 +42,34 @@ export class SessionObserverRegistry {
|
|
|
37
42
|
for (const cb of this.#listeners) cb();
|
|
38
43
|
}
|
|
39
44
|
|
|
45
|
+
#ensureSortOrder(id: string): number {
|
|
46
|
+
const existing = this.#sortOrderById.get(id);
|
|
47
|
+
if (existing !== undefined) return existing;
|
|
48
|
+
const order = this.#nextSortOrder++;
|
|
49
|
+
this.#sortOrderById.set(id, order);
|
|
50
|
+
return order;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
#ensureParentSortOrder(parentToolCallId: string | undefined, order: number): void {
|
|
54
|
+
if (!parentToolCallId) return;
|
|
55
|
+
if (this.#parentSortOrderById.has(parentToolCallId)) return;
|
|
56
|
+
this.#parentSortOrderById.set(parentToolCallId, order);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
#getStableOrder(session: ObservableSession): number {
|
|
60
|
+
return this.#sortOrderById.get(session.id) ?? Number.MAX_SAFE_INTEGER;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
#getGroupOrder(session: ObservableSession): number {
|
|
64
|
+
const parentOrder = session.parentToolCallId
|
|
65
|
+
? this.#parentSortOrderById.get(session.parentToolCallId)
|
|
66
|
+
: undefined;
|
|
67
|
+
return parentOrder ?? this.#getStableOrder(session);
|
|
68
|
+
}
|
|
69
|
+
|
|
40
70
|
setMainSession(sessionFile?: string): void {
|
|
41
71
|
const existing = this.#sessions.get("main");
|
|
72
|
+
this.#ensureSortOrder("main");
|
|
42
73
|
this.#sessions.set("main", {
|
|
43
74
|
id: "main",
|
|
44
75
|
kind: "main",
|
|
@@ -53,9 +84,18 @@ export class SessionObserverRegistry {
|
|
|
53
84
|
getSessions(): ObservableSession[] {
|
|
54
85
|
const sessions = [...this.#sessions.values()];
|
|
55
86
|
sessions.sort((a, b) => {
|
|
56
|
-
if (a.kind === "main") return -1;
|
|
57
|
-
if (b.kind === "main") return 1;
|
|
58
|
-
|
|
87
|
+
if (a.kind === "main" && b.kind !== "main") return -1;
|
|
88
|
+
if (b.kind === "main" && a.kind !== "main") return 1;
|
|
89
|
+
if (a.kind === "main" || b.kind === "main") return 0;
|
|
90
|
+
|
|
91
|
+
const groupDiff = this.#getGroupOrder(a) - this.#getGroupOrder(b);
|
|
92
|
+
if (groupDiff !== 0) return groupDiff;
|
|
93
|
+
|
|
94
|
+
const aIndex = a.index ?? Number.MAX_SAFE_INTEGER;
|
|
95
|
+
const bIndex = b.index ?? Number.MAX_SAFE_INTEGER;
|
|
96
|
+
if (aIndex !== bIndex) return aIndex - bIndex;
|
|
97
|
+
|
|
98
|
+
return this.#getStableOrder(a) - this.#getStableOrder(b);
|
|
59
99
|
});
|
|
60
100
|
return sessions;
|
|
61
101
|
}
|
|
@@ -71,6 +111,9 @@ export class SessionObserverRegistry {
|
|
|
71
111
|
/** Clear all tracked sessions (e.g. on session switch). Keeps EventBus subscriptions and listeners. */
|
|
72
112
|
resetSessions(): void {
|
|
73
113
|
this.#sessions.clear();
|
|
114
|
+
this.#sortOrderById.clear();
|
|
115
|
+
this.#parentSortOrderById.clear();
|
|
116
|
+
this.#nextSortOrder = 0;
|
|
74
117
|
this.#notifyListeners();
|
|
75
118
|
}
|
|
76
119
|
|
|
@@ -78,6 +121,9 @@ export class SessionObserverRegistry {
|
|
|
78
121
|
for (const unsub of this.#eventBusUnsubscribers) unsub();
|
|
79
122
|
this.#eventBusUnsubscribers = [];
|
|
80
123
|
this.#sessions.clear();
|
|
124
|
+
this.#sortOrderById.clear();
|
|
125
|
+
this.#parentSortOrderById.clear();
|
|
126
|
+
this.#nextSortOrder = 0;
|
|
81
127
|
this.#listeners.clear();
|
|
82
128
|
}
|
|
83
129
|
|
|
@@ -92,10 +138,14 @@ export class SessionObserverRegistry {
|
|
|
92
138
|
const status = STATUS_MAP[payload.status];
|
|
93
139
|
if (!status) return;
|
|
94
140
|
|
|
141
|
+
const sortOrder = this.#ensureSortOrder(payload.id);
|
|
142
|
+
this.#ensureParentSortOrder(payload.parentToolCallId, sortOrder);
|
|
95
143
|
const existing = this.#sessions.get(payload.id);
|
|
96
144
|
if (existing) {
|
|
97
145
|
existing.status = status;
|
|
98
146
|
existing.lastUpdate = Date.now();
|
|
147
|
+
existing.index = payload.index;
|
|
148
|
+
existing.parentToolCallId = payload.parentToolCallId ?? existing.parentToolCallId;
|
|
99
149
|
if (payload.description) existing.description = payload.description;
|
|
100
150
|
if (payload.sessionFile) existing.sessionFile = payload.sessionFile;
|
|
101
151
|
} else {
|
|
@@ -107,6 +157,8 @@ export class SessionObserverRegistry {
|
|
|
107
157
|
description: payload.description,
|
|
108
158
|
status,
|
|
109
159
|
sessionFile: payload.sessionFile,
|
|
160
|
+
parentToolCallId: payload.parentToolCallId,
|
|
161
|
+
index: payload.index,
|
|
110
162
|
lastUpdate: Date.now(),
|
|
111
163
|
});
|
|
112
164
|
}
|
|
@@ -121,8 +173,12 @@ export class SessionObserverRegistry {
|
|
|
121
173
|
const id = progress.id;
|
|
122
174
|
const existing = this.#sessions.get(id);
|
|
123
175
|
|
|
176
|
+
const sortOrder = this.#ensureSortOrder(id);
|
|
177
|
+
this.#ensureParentSortOrder(payload.parentToolCallId, sortOrder);
|
|
124
178
|
if (existing) {
|
|
125
179
|
existing.lastUpdate = Date.now();
|
|
180
|
+
existing.index = payload.index;
|
|
181
|
+
existing.parentToolCallId = payload.parentToolCallId ?? existing.parentToolCallId;
|
|
126
182
|
existing.progress = progress;
|
|
127
183
|
if (progress.description) existing.description = progress.description;
|
|
128
184
|
if (payload.sessionFile) existing.sessionFile = payload.sessionFile;
|
|
@@ -135,6 +191,8 @@ export class SessionObserverRegistry {
|
|
|
135
191
|
description: progress.description,
|
|
136
192
|
status: "active",
|
|
137
193
|
sessionFile: payload.sessionFile,
|
|
194
|
+
parentToolCallId: payload.parentToolCallId,
|
|
195
|
+
index: payload.index,
|
|
138
196
|
lastUpdate: Date.now(),
|
|
139
197
|
progress,
|
|
140
198
|
});
|
package/src/modes/shared.ts
CHANGED
|
@@ -24,6 +24,8 @@ export function getTabBarTheme(): TabBarTheme {
|
|
|
24
24
|
label: (text: string) => theme.bold(theme.fg("accent", text)),
|
|
25
25
|
activeTab: (text: string) => theme.bold(theme.bg("selectedBg", theme.fg("text", text))),
|
|
26
26
|
inactiveTab: (text: string) => theme.fg("muted", text),
|
|
27
|
+
mutedTab: (text: string) => theme.fg("dim", text),
|
|
28
|
+
hoverTab: (text: string) => theme.bg("selectedBg", theme.fg("text", text)),
|
|
27
29
|
hint: (text: string) => theme.fg("dim", text),
|
|
28
30
|
};
|
|
29
31
|
}
|