aoaoe 0.77.0 → 0.78.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/dist/index.js CHANGED
@@ -15,7 +15,7 @@ import { wakeableSleep } from "./wake.js";
15
15
  import { classifyMessages, formatUserMessages, buildReceipts, shouldSkipSleep, hasPendingFile, isInsistMessage, stripInsistPrefix } from "./message.js";
16
16
  import { TaskManager, loadTaskDefinitions, loadTaskState, formatTaskTable } from "./task-manager.js";
17
17
  import { runTaskCli, handleTaskSlashCommand } from "./task-cli.js";
18
- import { TUI, hitTestSession } from "./tui.js";
18
+ import { TUI, hitTestSession, nextSortMode, SORT_MODES } from "./tui.js";
19
19
  import { isDaemonRunningFromState } from "./chat.js";
20
20
  import { sendNotification, sendTestNotification } from "./notify.js";
21
21
  import { startHealthServer } from "./health.js";
@@ -354,6 +354,21 @@ async function main() {
354
354
  tui.log("system", "search cleared");
355
355
  }
356
356
  });
357
+ // wire /sort command to TUI session sort
358
+ input.onSort((mode) => {
359
+ if (mode && SORT_MODES.includes(mode)) {
360
+ tui.setSortMode(mode);
361
+ tui.log("system", `sort: ${mode}`);
362
+ }
363
+ else if (!mode) {
364
+ const next = nextSortMode(tui.getSortMode());
365
+ tui.setSortMode(next);
366
+ tui.log("system", `sort: ${next}`);
367
+ }
368
+ else {
369
+ tui.log("system", `unknown sort mode: ${mode} (try: status, name, activity, default)`);
370
+ }
371
+ });
357
372
  // wire mouse move to hover highlight on session cards
358
373
  input.onMouseMove((row, _col) => {
359
374
  if (tui.getViewMode() === "overview") {
package/dist/input.d.ts CHANGED
@@ -3,6 +3,7 @@ export declare const INSIST_PREFIX = "__INSIST__";
3
3
  export type ViewHandler = (target: string | null) => void;
4
4
  export type SearchHandler = (pattern: string | null) => void;
5
5
  export type QuickSwitchHandler = (sessionNum: number) => void;
6
+ export type SortHandler = (mode: string | null) => void;
6
7
  export interface MouseEvent {
7
8
  button: number;
8
9
  col: number;
@@ -28,6 +29,7 @@ export declare class InputReader {
28
29
  private lastMoveRow;
29
30
  private searchHandler;
30
31
  private quickSwitchHandler;
32
+ private sortHandler;
31
33
  private mouseDataListener;
32
34
  onScroll(handler: (dir: ScrollDirection) => void): void;
33
35
  onQueueChange(handler: (count: number) => void): void;
@@ -37,6 +39,7 @@ export declare class InputReader {
37
39
  onMouseMove(handler: MouseMoveHandler): void;
38
40
  onSearch(handler: SearchHandler): void;
39
41
  onQuickSwitch(handler: QuickSwitchHandler): void;
42
+ onSort(handler: SortHandler): void;
40
43
  private notifyQueueChange;
41
44
  start(): void;
42
45
  drain(): string[];
package/dist/input.js CHANGED
@@ -35,6 +35,7 @@ export class InputReader {
35
35
  lastMoveRow = 0; // debounce: only fire move handler when row changes
36
36
  searchHandler = null;
37
37
  quickSwitchHandler = null;
38
+ sortHandler = null;
38
39
  mouseDataListener = null;
39
40
  // register a callback for scroll key events (PgUp/PgDn/Home/End)
40
41
  onScroll(handler) {
@@ -68,6 +69,10 @@ export class InputReader {
68
69
  onQuickSwitch(handler) {
69
70
  this.quickSwitchHandler = handler;
70
71
  }
72
+ // register a callback for sort commands (/sort <mode> or /sort to cycle)
73
+ onSort(handler) {
74
+ this.sortHandler = handler;
75
+ }
71
76
  notifyQueueChange() {
72
77
  this.queueChangeHandler?.(this.queue.length);
73
78
  }
@@ -245,6 +250,7 @@ ${BOLD}navigation:${RESET}
245
250
  1-9 quick-switch: jump to session N (type digit + Enter)
246
251
  /view [N|name] drill into a session's live output (default: 1)
247
252
  /back return to overview from drill-down
253
+ /sort [mode] sort sessions: status, name, activity, default (or cycle)
248
254
  /search <pattern> filter activity entries by substring (case-insensitive)
249
255
  /search clear active search filter
250
256
  click session click an agent card to drill down (click again to go back)
@@ -324,6 +330,16 @@ ${BOLD}other:${RESET}
324
330
  console.error(`${DIM}already in overview${RESET}`);
325
331
  }
326
332
  break;
333
+ case "/sort": {
334
+ const sortArg = line.slice("/sort".length).trim().toLowerCase();
335
+ if (this.sortHandler) {
336
+ this.sortHandler(sortArg || null); // empty = cycle to next mode
337
+ }
338
+ else {
339
+ console.error(`${DIM}sort not available (no TUI)${RESET}`);
340
+ }
341
+ break;
342
+ }
327
343
  case "/search": {
328
344
  const searchArg = line.slice("/search".length).trim();
329
345
  if (this.searchHandler) {
package/dist/tui.d.ts CHANGED
@@ -1,5 +1,11 @@
1
1
  import type { DaemonSessionState, DaemonPhase } from "./types.js";
2
2
  import type { HistoryEntry } from "./tui-history.js";
3
+ export type SortMode = "default" | "status" | "name" | "activity";
4
+ declare const SORT_MODES: SortMode[];
5
+ /** Sort sessions by mode. Returns a new array (never mutates). */
6
+ declare function sortSessions(sessions: DaemonSessionState[], mode: SortMode, lastChangeAt?: Map<string, number>): DaemonSessionState[];
7
+ /** Cycle to the next sort mode. */
8
+ declare function nextSortMode(current: SortMode): SortMode;
3
9
  declare function phaseDisplay(phase: DaemonPhase, paused: boolean, spinnerFrame: number): string;
4
10
  export interface ActivityEntry {
5
11
  time: string;
@@ -26,6 +32,9 @@ export declare class TUI {
26
32
  private searchPattern;
27
33
  private hoverSessionIdx;
28
34
  private activityTimestamps;
35
+ private sortMode;
36
+ private lastChangeAt;
37
+ private prevLastActivity;
29
38
  private viewMode;
30
39
  private drilldownSessionId;
31
40
  private sessionOutputs;
@@ -43,6 +52,10 @@ export declare class TUI {
43
52
  isActive(): boolean;
44
53
  /** Return the current number of sessions (for mouse hit testing) */
45
54
  getSessionCount(): number;
55
+ /** Set the session sort mode and repaint. */
56
+ setSortMode(mode: SortMode): void;
57
+ /** Return the current sort mode. */
58
+ getSortMode(): SortMode;
46
59
  updateState(opts: {
47
60
  phase?: DaemonPhase;
48
61
  pollCount?: number;
@@ -133,5 +146,5 @@ declare function formatSearchIndicator(pattern: string, matchCount: number, tota
133
146
  * (row = headerHeight + 2 + i for 0-indexed session i)
134
147
  */
135
148
  export declare function hitTestSession(row: number, headerHeight: number, sessionCount: number): number | null;
136
- export { formatActivity, formatSessionCard, truncateAnsi, truncatePlain, padBoxLine, padBoxLineHover, padToWidth, stripAnsiForLen, phaseDisplay, computeScrollSlice, formatScrollIndicator, formatDrilldownScrollIndicator, formatPrompt, formatDrilldownHeader, matchesSearch, formatSearchIndicator, computeSparkline, formatSparkline };
149
+ export { formatActivity, formatSessionCard, truncateAnsi, truncatePlain, padBoxLine, padBoxLineHover, padToWidth, stripAnsiForLen, phaseDisplay, computeScrollSlice, formatScrollIndicator, formatDrilldownScrollIndicator, formatPrompt, formatDrilldownHeader, matchesSearch, formatSearchIndicator, computeSparkline, formatSparkline, sortSessions, nextSortMode, SORT_MODES };
137
150
  //# sourceMappingURL=tui.d.ts.map
package/dist/tui.js CHANGED
@@ -21,6 +21,33 @@ const MOUSE_OFF = `${CSI}?1003l${CSI}?1006l`;
21
21
  const moveTo = (row, col) => `${CSI}${row};${col}H`;
22
22
  const setScrollRegion = (top, bottom) => `${CSI}${top};${bottom}r`;
23
23
  const resetScrollRegion = () => `${CSI}r`;
24
+ const SORT_MODES = ["default", "status", "name", "activity"];
25
+ const STATUS_PRIORITY = {
26
+ error: 0, waiting: 1, working: 2, running: 2,
27
+ idle: 3, done: 4, stopped: 5, unknown: 6,
28
+ };
29
+ /** Sort sessions by mode. Returns a new array (never mutates). */
30
+ function sortSessions(sessions, mode, lastChangeAt) {
31
+ const copy = sessions.slice();
32
+ switch (mode) {
33
+ case "status":
34
+ copy.sort((a, b) => (STATUS_PRIORITY[a.status] ?? 6) - (STATUS_PRIORITY[b.status] ?? 6));
35
+ break;
36
+ case "name":
37
+ copy.sort((a, b) => a.title.toLowerCase().localeCompare(b.title.toLowerCase()));
38
+ break;
39
+ case "activity":
40
+ copy.sort((a, b) => (lastChangeAt?.get(b.id) ?? 0) - (lastChangeAt?.get(a.id) ?? 0));
41
+ break;
42
+ // "default" — preserve original order
43
+ }
44
+ return copy;
45
+ }
46
+ /** Cycle to the next sort mode. */
47
+ function nextSortMode(current) {
48
+ const idx = SORT_MODES.indexOf(current);
49
+ return SORT_MODES[(idx + 1) % SORT_MODES.length];
50
+ }
24
51
  // ── Status rendering ────────────────────────────────────────────────────────
25
52
  const STATUS_DOT = {
26
53
  working: `${LIME}${DOT.filled}${RESET}`,
@@ -66,6 +93,9 @@ export class TUI {
66
93
  searchPattern = null; // active search filter pattern
67
94
  hoverSessionIdx = null; // 1-indexed session under mouse cursor (null = none)
68
95
  activityTimestamps = []; // epoch ms of each log() call for sparkline
96
+ sortMode = "default";
97
+ lastChangeAt = new Map(); // session ID → epoch ms of last activity change
98
+ prevLastActivity = new Map(); // session ID → previous lastActivity string
69
99
  // drill-down mode: show a single session's full output
70
100
  viewMode = "overview";
71
101
  drilldownSessionId = null;
@@ -122,6 +152,21 @@ export class TUI {
122
152
  getSessionCount() {
123
153
  return this.sessions.length;
124
154
  }
155
+ /** Set the session sort mode and repaint. */
156
+ setSortMode(mode) {
157
+ if (mode === this.sortMode)
158
+ return;
159
+ this.sortMode = mode;
160
+ // re-sort current sessions and repaint
161
+ this.sessions = sortSessions(this.sessions, this.sortMode, this.lastChangeAt);
162
+ if (this.active) {
163
+ this.paintSessions();
164
+ }
165
+ }
166
+ /** Return the current sort mode. */
167
+ getSortMode() {
168
+ return this.sortMode;
169
+ }
125
170
  // ── State updates ───────────────────────────────────────────────────────
126
171
  updateState(opts) {
127
172
  if (opts.phase !== undefined)
@@ -137,8 +182,19 @@ export class TUI {
137
182
  if (opts.pendingCount !== undefined)
138
183
  this.pendingCount = opts.pendingCount;
139
184
  if (opts.sessions !== undefined) {
140
- const sessionCountChanged = opts.sessions.length !== this.sessions.length;
141
- this.sessions = opts.sessions;
185
+ // track activity changes for sort-by-activity
186
+ const now = Date.now();
187
+ for (const s of opts.sessions) {
188
+ const prev = this.prevLastActivity.get(s.id);
189
+ if (s.lastActivity !== undefined && s.lastActivity !== prev) {
190
+ this.lastChangeAt.set(s.id, now);
191
+ }
192
+ if (s.lastActivity !== undefined)
193
+ this.prevLastActivity.set(s.id, s.lastActivity);
194
+ }
195
+ const sorted = sortSessions(opts.sessions, this.sortMode, this.lastChangeAt);
196
+ const sessionCountChanged = sorted.length !== this.sessions.length;
197
+ this.sessions = sorted;
142
198
  if (sessionCountChanged) {
143
199
  this.computeLayout(this.sessions.length);
144
200
  this.paintAll();
@@ -464,8 +520,8 @@ export class TUI {
464
520
  paintSessions() {
465
521
  const startRow = this.headerHeight + 1;
466
522
  const innerWidth = this.cols - 2; // inside the box borders
467
- // top border with label
468
- const label = " agents ";
523
+ // top border with label (includes sort mode when not default)
524
+ const label = this.sortMode === "default" ? " agents " : ` agents (${this.sortMode}) `;
469
525
  const borderAfterLabel = Math.max(0, innerWidth - label.length);
470
526
  const topBorder = `${SLATE}${BOX.rtl}${BOX.h}${RESET}${SLATE}${label}${RESET}${SLATE}${BOX.h.repeat(borderAfterLabel)}${BOX.rtr}${RESET}`;
471
527
  process.stderr.write(SAVE_CURSOR + moveTo(startRow, 1) + CLEAR_LINE + truncateAnsi(topBorder, this.cols));
@@ -875,5 +931,5 @@ export function hitTestSession(row, headerHeight, sessionCount) {
875
931
  return row - firstSessionRow + 1; // 1-indexed
876
932
  }
877
933
  // ── Exported pure helpers (for testing) ─────────────────────────────────────
878
- export { formatActivity, formatSessionCard, truncateAnsi, truncatePlain, padBoxLine, padBoxLineHover, padToWidth, stripAnsiForLen, phaseDisplay, computeScrollSlice, formatScrollIndicator, formatDrilldownScrollIndicator, formatPrompt, formatDrilldownHeader, matchesSearch, formatSearchIndicator, computeSparkline, formatSparkline };
934
+ export { formatActivity, formatSessionCard, truncateAnsi, truncatePlain, padBoxLine, padBoxLineHover, padToWidth, stripAnsiForLen, phaseDisplay, computeScrollSlice, formatScrollIndicator, formatDrilldownScrollIndicator, formatPrompt, formatDrilldownHeader, matchesSearch, formatSearchIndicator, computeSparkline, formatSparkline, sortSessions, nextSortMode, SORT_MODES };
879
935
  //# sourceMappingURL=tui.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aoaoe",
3
- "version": "0.77.0",
3
+ "version": "0.78.0",
4
4
  "description": "Autonomous supervisor for agent-of-empires sessions using OpenCode or Claude Code",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",