@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
|
@@ -2,6 +2,7 @@ import * as fs from "node:fs/promises";
|
|
|
2
2
|
import * as os from "node:os";
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import { getOAuthProviders } from "@oh-my-pi/pi-ai/oauth";
|
|
5
|
+
import { setNextRequestDebugPath } from "@oh-my-pi/pi-ai/utils/request-debug";
|
|
5
6
|
import { Snowflake, setProjectDir } from "@oh-my-pi/pi-utils";
|
|
6
7
|
import { $ } from "bun";
|
|
7
8
|
import type { SettingPath, SettingValue } from "../config/settings";
|
|
@@ -21,7 +22,7 @@ import {
|
|
|
21
22
|
} from "../extensibility/plugins/marketplace";
|
|
22
23
|
import { resolveMemoryBackend } from "../memory-backend";
|
|
23
24
|
import type { InteractiveModeContext } from "../modes/types";
|
|
24
|
-
import type { FreshSessionResult } from "../session/agent-session";
|
|
25
|
+
import type { AgentSession, FreshSessionResult } from "../session/agent-session";
|
|
25
26
|
import { formatShakeSummary, type ShakeMode } from "../session/shake-types";
|
|
26
27
|
import { getChangelogPath, parseChangelog } from "../utils/changelog";
|
|
27
28
|
import { buildContextReportText } from "./helpers/context-report";
|
|
@@ -29,6 +30,7 @@ import { formatDuration } from "./helpers/format";
|
|
|
29
30
|
import { createMarketplaceManager } from "./helpers/marketplace-manager";
|
|
30
31
|
import { handleMcpAcp } from "./helpers/mcp";
|
|
31
32
|
import { commandConsumed, errorMessage, parseSlashCommand, parseSubcommand, usage } from "./helpers/parse";
|
|
33
|
+
import { describeRedeemOutcome, type ResetUsageAccount, toResetUsageAccounts } from "./helpers/reset-usage";
|
|
32
34
|
import { handleSshAcp } from "./helpers/ssh";
|
|
33
35
|
import { launchStatsDashboard, parseStatsDashboardArgs } from "./helpers/stats-dashboard";
|
|
34
36
|
import { handleTodoAcp } from "./helpers/todo";
|
|
@@ -65,6 +67,95 @@ const shutdownHandlerTui = (_command: ParsedSlashCommand, runtime: TuiSlashComma
|
|
|
65
67
|
return commandConsumed();
|
|
66
68
|
};
|
|
67
69
|
|
|
70
|
+
async function handleUsageResetCommand(
|
|
71
|
+
arg: string,
|
|
72
|
+
session: AgentSession,
|
|
73
|
+
output: SlashCommandRuntime["output"],
|
|
74
|
+
): Promise<void> {
|
|
75
|
+
let accounts: ResetUsageAccount[];
|
|
76
|
+
try {
|
|
77
|
+
accounts = toResetUsageAccounts(await session.listResetCredits());
|
|
78
|
+
} catch (error) {
|
|
79
|
+
await output(`Could not load saved resets: ${errorMessage(error)}`);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (accounts.length === 0) {
|
|
83
|
+
await output("No Codex accounts found. Use /login to add one.");
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const targetArg = arg.trim();
|
|
87
|
+
if (!targetArg) {
|
|
88
|
+
const lines = ["Saved Codex rate-limit resets:"];
|
|
89
|
+
for (const account of accounts) {
|
|
90
|
+
const detail = account.error ? `unavailable (${account.error})` : `${account.availableCount} available`;
|
|
91
|
+
lines.push(`- ${account.label}: ${detail}${account.active ? " (active)" : ""}`);
|
|
92
|
+
}
|
|
93
|
+
lines.push("", "Spend one with `/usage reset <account email>` or `/usage reset active`.");
|
|
94
|
+
await output(lines.join("\n"));
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const wanted = targetArg.toLowerCase();
|
|
98
|
+
const target =
|
|
99
|
+
wanted === "active"
|
|
100
|
+
? accounts.find(account => account.active)
|
|
101
|
+
: accounts.find(
|
|
102
|
+
account =>
|
|
103
|
+
account.label.toLowerCase() === wanted ||
|
|
104
|
+
account.target.email?.toLowerCase() === wanted ||
|
|
105
|
+
account.target.accountId?.toLowerCase() === wanted,
|
|
106
|
+
);
|
|
107
|
+
if (!target) {
|
|
108
|
+
await output(`No Codex account matches "${targetArg}".`);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (target.availableCount <= 0) {
|
|
112
|
+
await output(`${target.label}: no saved resets to spend.`);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const outcome = await session.redeemResetCredit(target.target);
|
|
116
|
+
await output(describeRedeemOutcome(outcome, target.label));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const DEBUG_DUMP_NEXT_REQUEST_USAGE = "Usage: /debug dump-next-request <path>";
|
|
120
|
+
|
|
121
|
+
function resolveDebugRequestDumpPath(target: string, cwd: string): string {
|
|
122
|
+
const expanded =
|
|
123
|
+
target === "~"
|
|
124
|
+
? os.homedir()
|
|
125
|
+
: target.startsWith("~/") || target.startsWith("~\\")
|
|
126
|
+
? path.join(os.homedir(), target.slice(2))
|
|
127
|
+
: target;
|
|
128
|
+
return path.resolve(cwd, expanded);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function handleDebugSubcommand(
|
|
132
|
+
args: string,
|
|
133
|
+
cwd: string,
|
|
134
|
+
output: (text: string) => Promise<void> | void,
|
|
135
|
+
): Promise<SlashCommandResult> {
|
|
136
|
+
const { verb, rest } = parseSubcommand(args);
|
|
137
|
+
switch (verb) {
|
|
138
|
+
case "":
|
|
139
|
+
await output(DEBUG_DUMP_NEXT_REQUEST_USAGE);
|
|
140
|
+
return commandConsumed();
|
|
141
|
+
case "dump-next-request":
|
|
142
|
+
case "dump-request":
|
|
143
|
+
case "next-request": {
|
|
144
|
+
if (!rest) {
|
|
145
|
+
await output(DEBUG_DUMP_NEXT_REQUEST_USAGE);
|
|
146
|
+
return commandConsumed();
|
|
147
|
+
}
|
|
148
|
+
const requestPath = resolveDebugRequestDumpPath(rest, cwd);
|
|
149
|
+
setNextRequestDebugPath(requestPath);
|
|
150
|
+
await output(`Next AI provider request will be dumped to ${requestPath}`);
|
|
151
|
+
return commandConsumed();
|
|
152
|
+
}
|
|
153
|
+
default:
|
|
154
|
+
await output(`Unknown /debug subcommand "${verb}". ${DEBUG_DUMP_NEXT_REQUEST_USAGE}`);
|
|
155
|
+
return commandConsumed();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
68
159
|
/** Parse the `/shake` subcommand into a {@link ShakeMode}; empty defaults to elide. */
|
|
69
160
|
function parseShakeMode(args: string): ShakeMode | { error: string } {
|
|
70
161
|
const verb = args.trim().toLowerCase();
|
|
@@ -551,12 +642,41 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
|
|
|
551
642
|
name: "usage",
|
|
552
643
|
description: "Show provider usage and limits",
|
|
553
644
|
acpDescription: "Show token usage",
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
645
|
+
acpInputHint: "[show|reset [account|active]]",
|
|
646
|
+
subcommands: [
|
|
647
|
+
{ name: "show", description: "Show provider usage and limits" },
|
|
648
|
+
{ name: "reset", description: "Spend a saved Codex rate-limit reset", usage: "[account|active]" },
|
|
649
|
+
],
|
|
650
|
+
allowArgs: true,
|
|
651
|
+
handle: async (command, runtime) => {
|
|
652
|
+
const { verb, rest } = parseSubcommand(command.args);
|
|
653
|
+
if (!verb || (verb === "show" && !rest)) {
|
|
654
|
+
await runtime.output(await buildUsageReportText(runtime));
|
|
655
|
+
return commandConsumed();
|
|
656
|
+
}
|
|
657
|
+
if (verb === "reset") {
|
|
658
|
+
await handleUsageResetCommand(rest, runtime.session, runtime.output);
|
|
659
|
+
return commandConsumed();
|
|
660
|
+
}
|
|
661
|
+
return usage("Usage: /usage [show|reset [account|active]]", runtime);
|
|
557
662
|
},
|
|
558
|
-
handleTui: async (
|
|
559
|
-
|
|
663
|
+
handleTui: async (command, runtime) => {
|
|
664
|
+
const { verb, rest } = parseSubcommand(command.args);
|
|
665
|
+
if (!verb || (verb === "show" && !rest)) {
|
|
666
|
+
await runtime.ctx.handleUsageCommand();
|
|
667
|
+
runtime.ctx.editor.setText("");
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
if (verb === "reset") {
|
|
671
|
+
if (rest) {
|
|
672
|
+
await handleUsageResetCommand(rest, runtime.ctx.session, text => runtime.ctx.showStatus(text));
|
|
673
|
+
} else {
|
|
674
|
+
await runtime.ctx.showResetUsageSelector();
|
|
675
|
+
}
|
|
676
|
+
runtime.ctx.editor.setText("");
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
runtime.ctx.showStatus("Usage: /usage [show|reset [account|active]]");
|
|
560
680
|
runtime.ctx.editor.setText("");
|
|
561
681
|
},
|
|
562
682
|
},
|
|
@@ -974,8 +1094,25 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
|
|
|
974
1094
|
{
|
|
975
1095
|
name: "debug",
|
|
976
1096
|
description: "Open debug tools selector",
|
|
977
|
-
|
|
978
|
-
|
|
1097
|
+
allowArgs: true,
|
|
1098
|
+
subcommands: [
|
|
1099
|
+
{
|
|
1100
|
+
name: "dump-next-request",
|
|
1101
|
+
description: "Dump the next AI provider HTTP request as JSON",
|
|
1102
|
+
usage: "<path>",
|
|
1103
|
+
},
|
|
1104
|
+
],
|
|
1105
|
+
handle: async (command, runtime) =>
|
|
1106
|
+
handleDebugSubcommand(command.args, runtime.cwd, text => runtime.output(text)),
|
|
1107
|
+
handleTui: async (command, runtime) => {
|
|
1108
|
+
const args = command.args.trim();
|
|
1109
|
+
if (args.length === 0) {
|
|
1110
|
+
runtime.ctx.showDebugSelector();
|
|
1111
|
+
} else {
|
|
1112
|
+
await handleDebugSubcommand(args, runtime.ctx.sessionManager.getCwd(), text =>
|
|
1113
|
+
runtime.ctx.showStatus(text),
|
|
1114
|
+
);
|
|
1115
|
+
}
|
|
979
1116
|
runtime.ctx.editor.setText("");
|
|
980
1117
|
},
|
|
981
1118
|
},
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { UsageLimit, UsageReport } from "@oh-my-pi/pi-ai";
|
|
2
|
+
import type { OAuthAccountIdentity } from "../../session/auth-storage";
|
|
3
|
+
|
|
4
|
+
function normalizeIdentityValue(value: unknown): string | undefined {
|
|
5
|
+
return typeof value === "string" && value.trim() ? value.trim().toLowerCase() : undefined;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* True when a single usage-limit column belongs to the given OAuth identity.
|
|
10
|
+
*
|
|
11
|
+
* Single definition of the matching rules for both `/usage` renderers:
|
|
12
|
+
* - `accountId` ↔ report metadata `accountId`/`account_id` or `limit.scope.accountId`
|
|
13
|
+
* - `email` ↔ report metadata `email`
|
|
14
|
+
* - `projectId` ↔ report metadata `projectId` or `limit.scope.projectId`
|
|
15
|
+
* (Google-style providers key usage on the GCP project, not an account id)
|
|
16
|
+
*/
|
|
17
|
+
export function limitMatchesActiveAccount(
|
|
18
|
+
report: UsageReport,
|
|
19
|
+
limit: UsageLimit,
|
|
20
|
+
identity: OAuthAccountIdentity | undefined,
|
|
21
|
+
): boolean {
|
|
22
|
+
if (!identity) return false;
|
|
23
|
+
const metadata = report.metadata ?? {};
|
|
24
|
+
const activeAccountId = normalizeIdentityValue(identity.accountId);
|
|
25
|
+
if (activeAccountId) {
|
|
26
|
+
const reportAccountId = normalizeIdentityValue(metadata.accountId) ?? normalizeIdentityValue(metadata.account_id);
|
|
27
|
+
if (reportAccountId === activeAccountId) return true;
|
|
28
|
+
if (normalizeIdentityValue(limit.scope.accountId) === activeAccountId) return true;
|
|
29
|
+
}
|
|
30
|
+
const activeEmail = normalizeIdentityValue(identity.email);
|
|
31
|
+
if (activeEmail && normalizeIdentityValue(metadata.email) === activeEmail) return true;
|
|
32
|
+
const activeProjectId = normalizeIdentityValue(identity.projectId);
|
|
33
|
+
if (activeProjectId) {
|
|
34
|
+
if (normalizeIdentityValue(metadata.projectId) === activeProjectId) return true;
|
|
35
|
+
if (normalizeIdentityValue(limit.scope.projectId) === activeProjectId) return true;
|
|
36
|
+
}
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** True when any limit column in `report` belongs to the given OAuth identity. */
|
|
41
|
+
export function reportMatchesActiveAccount(report: UsageReport, identity: OAuthAccountIdentity | undefined): boolean {
|
|
42
|
+
if (!identity) return false;
|
|
43
|
+
return report.limits.some(limit => limitMatchesActiveAccount(report, limit, identity));
|
|
44
|
+
}
|
|
@@ -9,7 +9,7 @@ import { renderAsciiBar } from "./format";
|
|
|
9
9
|
*/
|
|
10
10
|
export function buildContextReportText(runtime: SlashCommandRuntime): string {
|
|
11
11
|
try {
|
|
12
|
-
const breakdown = computeContextBreakdown(runtime.session);
|
|
12
|
+
const breakdown = computeContextBreakdown(runtime.session, { snapcompactSavings: true });
|
|
13
13
|
if (breakdown.contextWindow <= 0) {
|
|
14
14
|
return "Context usage is unavailable: no model is selected for this session.";
|
|
15
15
|
}
|
|
@@ -30,6 +30,33 @@ export function buildContextReportText(runtime: SlashCommandRuntime): string {
|
|
|
30
30
|
const fraction = breakdown.freeTokens / breakdown.contextWindow;
|
|
31
31
|
lines.push(` ${"Free".padEnd(16)} ${renderAsciiBar(fraction)} ${breakdown.freeTokens} tokens`);
|
|
32
32
|
}
|
|
33
|
+
const snap = breakdown.snapcompact;
|
|
34
|
+
if (snap) {
|
|
35
|
+
if (!snap.visionCapable) {
|
|
36
|
+
lines.push("Snapcompact: inactive (model has no image input)");
|
|
37
|
+
} else {
|
|
38
|
+
lines.push("Snapcompact (estimated wire savings):");
|
|
39
|
+
if (snap.systemPrompt) {
|
|
40
|
+
const sp = snap.systemPrompt;
|
|
41
|
+
lines.push(
|
|
42
|
+
sp.applied
|
|
43
|
+
? ` System prompt: ${sp.textTokens} text tokens → ${sp.frames} frame${sp.frames === 1 ? "" : "s"} ≈ ${sp.imageTokens} tokens (saves ~${sp.savedTokens})`
|
|
44
|
+
: " System prompt: stays text (no net savings)",
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
if (snap.toolResults) {
|
|
48
|
+
const tr = snap.toolResults;
|
|
49
|
+
lines.push(
|
|
50
|
+
tr.swapped > 0
|
|
51
|
+
? ` Tool results: ${tr.swapped} of ${tr.total} imaged, ${tr.textTokens} text tokens → ${tr.frames} frames ≈ ${tr.imageTokens} tokens (saves ~${tr.savedTokens})`
|
|
52
|
+
: ` Tool results: none imaged (${tr.total} in history)`,
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
if (snap.savedTokens > 0) {
|
|
56
|
+
lines.push(` Estimated next request: ~${breakdown.usedTokens - snap.savedTokens} tokens on the wire`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
33
60
|
return lines.join("\n");
|
|
34
61
|
} catch {
|
|
35
62
|
const fallback = runtime.session.getContextUsage();
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helpers for the `/usage reset` command (TUI selector + ACP): turn the
|
|
3
|
+
* live per-account reset-credit status into selector rows, and map a redeem
|
|
4
|
+
* outcome code to a human message.
|
|
5
|
+
*/
|
|
6
|
+
import type { ResetCreditAccountStatus, ResetCreditRedeemOutcome, ResetCreditTarget } from "../../session/auth-storage";
|
|
7
|
+
|
|
8
|
+
export const CODEX_PROVIDER_ID = "openai-codex";
|
|
9
|
+
|
|
10
|
+
/** One Codex account row for the reset-usage selector. */
|
|
11
|
+
export interface ResetUsageAccount {
|
|
12
|
+
/** Display label (email, else account id). */
|
|
13
|
+
label: string;
|
|
14
|
+
/** Saved resets redeemable for this account right now. */
|
|
15
|
+
availableCount: number;
|
|
16
|
+
/** Identifies the account when redeeming. */
|
|
17
|
+
target: ResetCreditTarget;
|
|
18
|
+
/** Whether this is the session's active Codex account. */
|
|
19
|
+
active: boolean;
|
|
20
|
+
/** Set when this account could not be reached (token/list failure). */
|
|
21
|
+
error?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Map live per-account reset status to selector rows. Sorted with the active
|
|
26
|
+
* account first, then most-credits, then label.
|
|
27
|
+
*/
|
|
28
|
+
export function toResetUsageAccounts(statuses: ResetCreditAccountStatus[]): ResetUsageAccount[] {
|
|
29
|
+
return statuses
|
|
30
|
+
.map(status => ({
|
|
31
|
+
label: status.email ?? status.accountId ?? "account",
|
|
32
|
+
availableCount: status.availableCount,
|
|
33
|
+
target: {
|
|
34
|
+
credentialId: status.credentialId,
|
|
35
|
+
accountId: status.accountId,
|
|
36
|
+
email: status.email,
|
|
37
|
+
} satisfies ResetCreditTarget,
|
|
38
|
+
active: status.active,
|
|
39
|
+
error: status.error,
|
|
40
|
+
}))
|
|
41
|
+
.sort((a, b) => {
|
|
42
|
+
if (a.active !== b.active) return a.active ? -1 : 1;
|
|
43
|
+
if (a.availableCount !== b.availableCount) return b.availableCount - a.availableCount;
|
|
44
|
+
return a.label.localeCompare(b.label);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Human-facing summary of a redeem outcome for status lines and ACP output. */
|
|
49
|
+
export function describeRedeemOutcome(outcome: ResetCreditRedeemOutcome, label: string): string {
|
|
50
|
+
switch (outcome.code) {
|
|
51
|
+
case "reset":
|
|
52
|
+
return `Reset applied for ${label} — your rate-limit window has been refreshed.`;
|
|
53
|
+
case "already_redeemed":
|
|
54
|
+
return `${label}: that reset was already redeemed.`;
|
|
55
|
+
case "no_credit":
|
|
56
|
+
return `${label}: no saved resets available to spend.`;
|
|
57
|
+
case "nothing_to_reset":
|
|
58
|
+
return `${label}: nothing to reset right now — your limits aren't constrained, so no credit was spent.`;
|
|
59
|
+
case "no_account":
|
|
60
|
+
return `Could not find a stored Codex account matching "${label}".`;
|
|
61
|
+
case "account_unavailable":
|
|
62
|
+
return `${label}: could not authenticate this account — try /login.`;
|
|
63
|
+
default:
|
|
64
|
+
return `${label}: reset did not apply (${outcome.code}).`;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { UsageLimit, UsageReport } from "@oh-my-pi/pi-ai";
|
|
2
|
+
import type { OAuthAccountIdentity } from "../../session/auth-storage";
|
|
2
3
|
import type { SlashCommandRuntime } from "../types";
|
|
4
|
+
import { reportMatchesActiveAccount } from "./active-oauth-account";
|
|
3
5
|
import { formatDuration, renderAsciiBar } from "./format";
|
|
4
6
|
|
|
5
7
|
function formatProviderName(provider: string): string {
|
|
@@ -31,7 +33,11 @@ function formatUsageReportAccount(report: UsageReport, limit: UsageLimit, index:
|
|
|
31
33
|
return `account ${index + 1}`;
|
|
32
34
|
}
|
|
33
35
|
|
|
34
|
-
function renderUsageReports(
|
|
36
|
+
function renderUsageReports(
|
|
37
|
+
reports: UsageReport[],
|
|
38
|
+
nowMs: number,
|
|
39
|
+
resolveActiveAccount?: (provider: string) => OAuthAccountIdentity | undefined,
|
|
40
|
+
): string {
|
|
35
41
|
const latestFetchedAt = Math.max(...reports.map(report => report.fetchedAt ?? 0));
|
|
36
42
|
const lines = [`Usage${latestFetchedAt ? ` (${formatDuration(nowMs - latestFetchedAt)} ago)` : ""}`];
|
|
37
43
|
const grouped = new Map<string, UsageReport[]>();
|
|
@@ -45,7 +51,21 @@ function renderUsageReports(reports: UsageReport[], nowMs: number): string {
|
|
|
45
51
|
left.localeCompare(right),
|
|
46
52
|
)) {
|
|
47
53
|
lines.push("", formatProviderName(provider));
|
|
54
|
+
const activeAccount = resolveActiveAccount?.(provider);
|
|
48
55
|
for (const report of providerReports) {
|
|
56
|
+
const inUse = reportMatchesActiveAccount(report, activeAccount);
|
|
57
|
+
const savedResets = report.resetCredits?.availableCount ?? 0;
|
|
58
|
+
if (savedResets > 0) {
|
|
59
|
+
const resetLabel =
|
|
60
|
+
typeof report.metadata?.email === "string"
|
|
61
|
+
? report.metadata.email
|
|
62
|
+
: typeof report.metadata?.accountId === "string"
|
|
63
|
+
? report.metadata.accountId
|
|
64
|
+
: "account";
|
|
65
|
+
lines.push(
|
|
66
|
+
`- ${resetLabel}: ${savedResets} saved rate-limit reset${savedResets === 1 ? "" : "s"} available — /usage reset to spend`,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
49
69
|
if (report.limits.length === 0) {
|
|
50
70
|
const email = typeof report.metadata?.email === "string" ? report.metadata.email : "account";
|
|
51
71
|
lines.push(`- ${email}: no limits reported`);
|
|
@@ -56,7 +76,9 @@ function renderUsageReports(reports: UsageReport[], nowMs: number): string {
|
|
|
56
76
|
const window = limit.window?.label ?? limit.scope.windowId;
|
|
57
77
|
const tier = limit.scope.tier ? ` (${limit.scope.tier})` : "";
|
|
58
78
|
lines.push(`- ${limit.label}${tier}${window ? ` — ${window}` : ""}`);
|
|
59
|
-
lines.push(
|
|
79
|
+
lines.push(
|
|
80
|
+
` ${formatUsageReportAccount(report, limit, index)}: ${formatUsageAmount(limit)}${inUse ? " ← in use by this session" : ""}`,
|
|
81
|
+
);
|
|
60
82
|
lines.push(` ${renderAsciiBar(limit.amount.usedFraction)}`);
|
|
61
83
|
if (limit.window?.resetsAt && limit.window.resetsAt > nowMs) {
|
|
62
84
|
lines.push(` resets in ${formatDuration(limit.window.resetsAt - nowMs)}`);
|
|
@@ -79,7 +101,18 @@ export async function buildUsageReportText(runtime: SlashCommandRuntime): Promis
|
|
|
79
101
|
};
|
|
80
102
|
if (provider.fetchUsageReports) {
|
|
81
103
|
const reports = await provider.fetchUsageReports();
|
|
82
|
-
if (reports && reports.length > 0)
|
|
104
|
+
if (reports && reports.length > 0) {
|
|
105
|
+
const currentProvider = runtime.session.model?.provider;
|
|
106
|
+
const activeAccount = currentProvider
|
|
107
|
+
? runtime.session.modelRegistry.authStorage.getOAuthAccountIdentity(
|
|
108
|
+
currentProvider,
|
|
109
|
+
runtime.session.sessionId,
|
|
110
|
+
)
|
|
111
|
+
: undefined;
|
|
112
|
+
return renderUsageReports(reports, Date.now(), providerId =>
|
|
113
|
+
providerId === currentProvider ? activeAccount : undefined,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
83
116
|
}
|
|
84
117
|
|
|
85
118
|
const stats = runtime.session.sessionManager.getUsageStatistics();
|
package/src/system-prompt.ts
CHANGED
|
@@ -9,17 +9,27 @@ import { $ } from "bun";
|
|
|
9
9
|
import { contextFileCapability } from "./capability/context-file";
|
|
10
10
|
import { systemPromptCapability } from "./capability/system-prompt";
|
|
11
11
|
import { findConfigFile } from "./config";
|
|
12
|
-
import type { SkillsSettings } from "./config/settings";
|
|
12
|
+
import type { Personality, SkillsSettings } from "./config/settings";
|
|
13
13
|
import { type ContextFile, loadCapability, type SystemPrompt as SystemPromptFile } from "./discovery";
|
|
14
14
|
import { expandAtImports } from "./discovery/at-imports";
|
|
15
15
|
import { loadSkills, type Skill } from "./extensibility/skills";
|
|
16
16
|
import { hasObsidian } from "./internal-urls/vault-protocol";
|
|
17
17
|
import customSystemPromptTemplate from "./prompts/system/custom-system-prompt.md" with { type: "text" };
|
|
18
|
+
import defaultPersonality from "./prompts/system/personalities/default.md" with { type: "text" };
|
|
19
|
+
import friendlyPersonality from "./prompts/system/personalities/friendly.md" with { type: "text" };
|
|
20
|
+
import pragmaticPersonality from "./prompts/system/personalities/pragmatic.md" with { type: "text" };
|
|
18
21
|
import projectPromptTemplate from "./prompts/system/project-prompt.md" with { type: "text" };
|
|
19
22
|
import systemPromptTemplate from "./prompts/system/system-prompt.md" with { type: "text" };
|
|
20
23
|
import { shortenPath } from "./tools/render-utils";
|
|
21
24
|
import { AGENTS_MD_LIMIT, buildWorkspaceTree, type WorkspaceTree } from "./workspace-tree";
|
|
22
25
|
|
|
26
|
+
/** Bundled personality specs, keyed by the `personality` setting value. */
|
|
27
|
+
const PERSONALITY_SPECS: Record<Exclude<Personality, "none">, string> = {
|
|
28
|
+
default: defaultPersonality,
|
|
29
|
+
friendly: friendlyPersonality,
|
|
30
|
+
pragmatic: pragmaticPersonality,
|
|
31
|
+
};
|
|
32
|
+
|
|
23
33
|
interface AlwaysApplyRule {
|
|
24
34
|
name: string;
|
|
25
35
|
content: string;
|
|
@@ -385,6 +395,8 @@ export interface BuildSystemPromptOptions {
|
|
|
385
395
|
memoryRootEnabled?: boolean;
|
|
386
396
|
/** Active model identifier (e.g. "anthropic/claude-opus-4") surfaced to the agent. */
|
|
387
397
|
model?: string;
|
|
398
|
+
/** Personality preset rendered into the default system prompt. "none" omits the block. Default: "default" */
|
|
399
|
+
personality?: Personality;
|
|
388
400
|
}
|
|
389
401
|
|
|
390
402
|
/** Result of building provider-facing system prompt messages. */
|
|
@@ -419,6 +431,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
419
431
|
workspaceTree: providedWorkspaceTree,
|
|
420
432
|
memoryRootEnabled = false,
|
|
421
433
|
model,
|
|
434
|
+
personality = "default",
|
|
422
435
|
} = options;
|
|
423
436
|
const resolvedCwd = cwd ?? getProjectDir();
|
|
424
437
|
|
|
@@ -590,6 +603,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
590
603
|
dateTime,
|
|
591
604
|
cwd: promptCwd,
|
|
592
605
|
model: model ?? "",
|
|
606
|
+
personality: personality === "none" ? "" : PERSONALITY_SPECS[personality].trim(),
|
|
593
607
|
intentTracing: !!intentField,
|
|
594
608
|
intentField: intentField ?? "",
|
|
595
609
|
mcpDiscoveryMode,
|
package/src/task/index.ts
CHANGED
|
@@ -698,7 +698,14 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
698
698
|
buildDetails("running", ownJobId) as unknown as Record<string, unknown>,
|
|
699
699
|
);
|
|
700
700
|
try {
|
|
701
|
-
const result = await this.#executeSync(
|
|
701
|
+
const result = await this.#executeSync(
|
|
702
|
+
toolCallId,
|
|
703
|
+
spawnParams,
|
|
704
|
+
runSignal,
|
|
705
|
+
undefined,
|
|
706
|
+
agentId,
|
|
707
|
+
progress.index,
|
|
708
|
+
);
|
|
702
709
|
const finalText = result.content.find(part => part.type === "text")?.text ?? "(no output)";
|
|
703
710
|
const singleResult = result.details?.results[0];
|
|
704
711
|
// A missing result means the sync path failed at the tool level
|
|
@@ -781,7 +788,14 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
781
788
|
if (spawnItems.length === 1) {
|
|
782
789
|
await semaphore.acquire();
|
|
783
790
|
try {
|
|
784
|
-
return await this.#executeSync(
|
|
791
|
+
return await this.#executeSync(
|
|
792
|
+
toolCallId,
|
|
793
|
+
spawnParamsFor(params, spawnItems[0]),
|
|
794
|
+
signal,
|
|
795
|
+
onUpdate,
|
|
796
|
+
undefined,
|
|
797
|
+
0,
|
|
798
|
+
);
|
|
785
799
|
} finally {
|
|
786
800
|
semaphore.release();
|
|
787
801
|
}
|
|
@@ -818,7 +832,14 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
818
832
|
}
|
|
819
833
|
}
|
|
820
834
|
: undefined;
|
|
821
|
-
return await this.#executeSync(
|
|
835
|
+
return await this.#executeSync(
|
|
836
|
+
toolCallId,
|
|
837
|
+
spawnParamsFor(params, item),
|
|
838
|
+
workerSignal,
|
|
839
|
+
itemOnUpdate,
|
|
840
|
+
undefined,
|
|
841
|
+
index,
|
|
842
|
+
);
|
|
822
843
|
} finally {
|
|
823
844
|
semaphore.release();
|
|
824
845
|
}
|
|
@@ -875,8 +896,9 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
875
896
|
signal?: AbortSignal,
|
|
876
897
|
onUpdate?: AgentToolUpdateCallback<TaskToolDetails>,
|
|
877
898
|
preAllocatedId?: string,
|
|
899
|
+
spawnIndex = 0,
|
|
878
900
|
): Promise<AgentToolResult<TaskToolDetails>> {
|
|
879
|
-
return this.#runSpawn(toolCallId, params, signal, onUpdate, preAllocatedId);
|
|
901
|
+
return this.#runSpawn(toolCallId, params, signal, onUpdate, preAllocatedId, spawnIndex);
|
|
880
902
|
}
|
|
881
903
|
|
|
882
904
|
/** Spawn a fresh subagent and run it to completion. */
|
|
@@ -886,6 +908,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
886
908
|
signal?: AbortSignal,
|
|
887
909
|
onUpdate?: AgentToolUpdateCallback<TaskToolDetails>,
|
|
888
910
|
preAllocatedId?: string,
|
|
911
|
+
spawnIndex = 0,
|
|
889
912
|
): Promise<AgentToolResult<TaskToolDetails>> {
|
|
890
913
|
const startTime = Date.now();
|
|
891
914
|
const { agents, projectAgentsDir } = await discoverAgents(this.session.cwd);
|
|
@@ -1070,7 +1093,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
1070
1093
|
|
|
1071
1094
|
// Progress tracking for the single agent
|
|
1072
1095
|
let latestProgress: AgentProgress = {
|
|
1073
|
-
index:
|
|
1096
|
+
index: spawnIndex,
|
|
1074
1097
|
id: agentId,
|
|
1075
1098
|
agent: agentName,
|
|
1076
1099
|
agentSource: agent.source,
|
|
@@ -1120,7 +1143,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
1120
1143
|
context: sharedContext,
|
|
1121
1144
|
planReference,
|
|
1122
1145
|
description: params.description,
|
|
1123
|
-
index:
|
|
1146
|
+
index: spawnIndex,
|
|
1124
1147
|
parentToolCallId: toolCallId,
|
|
1125
1148
|
id: agentId,
|
|
1126
1149
|
taskDepth,
|
|
@@ -1226,7 +1249,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
1226
1249
|
} catch (err) {
|
|
1227
1250
|
const message = err instanceof Error ? err.message : String(err);
|
|
1228
1251
|
return {
|
|
1229
|
-
index:
|
|
1252
|
+
index: spawnIndex,
|
|
1230
1253
|
id: agentId,
|
|
1231
1254
|
agent: agent.name,
|
|
1232
1255
|
agentSource: agent.source,
|