aoaoe 0.88.0 → 0.90.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
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { execSync } from "node:child_process";
2
3
  import { loadConfig, validateEnvironment, parseCliArgs, printHelp, configFileExists, findConfigFile, DEFAULTS, computeConfigDiff } from "./config.js";
3
4
  import { Poller, computeTmuxName } from "./poller.js";
4
5
  import { createReasoner } from "./reasoner/index.js";
@@ -15,7 +16,7 @@ import { wakeableSleep } from "./wake.js";
15
16
  import { classifyMessages, formatUserMessages, buildReceipts, shouldSkipSleep, hasPendingFile, isInsistMessage, stripInsistPrefix } from "./message.js";
16
17
  import { TaskManager, loadTaskDefinitions, loadTaskState, formatTaskTable } from "./task-manager.js";
17
18
  import { runTaskCli, handleTaskSlashCommand } from "./task-cli.js";
18
- import { TUI, hitTestSession, nextSortMode, SORT_MODES, formatUptime } from "./tui.js";
19
+ import { TUI, hitTestSession, nextSortMode, SORT_MODES, formatUptime, formatClipText } from "./tui.js";
19
20
  import { isDaemonRunningFromState } from "./chat.js";
20
21
  import { sendNotification, sendTestNotification } from "./notify.js";
21
22
  import { startHealthServer } from "./health.js";
@@ -480,6 +481,12 @@ async function main() {
480
481
  }
481
482
  }
482
483
  });
484
+ // wire /auto-pin toggle
485
+ input.onAutoPin(() => {
486
+ const enabled = !tui.isAutoPinEnabled();
487
+ tui.setAutoPin(enabled);
488
+ tui.log("system", `auto-pin on error: ${enabled ? "on" : "off"}`);
489
+ });
483
490
  // wire /note set/clear
484
491
  input.onNote((target, text) => {
485
492
  const num = /^\d+$/.test(target) ? parseInt(target, 10) : undefined;
@@ -511,6 +518,30 @@ async function main() {
511
518
  }
512
519
  }
513
520
  });
521
+ // wire /clip to export activity entries to clipboard or file
522
+ input.onClip((count) => {
523
+ const buffer = tui.getActivityBuffer();
524
+ if (buffer.length === 0) {
525
+ tui.log("system", "no activity to clip");
526
+ return;
527
+ }
528
+ const text = formatClipText(buffer, count);
529
+ const entryCount = Math.min(count, buffer.length);
530
+ try {
531
+ execSync("pbcopy", { input: text, timeout: 5000 });
532
+ tui.log("system", `copied ${entryCount} entries to clipboard`);
533
+ }
534
+ catch {
535
+ try {
536
+ const clipPath = join(homedir(), ".aoaoe", "clip.txt");
537
+ writeFileSync(clipPath, text, "utf-8");
538
+ tui.log("system", `saved ${entryCount} entries to ~/.aoaoe/clip.txt`);
539
+ }
540
+ catch (writeErr) {
541
+ tui.log("error", `clip failed: ${writeErr}`);
542
+ }
543
+ }
544
+ });
514
545
  // wire mouse move to hover highlight on session cards (disabled in compact)
515
546
  input.onMouseMove((row, _col) => {
516
547
  if (tui.getViewMode() === "overview" && !tui.isCompact()) {
package/dist/input.d.ts CHANGED
@@ -15,8 +15,10 @@ export type MuteHandler = (target: string) => void;
15
15
  export type UnmuteAllHandler = () => void;
16
16
  export type TagFilterHandler = (tag: string | null) => void;
17
17
  export type UptimeHandler = () => void;
18
+ export type AutoPinHandler = () => void;
18
19
  export type NoteHandler = (target: string, text: string) => void;
19
20
  export type NotesHandler = () => void;
21
+ export type ClipHandler = (count: number) => void;
20
22
  export interface MouseEvent {
21
23
  button: number;
22
24
  col: number;
@@ -54,8 +56,10 @@ export declare class InputReader {
54
56
  private unmuteAllHandler;
55
57
  private tagFilterHandler;
56
58
  private uptimeHandler;
59
+ private autoPinHandler;
57
60
  private noteHandler;
58
61
  private notesHandler;
62
+ private clipHandler;
59
63
  private mouseDataListener;
60
64
  onScroll(handler: (dir: ScrollDirection) => void): void;
61
65
  onQueueChange(handler: (count: number) => void): void;
@@ -77,8 +81,10 @@ export declare class InputReader {
77
81
  onUnmuteAll(handler: UnmuteAllHandler): void;
78
82
  onTagFilter(handler: TagFilterHandler): void;
79
83
  onUptime(handler: UptimeHandler): void;
84
+ onAutoPin(handler: AutoPinHandler): void;
80
85
  onNote(handler: NoteHandler): void;
81
86
  onNotes(handler: NotesHandler): void;
87
+ onClip(handler: ClipHandler): void;
82
88
  private notifyQueueChange;
83
89
  start(): void;
84
90
  drain(): string[];
package/dist/input.js CHANGED
@@ -47,8 +47,10 @@ export class InputReader {
47
47
  unmuteAllHandler = null;
48
48
  tagFilterHandler = null;
49
49
  uptimeHandler = null;
50
+ autoPinHandler = null;
50
51
  noteHandler = null;
51
52
  notesHandler = null;
53
+ clipHandler = null;
52
54
  mouseDataListener = null;
53
55
  // register a callback for scroll key events (PgUp/PgDn/Home/End)
54
56
  onScroll(handler) {
@@ -130,6 +132,10 @@ export class InputReader {
130
132
  onUptime(handler) {
131
133
  this.uptimeHandler = handler;
132
134
  }
135
+ // register a callback for auto-pin toggle (/auto-pin)
136
+ onAutoPin(handler) {
137
+ this.autoPinHandler = handler;
138
+ }
133
139
  // register a callback for note commands (/note <target> <text>)
134
140
  onNote(handler) {
135
141
  this.noteHandler = handler;
@@ -138,6 +144,10 @@ export class InputReader {
138
144
  onNotes(handler) {
139
145
  this.notesHandler = handler;
140
146
  }
147
+ // register a callback for clipboard export (/clip [N])
148
+ onClip(handler) {
149
+ this.clipHandler = handler;
150
+ }
141
151
  notifyQueueChange() {
142
152
  this.queueChangeHandler?.(this.queue.length);
143
153
  }
@@ -324,8 +334,10 @@ ${BOLD}navigation:${RESET}
324
334
  /unmute-all unmute all sessions at once
325
335
  /filter [tag] filter activity by tag (error, system, etc. — no arg = clear)
326
336
  /uptime show session uptimes (time since first observed)
337
+ /auto-pin toggle auto-pin on error (pin sessions that emit errors)
327
338
  /note N|name text attach a note to a session (no text = clear)
328
339
  /notes list all session notes
340
+ /clip [N] copy last N activity entries to clipboard (default 20)
329
341
  /mark bookmark current activity position
330
342
  /jump N jump to bookmark N
331
343
  /marks list all bookmarks
@@ -498,6 +510,14 @@ ${BOLD}other:${RESET}
498
510
  console.error(`${DIM}uptime not available (no TUI)${RESET}`);
499
511
  }
500
512
  break;
513
+ case "/auto-pin":
514
+ if (this.autoPinHandler) {
515
+ this.autoPinHandler();
516
+ }
517
+ else {
518
+ console.error(`${DIM}auto-pin not available (no TUI)${RESET}`);
519
+ }
520
+ break;
501
521
  case "/note": {
502
522
  const noteArg = line.slice("/note".length).trim();
503
523
  if (this.noteHandler) {
@@ -529,6 +549,20 @@ ${BOLD}other:${RESET}
529
549
  console.error(`${DIM}notes not available (no TUI)${RESET}`);
530
550
  }
531
551
  break;
552
+ case "/clip": {
553
+ const clipArg = line.slice("/clip".length).trim();
554
+ const clipCount = clipArg ? parseInt(clipArg, 10) : 20;
555
+ if (this.clipHandler && !isNaN(clipCount) && clipCount > 0) {
556
+ this.clipHandler(clipCount);
557
+ }
558
+ else if (!this.clipHandler) {
559
+ console.error(`${DIM}clip not available (no TUI)${RESET}`);
560
+ }
561
+ else {
562
+ console.error(`${DIM}usage: /clip [N] — copy last N activity entries to clipboard${RESET}`);
563
+ }
564
+ break;
565
+ }
532
566
  case "/mark":
533
567
  if (this.markHandler) {
534
568
  this.markHandler();
package/dist/tui.d.ts CHANGED
@@ -57,6 +57,12 @@ export declare function formatMuteBadge(count: number): string;
57
57
  export declare function matchesTagFilter(entry: ActivityEntry, tag: string): boolean;
58
58
  /** Format the tag filter indicator text for the separator bar. */
59
59
  export declare function formatTagFilterIndicator(tag: string, matchCount: number, totalCount: number): string;
60
+ /** Default number of entries for /clip when no count specified. */
61
+ export declare const CLIP_DEFAULT_COUNT = 20;
62
+ /** Format activity entries as plain text for clipboard/export. One line per entry. */
63
+ export declare function formatClipText(entries: readonly ActivityEntry[], n?: number): string;
64
+ /** Determine if a log entry should trigger auto-pin (error-like tags). */
65
+ export declare function shouldAutoPin(tag: string): boolean;
60
66
  /** Format milliseconds as human-readable uptime: "2h 15m", "45m", "3d 2h", "< 1m". */
61
67
  export declare function formatUptime(ms: number): string;
62
68
  export declare class TUI {
@@ -93,6 +99,7 @@ export declare class TUI {
93
99
  private mutedEntryCounts;
94
100
  private sessionNotes;
95
101
  private sessionFirstSeen;
102
+ private autoPinOnError;
96
103
  private viewMode;
97
104
  private drilldownSessionId;
98
105
  private sessionOutputs;
@@ -137,6 +144,10 @@ export declare class TUI {
137
144
  setBell(enabled: boolean): void;
138
145
  /** Return whether terminal bell is enabled. */
139
146
  isBellEnabled(): boolean;
147
+ /** Enable or disable auto-pin on error. */
148
+ setAutoPin(enabled: boolean): void;
149
+ /** Return whether auto-pin on error is enabled. */
150
+ isAutoPinEnabled(): boolean;
140
151
  /**
141
152
  * Toggle mute for a session (by 1-indexed number, ID, ID prefix, or title).
142
153
  * Muted sessions' activity entries are hidden from the log (still buffered + persisted).
@@ -168,6 +179,8 @@ export declare class TUI {
168
179
  getUptime(id: string): number;
169
180
  /** Return all session first-seen timestamps (for /uptime listing). */
170
181
  getAllFirstSeen(): ReadonlyMap<string, number>;
182
+ /** Return the activity buffer (for /clip export). */
183
+ getActivityBuffer(): readonly ActivityEntry[];
171
184
  /**
172
185
  * Add a bookmark at the current activity position.
173
186
  * Returns the bookmark number (1-indexed) or 0 if buffer is empty.
package/dist/tui.js CHANGED
@@ -191,6 +191,21 @@ export function matchesTagFilter(entry, tag) {
191
191
  export function formatTagFilterIndicator(tag, matchCount, totalCount) {
192
192
  return `${SLATE}filter:${RESET} ${AMBER}${tag}${RESET} ${DIM}(${matchCount}/${totalCount})${RESET}`;
193
193
  }
194
+ // ── Clip ─────────────────────────────────────────────────────────────────────
195
+ /** Default number of entries for /clip when no count specified. */
196
+ export const CLIP_DEFAULT_COUNT = 20;
197
+ /** Format activity entries as plain text for clipboard/export. One line per entry. */
198
+ export function formatClipText(entries, n) {
199
+ const count = n ?? CLIP_DEFAULT_COUNT;
200
+ const slice = entries.slice(-Math.max(1, count));
201
+ return slice.map((e) => `[${e.time}] ${e.tag}: ${e.text}`).join("\n") + "\n";
202
+ }
203
+ // ── Auto-pin ─────────────────────────────────────────────────────────────────
204
+ /** Determine if a log entry should trigger auto-pin (error-like tags). */
205
+ export function shouldAutoPin(tag) {
206
+ const lower = tag.toLowerCase();
207
+ return lower === "! action" || lower === "error";
208
+ }
194
209
  // ── Uptime ───────────────────────────────────────────────────────────────────
195
210
  /** Format milliseconds as human-readable uptime: "2h 15m", "45m", "3d 2h", "< 1m". */
196
211
  export function formatUptime(ms) {
@@ -243,6 +258,7 @@ export class TUI {
243
258
  mutedEntryCounts = new Map(); // session ID → suppressed entry count since mute
244
259
  sessionNotes = new Map(); // session ID → note text
245
260
  sessionFirstSeen = new Map(); // session ID → epoch ms when first observed
261
+ autoPinOnError = false; // auto-pin sessions that emit errors
246
262
  // drill-down mode: show a single session's full output
247
263
  viewMode = "overview";
248
264
  drilldownSessionId = null;
@@ -398,6 +414,14 @@ export class TUI {
398
414
  isBellEnabled() {
399
415
  return this.bellEnabled;
400
416
  }
417
+ /** Enable or disable auto-pin on error. */
418
+ setAutoPin(enabled) {
419
+ this.autoPinOnError = enabled;
420
+ }
421
+ /** Return whether auto-pin on error is enabled. */
422
+ isAutoPinEnabled() {
423
+ return this.autoPinOnError;
424
+ }
401
425
  /**
402
426
  * Toggle mute for a session (by 1-indexed number, ID, ID prefix, or title).
403
427
  * Muted sessions' activity entries are hidden from the log (still buffered + persisted).
@@ -512,6 +536,10 @@ export class TUI {
512
536
  getAllFirstSeen() {
513
537
  return this.sessionFirstSeen;
514
538
  }
539
+ /** Return the activity buffer (for /clip export). */
540
+ getActivityBuffer() {
541
+ return this.activityBuffer;
542
+ }
515
543
  /**
516
544
  * Add a bookmark at the current activity position.
517
545
  * Returns the bookmark number (1-indexed) or 0 if buffer is empty.
@@ -626,6 +654,12 @@ export class TUI {
626
654
  process.stderr.write("\x07");
627
655
  }
628
656
  }
657
+ // auto-pin sessions that emit errors (when enabled)
658
+ if (this.autoPinOnError && sessionId && shouldAutoPin(tag) && !this.pinnedIds.has(sessionId)) {
659
+ this.pinnedIds.add(sessionId);
660
+ if (this.active)
661
+ this.paintSessions();
662
+ }
629
663
  // track suppressed entry counts regardless of active state (for badge accuracy)
630
664
  if (shouldMuteEntry(entry, this.mutedIds) && entry.sessionId) {
631
665
  this.mutedEntryCounts.set(entry.sessionId, (this.mutedEntryCounts.get(entry.sessionId) ?? 0) + 1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aoaoe",
3
- "version": "0.88.0",
3
+ "version": "0.90.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",