pum-agent 0.2.12-beta.1 → 0.2.14-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 +92 -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/fork-session.ts +80 -0
- package/src/subagents/manager.ts +125 -35
- package/src/subagents/readonly.ts +19 -3
- package/src/subagents/spawn-preview-popup.tsx +14 -7
- package/src/subagents/types.ts +21 -0
- package/src/theme.ts +32 -0
- package/src/tool-line.ts +54 -0
- package/src/transcript.tsx +95 -1
- package/src/writing-style.ts +9 -6
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,10 @@ export function App({
|
|
|
1180
1209
|
: event.toolName.startsWith("message_cache_")
|
|
1181
1210
|
? messageCacheDetail(event.result)
|
|
1182
1211
|
: undefined,
|
|
1212
|
+
exitCode: bashResult.exitCode,
|
|
1183
1213
|
});
|
|
1184
1214
|
break;
|
|
1215
|
+
}
|
|
1185
1216
|
case "agent_start":
|
|
1186
1217
|
setWorking(true);
|
|
1187
1218
|
break;
|
|
@@ -1413,6 +1444,8 @@ export function App({
|
|
|
1413
1444
|
setTriggerPopup(false, false);
|
|
1414
1445
|
setNewsOpen(false);
|
|
1415
1446
|
newsOpenRef.current = false;
|
|
1447
|
+
setStatsOpen(false);
|
|
1448
|
+
statsOpenRef.current = false;
|
|
1416
1449
|
setLoginOpen(true);
|
|
1417
1450
|
loginControllerRef.current?.open();
|
|
1418
1451
|
};
|
|
@@ -1467,6 +1500,8 @@ export function App({
|
|
|
1467
1500
|
setTriggerPopup(false, false);
|
|
1468
1501
|
setNewsOpen(false);
|
|
1469
1502
|
newsOpenRef.current = false;
|
|
1503
|
+
setStatsOpen(false);
|
|
1504
|
+
statsOpenRef.current = false;
|
|
1470
1505
|
loadSessions()
|
|
1471
1506
|
.then((sessions) => {
|
|
1472
1507
|
setHistorySessions(sessions);
|
|
@@ -1489,6 +1524,8 @@ export function App({
|
|
|
1489
1524
|
setAgentSelectorOpen(false);
|
|
1490
1525
|
setTriggerPopup(false, false);
|
|
1491
1526
|
setLoginOpen(false);
|
|
1527
|
+
setStatsOpen(false);
|
|
1528
|
+
statsOpenRef.current = false;
|
|
1492
1529
|
newsCursorRef.current = 0;
|
|
1493
1530
|
setNewsCursor(0);
|
|
1494
1531
|
newsOpenRef.current = true;
|
|
@@ -1501,6 +1538,27 @@ export function App({
|
|
|
1501
1538
|
queueMicrotask(() => inputRef.current?.focus());
|
|
1502
1539
|
};
|
|
1503
1540
|
|
|
1541
|
+
const openStats = () => {
|
|
1542
|
+
settingsOpenRef.current = false;
|
|
1543
|
+
setSettingsOpen(false);
|
|
1544
|
+
setHelpOpen(false);
|
|
1545
|
+
setHistoryOpen(false);
|
|
1546
|
+
setAgentSelectorOpen(false);
|
|
1547
|
+
setTriggerPopup(false, false);
|
|
1548
|
+
setLoginOpen(false);
|
|
1549
|
+
setNewsOpen(false);
|
|
1550
|
+
newsOpenRef.current = false;
|
|
1551
|
+
setStatsScrollOffset(0);
|
|
1552
|
+
statsOpenRef.current = true;
|
|
1553
|
+
setStatsOpen(true);
|
|
1554
|
+
};
|
|
1555
|
+
|
|
1556
|
+
const closeStats = () => {
|
|
1557
|
+
statsOpenRef.current = false;
|
|
1558
|
+
setStatsOpen(false);
|
|
1559
|
+
queueMicrotask(() => inputRef.current?.focus());
|
|
1560
|
+
};
|
|
1561
|
+
|
|
1504
1562
|
const moveNewsCursor = (direction: number) => {
|
|
1505
1563
|
const count = newsRef.current.length;
|
|
1506
1564
|
if (count === 0) return;
|
|
@@ -1760,8 +1818,9 @@ export function App({
|
|
|
1760
1818
|
const checkPathCommand = /^\/check-path(?:\s|$)/.test(trimmed);
|
|
1761
1819
|
const triggersCommand = trimmed === "/triggers";
|
|
1762
1820
|
const newsCommand = trimmed === "/news";
|
|
1821
|
+
const statsCommand = trimmed === "/stats";
|
|
1763
1822
|
const worktreeCommand = /^\/worktree(?:\s+([a-zA-Z0-9_-]+))?$/.exec(trimmed);
|
|
1764
|
-
if (!compress && !clear && !historyCommand && !loginCommand && !checkPathCommand && !triggersCommand && !newsCommand && !worktreeCommand) return false;
|
|
1823
|
+
if (!compress && !clear && !historyCommand && !loginCommand && !checkPathCommand && !triggersCommand && !newsCommand && !statsCommand && !worktreeCommand) return false;
|
|
1765
1824
|
editingStashIndex.current = null;
|
|
1766
1825
|
|
|
1767
1826
|
if (historyCommand) {
|
|
@@ -1784,6 +1843,11 @@ export function App({
|
|
|
1784
1843
|
openNews();
|
|
1785
1844
|
return true;
|
|
1786
1845
|
}
|
|
1846
|
+
if (statsCommand) {
|
|
1847
|
+
setEditorText("");
|
|
1848
|
+
openStats();
|
|
1849
|
+
return true;
|
|
1850
|
+
}
|
|
1787
1851
|
|
|
1788
1852
|
setEditorText("");
|
|
1789
1853
|
histCursor.current = null;
|
|
@@ -2230,6 +2294,7 @@ export function App({
|
|
|
2230
2294
|
!agentSelectorOpen &&
|
|
2231
2295
|
!helpOpen &&
|
|
2232
2296
|
!historyOpen &&
|
|
2297
|
+
!statsOpenRef.current &&
|
|
2233
2298
|
!settingsOpenRef.current;
|
|
2234
2299
|
const inputValue = inputRef.current?.plainText ?? "";
|
|
2235
2300
|
if (promptOwnsInput && (inputValue.length > 0 || pendingImages.current.length > 0)) {
|
|
@@ -2305,6 +2370,20 @@ export function App({
|
|
|
2305
2370
|
return;
|
|
2306
2371
|
}
|
|
2307
2372
|
|
|
2373
|
+
if (statsOpenRef.current || statsOpen) {
|
|
2374
|
+
key.stopPropagation();
|
|
2375
|
+
const maxOffset = maxStatsScrollOffset(statsSnapshot, width, height);
|
|
2376
|
+
if (key.name === "escape") closeStats();
|
|
2377
|
+
else if (key.name === "home") setStatsScrollOffset(0);
|
|
2378
|
+
else if (key.name === "end") setStatsScrollOffset(maxOffset);
|
|
2379
|
+
else if (key.name === "up" || key.name === "down" || key.name === "pageup" || key.name === "pagedown") {
|
|
2380
|
+
const amount = key.name === "pageup" || key.name === "pagedown" ? 5 : 1;
|
|
2381
|
+
const direction = key.name === "up" || key.name === "pageup" ? -1 : 1;
|
|
2382
|
+
setStatsScrollOffset((offset) => Math.max(0, Math.min(maxOffset, offset + direction * amount)));
|
|
2383
|
+
}
|
|
2384
|
+
return;
|
|
2385
|
+
}
|
|
2386
|
+
|
|
2308
2387
|
if (key.ctrl && key.name === "t") {
|
|
2309
2388
|
key.stopPropagation();
|
|
2310
2389
|
if (triggersOpenRef.current) setTriggerPopup(false);
|
|
@@ -2989,7 +3068,7 @@ export function App({
|
|
|
2989
3068
|
selectionBg={theme.selectionBg}
|
|
2990
3069
|
wrapMode="word"
|
|
2991
3070
|
scrollMargin={1}
|
|
2992
|
-
focused={!settingsOpen && !helpOpen && !historyOpen && !agentSelectorOpen && !triggersOpen && !loginOpen && !questionnaire && !spawnPreview && !newsOpen}
|
|
3071
|
+
focused={!settingsOpen && !helpOpen && !historyOpen && !statsOpen && !agentSelectorOpen && !triggersOpen && !loginOpen && !questionnaire && !spawnPreview && !newsOpen}
|
|
2993
3072
|
onContentChange={handleTextareaChange}
|
|
2994
3073
|
onCursorChange={scheduleInputMetrics}
|
|
2995
3074
|
onSubmit={() => submitPrompt()}
|
|
@@ -3079,6 +3158,15 @@ export function App({
|
|
|
3079
3158
|
terminalHeight={height}
|
|
3080
3159
|
/>
|
|
3081
3160
|
) : null}
|
|
3161
|
+
{statsOpen ? (
|
|
3162
|
+
<StatsPopup
|
|
3163
|
+
theme={theme}
|
|
3164
|
+
snapshot={statsSnapshot}
|
|
3165
|
+
terminalWidth={width}
|
|
3166
|
+
terminalHeight={height}
|
|
3167
|
+
scrollOffset={statsScrollOffset}
|
|
3168
|
+
/>
|
|
3169
|
+
) : null}
|
|
3082
3170
|
{settingsOpen ? (
|
|
3083
3171
|
<SettingsPopup
|
|
3084
3172
|
theme={theme}
|