@oh-my-pi/pi-coding-agent 15.11.4 → 15.11.7
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 +82 -1
- package/dist/cli.js +520 -451
- package/dist/types/cli/bench-cli.d.ts +78 -0
- package/dist/types/cli/usage-cli.d.ts +10 -1
- package/dist/types/commands/bench.d.ts +29 -0
- package/dist/types/commands/usage.d.ts +9 -0
- package/dist/types/config/model-resolver.d.ts +3 -2
- package/dist/types/config/settings-schema.d.ts +125 -3
- package/dist/types/edit/renderer.d.ts +1 -0
- package/dist/types/modes/components/oauth-selector.d.ts +10 -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-selector.d.ts +8 -1
- package/dist/types/modes/components/snapcompact-shape-preview.d.ts +31 -0
- package/dist/types/modes/components/tool-execution.d.ts +18 -0
- 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/setup-wizard/scenes/sign-in.d.ts +3 -0
- package/dist/types/modes/setup-wizard/scenes/types.d.ts +10 -1
- package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +3 -0
- 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 +14 -1
- 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 +107 -4
- package/dist/types/slash-commands/helpers/reset-usage.d.ts +27 -0
- package/dist/types/task/render.d.ts +1 -0
- package/dist/types/tools/bash.d.ts +2 -0
- package/dist/types/tools/eval-render.d.ts +1 -0
- package/dist/types/tools/renderers.d.ts +13 -0
- package/dist/types/tools/ssh.d.ts +1 -0
- package/dist/types/tools/todo.d.ts +0 -11
- package/package.json +11 -11
- package/src/cli/bench-cli.ts +437 -0
- package/src/cli/usage-cli.ts +187 -16
- package/src/cli-commands.ts +1 -0
- package/src/commands/bench.ts +42 -0
- package/src/commands/usage.ts +8 -0
- package/src/config/model-registry.ts +52 -5
- package/src/config/model-resolver.ts +36 -5
- package/src/config/settings-schema.ts +148 -3
- package/src/config/settings.ts +9 -0
- package/src/edit/renderer.ts +5 -0
- package/src/hindsight/client.ts +26 -1
- package/src/hindsight/state.ts +6 -2
- package/src/internal-urls/docs-index.generated.ts +2 -2
- package/src/mcp/transports/stdio.ts +81 -7
- package/src/modes/components/oauth-selector.ts +67 -7
- package/src/modes/components/reset-usage-selector.ts +161 -0
- package/src/modes/components/session-selector.ts +8 -2
- package/src/modes/components/settings-selector.ts +89 -47
- package/src/modes/components/snapcompact-shape-preview-doc.md +11 -0
- package/src/modes/components/snapcompact-shape-preview.ts +192 -0
- package/src/modes/components/tool-execution.ts +26 -0
- package/src/modes/components/transcript-container.ts +23 -1
- package/src/modes/controllers/command-controller.ts +24 -1
- package/src/modes/controllers/input-controller.ts +8 -6
- package/src/modes/controllers/selector-controller.ts +72 -2
- package/src/modes/interactive-mode.ts +83 -0
- package/src/modes/session-observer-registry.ts +61 -3
- package/src/modes/setup-wizard/index.ts +1 -0
- package/src/modes/setup-wizard/scenes/glyph.ts +24 -6
- package/src/modes/setup-wizard/scenes/providers.ts +36 -2
- package/src/modes/setup-wizard/scenes/sign-in.ts +10 -1
- package/src/modes/setup-wizard/scenes/theme.ts +28 -1
- package/src/modes/setup-wizard/scenes/types.ts +10 -1
- package/src/modes/setup-wizard/scenes/web-search.ts +22 -6
- package/src/modes/setup-wizard/wizard-overlay.ts +38 -1
- package/src/modes/theme/theme.ts +2 -2
- package/src/modes/types.ts +2 -0
- package/src/modes/utils/context-usage.ts +75 -1
- package/src/prompts/bench.md +7 -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-toolresult-note.md +1 -1
- 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/todo.md +1 -2
- package/src/sdk.ts +4 -2
- package/src/session/agent-session.ts +136 -6
- package/src/session/auth-storage.ts +3 -0
- package/src/session/codex-auto-reset.ts +190 -0
- package/src/session/snapcompact-inline.ts +404 -75
- package/src/slash-commands/builtin-registry.ts +145 -8
- 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 +12 -0
- package/src/task/index.ts +30 -7
- package/src/task/render.ts +34 -19
- package/src/tools/bash.ts +3 -0
- package/src/tools/eval-render.ts +4 -0
- package/src/tools/renderers.ts +13 -0
- package/src/tools/ssh.ts +3 -0
- package/src/tools/todo.ts +8 -128
|
@@ -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
|
},
|
|
@@ -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
|
+
}
|
|
@@ -54,6 +54,18 @@ function renderUsageReports(
|
|
|
54
54
|
const activeAccount = resolveActiveAccount?.(provider);
|
|
55
55
|
for (const report of providerReports) {
|
|
56
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
|
+
}
|
|
57
69
|
if (report.limits.length === 0) {
|
|
58
70
|
const email = typeof report.metadata?.email === "string" ? report.metadata.email : "account";
|
|
59
71
|
lines.push(`- ${email}: no limits reported`);
|
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,
|
package/src/task/render.ts
CHANGED
|
@@ -165,7 +165,7 @@ function formatJsonScalar(value: unknown, _theme: Theme): string {
|
|
|
165
165
|
return "";
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
-
function formatTaskId(id: string): string {
|
|
168
|
+
export function formatTaskId(id: string): string {
|
|
169
169
|
// Ids are name-based (e.g. "Anna", "Anna-2"); a "." separates nesting levels
|
|
170
170
|
// (e.g. "Anna.Bob"). Render the hierarchy with a ">" breadcrumb.
|
|
171
171
|
const segments = id.split(".");
|
|
@@ -627,7 +627,13 @@ function createMarkdownSectionRenderer(text: string, theme: Theme): AssignmentSe
|
|
|
627
627
|
*/
|
|
628
628
|
export function renderCall(args: TaskParams, options: TaskRenderOptions, theme: Theme): Component {
|
|
629
629
|
const showIsolated = "isolated" in args && args.isolated === true;
|
|
630
|
-
|
|
630
|
+
// Dispatch glyph from the first frame: spawning is non-blocking, so a
|
|
631
|
+
// pending/hourglass icon would misread the call as something the turn
|
|
632
|
+
// waits on.
|
|
633
|
+
const header = renderStatusLine(
|
|
634
|
+
{ iconOverride: theme.styledSymbol("tool.task", "accent"), title: "Task", description: args.agent },
|
|
635
|
+
theme,
|
|
636
|
+
);
|
|
631
637
|
const assignmentSection = createAssignmentSectionRenderer(args, theme);
|
|
632
638
|
const contextSection = createContextSectionRenderer(args, theme);
|
|
633
639
|
return framedBlock(theme, width => {
|
|
@@ -692,21 +698,23 @@ function renderAgentProgress(
|
|
|
692
698
|
const indent = prefix ? `${prefix} ` : "";
|
|
693
699
|
let statusLine: string;
|
|
694
700
|
if (progress.status === "running" || progress.status === "pending") {
|
|
695
|
-
// Live (or queued) agents use the
|
|
696
|
-
// stay "pending" while real work is running, so a
|
|
697
|
-
// reads wrong in the transcript. Keep
|
|
698
|
-
// already carries
|
|
699
|
-
const
|
|
701
|
+
// Live (or queued) agents use the same dot finished rows keep: detached
|
|
702
|
+
// async spawns can stay "pending" while real work is running, so a
|
|
703
|
+
// pending/hourglass or spinner glyph reads wrong in the transcript. Keep
|
|
704
|
+
// the row static; the Task tool header already carries the dispatch icon.
|
|
705
|
+
const dot = theme.styledSymbol("status.done", frozen ? "dim" : "accent");
|
|
700
706
|
const nameColor = frozen ? "dim" : "accent";
|
|
701
707
|
const name = theme.fg(nameColor, description ? theme.bold(displayId) : displayId);
|
|
702
|
-
statusLine = `${indent}${
|
|
708
|
+
statusLine = `${indent}${dot} ${name}`;
|
|
703
709
|
if (description) {
|
|
704
710
|
statusLine += `${theme.fg(nameColor, ":")} ${theme.fg(nameColor, description)}`;
|
|
705
711
|
}
|
|
712
|
+
} else if (progress.status === "completed") {
|
|
713
|
+
// Finished rows keep the dot but settle from accent to the plain
|
|
714
|
+
// foreground: completion reads as a color change, not a new glyph.
|
|
715
|
+
statusLine = `${indent}${theme.styledSymbol("status.done", "text")} ${theme.fg("text", titlePart)}`;
|
|
706
716
|
} else {
|
|
707
|
-
|
|
708
|
-
progress.status === "completed" ? theme.styledSymbol("status.done", "accent") : theme.fg(iconColor, icon);
|
|
709
|
-
statusLine = `${indent}${glyph} ${theme.fg("accent", titlePart)}`;
|
|
717
|
+
statusLine = `${indent}${theme.fg(iconColor, icon)} ${theme.fg("accent", titlePart)}`;
|
|
710
718
|
}
|
|
711
719
|
|
|
712
720
|
// Show retry-blocked badge so the parent immediately sees that a child
|
|
@@ -982,7 +990,7 @@ function renderAgentResult(
|
|
|
982
990
|
: needsWarning
|
|
983
991
|
? theme.status.warning
|
|
984
992
|
: success
|
|
985
|
-
? theme.styledSymbol("status.done", "
|
|
993
|
+
? theme.styledSymbol("status.done", "text")
|
|
986
994
|
: theme.status.error;
|
|
987
995
|
const iconColor = needsWarning ? "warning" : success ? "success" : mergeFailed ? "warning" : "error";
|
|
988
996
|
const statusText = aborted
|
|
@@ -999,11 +1007,10 @@ function renderAgentResult(
|
|
|
999
1007
|
const description = result.description?.trim();
|
|
1000
1008
|
const displayId = formatTaskId(result.id);
|
|
1001
1009
|
const titlePart = description ? `${theme.bold(displayId)}: ${description}` : displayId;
|
|
1002
|
-
let statusLine = `${prefix ? `${prefix} ` : ""}${theme.fg(iconColor, icon)} ${theme.fg(
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
)}`;
|
|
1010
|
+
let statusLine = `${prefix ? `${prefix} ` : ""}${theme.fg(iconColor, icon)} ${theme.fg(
|
|
1011
|
+
success && !needsWarning ? "text" : "accent",
|
|
1012
|
+
titlePart,
|
|
1013
|
+
)} ${formatBadge(statusText, iconColor, theme)}`;
|
|
1007
1014
|
const showBadge = settings.get("task.showResolvedModelBadge");
|
|
1008
1015
|
statusLine = appendAgentStats(
|
|
1009
1016
|
statusLine,
|
|
@@ -1217,8 +1224,16 @@ export function renderResult(
|
|
|
1217
1224
|
const metaLabel = countLabel ? (agentLabel ? `${countLabel}: ${agentLabel}` : countLabel) : agentLabel;
|
|
1218
1225
|
const header = renderStatusLine(
|
|
1219
1226
|
{
|
|
1220
|
-
icon: icon === "success" ? undefined : icon,
|
|
1221
|
-
|
|
1227
|
+
icon: icon === "success" || icon === "running" ? undefined : icon,
|
|
1228
|
+
// While agents are in flight the header shows the dispatch glyph, not a
|
|
1229
|
+
// spinner: async spawns return immediately, so "running" means
|
|
1230
|
+
// "delegated to peers", not "this call is blocking the turn".
|
|
1231
|
+
iconOverride:
|
|
1232
|
+
icon === "running"
|
|
1233
|
+
? theme.styledSymbol("tool.task", "accent")
|
|
1234
|
+
: icon === "success"
|
|
1235
|
+
? theme.styledSymbol("status.done", "accent")
|
|
1236
|
+
: undefined,
|
|
1222
1237
|
title: "Task",
|
|
1223
1238
|
meta: metaLabel ? [metaLabel] : undefined,
|
|
1224
1239
|
},
|
package/src/tools/bash.ts
CHANGED
|
@@ -1385,6 +1385,9 @@ export function createShellRenderer<TArgs>(config: ShellRendererConfig<TArgs>) {
|
|
|
1385
1385
|
},
|
|
1386
1386
|
mergeCallAndResult: true,
|
|
1387
1387
|
inline: true,
|
|
1388
|
+
// Pending preview caps the command to a viewport-sized tail window that
|
|
1389
|
+
// shifts while args stream; keep it out of native scrollback mid-run.
|
|
1390
|
+
provisionalPendingPreview: true,
|
|
1388
1391
|
};
|
|
1389
1392
|
}
|
|
1390
1393
|
|
package/src/tools/eval-render.ts
CHANGED
|
@@ -754,4 +754,8 @@ export const evalToolRenderer = {
|
|
|
754
754
|
|
|
755
755
|
mergeCallAndResult: true,
|
|
756
756
|
inline: true,
|
|
757
|
+
// Pending preview shows tail-window code cells; the result render
|
|
758
|
+
// interleaves each cell's output under its code, re-laying-out every row
|
|
759
|
+
// below the first cell. Keep the preview out of native scrollback mid-run.
|
|
760
|
+
provisionalPendingPreview: true,
|
|
757
761
|
};
|
package/src/tools/renderers.ts
CHANGED
|
@@ -43,6 +43,19 @@ export type ToolRenderer = {
|
|
|
43
43
|
mergeCallAndResult?: boolean;
|
|
44
44
|
/** Render without background box, inline in the response flow */
|
|
45
45
|
inline?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Collapsed pending preview is provisional — a tail-window or otherwise
|
|
48
|
+
* re-anchored view the result render replaces wholesale (an edit's
|
|
49
|
+
* streamed-diff tail, bash/ssh command caps, eval cells whose outputs
|
|
50
|
+
* interleave under each cell). Its rows must never commit to native
|
|
51
|
+
* scrollback mid-run; see
|
|
52
|
+
* `ToolExecutionComponent.isTranscriptBlockCommitStable`. Absent = the
|
|
53
|
+
* pending preview streams top-anchored append-shaped rows the result
|
|
54
|
+
* render preserves (task context/assignment, write content), which stay
|
|
55
|
+
* commit-eligible so a call taller than the viewport scrolls into history
|
|
56
|
+
* instead of reading as cut off.
|
|
57
|
+
*/
|
|
58
|
+
provisionalPendingPreview?: boolean;
|
|
46
59
|
};
|
|
47
60
|
|
|
48
61
|
export const toolRenderers: Record<string, ToolRenderer> = {
|
package/src/tools/ssh.ts
CHANGED
|
@@ -346,4 +346,7 @@ export const sshToolRenderer = {
|
|
|
346
346
|
});
|
|
347
347
|
},
|
|
348
348
|
mergeCallAndResult: true,
|
|
349
|
+
// Pending preview caps the command to a viewport-sized tail window that
|
|
350
|
+
// shifts while args stream; keep it out of native scrollback mid-run.
|
|
351
|
+
provisionalPendingPreview: true,
|
|
349
352
|
};
|