pum-agent 0.2.12-beta.1 → 0.2.13-beta.1
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/README.md +2 -0
- package/package.json +1 -1
- package/src/app.tsx +93 -4
- package/src/bash-output.ts +688 -0
- package/src/check-mode.ts +34 -14
- package/src/cli.ts +38 -5
- package/src/commands.ts +4 -0
- package/src/filesystem-sandbox.ts +20 -2
- package/src/headless-stats.ts +58 -0
- package/src/headless.ts +71 -1
- package/src/help-popup.tsx +1 -0
- package/src/index.tsx +3 -0
- package/src/main.tsx +26 -1
- package/src/replay.ts +5 -1
- package/src/sandbox/index.ts +37 -8
- package/src/session-stats.ts +702 -0
- package/src/settings.ts +5 -0
- package/src/stats-popup.tsx +182 -0
- package/src/subagents/manager.ts +23 -2
- package/src/subagents/readonly.ts +19 -3
- package/src/theme.ts +32 -0
- package/src/tool-line.ts +54 -0
- package/src/transcript.tsx +59 -1
package/README.md
CHANGED
|
@@ -152,6 +152,8 @@ Set `PUM_DIR` to override PUM's complete configuration and data directory. Run `
|
|
|
152
152
|
|
|
153
153
|
Useful commands include `/login`, `/history`, `/news`, `/triggers`, `/check-path`, `/clear`, `/compress`, and `/worktree`.
|
|
154
154
|
|
|
155
|
+
For automated benchmarks, add `--statsFile <path>` to a headless `-p` run. PUM writes a versioned JSON artifact with run metadata and all `/stats` data. PUM creates missing parent directories. PUM rejects an existing file before startup unless `--override` is present. The alias `--stats-file` is also accepted.
|
|
156
|
+
|
|
155
157
|
### Copy transcript text
|
|
156
158
|
|
|
157
159
|
Drag across transcript text with the left mouse button. PUM copies the completed selection when you release the button.
|
package/package.json
CHANGED
package/src/app.tsx
CHANGED
|
@@ -47,7 +47,7 @@ import {
|
|
|
47
47
|
type PendingLine,
|
|
48
48
|
type Role,
|
|
49
49
|
} from "./transcript";
|
|
50
|
-
import { editCounts, toolArg, type ToolCall } from "./tool-line";
|
|
50
|
+
import { bashOutput, bashResultDisplay, editCounts, toolArg, type ToolCall } from "./tool-line";
|
|
51
51
|
import { readBranch, watchBranch } from "./git-branch";
|
|
52
52
|
import { HelpPopup, maxHelpScrollOffset } from "./help-popup";
|
|
53
53
|
import { appendHistory, loadHistory, removeHistory } from "./history";
|
|
@@ -135,6 +135,8 @@ import {
|
|
|
135
135
|
type NewsItem,
|
|
136
136
|
type NewsPrompt,
|
|
137
137
|
} from "./news";
|
|
138
|
+
import { statsFromEntries, type SessionStatsManager } from "./session-stats";
|
|
139
|
+
import { maxStatsScrollOffset, StatsPopup } from "./stats-popup";
|
|
138
140
|
|
|
139
141
|
type Stream = { kind: "assistant" | "thinking"; text: string } | null;
|
|
140
142
|
type Transcript = { lines: Line[]; stream: Stream; pending: PendingLine[] };
|
|
@@ -391,6 +393,7 @@ export function App({
|
|
|
391
393
|
settings: initial,
|
|
392
394
|
searchProviders,
|
|
393
395
|
subagentManager,
|
|
396
|
+
statsManager,
|
|
394
397
|
questionnaireManager,
|
|
395
398
|
spawnPreviewManager,
|
|
396
399
|
loginRequired = false,
|
|
@@ -417,6 +420,7 @@ export function App({
|
|
|
417
420
|
/** Provider ids that carry the hosted web-search tool; empty means none. */
|
|
418
421
|
searchProviders: string[];
|
|
419
422
|
subagentManager: SubagentManager;
|
|
423
|
+
statsManager?: SessionStatsManager;
|
|
420
424
|
questionnaireManager?: QuestionnaireManager;
|
|
421
425
|
spawnPreviewManager?: SpawnPreviewManager;
|
|
422
426
|
loginRequired?: boolean;
|
|
@@ -476,6 +480,9 @@ export function App({
|
|
|
476
480
|
const [helpOpen, setHelpOpen] = useState(false);
|
|
477
481
|
const [helpScrollOffset, setHelpScrollOffset] = useState(0);
|
|
478
482
|
const [historyOpen, setHistoryOpen] = useState(false);
|
|
483
|
+
const [statsOpen, setStatsOpen] = useState(false);
|
|
484
|
+
const [statsScrollOffset, setStatsScrollOffset] = useState(0);
|
|
485
|
+
const [statsRevision, setStatsRevision] = useState(0);
|
|
479
486
|
const [historySessions, setHistorySessions] = useState<SessionHistoryItem[]>([]);
|
|
480
487
|
const [page, setPage] = useState<"main" | "models" | "checkModels">("main");
|
|
481
488
|
const [settingsQuery, setSettingsQuery] = useState("");
|
|
@@ -528,6 +535,7 @@ export function App({
|
|
|
528
535
|
const newsOpenRef = useRef(false);
|
|
529
536
|
const [newsCursor, setNewsCursor] = useState(0);
|
|
530
537
|
const newsCursorRef = useRef(0);
|
|
538
|
+
const statsOpenRef = useRef(false);
|
|
531
539
|
|
|
532
540
|
const theme = useMemo(() => loadTheme(settings.theme), [settings.theme]);
|
|
533
541
|
const { width, height } = useTerminalDimensions();
|
|
@@ -555,6 +563,10 @@ export function App({
|
|
|
555
563
|
const visibleUsage = activeAgent?.usage ?? usage;
|
|
556
564
|
const agentTreeRows = buildAgentTree(agents);
|
|
557
565
|
const triggers = sortTriggers(triggerManager?.getTriggers() ?? []);
|
|
566
|
+
const statsSnapshot = useMemo(() => statsManager?.snapshot() ?? statsFromEntries(
|
|
567
|
+
(session.sessionManager as any).getEntries?.() ?? session.sessionManager.buildContextEntries(),
|
|
568
|
+
`${session.agent.state.model.provider}/${session.agent.state.model.id}`,
|
|
569
|
+
), [statsManager, session, statsRevision]);
|
|
558
570
|
const inputHint = cancelArmed
|
|
559
571
|
? " esc again to cancel "
|
|
560
572
|
: quitArmed
|
|
@@ -663,6 +675,8 @@ export function App({
|
|
|
663
675
|
setAgentSelectorOpen(false);
|
|
664
676
|
setNewsOpen(false);
|
|
665
677
|
newsOpenRef.current = false;
|
|
678
|
+
setStatsOpen(false);
|
|
679
|
+
statsOpenRef.current = false;
|
|
666
680
|
setStashMode(false);
|
|
667
681
|
const nextCursor = Math.min(triggerCursorRef.current, Math.max(0, triggers.length - 1));
|
|
668
682
|
triggerCursorRef.current = nextCursor;
|
|
@@ -967,6 +981,7 @@ export function App({
|
|
|
967
981
|
helpOpen ||
|
|
968
982
|
historyOpen ||
|
|
969
983
|
newsOpenRef.current ||
|
|
984
|
+
statsOpenRef.current ||
|
|
970
985
|
settingsOpenRef.current
|
|
971
986
|
) return;
|
|
972
987
|
const input = inputRef.current;
|
|
@@ -1022,6 +1037,11 @@ export function App({
|
|
|
1022
1037
|
append({ kind: "text", role: "system", text: warning });
|
|
1023
1038
|
}), [sandboxWarningSource]);
|
|
1024
1039
|
|
|
1040
|
+
useEffect(() => {
|
|
1041
|
+
setStatsRevision((revision) => revision + 1);
|
|
1042
|
+
return statsManager?.subscribe(() => setStatsRevision((revision) => revision + 1));
|
|
1043
|
+
}, [statsManager, session]);
|
|
1044
|
+
|
|
1025
1045
|
|
|
1026
1046
|
useEffect(() => triggerManager?.subscribe(() => {
|
|
1027
1047
|
setTriggerRevision((revision) => revision + 1);
|
|
@@ -1072,6 +1092,8 @@ export function App({
|
|
|
1072
1092
|
setLoginOpen(false);
|
|
1073
1093
|
setNewsOpen(false);
|
|
1074
1094
|
newsOpenRef.current = false;
|
|
1095
|
+
setStatsOpen(false);
|
|
1096
|
+
statsOpenRef.current = false;
|
|
1075
1097
|
}
|
|
1076
1098
|
setQuestionnaireRevision((revision) => revision + 1);
|
|
1077
1099
|
});
|
|
@@ -1161,10 +1183,17 @@ export function App({
|
|
|
1161
1183
|
name: event.toolName,
|
|
1162
1184
|
arg: toolArg(event.toolName, event.args, cwd),
|
|
1163
1185
|
state: "running",
|
|
1186
|
+
startedAt: Date.now(),
|
|
1164
1187
|
},
|
|
1165
1188
|
});
|
|
1166
1189
|
break;
|
|
1167
|
-
case "
|
|
1190
|
+
case "tool_execution_update":
|
|
1191
|
+
if (event.toolName === "bash") {
|
|
1192
|
+
patchTool(event.toolCallId, { output: bashOutput(event.partialResult) });
|
|
1193
|
+
}
|
|
1194
|
+
break;
|
|
1195
|
+
case "tool_execution_end": {
|
|
1196
|
+
const bashResult = event.toolName === "bash" ? bashResultDisplay(event.result) : {};
|
|
1168
1197
|
patchTool(event.toolCallId, {
|
|
1169
1198
|
state: isRejectedToolResult(event.result, event.toolCallId)
|
|
1170
1199
|
? "rejected"
|
|
@@ -1180,8 +1209,11 @@ export function App({
|
|
|
1180
1209
|
: event.toolName.startsWith("message_cache_")
|
|
1181
1210
|
? messageCacheDetail(event.result)
|
|
1182
1211
|
: undefined,
|
|
1212
|
+
output: undefined,
|
|
1213
|
+
exitCode: bashResult.exitCode,
|
|
1183
1214
|
});
|
|
1184
1215
|
break;
|
|
1216
|
+
}
|
|
1185
1217
|
case "agent_start":
|
|
1186
1218
|
setWorking(true);
|
|
1187
1219
|
break;
|
|
@@ -1413,6 +1445,8 @@ export function App({
|
|
|
1413
1445
|
setTriggerPopup(false, false);
|
|
1414
1446
|
setNewsOpen(false);
|
|
1415
1447
|
newsOpenRef.current = false;
|
|
1448
|
+
setStatsOpen(false);
|
|
1449
|
+
statsOpenRef.current = false;
|
|
1416
1450
|
setLoginOpen(true);
|
|
1417
1451
|
loginControllerRef.current?.open();
|
|
1418
1452
|
};
|
|
@@ -1467,6 +1501,8 @@ export function App({
|
|
|
1467
1501
|
setTriggerPopup(false, false);
|
|
1468
1502
|
setNewsOpen(false);
|
|
1469
1503
|
newsOpenRef.current = false;
|
|
1504
|
+
setStatsOpen(false);
|
|
1505
|
+
statsOpenRef.current = false;
|
|
1470
1506
|
loadSessions()
|
|
1471
1507
|
.then((sessions) => {
|
|
1472
1508
|
setHistorySessions(sessions);
|
|
@@ -1489,6 +1525,8 @@ export function App({
|
|
|
1489
1525
|
setAgentSelectorOpen(false);
|
|
1490
1526
|
setTriggerPopup(false, false);
|
|
1491
1527
|
setLoginOpen(false);
|
|
1528
|
+
setStatsOpen(false);
|
|
1529
|
+
statsOpenRef.current = false;
|
|
1492
1530
|
newsCursorRef.current = 0;
|
|
1493
1531
|
setNewsCursor(0);
|
|
1494
1532
|
newsOpenRef.current = true;
|
|
@@ -1501,6 +1539,27 @@ export function App({
|
|
|
1501
1539
|
queueMicrotask(() => inputRef.current?.focus());
|
|
1502
1540
|
};
|
|
1503
1541
|
|
|
1542
|
+
const openStats = () => {
|
|
1543
|
+
settingsOpenRef.current = false;
|
|
1544
|
+
setSettingsOpen(false);
|
|
1545
|
+
setHelpOpen(false);
|
|
1546
|
+
setHistoryOpen(false);
|
|
1547
|
+
setAgentSelectorOpen(false);
|
|
1548
|
+
setTriggerPopup(false, false);
|
|
1549
|
+
setLoginOpen(false);
|
|
1550
|
+
setNewsOpen(false);
|
|
1551
|
+
newsOpenRef.current = false;
|
|
1552
|
+
setStatsScrollOffset(0);
|
|
1553
|
+
statsOpenRef.current = true;
|
|
1554
|
+
setStatsOpen(true);
|
|
1555
|
+
};
|
|
1556
|
+
|
|
1557
|
+
const closeStats = () => {
|
|
1558
|
+
statsOpenRef.current = false;
|
|
1559
|
+
setStatsOpen(false);
|
|
1560
|
+
queueMicrotask(() => inputRef.current?.focus());
|
|
1561
|
+
};
|
|
1562
|
+
|
|
1504
1563
|
const moveNewsCursor = (direction: number) => {
|
|
1505
1564
|
const count = newsRef.current.length;
|
|
1506
1565
|
if (count === 0) return;
|
|
@@ -1760,8 +1819,9 @@ export function App({
|
|
|
1760
1819
|
const checkPathCommand = /^\/check-path(?:\s|$)/.test(trimmed);
|
|
1761
1820
|
const triggersCommand = trimmed === "/triggers";
|
|
1762
1821
|
const newsCommand = trimmed === "/news";
|
|
1822
|
+
const statsCommand = trimmed === "/stats";
|
|
1763
1823
|
const worktreeCommand = /^\/worktree(?:\s+([a-zA-Z0-9_-]+))?$/.exec(trimmed);
|
|
1764
|
-
if (!compress && !clear && !historyCommand && !loginCommand && !checkPathCommand && !triggersCommand && !newsCommand && !worktreeCommand) return false;
|
|
1824
|
+
if (!compress && !clear && !historyCommand && !loginCommand && !checkPathCommand && !triggersCommand && !newsCommand && !statsCommand && !worktreeCommand) return false;
|
|
1765
1825
|
editingStashIndex.current = null;
|
|
1766
1826
|
|
|
1767
1827
|
if (historyCommand) {
|
|
@@ -1784,6 +1844,11 @@ export function App({
|
|
|
1784
1844
|
openNews();
|
|
1785
1845
|
return true;
|
|
1786
1846
|
}
|
|
1847
|
+
if (statsCommand) {
|
|
1848
|
+
setEditorText("");
|
|
1849
|
+
openStats();
|
|
1850
|
+
return true;
|
|
1851
|
+
}
|
|
1787
1852
|
|
|
1788
1853
|
setEditorText("");
|
|
1789
1854
|
histCursor.current = null;
|
|
@@ -2230,6 +2295,7 @@ export function App({
|
|
|
2230
2295
|
!agentSelectorOpen &&
|
|
2231
2296
|
!helpOpen &&
|
|
2232
2297
|
!historyOpen &&
|
|
2298
|
+
!statsOpenRef.current &&
|
|
2233
2299
|
!settingsOpenRef.current;
|
|
2234
2300
|
const inputValue = inputRef.current?.plainText ?? "";
|
|
2235
2301
|
if (promptOwnsInput && (inputValue.length > 0 || pendingImages.current.length > 0)) {
|
|
@@ -2305,6 +2371,20 @@ export function App({
|
|
|
2305
2371
|
return;
|
|
2306
2372
|
}
|
|
2307
2373
|
|
|
2374
|
+
if (statsOpenRef.current || statsOpen) {
|
|
2375
|
+
key.stopPropagation();
|
|
2376
|
+
const maxOffset = maxStatsScrollOffset(statsSnapshot, width, height);
|
|
2377
|
+
if (key.name === "escape") closeStats();
|
|
2378
|
+
else if (key.name === "home") setStatsScrollOffset(0);
|
|
2379
|
+
else if (key.name === "end") setStatsScrollOffset(maxOffset);
|
|
2380
|
+
else if (key.name === "up" || key.name === "down" || key.name === "pageup" || key.name === "pagedown") {
|
|
2381
|
+
const amount = key.name === "pageup" || key.name === "pagedown" ? 5 : 1;
|
|
2382
|
+
const direction = key.name === "up" || key.name === "pageup" ? -1 : 1;
|
|
2383
|
+
setStatsScrollOffset((offset) => Math.max(0, Math.min(maxOffset, offset + direction * amount)));
|
|
2384
|
+
}
|
|
2385
|
+
return;
|
|
2386
|
+
}
|
|
2387
|
+
|
|
2308
2388
|
if (key.ctrl && key.name === "t") {
|
|
2309
2389
|
key.stopPropagation();
|
|
2310
2390
|
if (triggersOpenRef.current) setTriggerPopup(false);
|
|
@@ -2989,7 +3069,7 @@ export function App({
|
|
|
2989
3069
|
selectionBg={theme.selectionBg}
|
|
2990
3070
|
wrapMode="word"
|
|
2991
3071
|
scrollMargin={1}
|
|
2992
|
-
focused={!settingsOpen && !helpOpen && !historyOpen && !agentSelectorOpen && !triggersOpen && !loginOpen && !questionnaire && !spawnPreview && !newsOpen}
|
|
3072
|
+
focused={!settingsOpen && !helpOpen && !historyOpen && !statsOpen && !agentSelectorOpen && !triggersOpen && !loginOpen && !questionnaire && !spawnPreview && !newsOpen}
|
|
2993
3073
|
onContentChange={handleTextareaChange}
|
|
2994
3074
|
onCursorChange={scheduleInputMetrics}
|
|
2995
3075
|
onSubmit={() => submitPrompt()}
|
|
@@ -3079,6 +3159,15 @@ export function App({
|
|
|
3079
3159
|
terminalHeight={height}
|
|
3080
3160
|
/>
|
|
3081
3161
|
) : null}
|
|
3162
|
+
{statsOpen ? (
|
|
3163
|
+
<StatsPopup
|
|
3164
|
+
theme={theme}
|
|
3165
|
+
snapshot={statsSnapshot}
|
|
3166
|
+
terminalWidth={width}
|
|
3167
|
+
terminalHeight={height}
|
|
3168
|
+
scrollOffset={statsScrollOffset}
|
|
3169
|
+
/>
|
|
3170
|
+
) : null}
|
|
3082
3171
|
{settingsOpen ? (
|
|
3083
3172
|
<SettingsPopup
|
|
3084
3173
|
theme={theme}
|