agenthud 0.8.5 → 0.9.0
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 +96 -59
- package/dist/index.js +1 -1
- package/dist/{main-LOBTW45O.js → main-NHQJ23YJ.js} +734 -229
- package/package.json +1 -1
|
@@ -247,12 +247,14 @@ import { homedir } from "os";
|
|
|
247
247
|
import { join as join2 } from "path";
|
|
248
248
|
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
249
249
|
var CONFIG_PATH = join2(homedir(), ".agenthud", "config.yaml");
|
|
250
|
+
var STATE_PATH = join2(homedir(), ".agenthud", "state.yaml");
|
|
250
251
|
var DEFAULT_GLOBAL_CONFIG = {
|
|
251
252
|
refreshIntervalMs: 2e3,
|
|
252
253
|
logDir: join2(homedir(), ".agenthud", "logs"),
|
|
253
254
|
hiddenSessions: [],
|
|
254
255
|
hiddenSubAgents: [],
|
|
255
|
-
filterPresets: [[], ["response"], ["commit"]]
|
|
256
|
+
filterPresets: [[], ["response"], ["commit"]],
|
|
257
|
+
hiddenProjects: []
|
|
256
258
|
};
|
|
257
259
|
function parseInterval(value) {
|
|
258
260
|
const match = value.match(/^(\d+)(s|m)$/);
|
|
@@ -260,76 +262,175 @@ function parseInterval(value) {
|
|
|
260
262
|
const n = parseInt(match[1], 10);
|
|
261
263
|
return match[2] === "m" ? n * 60 * 1e3 : n * 1e3;
|
|
262
264
|
}
|
|
263
|
-
function
|
|
264
|
-
const
|
|
265
|
-
if (!existsSync(
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
265
|
+
function ensureAgenthudDir() {
|
|
266
|
+
const dir = join2(homedir(), ".agenthud");
|
|
267
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
268
|
+
}
|
|
269
|
+
function writeDefaultConfig() {
|
|
270
|
+
ensureAgenthudDir();
|
|
271
|
+
const defaultYaml = `# AgentHUD user settings.
|
|
272
|
+
# App-managed state (hidden sessions/projects) lives in state.yaml.
|
|
273
|
+
|
|
274
|
+
# How often to poll for activity updates
|
|
275
|
+
refreshInterval: 2s
|
|
276
|
+
|
|
277
|
+
# Where 's' key saves activity logs
|
|
278
|
+
logDir: ~/.agenthud/logs
|
|
279
|
+
|
|
280
|
+
# Activity filter presets (cycle with 'f' key in viewer)
|
|
281
|
+
# Each list is one preset; [] means "all". First preset is the default.
|
|
282
|
+
filterPresets:
|
|
283
|
+
- []
|
|
284
|
+
- ["response"]
|
|
285
|
+
- ["commit"]
|
|
286
|
+
`;
|
|
269
287
|
try {
|
|
270
|
-
|
|
288
|
+
writeFileSync(CONFIG_PATH, defaultYaml, "utf-8");
|
|
271
289
|
} catch {
|
|
272
|
-
return config;
|
|
273
290
|
}
|
|
274
|
-
|
|
291
|
+
}
|
|
292
|
+
function writeState(state) {
|
|
293
|
+
ensureAgenthudDir();
|
|
275
294
|
try {
|
|
276
|
-
|
|
295
|
+
writeFileSync(STATE_PATH, stringifyYaml(state), "utf-8");
|
|
277
296
|
} catch {
|
|
278
|
-
return config;
|
|
279
297
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
298
|
+
}
|
|
299
|
+
function rewriteConfigWithoutHideFields(raw) {
|
|
300
|
+
const cleaned = {};
|
|
301
|
+
for (const [k, v] of Object.entries(raw)) {
|
|
302
|
+
if (k === "hiddenSessions" || k === "hiddenSubAgents" || k === "hiddenProjects")
|
|
303
|
+
continue;
|
|
304
|
+
cleaned[k] = v;
|
|
283
305
|
}
|
|
284
|
-
|
|
285
|
-
|
|
306
|
+
try {
|
|
307
|
+
writeFileSync(CONFIG_PATH, stringifyYaml(cleaned), "utf-8");
|
|
308
|
+
} catch {
|
|
286
309
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
310
|
+
}
|
|
311
|
+
function loadGlobalConfig() {
|
|
312
|
+
const config = { ...DEFAULT_GLOBAL_CONFIG };
|
|
313
|
+
let configRaw = {};
|
|
314
|
+
let configHadHideFields = false;
|
|
315
|
+
if (existsSync(CONFIG_PATH)) {
|
|
316
|
+
try {
|
|
317
|
+
const text = readFileSync2(CONFIG_PATH, "utf-8");
|
|
318
|
+
configRaw = parseYaml(text) ?? {};
|
|
319
|
+
} catch {
|
|
320
|
+
configRaw = {};
|
|
321
|
+
}
|
|
322
|
+
} else {
|
|
323
|
+
writeDefaultConfig();
|
|
291
324
|
}
|
|
292
|
-
if (
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
325
|
+
if (typeof configRaw.refreshInterval === "string") {
|
|
326
|
+
const ms = parseInterval(configRaw.refreshInterval);
|
|
327
|
+
if (ms !== null) config.refreshIntervalMs = ms;
|
|
328
|
+
}
|
|
329
|
+
if (typeof configRaw.logDir === "string") {
|
|
330
|
+
config.logDir = configRaw.logDir.replace(/^~/, homedir());
|
|
296
331
|
}
|
|
297
|
-
if (Array.isArray(
|
|
298
|
-
const presets =
|
|
332
|
+
if (Array.isArray(configRaw.filterPresets)) {
|
|
333
|
+
const presets = configRaw.filterPresets.filter(Array.isArray).map(
|
|
299
334
|
(p) => p.filter((t) => typeof t === "string")
|
|
300
335
|
);
|
|
301
336
|
if (presets.length > 0) config.filterPresets = presets;
|
|
302
337
|
}
|
|
338
|
+
const legacyHidden = {};
|
|
339
|
+
for (const key of [
|
|
340
|
+
"hiddenSessions",
|
|
341
|
+
"hiddenSubAgents",
|
|
342
|
+
"hiddenProjects"
|
|
343
|
+
]) {
|
|
344
|
+
if (Array.isArray(configRaw[key])) {
|
|
345
|
+
configHadHideFields = true;
|
|
346
|
+
legacyHidden[key] = configRaw[key].filter(
|
|
347
|
+
(s) => typeof s === "string"
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
let stateRaw = {};
|
|
352
|
+
if (existsSync(STATE_PATH)) {
|
|
353
|
+
try {
|
|
354
|
+
const text = readFileSync2(STATE_PATH, "utf-8");
|
|
355
|
+
stateRaw = parseYaml(text) ?? {};
|
|
356
|
+
} catch {
|
|
357
|
+
stateRaw = {};
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
for (const key of [
|
|
361
|
+
"hiddenSessions",
|
|
362
|
+
"hiddenSubAgents",
|
|
363
|
+
"hiddenProjects"
|
|
364
|
+
]) {
|
|
365
|
+
if (Array.isArray(stateRaw[key])) {
|
|
366
|
+
config[key] = stateRaw[key].filter(
|
|
367
|
+
(s) => typeof s === "string"
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
if (configHadHideFields) {
|
|
372
|
+
const merged = {
|
|
373
|
+
hiddenSessions: config.hiddenSessions.length > 0 ? config.hiddenSessions : legacyHidden.hiddenSessions ?? [],
|
|
374
|
+
hiddenSubAgents: config.hiddenSubAgents.length > 0 ? config.hiddenSubAgents : legacyHidden.hiddenSubAgents ?? [],
|
|
375
|
+
hiddenProjects: config.hiddenProjects.length > 0 ? config.hiddenProjects : legacyHidden.hiddenProjects ?? []
|
|
376
|
+
};
|
|
377
|
+
writeState(merged);
|
|
378
|
+
rewriteConfigWithoutHideFields(configRaw);
|
|
379
|
+
config.hiddenSessions = merged.hiddenSessions;
|
|
380
|
+
config.hiddenSubAgents = merged.hiddenSubAgents;
|
|
381
|
+
config.hiddenProjects = merged.hiddenProjects;
|
|
382
|
+
}
|
|
303
383
|
return config;
|
|
304
384
|
}
|
|
305
|
-
function
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
if (existsSync(
|
|
385
|
+
function updateState(updates) {
|
|
386
|
+
let state = {
|
|
387
|
+
hiddenSessions: [],
|
|
388
|
+
hiddenSubAgents: [],
|
|
389
|
+
hiddenProjects: []
|
|
390
|
+
};
|
|
391
|
+
if (existsSync(STATE_PATH)) {
|
|
312
392
|
try {
|
|
313
|
-
|
|
393
|
+
const text = readFileSync2(STATE_PATH, "utf-8");
|
|
394
|
+
const raw = parseYaml(text) ?? {};
|
|
395
|
+
for (const key of [
|
|
396
|
+
"hiddenSessions",
|
|
397
|
+
"hiddenSubAgents",
|
|
398
|
+
"hiddenProjects"
|
|
399
|
+
]) {
|
|
400
|
+
if (Array.isArray(raw[key])) {
|
|
401
|
+
state[key] = raw[key].filter(
|
|
402
|
+
(s) => typeof s === "string"
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
314
406
|
} catch {
|
|
315
|
-
raw = {};
|
|
316
407
|
}
|
|
317
408
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
409
|
+
for (const key of [
|
|
410
|
+
"hiddenSessions",
|
|
411
|
+
"hiddenSubAgents",
|
|
412
|
+
"hiddenProjects"
|
|
413
|
+
]) {
|
|
414
|
+
if (updates[key] !== void 0) {
|
|
415
|
+
state[key] = updates[key];
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
writeState(state);
|
|
323
419
|
}
|
|
324
420
|
function hideSession(id) {
|
|
325
421
|
const config = loadGlobalConfig();
|
|
326
422
|
if (config.hiddenSessions.includes(id)) return;
|
|
327
|
-
|
|
423
|
+
updateState({ hiddenSessions: [...config.hiddenSessions, id] });
|
|
328
424
|
}
|
|
329
425
|
function hideSubAgent(id) {
|
|
330
426
|
const config = loadGlobalConfig();
|
|
331
427
|
if (config.hiddenSubAgents.includes(id)) return;
|
|
332
|
-
|
|
428
|
+
updateState({ hiddenSubAgents: [...config.hiddenSubAgents, id] });
|
|
429
|
+
}
|
|
430
|
+
function hideProject(name) {
|
|
431
|
+
const config = loadGlobalConfig();
|
|
432
|
+
if (config.hiddenProjects.includes(name)) return;
|
|
433
|
+
updateState({ hiddenProjects: [...config.hiddenProjects, name] });
|
|
333
434
|
}
|
|
334
435
|
function ensureLogDir(logDir) {
|
|
335
436
|
if (!existsSync(logDir)) {
|
|
@@ -432,7 +533,7 @@ function parseModelName(modelId) {
|
|
|
432
533
|
}
|
|
433
534
|
function getToolDetail(_toolName, input) {
|
|
434
535
|
if (!input) return "";
|
|
435
|
-
if (input.command) return stripAnsi(input.command
|
|
536
|
+
if (input.command) return stripAnsi(input.command);
|
|
436
537
|
if (input.file_path) return basename(input.file_path);
|
|
437
538
|
if (input.pattern) return stripAnsi(input.pattern);
|
|
438
539
|
if (input.query) return stripAnsi(input.query);
|
|
@@ -471,7 +572,7 @@ function parseActivitiesFromLines(lines) {
|
|
|
471
572
|
type: "user",
|
|
472
573
|
icon: ICONS.User,
|
|
473
574
|
label: "User",
|
|
474
|
-
detail: userText
|
|
575
|
+
detail: userText
|
|
475
576
|
});
|
|
476
577
|
}
|
|
477
578
|
}
|
|
@@ -493,7 +594,7 @@ function parseActivitiesFromLines(lines) {
|
|
|
493
594
|
type: "thinking",
|
|
494
595
|
icon: ICONS.Thinking,
|
|
495
596
|
label: "Thinking",
|
|
496
|
-
detail: block.thinking
|
|
597
|
+
detail: block.thinking
|
|
497
598
|
});
|
|
498
599
|
} else if (block.type === "tool_use" && block.name) {
|
|
499
600
|
if (block.name === "TodoWrite") continue;
|
|
@@ -518,7 +619,7 @@ function parseActivitiesFromLines(lines) {
|
|
|
518
619
|
type: "response",
|
|
519
620
|
icon: ICONS.Response,
|
|
520
621
|
label: "Response",
|
|
521
|
-
detail: block.text
|
|
622
|
+
detail: block.text
|
|
522
623
|
});
|
|
523
624
|
}
|
|
524
625
|
}
|
|
@@ -581,7 +682,15 @@ function sessionIsOnDate(session, date, activities) {
|
|
|
581
682
|
}
|
|
582
683
|
return activities.some((a) => isSameLocalDay(a.timestamp, date));
|
|
583
684
|
}
|
|
685
|
+
function flattenForOneLine(s) {
|
|
686
|
+
return s.replace(/[\r\n\t]+/g, " ").trim();
|
|
687
|
+
}
|
|
584
688
|
function truncateDetail(detail, limit) {
|
|
689
|
+
const flat = flattenForOneLine(detail);
|
|
690
|
+
if (limit === 0 || flat.length <= limit) return flat;
|
|
691
|
+
return flat.slice(0, limit);
|
|
692
|
+
}
|
|
693
|
+
function truncateRaw(detail, limit) {
|
|
585
694
|
if (limit === 0 || detail.length <= limit) return detail;
|
|
586
695
|
return detail.slice(0, limit);
|
|
587
696
|
}
|
|
@@ -628,7 +737,7 @@ function generateReport(sessions, options2) {
|
|
|
628
737
|
time: formatTime(a.timestamp),
|
|
629
738
|
icon: a.icon,
|
|
630
739
|
label: a.label,
|
|
631
|
-
detail:
|
|
740
|
+
detail: truncateRaw(a.detail, detailLimit)
|
|
632
741
|
}))
|
|
633
742
|
};
|
|
634
743
|
});
|
|
@@ -640,7 +749,7 @@ function generateReport(sessions, options2) {
|
|
|
640
749
|
time: formatTime(a.timestamp),
|
|
641
750
|
icon: a.icon,
|
|
642
751
|
label: a.label,
|
|
643
|
-
detail:
|
|
752
|
+
detail: truncateRaw(a.detail, detailLimit)
|
|
644
753
|
})),
|
|
645
754
|
subAgents: subAgentBlocks
|
|
646
755
|
};
|
|
@@ -723,7 +832,14 @@ function createBottomLine(panelWidth = DEFAULT_PANEL_WIDTH) {
|
|
|
723
832
|
return BOX.bl + BOX.h.repeat(getInnerWidth(panelWidth)) + BOX.br;
|
|
724
833
|
}
|
|
725
834
|
var SEPARATOR = "\u2500".repeat(CONTENT_WIDTH);
|
|
726
|
-
var
|
|
835
|
+
var widthCache = /* @__PURE__ */ new Map();
|
|
836
|
+
function getDisplayWidth(s) {
|
|
837
|
+
const cached = widthCache.get(s);
|
|
838
|
+
if (cached !== void 0) return cached;
|
|
839
|
+
const w = stringWidth(s);
|
|
840
|
+
widthCache.set(s, w);
|
|
841
|
+
return w;
|
|
842
|
+
}
|
|
727
843
|
|
|
728
844
|
// src/data/sessions.ts
|
|
729
845
|
function getProjectsDir() {
|
|
@@ -790,6 +906,71 @@ function readModelName(filePath) {
|
|
|
790
906
|
}
|
|
791
907
|
return null;
|
|
792
908
|
}
|
|
909
|
+
var SYSTEM_PREFIXES = [
|
|
910
|
+
"<command-name>",
|
|
911
|
+
"<command-message>",
|
|
912
|
+
"<command-args>",
|
|
913
|
+
"<local-command-stdout>",
|
|
914
|
+
"<local-command-caveat>",
|
|
915
|
+
"<system-reminder>",
|
|
916
|
+
"<bash-input>",
|
|
917
|
+
"<bash-stdout>",
|
|
918
|
+
"<bash-stderr>",
|
|
919
|
+
"<user-prompt-submit-hook>"
|
|
920
|
+
];
|
|
921
|
+
function isSystemNoise(text) {
|
|
922
|
+
const trimmed = text.trimStart();
|
|
923
|
+
return SYSTEM_PREFIXES.some((p) => trimmed.startsWith(p));
|
|
924
|
+
}
|
|
925
|
+
function readFirstUserPrompt(filePath) {
|
|
926
|
+
if (!existsSync3(filePath)) return null;
|
|
927
|
+
let content;
|
|
928
|
+
try {
|
|
929
|
+
content = readFileSync4(filePath, "utf-8");
|
|
930
|
+
} catch {
|
|
931
|
+
return null;
|
|
932
|
+
}
|
|
933
|
+
for (const line of content.split("\n")) {
|
|
934
|
+
if (!line.trim()) continue;
|
|
935
|
+
let entry;
|
|
936
|
+
try {
|
|
937
|
+
entry = JSON.parse(line);
|
|
938
|
+
} catch {
|
|
939
|
+
continue;
|
|
940
|
+
}
|
|
941
|
+
if (entry.type !== "user") continue;
|
|
942
|
+
if (entry.toolUseResult !== void 0) continue;
|
|
943
|
+
const raw = entry.message?.content;
|
|
944
|
+
let text;
|
|
945
|
+
if (typeof raw === "string") {
|
|
946
|
+
text = raw;
|
|
947
|
+
} else if (Array.isArray(raw)) {
|
|
948
|
+
const textBlock = raw.find(
|
|
949
|
+
(b) => b && b.type === "text" && typeof b.text === "string"
|
|
950
|
+
);
|
|
951
|
+
text = textBlock?.text ?? "";
|
|
952
|
+
} else {
|
|
953
|
+
continue;
|
|
954
|
+
}
|
|
955
|
+
if (!text || isSystemNoise(text)) continue;
|
|
956
|
+
const firstLine = text.split("\n").find((l) => l.trim()) ?? "";
|
|
957
|
+
if (!firstLine || isSystemNoise(firstLine)) continue;
|
|
958
|
+
const trimmed = firstLine.trim();
|
|
959
|
+
return trimmed.length > 80 ? trimmed.slice(0, 80) : trimmed;
|
|
960
|
+
}
|
|
961
|
+
return null;
|
|
962
|
+
}
|
|
963
|
+
function readEntrypoint(filePath) {
|
|
964
|
+
if (!existsSync3(filePath)) return null;
|
|
965
|
+
try {
|
|
966
|
+
const firstLine = readFileSync4(filePath, "utf-8").split("\n")[0];
|
|
967
|
+
if (!firstLine) return null;
|
|
968
|
+
const entry = JSON.parse(firstLine);
|
|
969
|
+
return typeof entry.entrypoint === "string" ? entry.entrypoint : null;
|
|
970
|
+
} catch {
|
|
971
|
+
return null;
|
|
972
|
+
}
|
|
973
|
+
}
|
|
793
974
|
function buildSubAgents(parentId, projectDir, config, projectName) {
|
|
794
975
|
const subagentsDir = join3(projectDir, parentId, "subagents");
|
|
795
976
|
if (!existsSync3(subagentsDir)) return [];
|
|
@@ -819,7 +1000,9 @@ function buildSubAgents(parentId, projectDir, config, projectName) {
|
|
|
819
1000
|
modelName: readModelName(filePath),
|
|
820
1001
|
subAgents: [],
|
|
821
1002
|
agentId: agentId ?? void 0,
|
|
822
|
-
taskDescription: taskDescription ?? void 0
|
|
1003
|
+
taskDescription: taskDescription ?? void 0,
|
|
1004
|
+
nonInteractive: false,
|
|
1005
|
+
firstUserPrompt: null
|
|
823
1006
|
};
|
|
824
1007
|
} catch {
|
|
825
1008
|
return null;
|
|
@@ -831,7 +1014,12 @@ function buildSubAgents(parentId, projectDir, config, projectName) {
|
|
|
831
1014
|
function discoverSessions(config) {
|
|
832
1015
|
const projectsDir = getProjectsDir();
|
|
833
1016
|
if (!existsSync3(projectsDir)) {
|
|
834
|
-
return {
|
|
1017
|
+
return {
|
|
1018
|
+
projects: [],
|
|
1019
|
+
coldProjects: [],
|
|
1020
|
+
totalCount: 0,
|
|
1021
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1022
|
+
};
|
|
835
1023
|
}
|
|
836
1024
|
let projectDirs;
|
|
837
1025
|
try {
|
|
@@ -843,7 +1031,12 @@ function discoverSessions(config) {
|
|
|
843
1031
|
}
|
|
844
1032
|
});
|
|
845
1033
|
} catch {
|
|
846
|
-
return {
|
|
1034
|
+
return {
|
|
1035
|
+
projects: [],
|
|
1036
|
+
coldProjects: [],
|
|
1037
|
+
totalCount: 0,
|
|
1038
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1039
|
+
};
|
|
847
1040
|
}
|
|
848
1041
|
const allSessions = [];
|
|
849
1042
|
for (const encodedDir of projectDirs) {
|
|
@@ -874,37 +1067,71 @@ function discoverSessions(config) {
|
|
|
874
1067
|
lastModifiedMs: stat.mtimeMs,
|
|
875
1068
|
status: getSessionStatus(stat.mtimeMs),
|
|
876
1069
|
modelName: readModelName(filePath),
|
|
877
|
-
subAgents
|
|
1070
|
+
subAgents,
|
|
1071
|
+
nonInteractive: readEntrypoint(filePath) === "sdk-cli",
|
|
1072
|
+
firstUserPrompt: readFirstUserPrompt(filePath)
|
|
878
1073
|
});
|
|
879
1074
|
} catch {
|
|
880
1075
|
}
|
|
881
1076
|
}
|
|
882
1077
|
}
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
1078
|
+
const byProject = /* @__PURE__ */ new Map();
|
|
1079
|
+
for (const s of allSessions) {
|
|
1080
|
+
if (config.hiddenSessions.includes(s.hideKey)) continue;
|
|
1081
|
+
const arr = byProject.get(s.projectPath) ?? [];
|
|
1082
|
+
arr.push(s);
|
|
1083
|
+
byProject.set(s.projectPath, arr);
|
|
1084
|
+
}
|
|
1085
|
+
const statusOrder = {
|
|
1086
|
+
hot: 0,
|
|
1087
|
+
warm: 1,
|
|
1088
|
+
cool: 2,
|
|
1089
|
+
cold: 3
|
|
1090
|
+
};
|
|
1091
|
+
const allProjects = [];
|
|
1092
|
+
for (const [projectPath, sessions] of byProject) {
|
|
1093
|
+
if (sessions.length === 0) continue;
|
|
1094
|
+
const projectName = sessions[0].projectName;
|
|
1095
|
+
if (config.hiddenProjects.includes(projectName)) continue;
|
|
1096
|
+
sessions.sort((a, b) => {
|
|
1097
|
+
if (a.nonInteractive !== b.nonInteractive) {
|
|
1098
|
+
return a.nonInteractive ? 1 : -1;
|
|
1099
|
+
}
|
|
1100
|
+
const statusDiff = statusOrder[a.status] - statusOrder[b.status];
|
|
1101
|
+
if (statusDiff !== 0) return statusDiff;
|
|
1102
|
+
return b.lastModifiedMs - a.lastModifiedMs;
|
|
1103
|
+
});
|
|
1104
|
+
const hotness = sessions[0].status;
|
|
1105
|
+
allProjects.push({ name: projectName, projectPath, sessions, hotness });
|
|
1106
|
+
}
|
|
1107
|
+
const activeProjects = allProjects.filter((p) => p.hotness !== "cold");
|
|
1108
|
+
const coldProjects = allProjects.filter((p) => p.hotness === "cold");
|
|
1109
|
+
activeProjects.sort((a, b) => {
|
|
1110
|
+
const statusDiff = statusOrder[a.hotness] - statusOrder[b.hotness];
|
|
891
1111
|
if (statusDiff !== 0) return statusDiff;
|
|
892
|
-
return b.lastModifiedMs - a.lastModifiedMs;
|
|
1112
|
+
return b.sessions[0].lastModifiedMs - a.sessions[0].lastModifiedMs;
|
|
893
1113
|
});
|
|
894
|
-
const
|
|
895
|
-
(
|
|
1114
|
+
const countSessions = (projects) => projects.reduce(
|
|
1115
|
+
(sum, p) => sum + p.sessions.length + p.sessions.reduce((s, sn) => s + sn.subAgents.length, 0),
|
|
1116
|
+
0
|
|
896
1117
|
);
|
|
897
|
-
const totalCount =
|
|
1118
|
+
const totalCount = countSessions(activeProjects) + countSessions(coldProjects);
|
|
898
1119
|
return {
|
|
899
|
-
|
|
1120
|
+
projects: activeProjects,
|
|
1121
|
+
coldProjects,
|
|
900
1122
|
totalCount,
|
|
901
1123
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
902
1124
|
};
|
|
903
1125
|
}
|
|
904
1126
|
|
|
905
1127
|
// src/data/summaryRunner.ts
|
|
1128
|
+
function agenthudHomeDir() {
|
|
1129
|
+
const dir = join4(homedir3(), ".agenthud");
|
|
1130
|
+
if (!existsSync4(dir)) mkdirSync2(dir, { recursive: true });
|
|
1131
|
+
return dir;
|
|
1132
|
+
}
|
|
906
1133
|
function summariesDir() {
|
|
907
|
-
const dir = join4(
|
|
1134
|
+
const dir = join4(agenthudHomeDir(), "summaries");
|
|
908
1135
|
if (!existsSync4(dir)) mkdirSync2(dir, { recursive: true });
|
|
909
1136
|
return dir;
|
|
910
1137
|
}
|
|
@@ -974,7 +1201,8 @@ async function runSummary(options2) {
|
|
|
974
1201
|
const prompt = resolvePrompt(options2.prompt);
|
|
975
1202
|
return new Promise((resolve) => {
|
|
976
1203
|
const proc = spawn("claude", ["-p", prompt], {
|
|
977
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
1204
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
1205
|
+
cwd: agenthudHomeDir()
|
|
978
1206
|
});
|
|
979
1207
|
let cacheStream = null;
|
|
980
1208
|
cacheStream = createWriteStream(cached, { encoding: "utf-8" });
|
|
@@ -1025,7 +1253,7 @@ async function runSummary(options2) {
|
|
|
1025
1253
|
// src/ui/App.tsx
|
|
1026
1254
|
import { existsSync as existsSync5, watch, writeFileSync as writeFileSync2 } from "fs";
|
|
1027
1255
|
import { join as join5 } from "path";
|
|
1028
|
-
import { Box as
|
|
1256
|
+
import { Box as Box5, Text as Text5, useApp, useInput, useStdout } from "ink";
|
|
1029
1257
|
import { useCallback, useEffect as useEffect2, useMemo, useRef, useState as useState2 } from "react";
|
|
1030
1258
|
|
|
1031
1259
|
// src/ui/ActivityViewerPanel.tsx
|
|
@@ -1063,20 +1291,21 @@ function formatActivityTime(date, now) {
|
|
|
1063
1291
|
const day = String(date.getDate()).padStart(2, "0");
|
|
1064
1292
|
return `${month}/${day} ${time}`;
|
|
1065
1293
|
}
|
|
1294
|
+
function flattenForOneLine2(detail) {
|
|
1295
|
+
return detail.replace(/[\r\n\t]+/g, " ").trim();
|
|
1296
|
+
}
|
|
1066
1297
|
function truncateDetail2(detail, maxWidth) {
|
|
1067
|
-
|
|
1298
|
+
const flat = flattenForOneLine2(detail);
|
|
1299
|
+
if (getDisplayWidth(flat) <= maxWidth) return flat;
|
|
1068
1300
|
let truncated = "";
|
|
1069
|
-
let
|
|
1070
|
-
for (const char of
|
|
1301
|
+
let width = 0;
|
|
1302
|
+
for (const char of flat) {
|
|
1071
1303
|
const charWidth = getDisplayWidth(char);
|
|
1072
|
-
if (
|
|
1073
|
-
truncated += "...";
|
|
1074
|
-
break;
|
|
1075
|
-
}
|
|
1304
|
+
if (width + charWidth > maxWidth - 1) break;
|
|
1076
1305
|
truncated += char;
|
|
1077
|
-
|
|
1306
|
+
width += charWidth;
|
|
1078
1307
|
}
|
|
1079
|
-
return truncated
|
|
1308
|
+
return `${truncated}\u2026`;
|
|
1080
1309
|
}
|
|
1081
1310
|
function ActivityViewerPanel({
|
|
1082
1311
|
activities,
|
|
@@ -1279,10 +1508,114 @@ function DetailViewPanel({
|
|
|
1279
1508
|
] });
|
|
1280
1509
|
}
|
|
1281
1510
|
|
|
1511
|
+
// src/ui/HelpPanel.tsx
|
|
1512
|
+
import { Box as Box3, Text as Text3 } from "ink";
|
|
1513
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1514
|
+
var SECTIONS = [
|
|
1515
|
+
{
|
|
1516
|
+
title: "Session tree",
|
|
1517
|
+
rows: [
|
|
1518
|
+
["\u2191 \u2193 / k j", "Move selection"],
|
|
1519
|
+
["PgUp / Ctrl+B", "Page up"],
|
|
1520
|
+
["PgDn / Ctrl+F", "Page down"],
|
|
1521
|
+
["\u21B5", "Expand/collapse project, session, or summary"],
|
|
1522
|
+
["h", "Hide selected (project/session/sub-agent)"],
|
|
1523
|
+
["Tab", "Switch focus to activity viewer"],
|
|
1524
|
+
["r", "Refresh now"]
|
|
1525
|
+
]
|
|
1526
|
+
},
|
|
1527
|
+
{
|
|
1528
|
+
title: "Activity viewer",
|
|
1529
|
+
rows: [
|
|
1530
|
+
["\u2191 \u2193 / k j", "Scroll one line"],
|
|
1531
|
+
["PgUp/Dn, Ctrl+B/F", "Scroll one page"],
|
|
1532
|
+
["Ctrl+U / Ctrl+D", "Scroll half page"],
|
|
1533
|
+
["g", "Jump to live (newest)"],
|
|
1534
|
+
["G", "Jump to oldest"],
|
|
1535
|
+
["\u21B5", "Open detail view for selected activity"],
|
|
1536
|
+
["f", "Cycle filter preset (set in config.yaml)"],
|
|
1537
|
+
["s", "Save activity log to ~/.agenthud/logs/"],
|
|
1538
|
+
["Tab", "Switch focus to session tree"]
|
|
1539
|
+
]
|
|
1540
|
+
},
|
|
1541
|
+
{
|
|
1542
|
+
title: "Detail view",
|
|
1543
|
+
rows: [
|
|
1544
|
+
["\u2191 \u2193 / k j", "Scroll"],
|
|
1545
|
+
["\u21B5 / Esc / q", "Close"]
|
|
1546
|
+
]
|
|
1547
|
+
},
|
|
1548
|
+
{
|
|
1549
|
+
title: "Always available",
|
|
1550
|
+
rows: [
|
|
1551
|
+
["?", "Toggle this help"],
|
|
1552
|
+
["q", "Quit (or close detail/help)"]
|
|
1553
|
+
]
|
|
1554
|
+
},
|
|
1555
|
+
{
|
|
1556
|
+
title: "CLI commands",
|
|
1557
|
+
rows: [
|
|
1558
|
+
["agenthud report", "Print activity for a date as Markdown/JSON"],
|
|
1559
|
+
["agenthud summary", "LLM-summarize a day via claude -p (cached)"],
|
|
1560
|
+
["agenthud --help", "Full CLI usage"]
|
|
1561
|
+
]
|
|
1562
|
+
},
|
|
1563
|
+
{
|
|
1564
|
+
title: "Files",
|
|
1565
|
+
rows: [
|
|
1566
|
+
["~/.agenthud/config.yaml", "User settings (edit freely)"],
|
|
1567
|
+
["~/.agenthud/state.yaml", "Hidden items (app-managed)"],
|
|
1568
|
+
["~/.agenthud/summary-prompt.md", "LLM prompt template"],
|
|
1569
|
+
["~/.agenthud/summaries/", "Cached daily summaries"]
|
|
1570
|
+
]
|
|
1571
|
+
}
|
|
1572
|
+
];
|
|
1573
|
+
function HelpPanel({
|
|
1574
|
+
width,
|
|
1575
|
+
height
|
|
1576
|
+
}) {
|
|
1577
|
+
const allKeys = SECTIONS.flatMap((s) => s.rows.map((r) => r[0]));
|
|
1578
|
+
const keyColumn = Math.min(
|
|
1579
|
+
30,
|
|
1580
|
+
Math.max(...allKeys.map((k) => getDisplayWidth(k)))
|
|
1581
|
+
);
|
|
1582
|
+
const padTo = (s, w) => {
|
|
1583
|
+
const pad = Math.max(0, w - getDisplayWidth(s));
|
|
1584
|
+
return s + " ".repeat(pad);
|
|
1585
|
+
};
|
|
1586
|
+
const lines = [];
|
|
1587
|
+
lines.push(
|
|
1588
|
+
/* @__PURE__ */ jsx3(Text3, { bold: true, children: "AgentHUD Help" }, "title")
|
|
1589
|
+
);
|
|
1590
|
+
lines.push(/* @__PURE__ */ jsx3(Text3, { children: " " }, "title-sp"));
|
|
1591
|
+
for (let s = 0; s < SECTIONS.length; s++) {
|
|
1592
|
+
if (s > 0) lines.push(/* @__PURE__ */ jsx3(Text3, { children: " " }, `sp-${s}`));
|
|
1593
|
+
lines.push(
|
|
1594
|
+
/* @__PURE__ */ jsx3(Text3, { bold: true, color: "cyan", children: SECTIONS[s].title }, `title-${s}`)
|
|
1595
|
+
);
|
|
1596
|
+
for (let r = 0; r < SECTIONS[s].rows.length; r++) {
|
|
1597
|
+
const [key, desc] = SECTIONS[s].rows[r];
|
|
1598
|
+
const isCli = key.trim().startsWith("agenthud");
|
|
1599
|
+
const isFile = key.includes("~/.agenthud");
|
|
1600
|
+
lines.push(
|
|
1601
|
+
/* @__PURE__ */ jsxs3(Text3, { children: [
|
|
1602
|
+
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children: " " }),
|
|
1603
|
+
/* @__PURE__ */ jsx3(Text3, { color: isCli ? "cyan" : isFile ? "green" : void 0, children: padTo(key, keyColumn) }),
|
|
1604
|
+
/* @__PURE__ */ jsx3(Text3, { children: " " }),
|
|
1605
|
+
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children: desc })
|
|
1606
|
+
] }, `row-${s}-${r}`)
|
|
1607
|
+
);
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
const visible = lines.slice(0, height);
|
|
1611
|
+
return /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", width, children: visible });
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1282
1614
|
// src/ui/hooks/useHotkeys.ts
|
|
1283
1615
|
function useHotkeys({
|
|
1284
1616
|
focus,
|
|
1285
1617
|
detailMode,
|
|
1618
|
+
helpMode,
|
|
1286
1619
|
onSwitchFocus,
|
|
1287
1620
|
onScrollUp,
|
|
1288
1621
|
onScrollDown,
|
|
@@ -1301,9 +1634,21 @@ function useHotkeys({
|
|
|
1301
1634
|
onDetailScrollUp,
|
|
1302
1635
|
onDetailScrollDown,
|
|
1303
1636
|
onFilter,
|
|
1637
|
+
onHelp,
|
|
1304
1638
|
filterLabel
|
|
1305
1639
|
}) {
|
|
1306
1640
|
const handleInput = (input, key) => {
|
|
1641
|
+
if (helpMode) {
|
|
1642
|
+
if (key.return || key.escape || input === "q" || input === "?") {
|
|
1643
|
+
onHelp();
|
|
1644
|
+
return;
|
|
1645
|
+
}
|
|
1646
|
+
return;
|
|
1647
|
+
}
|
|
1648
|
+
if (input === "?") {
|
|
1649
|
+
onHelp();
|
|
1650
|
+
return;
|
|
1651
|
+
}
|
|
1307
1652
|
if (detailMode) {
|
|
1308
1653
|
if (key.upArrow || input === "k") {
|
|
1309
1654
|
onDetailScrollUp();
|
|
@@ -1402,13 +1747,14 @@ function useHotkeys({
|
|
|
1402
1747
|
}
|
|
1403
1748
|
}
|
|
1404
1749
|
};
|
|
1405
|
-
const statusBarItems = detailMode ? ["\u2191\u2193/jk: scroll", "\u21B5/Esc: close"] : focus === "tree" ? [
|
|
1750
|
+
const statusBarItems = helpMode ? ["\u21B5/Esc/q/?: close"] : detailMode ? ["\u2191\u2193/jk: scroll", "\u21B5/Esc: close", "?: help"] : focus === "tree" ? [
|
|
1406
1751
|
"Tab: viewer",
|
|
1407
1752
|
"\u2191\u2193/jk: select",
|
|
1408
1753
|
"PgUp/Dn: page",
|
|
1409
1754
|
"\u21B5: expand",
|
|
1410
1755
|
"h: hide",
|
|
1411
1756
|
"r: refresh",
|
|
1757
|
+
"?: help",
|
|
1412
1758
|
"q: quit"
|
|
1413
1759
|
] : [
|
|
1414
1760
|
"Tab: sessions",
|
|
@@ -1418,6 +1764,7 @@ function useHotkeys({
|
|
|
1418
1764
|
"G: oldest",
|
|
1419
1765
|
"\u21B5: detail",
|
|
1420
1766
|
`f: ${filterLabel}`,
|
|
1767
|
+
"?: help",
|
|
1421
1768
|
"q: quit"
|
|
1422
1769
|
];
|
|
1423
1770
|
return { handleInput, statusBarItems };
|
|
@@ -1440,8 +1787,8 @@ function useSpinner(active, intervalMs = 100) {
|
|
|
1440
1787
|
|
|
1441
1788
|
// src/ui/SessionTreePanel.tsx
|
|
1442
1789
|
import { homedir as homedir4 } from "os";
|
|
1443
|
-
import { Box as
|
|
1444
|
-
import { jsx as
|
|
1790
|
+
import { Box as Box4, Text as Text4 } from "ink";
|
|
1791
|
+
import { Fragment, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1445
1792
|
function formatElapsed(lastModifiedMs) {
|
|
1446
1793
|
const elapsed = Date.now() - lastModifiedMs;
|
|
1447
1794
|
const seconds = Math.floor(elapsed / 1e3);
|
|
@@ -1481,23 +1828,24 @@ function SessionRow({
|
|
|
1481
1828
|
prefix,
|
|
1482
1829
|
contentWidth
|
|
1483
1830
|
}) {
|
|
1484
|
-
const isParent = prefix === "";
|
|
1831
|
+
const isParent = prefix === " ";
|
|
1485
1832
|
const statusColor = getStatusColor(session.status);
|
|
1486
1833
|
const badge = `[${session.status}]`;
|
|
1487
1834
|
const elapsed = formatElapsed(session.lastModifiedMs);
|
|
1488
1835
|
const model = session.modelName ?? "";
|
|
1489
|
-
const
|
|
1490
|
-
const
|
|
1836
|
+
const isNonInteractive = session.nonInteractive;
|
|
1837
|
+
const rawName = isParent ? isNonInteractive ? `(#${session.id.slice(0, 4)})` : `#${session.id.slice(0, 4)}` : session.agentId ?? session.id.slice(0, 8);
|
|
1838
|
+
const shortIdDisplay = "";
|
|
1491
1839
|
const rightParts = [elapsed];
|
|
1492
1840
|
if (model) rightParts.push(model);
|
|
1493
1841
|
const rightSide = rightParts.join(" ");
|
|
1494
|
-
const
|
|
1495
|
-
const leftCoreWidth = getDisplayWidth(
|
|
1842
|
+
const leftCoreBase = `${prefix}${rawName}${shortIdDisplay} ${badge}`;
|
|
1843
|
+
const leftCoreWidth = getDisplayWidth(leftCoreBase);
|
|
1496
1844
|
const rightWidth = getDisplayWidth(rightSide);
|
|
1497
1845
|
const middleAvailable = contentWidth - leftCoreWidth - 1 - rightWidth - 1;
|
|
1498
1846
|
let middleText = "";
|
|
1499
1847
|
if (middleAvailable > 3) {
|
|
1500
|
-
const raw = isParent ? session.
|
|
1848
|
+
const raw = isParent ? session.firstUserPrompt ?? "" : session.taskDescription ?? "";
|
|
1501
1849
|
if (raw) {
|
|
1502
1850
|
const truncated = truncatePath(raw, middleAvailable);
|
|
1503
1851
|
if (truncated) middleText = truncated;
|
|
@@ -1509,42 +1857,56 @@ function SessionRow({
|
|
|
1509
1857
|
contentWidth - leftCoreWidth - getDisplayWidth(middleSection) - rightWidth
|
|
1510
1858
|
);
|
|
1511
1859
|
const gap = " ".repeat(gapWidth);
|
|
1512
|
-
const fullLine =
|
|
1860
|
+
const fullLine = leftCoreBase + middleSection + gap + rightSide;
|
|
1513
1861
|
const linePadding = Math.max(0, contentWidth - getDisplayWidth(fullLine));
|
|
1514
1862
|
const highlight = isSelected && hasFocus;
|
|
1515
|
-
|
|
1863
|
+
const shouldDim = isNonInteractive;
|
|
1864
|
+
return /* @__PURE__ */ jsxs4(Text4, { children: [
|
|
1516
1865
|
BOX.v,
|
|
1517
1866
|
" ",
|
|
1518
|
-
/* @__PURE__ */
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1867
|
+
/* @__PURE__ */ jsxs4(
|
|
1868
|
+
Text4,
|
|
1869
|
+
{
|
|
1870
|
+
backgroundColor: highlight ? "blue" : void 0,
|
|
1871
|
+
bold: highlight,
|
|
1872
|
+
dimColor: shouldDim && !highlight,
|
|
1873
|
+
children: [
|
|
1874
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: shouldDim && !highlight, children: prefix }),
|
|
1875
|
+
/* @__PURE__ */ jsx4(Text4, { bold: !shouldDim, children: rawName }),
|
|
1876
|
+
shortIdDisplay ? /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: shortIdDisplay }) : null,
|
|
1877
|
+
/* @__PURE__ */ jsx4(Text4, { children: " " }),
|
|
1878
|
+
/* @__PURE__ */ jsx4(Text4, { color: statusColor, children: badge }),
|
|
1879
|
+
middleText ? /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: middleSection }) : null,
|
|
1880
|
+
/* @__PURE__ */ jsx4(Text4, { children: gap }),
|
|
1881
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: elapsed }),
|
|
1882
|
+
model ? /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: ` ${model}` }) : null
|
|
1883
|
+
]
|
|
1884
|
+
}
|
|
1885
|
+
),
|
|
1529
1886
|
" ".repeat(linePadding),
|
|
1530
1887
|
BOX.v
|
|
1531
1888
|
] });
|
|
1532
1889
|
}
|
|
1533
1890
|
function appendSessionRows(result, session, expandedIds) {
|
|
1534
|
-
const
|
|
1891
|
+
const isCold = session.status === "cold";
|
|
1892
|
+
const sessionCollapsedKey = `__collapsed-session-${session.id}`;
|
|
1893
|
+
const sessionExpandedKey = `__expanded-session-${session.id}`;
|
|
1894
|
+
const sessionHidden = isCold ? !expandedIds.has(sessionExpandedKey) : expandedIds.has(sessionCollapsedKey);
|
|
1895
|
+
if (sessionHidden) return;
|
|
1896
|
+
const subAgentsFullyExpanded = expandedIds.has(session.id) || isCold && expandedIds.has(sessionExpandedKey);
|
|
1535
1897
|
const hotWarm = session.subAgents.filter(
|
|
1536
1898
|
(s) => s.status === "hot" || s.status === "warm"
|
|
1537
1899
|
);
|
|
1538
1900
|
const cool = session.subAgents.filter((s) => s.status === "cool");
|
|
1539
1901
|
const cold = session.subAgents.filter((s) => s.status === "cold");
|
|
1540
|
-
if (
|
|
1902
|
+
if (subAgentsFullyExpanded) {
|
|
1541
1903
|
const all = [...hotWarm, ...cool, ...cold];
|
|
1542
1904
|
for (let i = 0; i < all.length; i++) {
|
|
1543
1905
|
const isLast = i === all.length - 1;
|
|
1544
1906
|
result.push({
|
|
1545
1907
|
kind: "session",
|
|
1546
1908
|
session: all[i],
|
|
1547
|
-
prefix:
|
|
1909
|
+
prefix: ` ${isLast ? "\u2514\u2500 " : "\u251C\u2500 "}\xBB `
|
|
1548
1910
|
});
|
|
1549
1911
|
}
|
|
1550
1912
|
} else {
|
|
@@ -1554,7 +1916,7 @@ function appendSessionRows(result, session, expandedIds) {
|
|
|
1554
1916
|
result.push({
|
|
1555
1917
|
kind: "session",
|
|
1556
1918
|
session: hotWarm[i],
|
|
1557
|
-
prefix:
|
|
1919
|
+
prefix: ` ${isLast ? "\u2514\u2500 " : "\u251C\u2500 "}\xBB `
|
|
1558
1920
|
});
|
|
1559
1921
|
}
|
|
1560
1922
|
if (hasSummary) {
|
|
@@ -1567,25 +1929,65 @@ function appendSessionRows(result, session, expandedIds) {
|
|
|
1567
1929
|
}
|
|
1568
1930
|
}
|
|
1569
1931
|
}
|
|
1570
|
-
function flattenSessions(
|
|
1932
|
+
function flattenSessions(projects, coldProjects, expandedIds) {
|
|
1571
1933
|
const result = [];
|
|
1572
|
-
const
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
result.push({ kind: "cold-sessions-summary", count: coldSessions.length });
|
|
1580
|
-
if (expandedIds.has("__cold__")) {
|
|
1581
|
-
for (const session of coldSessions) {
|
|
1582
|
-
result.push({ kind: "session", session, prefix: "" });
|
|
1934
|
+
for (const project of projects) {
|
|
1935
|
+
const sentinelId = `__proj-${project.name}__`;
|
|
1936
|
+
result.push({ kind: "project", project, sentinelId });
|
|
1937
|
+
const collapsed = expandedIds.has(`__collapsed-${sentinelId}`);
|
|
1938
|
+
if (!collapsed) {
|
|
1939
|
+
for (const session of project.sessions) {
|
|
1940
|
+
result.push({ kind: "session", session, prefix: " " });
|
|
1583
1941
|
appendSessionRows(result, session, expandedIds);
|
|
1584
1942
|
}
|
|
1585
1943
|
}
|
|
1586
1944
|
}
|
|
1945
|
+
if (coldProjects.length > 0) {
|
|
1946
|
+
result.push({ kind: "cold-projects-summary", count: coldProjects.length });
|
|
1947
|
+
if (expandedIds.has("__cold__")) {
|
|
1948
|
+
for (const project of coldProjects) {
|
|
1949
|
+
const sentinelId = `__proj-${project.name}__`;
|
|
1950
|
+
result.push({ kind: "project", project, sentinelId });
|
|
1951
|
+
const expanded = expandedIds.has(`__expanded-${sentinelId}`);
|
|
1952
|
+
if (expanded) {
|
|
1953
|
+
for (const session of project.sessions) {
|
|
1954
|
+
result.push({ kind: "session", session, prefix: " " });
|
|
1955
|
+
appendSessionRows(result, session, expandedIds);
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1587
1961
|
return result;
|
|
1588
1962
|
}
|
|
1963
|
+
function ProjectRow({
|
|
1964
|
+
project,
|
|
1965
|
+
isSelected,
|
|
1966
|
+
hasFocus,
|
|
1967
|
+
contentWidth
|
|
1968
|
+
}) {
|
|
1969
|
+
const nameText = `> ${project.name}`;
|
|
1970
|
+
const pathText = project.projectPath ? formatProjectPath(project.projectPath) : "";
|
|
1971
|
+
const nameWidth = getDisplayWidth(nameText);
|
|
1972
|
+
const pathWidth = pathText ? getDisplayWidth(pathText) : 0;
|
|
1973
|
+
const gapWidth = pathText ? 2 : 0;
|
|
1974
|
+
const totalWidth = nameWidth + gapWidth + pathWidth;
|
|
1975
|
+
const padding = Math.max(0, contentWidth - totalWidth);
|
|
1976
|
+
const highlight = isSelected && hasFocus;
|
|
1977
|
+
return /* @__PURE__ */ jsxs4(Text4, { children: [
|
|
1978
|
+
BOX.v,
|
|
1979
|
+
" ",
|
|
1980
|
+
/* @__PURE__ */ jsxs4(Text4, { backgroundColor: highlight ? "blue" : void 0, bold: !highlight, children: [
|
|
1981
|
+
nameText,
|
|
1982
|
+
pathText ? /* @__PURE__ */ jsxs4(Fragment, { children: [
|
|
1983
|
+
" ",
|
|
1984
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: pathText })
|
|
1985
|
+
] }) : null,
|
|
1986
|
+
" ".repeat(padding)
|
|
1987
|
+
] }),
|
|
1988
|
+
BOX.v
|
|
1989
|
+
] });
|
|
1990
|
+
}
|
|
1589
1991
|
function SubagentSummaryRow({
|
|
1590
1992
|
coolCount,
|
|
1591
1993
|
coldCount,
|
|
@@ -1597,16 +1999,16 @@ function SubagentSummaryRow({
|
|
|
1597
1999
|
if (coolCount > 0) parts.push(`${coolCount} cool`);
|
|
1598
2000
|
if (coldCount > 0) parts.push(`${coldCount} cold`);
|
|
1599
2001
|
const hint = " +";
|
|
1600
|
-
const text =
|
|
2002
|
+
const text = ` \u2514\u2500 ... ${parts.join(" ")}`;
|
|
1601
2003
|
const padding = Math.max(
|
|
1602
2004
|
0,
|
|
1603
2005
|
contentWidth - getDisplayWidth(text) - getDisplayWidth(hint)
|
|
1604
2006
|
);
|
|
1605
2007
|
const active = isSelected && hasFocus;
|
|
1606
|
-
return /* @__PURE__ */
|
|
2008
|
+
return /* @__PURE__ */ jsxs4(Text4, { children: [
|
|
1607
2009
|
BOX.v,
|
|
1608
2010
|
" ",
|
|
1609
|
-
/* @__PURE__ */
|
|
2011
|
+
/* @__PURE__ */ jsxs4(Text4, { dimColor: !active, inverse: active, children: [
|
|
1610
2012
|
text,
|
|
1611
2013
|
" ".repeat(padding),
|
|
1612
2014
|
hint
|
|
@@ -1614,7 +2016,7 @@ function SubagentSummaryRow({
|
|
|
1614
2016
|
BOX.v
|
|
1615
2017
|
] });
|
|
1616
2018
|
}
|
|
1617
|
-
function
|
|
2019
|
+
function ColdProjectsSummaryRow({
|
|
1618
2020
|
count,
|
|
1619
2021
|
isSelected,
|
|
1620
2022
|
hasFocus,
|
|
@@ -1629,8 +2031,8 @@ function ColdSessionsSummaryRow({
|
|
|
1629
2031
|
const dashes = BOX.h.repeat(dashCount);
|
|
1630
2032
|
const line = `${BOX.ml}${BOX.h}${label}${dashes}${hint}${BOX.mr}`;
|
|
1631
2033
|
const highlight = isSelected && hasFocus;
|
|
1632
|
-
return /* @__PURE__ */
|
|
1633
|
-
|
|
2034
|
+
return /* @__PURE__ */ jsx4(
|
|
2035
|
+
Text4,
|
|
1634
2036
|
{
|
|
1635
2037
|
backgroundColor: highlight ? "blue" : void 0,
|
|
1636
2038
|
bold: highlight,
|
|
@@ -1640,7 +2042,8 @@ function ColdSessionsSummaryRow({
|
|
|
1640
2042
|
);
|
|
1641
2043
|
}
|
|
1642
2044
|
function SessionTreePanel({
|
|
1643
|
-
|
|
2045
|
+
projects,
|
|
2046
|
+
coldProjects,
|
|
1644
2047
|
selectedId,
|
|
1645
2048
|
hasFocus,
|
|
1646
2049
|
width = DEFAULT_PANEL_WIDTH,
|
|
@@ -1651,28 +2054,30 @@ function SessionTreePanel({
|
|
|
1651
2054
|
const contentWidth = innerWidth - 1;
|
|
1652
2055
|
const titleLine = createTitleLine("Sessions", "", width);
|
|
1653
2056
|
const bottomLine = createBottomLine(width);
|
|
1654
|
-
|
|
2057
|
+
const totalProjectCount = projects.length + coldProjects.length;
|
|
2058
|
+
if (totalProjectCount === 0) {
|
|
1655
2059
|
const emptyText = "No Claude sessions";
|
|
1656
2060
|
const emptyPadding = Math.max(0, contentWidth - emptyText.length);
|
|
1657
|
-
return /* @__PURE__ */
|
|
1658
|
-
/* @__PURE__ */
|
|
1659
|
-
/* @__PURE__ */
|
|
2061
|
+
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", width, children: [
|
|
2062
|
+
/* @__PURE__ */ jsx4(Text4, { children: titleLine }),
|
|
2063
|
+
/* @__PURE__ */ jsxs4(Text4, { children: [
|
|
1660
2064
|
BOX.v,
|
|
1661
2065
|
" ",
|
|
1662
|
-
/* @__PURE__ */
|
|
2066
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: emptyText }),
|
|
1663
2067
|
" ".repeat(emptyPadding),
|
|
1664
2068
|
BOX.v
|
|
1665
2069
|
] }),
|
|
1666
|
-
/* @__PURE__ */
|
|
2070
|
+
/* @__PURE__ */ jsx4(Text4, { children: bottomLine })
|
|
1667
2071
|
] });
|
|
1668
2072
|
}
|
|
1669
|
-
const flatRows = flattenSessions(
|
|
2073
|
+
const flatRows = flattenSessions(projects, coldProjects, expandedIds);
|
|
1670
2074
|
const totalRows = flatRows.length;
|
|
1671
2075
|
const selectedFlatIndex = flatRows.findIndex((row) => {
|
|
2076
|
+
if (row.kind === "project") return selectedId === row.sentinelId;
|
|
1672
2077
|
if (row.kind === "session") return row.session.id === selectedId;
|
|
1673
2078
|
if (row.kind === "subagent-summary")
|
|
1674
2079
|
return selectedId === `__sub-${row.parentId}__`;
|
|
1675
|
-
if (row.kind === "cold-
|
|
2080
|
+
if (row.kind === "cold-projects-summary") return selectedId === "__cold__";
|
|
1676
2081
|
return false;
|
|
1677
2082
|
});
|
|
1678
2083
|
const needsOverflow = maxRows !== void 0 && totalRows > maxRows;
|
|
@@ -1684,10 +2089,19 @@ function SessionTreePanel({
|
|
|
1684
2089
|
}
|
|
1685
2090
|
const displayRows = flatRows.slice(scrollTop, scrollTop + visibleCount);
|
|
1686
2091
|
const hiddenBelow = totalRows - (scrollTop + displayRows.length);
|
|
1687
|
-
return /* @__PURE__ */
|
|
1688
|
-
/* @__PURE__ */
|
|
2092
|
+
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", width, children: [
|
|
2093
|
+
/* @__PURE__ */ jsx4(Text4, { children: titleLine }),
|
|
1689
2094
|
displayRows.map(
|
|
1690
|
-
(row, idx) => row.kind === "
|
|
2095
|
+
(row, idx) => row.kind === "project" ? /* @__PURE__ */ jsx4(
|
|
2096
|
+
ProjectRow,
|
|
2097
|
+
{
|
|
2098
|
+
project: row.project,
|
|
2099
|
+
isSelected: selectedId === row.sentinelId,
|
|
2100
|
+
hasFocus,
|
|
2101
|
+
contentWidth
|
|
2102
|
+
},
|
|
2103
|
+
`project-${row.project.name}-${idx}`
|
|
2104
|
+
) : row.kind === "session" ? /* @__PURE__ */ jsx4(
|
|
1691
2105
|
SessionRow,
|
|
1692
2106
|
{
|
|
1693
2107
|
session: row.session,
|
|
@@ -1697,7 +2111,7 @@ function SessionTreePanel({
|
|
|
1697
2111
|
contentWidth
|
|
1698
2112
|
},
|
|
1699
2113
|
`${row.session.id}-${idx}`
|
|
1700
|
-
) : row.kind === "subagent-summary" ? /* @__PURE__ */
|
|
2114
|
+
) : row.kind === "subagent-summary" ? /* @__PURE__ */ jsx4(
|
|
1701
2115
|
SubagentSummaryRow,
|
|
1702
2116
|
{
|
|
1703
2117
|
coolCount: row.coolCount,
|
|
@@ -1707,8 +2121,8 @@ function SessionTreePanel({
|
|
|
1707
2121
|
hasFocus
|
|
1708
2122
|
},
|
|
1709
2123
|
`subagent-summary-${idx}`
|
|
1710
|
-
) : /* @__PURE__ */
|
|
1711
|
-
|
|
2124
|
+
) : /* @__PURE__ */ jsx4(
|
|
2125
|
+
ColdProjectsSummaryRow,
|
|
1712
2126
|
{
|
|
1713
2127
|
count: row.count,
|
|
1714
2128
|
isSelected: selectedId === "__cold__",
|
|
@@ -1718,35 +2132,42 @@ function SessionTreePanel({
|
|
|
1718
2132
|
"cold-summary"
|
|
1719
2133
|
)
|
|
1720
2134
|
),
|
|
1721
|
-
hiddenBelow > 0 && /* @__PURE__ */
|
|
2135
|
+
hiddenBelow > 0 && /* @__PURE__ */ jsxs4(Text4, { children: [
|
|
1722
2136
|
BOX.v,
|
|
1723
2137
|
" ",
|
|
1724
|
-
/* @__PURE__ */
|
|
2138
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: `... ${hiddenBelow} more` }),
|
|
1725
2139
|
" ".repeat(
|
|
1726
2140
|
Math.max(0, contentWidth - `... ${hiddenBelow} more`.length - 1)
|
|
1727
2141
|
),
|
|
1728
2142
|
BOX.v
|
|
1729
2143
|
] }),
|
|
1730
|
-
/* @__PURE__ */
|
|
2144
|
+
/* @__PURE__ */ jsx4(Text4, { children: bottomLine })
|
|
1731
2145
|
] });
|
|
1732
2146
|
}
|
|
1733
2147
|
|
|
1734
2148
|
// src/ui/App.tsx
|
|
1735
|
-
import { jsx as
|
|
2149
|
+
import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1736
2150
|
var VIEWER_HEIGHT_FRACTION = 0.55;
|
|
1737
2151
|
function subSummarySentinel(parentId) {
|
|
1738
2152
|
return {
|
|
1739
2153
|
id: `__sub-${parentId}__`,
|
|
2154
|
+
hideKey: "",
|
|
1740
2155
|
filePath: "",
|
|
1741
2156
|
projectPath: "",
|
|
1742
2157
|
projectName: "",
|
|
1743
2158
|
lastModifiedMs: 0,
|
|
1744
2159
|
status: "cold",
|
|
1745
2160
|
modelName: null,
|
|
1746
|
-
subAgents: []
|
|
2161
|
+
subAgents: [],
|
|
2162
|
+
nonInteractive: false
|
|
1747
2163
|
};
|
|
1748
2164
|
}
|
|
1749
2165
|
function appendSubAgentRows(result, session, expandedIds) {
|
|
2166
|
+
const isCold = session.status === "cold";
|
|
2167
|
+
const sessionCollapsedKey = `__collapsed-session-${session.id}`;
|
|
2168
|
+
const sessionExpandedKey = `__expanded-session-${session.id}`;
|
|
2169
|
+
const sessionHidden = isCold ? !expandedIds.has(sessionExpandedKey) : expandedIds.has(sessionCollapsedKey);
|
|
2170
|
+
if (sessionHidden) return;
|
|
1750
2171
|
if (expandedIds.has(session.id)) {
|
|
1751
2172
|
result.push(...session.subAgents);
|
|
1752
2173
|
} else {
|
|
@@ -1764,27 +2185,47 @@ function appendSubAgentRows(result, session, expandedIds) {
|
|
|
1764
2185
|
}
|
|
1765
2186
|
function flattenSessions2(tree, expandedIds) {
|
|
1766
2187
|
const result = [];
|
|
1767
|
-
const
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
2188
|
+
const projectToFlat = (project, isCold) => {
|
|
2189
|
+
const sentinelId = `__proj-${project.name}__`;
|
|
2190
|
+
result.push({
|
|
2191
|
+
id: sentinelId,
|
|
2192
|
+
hideKey: "",
|
|
2193
|
+
filePath: "",
|
|
2194
|
+
projectPath: project.projectPath,
|
|
2195
|
+
projectName: project.name,
|
|
2196
|
+
lastModifiedMs: 0,
|
|
2197
|
+
status: project.hotness,
|
|
2198
|
+
modelName: null,
|
|
2199
|
+
subAgents: [],
|
|
2200
|
+
nonInteractive: false
|
|
2201
|
+
});
|
|
2202
|
+
const shouldShowSessions = isCold ? expandedIds.has(`__expanded-${sentinelId}`) : !expandedIds.has(`__collapsed-${sentinelId}`);
|
|
2203
|
+
if (shouldShowSessions) {
|
|
2204
|
+
for (const session of project.sessions) {
|
|
2205
|
+
result.push(session);
|
|
2206
|
+
appendSubAgentRows(result, session, expandedIds);
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
};
|
|
2210
|
+
for (const project of tree.projects) {
|
|
2211
|
+
projectToFlat(project, false);
|
|
1772
2212
|
}
|
|
1773
|
-
if (
|
|
2213
|
+
if (tree.coldProjects.length > 0) {
|
|
1774
2214
|
result.push({
|
|
1775
2215
|
id: "__cold__",
|
|
2216
|
+
hideKey: "",
|
|
1776
2217
|
filePath: "",
|
|
1777
2218
|
projectPath: "",
|
|
1778
|
-
projectName: `${
|
|
2219
|
+
projectName: `${tree.coldProjects.length} cold`,
|
|
1779
2220
|
lastModifiedMs: 0,
|
|
1780
2221
|
status: "cold",
|
|
1781
2222
|
modelName: null,
|
|
1782
|
-
subAgents: []
|
|
2223
|
+
subAgents: [],
|
|
2224
|
+
nonInteractive: false
|
|
1783
2225
|
});
|
|
1784
2226
|
if (expandedIds.has("__cold__")) {
|
|
1785
|
-
for (const
|
|
1786
|
-
|
|
1787
|
-
appendSubAgentRows(result, s, expandedIds);
|
|
2227
|
+
for (const project of tree.coldProjects) {
|
|
2228
|
+
projectToFlat(project, true);
|
|
1788
2229
|
}
|
|
1789
2230
|
}
|
|
1790
2231
|
}
|
|
@@ -1813,8 +2254,9 @@ function App({ mode }) {
|
|
|
1813
2254
|
() => discoverSessions(config)
|
|
1814
2255
|
);
|
|
1815
2256
|
const [selectedId, setSelectedId] = useState2(() => {
|
|
1816
|
-
const
|
|
1817
|
-
|
|
2257
|
+
const firstProject = sessionTree.projects[0];
|
|
2258
|
+
if (firstProject) return `__proj-${firstProject.name}__`;
|
|
2259
|
+
return null;
|
|
1818
2260
|
});
|
|
1819
2261
|
const [focus, setFocus] = useState2("tree");
|
|
1820
2262
|
const [scrollOffset, setScrollOffset] = useState2(0);
|
|
@@ -1830,6 +2272,7 @@ function App({ mode }) {
|
|
|
1830
2272
|
);
|
|
1831
2273
|
const [detailScrollOffset, setDetailScrollOffset] = useState2(0);
|
|
1832
2274
|
const [filterIndex, setFilterIndex] = useState2(0);
|
|
2275
|
+
const [helpMode, setHelpMode] = useState2(false);
|
|
1833
2276
|
const allFlat = useMemo(
|
|
1834
2277
|
() => flattenSessions2(sessionTree, expandedIds),
|
|
1835
2278
|
[sessionTree, expandedIds]
|
|
@@ -1844,19 +2287,35 @@ function App({ mode }) {
|
|
|
1844
2287
|
activitiesLengthRef.current = activities.length;
|
|
1845
2288
|
activitiesRef.current = activities;
|
|
1846
2289
|
}, [activities]);
|
|
2290
|
+
const lastLoadedFileRef = useRef(null);
|
|
1847
2291
|
useEffect2(() => {
|
|
1848
|
-
|
|
2292
|
+
let node = allFlatRef.current.find((s) => s.id === selectedId);
|
|
2293
|
+
if (node && selectedId?.startsWith("__proj-") && selectedId.endsWith("__")) {
|
|
2294
|
+
const projectName = selectedId.slice(7, -2);
|
|
2295
|
+
const project = sessionTree.projects.find((p) => p.name === projectName) ?? sessionTree.coldProjects.find((p) => p.name === projectName);
|
|
2296
|
+
if (project && project.sessions.length > 0) {
|
|
2297
|
+
node = project.sessions[0];
|
|
2298
|
+
} else {
|
|
2299
|
+
node = void 0;
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
const newFile = node?.filePath ?? null;
|
|
2303
|
+
const fileChanged = lastLoadedFileRef.current !== newFile;
|
|
2304
|
+
lastLoadedFileRef.current = newFile;
|
|
1849
2305
|
if (node?.filePath) {
|
|
1850
2306
|
setActivities(parseSessionHistory(node.filePath));
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
2307
|
+
if (fileChanged) {
|
|
2308
|
+
setScrollOffset(0);
|
|
2309
|
+
setIsLive(true);
|
|
2310
|
+
setNewCount(0);
|
|
2311
|
+
setViewerCursorLine(0);
|
|
2312
|
+
setGitActivities([]);
|
|
2313
|
+
}
|
|
1855
2314
|
} else {
|
|
1856
2315
|
setActivities([]);
|
|
2316
|
+
if (fileChanged) setGitActivities([]);
|
|
1857
2317
|
}
|
|
1858
|
-
|
|
1859
|
-
}, [selectedId]);
|
|
2318
|
+
}, [selectedId, sessionTree]);
|
|
1860
2319
|
useEffect2(() => {
|
|
1861
2320
|
setScrollOffset(0);
|
|
1862
2321
|
setIsLive(true);
|
|
@@ -1900,7 +2359,8 @@ function App({ mode }) {
|
|
|
1900
2359
|
const updatedFlat = flattenSessions2(tree, expandedIds);
|
|
1901
2360
|
const node = updatedFlat.find((s) => s.id === selectedId);
|
|
1902
2361
|
if (!node) {
|
|
1903
|
-
const
|
|
2362
|
+
const allSessions = tree.projects?.flatMap((p) => p.sessions) ?? [];
|
|
2363
|
+
const parentSession = allSessions.find(
|
|
1904
2364
|
(s) => s.subAgents.some((sa) => sa.id === selectedId)
|
|
1905
2365
|
);
|
|
1906
2366
|
if (parentSession) setSelectedId(parentSession.id);
|
|
@@ -1995,6 +2455,8 @@ function App({ mode }) {
|
|
|
1995
2455
|
const { handleInput, statusBarItems } = useHotkeys({
|
|
1996
2456
|
focus,
|
|
1997
2457
|
detailMode,
|
|
2458
|
+
helpMode,
|
|
2459
|
+
onHelp: () => setHelpMode((m) => !m),
|
|
1998
2460
|
onSwitchFocus: () => setFocus((f) => f === "tree" ? "viewer" : "tree"),
|
|
1999
2461
|
onScrollUp: () => {
|
|
2000
2462
|
if (focus === "tree") {
|
|
@@ -2137,6 +2599,23 @@ function App({ mode }) {
|
|
|
2137
2599
|
return;
|
|
2138
2600
|
}
|
|
2139
2601
|
if (focus !== "tree" || !selectedId) return;
|
|
2602
|
+
if (selectedId.startsWith("__proj-") && selectedId.endsWith("__")) {
|
|
2603
|
+
const projectName = selectedId.slice(7, -2);
|
|
2604
|
+
const isCold = sessionTree.coldProjects.some(
|
|
2605
|
+
(p) => p.name === projectName
|
|
2606
|
+
);
|
|
2607
|
+
const toggleKey = isCold ? `__expanded-${selectedId}` : `__collapsed-${selectedId}`;
|
|
2608
|
+
setExpandedIds((prev) => {
|
|
2609
|
+
const next = new Set(prev);
|
|
2610
|
+
if (next.has(toggleKey)) {
|
|
2611
|
+
next.delete(toggleKey);
|
|
2612
|
+
} else {
|
|
2613
|
+
next.add(toggleKey);
|
|
2614
|
+
}
|
|
2615
|
+
return next;
|
|
2616
|
+
});
|
|
2617
|
+
return;
|
|
2618
|
+
}
|
|
2140
2619
|
if (selectedId === "__cold__") {
|
|
2141
2620
|
setExpandedIds((prev) => {
|
|
2142
2621
|
const next = new Set(prev);
|
|
@@ -2158,7 +2637,8 @@ function App({ mode }) {
|
|
|
2158
2637
|
setSelectedId(parentId);
|
|
2159
2638
|
} else {
|
|
2160
2639
|
next.add(parentId);
|
|
2161
|
-
const
|
|
2640
|
+
const allSessions2 = sessionTree.projects?.flatMap((p) => p.sessions) ?? [];
|
|
2641
|
+
const parent = allSessions2.find((s) => s.id === parentId);
|
|
2162
2642
|
const firstNew = parent?.subAgents.find(
|
|
2163
2643
|
(sa) => sa.status === "cool" || sa.status === "cold"
|
|
2164
2644
|
);
|
|
@@ -2168,38 +2648,51 @@ function App({ mode }) {
|
|
|
2168
2648
|
});
|
|
2169
2649
|
return;
|
|
2170
2650
|
}
|
|
2171
|
-
const
|
|
2172
|
-
(
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
)
|
|
2651
|
+
const allSessions3 = [
|
|
2652
|
+
...sessionTree.projects.flatMap((p) => p.sessions),
|
|
2653
|
+
...sessionTree.coldProjects.flatMap((p) => p.sessions)
|
|
2654
|
+
];
|
|
2655
|
+
const selectedSessionObj = allSessions3.find((s) => s.id === selectedId);
|
|
2656
|
+
if (selectedSessionObj && selectedSessionObj.subAgents.length > 0) {
|
|
2657
|
+
const isCold = selectedSessionObj.status === "cold";
|
|
2658
|
+
const toggleKey = isCold ? `__expanded-session-${selectedId}` : `__collapsed-session-${selectedId}`;
|
|
2659
|
+
setExpandedIds((prev) => {
|
|
2660
|
+
const next = new Set(prev);
|
|
2661
|
+
if (next.has(toggleKey)) {
|
|
2662
|
+
next.delete(toggleKey);
|
|
2663
|
+
if (!isCold) setSelectedId(selectedId);
|
|
2664
|
+
} else {
|
|
2665
|
+
next.add(toggleKey);
|
|
2666
|
+
if (isCold) {
|
|
2667
|
+
const firstSub = selectedSessionObj.subAgents[0];
|
|
2668
|
+
if (firstSub) setSelectedId(firstSub.id);
|
|
2669
|
+
}
|
|
2670
|
+
}
|
|
2671
|
+
return next;
|
|
2672
|
+
});
|
|
2177
2673
|
return;
|
|
2178
|
-
|
|
2179
|
-
const next = new Set(prev);
|
|
2180
|
-
if (next.has(selectedId)) {
|
|
2181
|
-
next.delete(selectedId);
|
|
2182
|
-
} else {
|
|
2183
|
-
next.add(selectedId);
|
|
2184
|
-
}
|
|
2185
|
-
return next;
|
|
2186
|
-
});
|
|
2674
|
+
}
|
|
2187
2675
|
},
|
|
2188
2676
|
onHide: () => {
|
|
2189
2677
|
if (focus !== "tree" || !selectedId) return;
|
|
2678
|
+
if (selectedId.startsWith("__proj-") && selectedId.endsWith("__")) {
|
|
2679
|
+
const projectName = selectedId.slice(7, -2);
|
|
2680
|
+
hideProject(projectName);
|
|
2681
|
+
refresh();
|
|
2682
|
+
const nextId = allFlat[selectedIndex + 1]?.id ?? allFlat[selectedIndex - 1]?.id ?? null;
|
|
2683
|
+
setSelectedId(nextId);
|
|
2684
|
+
return;
|
|
2685
|
+
}
|
|
2190
2686
|
if (selectedId === "__cold__") {
|
|
2191
|
-
const coldSessions = sessionTree.sessions
|
|
2192
|
-
(s) => s.status === "cold"
|
|
2193
|
-
);
|
|
2687
|
+
const coldSessions = sessionTree.coldProjects?.flatMap((p) => p.sessions) ?? [];
|
|
2194
2688
|
for (const s of coldSessions) hideSession(s.hideKey);
|
|
2195
2689
|
const nextId = allFlat[selectedIndex - 1]?.id ?? null;
|
|
2196
2690
|
refresh();
|
|
2197
2691
|
setSelectedId(nextId);
|
|
2198
2692
|
return;
|
|
2199
2693
|
}
|
|
2200
|
-
const
|
|
2201
|
-
|
|
2202
|
-
);
|
|
2694
|
+
const allSessions4 = sessionTree.projects?.flatMap((p) => p.sessions) ?? [];
|
|
2695
|
+
const selectedSession2 = allSessions4.find((s) => s.id === selectedId);
|
|
2203
2696
|
if (selectedSession2) {
|
|
2204
2697
|
hideSession(selectedSession2.hideKey);
|
|
2205
2698
|
const nextId = allFlat[selectedIndex + 1]?.id ?? allFlat[selectedIndex - 1]?.id ?? null;
|
|
@@ -2207,7 +2700,7 @@ function App({ mode }) {
|
|
|
2207
2700
|
setSelectedId(nextId);
|
|
2208
2701
|
return;
|
|
2209
2702
|
}
|
|
2210
|
-
for (const s of
|
|
2703
|
+
for (const s of allSessions4) {
|
|
2211
2704
|
const selectedSubAgent = s.subAgents.find((sa) => sa.id === selectedId);
|
|
2212
2705
|
if (selectedSubAgent) {
|
|
2213
2706
|
hideSubAgent(selectedSubAgent.hideKey);
|
|
@@ -2225,55 +2718,67 @@ function App({ mode }) {
|
|
|
2225
2718
|
filterLabel
|
|
2226
2719
|
});
|
|
2227
2720
|
useInput((input, key) => handleInput(input, key), { isActive: isWatchMode });
|
|
2228
|
-
const
|
|
2721
|
+
const rawSelected = allFlat.find((s) => s.id === selectedId);
|
|
2722
|
+
const isProjectSentinel = !!selectedId && selectedId.startsWith("__proj-") && selectedId.endsWith("__");
|
|
2723
|
+
let selectedSession = rawSelected;
|
|
2724
|
+
if (isProjectSentinel && selectedId) {
|
|
2725
|
+
const projectName = selectedId.slice(7, -2);
|
|
2726
|
+
const project = sessionTree.projects.find((p) => p.name === projectName) ?? sessionTree.coldProjects.find((p) => p.name === projectName);
|
|
2727
|
+
if (project && project.sessions.length > 0) {
|
|
2728
|
+
selectedSession = project.sessions[0];
|
|
2729
|
+
}
|
|
2730
|
+
}
|
|
2229
2731
|
const isPlaceholderSelected = !selectedSession || selectedId === "__cold__" || !!selectedId && selectedId.startsWith("__sub-") && selectedId.endsWith("__");
|
|
2230
2732
|
const sessionDisplayName = isPlaceholderSelected ? "No session selected" : selectedSession.projectPath ? selectedSession.projectName || selectedSession.id.slice(0, 8) : selectedSession.agentId ?? selectedSession.id.slice(0, 8);
|
|
2231
|
-
return /* @__PURE__ */
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
/* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
|
|
2733
|
+
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
|
|
2734
|
+
isWatchMode && /* @__PURE__ */ jsxs5(Box5, { marginBottom: 1, justifyContent: "space-between", width, children: [
|
|
2735
|
+
/* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
|
|
2235
2736
|
spinner,
|
|
2236
2737
|
" AgentHUD v",
|
|
2237
2738
|
getVersion()
|
|
2238
2739
|
] }),
|
|
2239
|
-
/* @__PURE__ */
|
|
2740
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: statusBarItems.join(" \xB7 ") })
|
|
2240
2741
|
] }),
|
|
2241
|
-
/* @__PURE__ */
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2742
|
+
helpMode ? /* @__PURE__ */ jsx5(HelpPanel, { width, height: height - 2 }) : /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
2743
|
+
migrationWarning && /* @__PURE__ */ jsx5(Box5, { marginBottom: 1, children: /* @__PURE__ */ jsx5(Text5, { color: "yellow", children: "Config moved to ~/.agenthud/config.yaml" }) }),
|
|
2744
|
+
/* @__PURE__ */ jsx5(
|
|
2745
|
+
SessionTreePanel,
|
|
2746
|
+
{
|
|
2747
|
+
projects: sessionTree.projects ?? [],
|
|
2748
|
+
coldProjects: sessionTree.coldProjects ?? [],
|
|
2749
|
+
selectedId,
|
|
2750
|
+
hasFocus: focus === "tree",
|
|
2751
|
+
width,
|
|
2752
|
+
maxRows: treeRows,
|
|
2753
|
+
expandedIds
|
|
2754
|
+
}
|
|
2755
|
+
),
|
|
2756
|
+
/* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: detailMode && detailActivity ? /* @__PURE__ */ jsx5(
|
|
2757
|
+
DetailViewPanel,
|
|
2758
|
+
{
|
|
2759
|
+
activity: detailActivity,
|
|
2760
|
+
sessionName: sessionDisplayName,
|
|
2761
|
+
scrollOffset: detailScrollOffset,
|
|
2762
|
+
visibleRows: viewerRows,
|
|
2763
|
+
width
|
|
2764
|
+
}
|
|
2765
|
+
) : /* @__PURE__ */ jsx5(
|
|
2766
|
+
ActivityViewerPanel,
|
|
2767
|
+
{
|
|
2768
|
+
activities: mergedActivities,
|
|
2769
|
+
sessionName: sessionDisplayName,
|
|
2770
|
+
scrollOffset,
|
|
2771
|
+
isLive,
|
|
2772
|
+
newCount,
|
|
2773
|
+
visibleRows: viewerRows,
|
|
2774
|
+
width,
|
|
2775
|
+
cursorLine: viewerCursorLine,
|
|
2776
|
+
hasFocus: focus === "viewer",
|
|
2777
|
+
spinner,
|
|
2778
|
+
filterLabel
|
|
2779
|
+
}
|
|
2780
|
+
) })
|
|
2781
|
+
] })
|
|
2277
2782
|
] });
|
|
2278
2783
|
}
|
|
2279
2784
|
|