aoaoe 0.65.0 → 0.66.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/config.js +3 -1
- package/dist/index.js +16 -1
- package/dist/input.d.ts +5 -0
- package/dist/input.js +45 -2
- package/dist/message.d.ts +13 -0
- package/dist/message.js +17 -0
- package/dist/tui.d.ts +4 -1
- package/dist/tui.js +15 -7
- package/package.json +1 -1
package/dist/config.js
CHANGED
|
@@ -592,6 +592,7 @@ example config:
|
|
|
592
592
|
interactive commands (while daemon is running):
|
|
593
593
|
/help show available commands
|
|
594
594
|
/explain ask the AI to explain what's happening in plain English
|
|
595
|
+
/insist <msg> interrupt + deliver message immediately (skip queue)
|
|
595
596
|
/status request daemon status
|
|
596
597
|
/dashboard request full dashboard output
|
|
597
598
|
/pause pause the daemon
|
|
@@ -600,6 +601,7 @@ interactive commands (while daemon is running):
|
|
|
600
601
|
/verbose toggle verbose logging
|
|
601
602
|
/clear clear the screen
|
|
602
603
|
ESC ESC interrupt the current reasoner (shortcut)
|
|
603
|
-
|
|
604
|
+
!message insist shortcut — same as /insist message
|
|
605
|
+
(anything) send a message to the AI — queued for next cycle`);
|
|
604
606
|
}
|
|
605
607
|
//# sourceMappingURL=config.js.map
|
package/dist/index.js
CHANGED
|
@@ -12,7 +12,7 @@ import { loadGlobalContext, resolveProjectDirWithSource, discoverContextFiles, l
|
|
|
12
12
|
import { tick as loopTick } from "./loop.js";
|
|
13
13
|
import { exec as shellExec } from "./shell.js";
|
|
14
14
|
import { wakeableSleep } from "./wake.js";
|
|
15
|
-
import { classifyMessages, formatUserMessages, buildReceipts, shouldSkipSleep, hasPendingFile } from "./message.js";
|
|
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
18
|
import { TUI } from "./tui.js";
|
|
@@ -253,6 +253,10 @@ async function main() {
|
|
|
253
253
|
break;
|
|
254
254
|
}
|
|
255
255
|
});
|
|
256
|
+
// wire queue count changes to TUI prompt display
|
|
257
|
+
input.onQueueChange((count) => {
|
|
258
|
+
tui.updateState({ pendingCount: count });
|
|
259
|
+
});
|
|
256
260
|
}
|
|
257
261
|
// start TUI (alternate screen buffer) after input is ready
|
|
258
262
|
if (tui) {
|
|
@@ -393,6 +397,17 @@ async function main() {
|
|
|
393
397
|
const allMessages = [...stdinMessages, ...consoleMessages];
|
|
394
398
|
// classify into commands vs. real user messages
|
|
395
399
|
const { commands, userMessages } = classifyMessages(allMessages);
|
|
400
|
+
// strip insist prefix from priority messages and log them distinctly
|
|
401
|
+
for (let i = 0; i < userMessages.length; i++) {
|
|
402
|
+
if (isInsistMessage(userMessages[i])) {
|
|
403
|
+
const raw = stripInsistPrefix(userMessages[i]);
|
|
404
|
+
userMessages[i] = raw;
|
|
405
|
+
if (tui)
|
|
406
|
+
tui.log("you", `! ${raw}`);
|
|
407
|
+
else
|
|
408
|
+
log(`[insist] ${raw}`);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
396
411
|
// auto-explain on first tick: inject an explain prompt so the AI introduces itself
|
|
397
412
|
if (autoExplainPending && pollCount === 1) {
|
|
398
413
|
const autoExplainPrompt = "This is your first observation. Please briefly introduce what you see: " +
|
package/dist/input.d.ts
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
export type ScrollDirection = "up" | "down" | "top" | "bottom";
|
|
2
|
+
export declare const INSIST_PREFIX = "__INSIST__";
|
|
2
3
|
export declare class InputReader {
|
|
3
4
|
private rl;
|
|
4
5
|
private queue;
|
|
5
6
|
private paused;
|
|
6
7
|
private lastEscTime;
|
|
7
8
|
private scrollHandler;
|
|
9
|
+
private queueChangeHandler;
|
|
8
10
|
onScroll(handler: (dir: ScrollDirection) => void): void;
|
|
11
|
+
onQueueChange(handler: (count: number) => void): void;
|
|
12
|
+
private notifyQueueChange;
|
|
9
13
|
start(): void;
|
|
10
14
|
drain(): string[];
|
|
11
15
|
isPaused(): boolean;
|
|
@@ -14,6 +18,7 @@ export declare class InputReader {
|
|
|
14
18
|
prompt(): void;
|
|
15
19
|
stop(): void;
|
|
16
20
|
private handleEscInterrupt;
|
|
21
|
+
private handleInsist;
|
|
17
22
|
private handleLine;
|
|
18
23
|
private handleCommand;
|
|
19
24
|
}
|
package/dist/input.js
CHANGED
|
@@ -6,16 +6,25 @@ import { requestInterrupt } from "./daemon-state.js";
|
|
|
6
6
|
import { GREEN, DIM, YELLOW, RED, BOLD, RESET } from "./colors.js";
|
|
7
7
|
// ESC-ESC interrupt detection
|
|
8
8
|
const ESC_DOUBLE_TAP_MS = 500;
|
|
9
|
+
export const INSIST_PREFIX = "__INSIST__";
|
|
9
10
|
export class InputReader {
|
|
10
11
|
rl = null;
|
|
11
12
|
queue = []; // pending user messages for the reasoner
|
|
12
13
|
paused = false;
|
|
13
14
|
lastEscTime = 0;
|
|
14
15
|
scrollHandler = null;
|
|
16
|
+
queueChangeHandler = null;
|
|
15
17
|
// register a callback for scroll key events (PgUp/PgDn/Home/End)
|
|
16
18
|
onScroll(handler) {
|
|
17
19
|
this.scrollHandler = handler;
|
|
18
20
|
}
|
|
21
|
+
// register a callback for queue size changes (for TUI pending count display)
|
|
22
|
+
onQueueChange(handler) {
|
|
23
|
+
this.queueChangeHandler = handler;
|
|
24
|
+
}
|
|
25
|
+
notifyQueueChange() {
|
|
26
|
+
this.queueChangeHandler?.(this.queue.length);
|
|
27
|
+
}
|
|
19
28
|
start() {
|
|
20
29
|
// only works if stdin is a TTY (not piped)
|
|
21
30
|
if (!process.stdin.isTTY)
|
|
@@ -67,6 +76,8 @@ export class InputReader {
|
|
|
67
76
|
// drain all pending user messages (called each tick)
|
|
68
77
|
drain() {
|
|
69
78
|
const msgs = this.queue.splice(0);
|
|
79
|
+
if (msgs.length > 0)
|
|
80
|
+
this.notifyQueueChange();
|
|
70
81
|
return msgs;
|
|
71
82
|
}
|
|
72
83
|
isPaused() {
|
|
@@ -79,6 +90,7 @@ export class InputReader {
|
|
|
79
90
|
// inject a message directly into the queue (used after interrupt to feed text into next tick)
|
|
80
91
|
inject(msg) {
|
|
81
92
|
this.queue.push(msg);
|
|
93
|
+
this.notifyQueueChange();
|
|
82
94
|
}
|
|
83
95
|
// re-show the prompt (called after daemon prints output)
|
|
84
96
|
prompt() {
|
|
@@ -91,10 +103,18 @@ export class InputReader {
|
|
|
91
103
|
handleEscInterrupt() {
|
|
92
104
|
requestInterrupt();
|
|
93
105
|
this.queue.push("__CMD_INTERRUPT__");
|
|
106
|
+
this.notifyQueueChange();
|
|
94
107
|
console.error(`\n${RED}${BOLD}>>> interrupting reasoner <<<${RESET}`);
|
|
95
108
|
console.error(`${YELLOW}type your message now -- it will be sent before the next cycle${RESET}`);
|
|
96
109
|
this.rl?.prompt(true);
|
|
97
110
|
}
|
|
111
|
+
handleInsist(msg) {
|
|
112
|
+
requestInterrupt();
|
|
113
|
+
this.queue.push("__CMD_INTERRUPT__");
|
|
114
|
+
this.queue.push(`${INSIST_PREFIX}${msg}`);
|
|
115
|
+
this.notifyQueueChange();
|
|
116
|
+
console.error(`${RED}${BOLD}!${RESET} ${GREEN}insist${RESET} ${DIM}— interrupting + delivering your message immediately${RESET}`);
|
|
117
|
+
}
|
|
98
118
|
handleLine(line) {
|
|
99
119
|
if (!line) {
|
|
100
120
|
this.rl?.prompt();
|
|
@@ -106,9 +126,20 @@ export class InputReader {
|
|
|
106
126
|
this.rl?.prompt();
|
|
107
127
|
return;
|
|
108
128
|
}
|
|
129
|
+
// ! prefix = insist mode: interrupt + priority message
|
|
130
|
+
if (line.startsWith("!") && line.length > 1) {
|
|
131
|
+
const msg = line.slice(1).trim();
|
|
132
|
+
if (msg) {
|
|
133
|
+
this.handleInsist(msg);
|
|
134
|
+
this.rl?.prompt();
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
109
138
|
// queue as a user message for the reasoner
|
|
110
139
|
this.queue.push(line);
|
|
111
|
-
|
|
140
|
+
this.notifyQueueChange();
|
|
141
|
+
const pending = this.queue.filter(m => !m.startsWith("__CMD_")).length;
|
|
142
|
+
console.error(`${GREEN}queued${RESET} ${DIM}(${pending} pending) — will be read next cycle${RESET}`);
|
|
112
143
|
this.rl?.prompt();
|
|
113
144
|
}
|
|
114
145
|
handleCommand(line) {
|
|
@@ -117,7 +148,9 @@ export class InputReader {
|
|
|
117
148
|
case "/help":
|
|
118
149
|
console.error(`
|
|
119
150
|
${BOLD}talking to the AI:${RESET}
|
|
120
|
-
just type send a message
|
|
151
|
+
just type send a message — queued for next cycle
|
|
152
|
+
!message insist — interrupt + deliver message immediately
|
|
153
|
+
/insist <msg> same as !message
|
|
121
154
|
/explain ask the AI to explain what's happening right now
|
|
122
155
|
|
|
123
156
|
${BOLD}controls:${RESET}
|
|
@@ -163,6 +196,16 @@ ${BOLD}other:${RESET}
|
|
|
163
196
|
case "/interrupt":
|
|
164
197
|
this.handleEscInterrupt();
|
|
165
198
|
break;
|
|
199
|
+
case "/insist": {
|
|
200
|
+
const insistMsg = line.slice("/insist".length).trim();
|
|
201
|
+
if (insistMsg) {
|
|
202
|
+
this.handleInsist(insistMsg);
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
console.error(`${DIM}usage: /insist <message> — interrupts and delivers your message immediately${RESET}`);
|
|
206
|
+
}
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
166
209
|
case "/tasks":
|
|
167
210
|
this.queue.push("__CMD_TASK__list");
|
|
168
211
|
break;
|
package/dist/message.d.ts
CHANGED
|
@@ -36,4 +36,17 @@ export declare function shouldSkipSleep(state: {
|
|
|
36
36
|
* Lightweight stat-only check — does not read or drain the file.
|
|
37
37
|
*/
|
|
38
38
|
export declare function hasPendingFile(filePath: string): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Insist prefix marker — messages prefixed with this bypass the normal queue
|
|
41
|
+
* and trigger an immediate interrupt + delivery.
|
|
42
|
+
*/
|
|
43
|
+
export declare const INSIST_PREFIX = "__INSIST__";
|
|
44
|
+
/**
|
|
45
|
+
* Check if a message is an insist (priority) message.
|
|
46
|
+
*/
|
|
47
|
+
export declare function isInsistMessage(msg: string): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Strip the insist prefix from a message, returning the raw user text.
|
|
50
|
+
*/
|
|
51
|
+
export declare function stripInsistPrefix(msg: string): string;
|
|
39
52
|
//# sourceMappingURL=message.d.ts.map
|
package/dist/message.js
CHANGED
|
@@ -73,6 +73,23 @@ export function hasPendingFile(filePath) {
|
|
|
73
73
|
return false;
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* Insist prefix marker — messages prefixed with this bypass the normal queue
|
|
78
|
+
* and trigger an immediate interrupt + delivery.
|
|
79
|
+
*/
|
|
80
|
+
export const INSIST_PREFIX = "__INSIST__";
|
|
81
|
+
/**
|
|
82
|
+
* Check if a message is an insist (priority) message.
|
|
83
|
+
*/
|
|
84
|
+
export function isInsistMessage(msg) {
|
|
85
|
+
return msg.startsWith(INSIST_PREFIX);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Strip the insist prefix from a message, returning the raw user text.
|
|
89
|
+
*/
|
|
90
|
+
export function stripInsistPrefix(msg) {
|
|
91
|
+
return msg.startsWith(INSIST_PREFIX) ? msg.slice(INSIST_PREFIX.length) : msg;
|
|
92
|
+
}
|
|
76
93
|
function truncate(s, max) {
|
|
77
94
|
return s.length <= max ? s : s.slice(0, max - 3) + "...";
|
|
78
95
|
}
|
package/dist/tui.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ export declare class TUI {
|
|
|
22
22
|
private spinnerFrame;
|
|
23
23
|
private scrollOffset;
|
|
24
24
|
private newWhileScrolled;
|
|
25
|
+
private pendingCount;
|
|
25
26
|
private phase;
|
|
26
27
|
private pollCount;
|
|
27
28
|
private sessions;
|
|
@@ -39,6 +40,7 @@ export declare class TUI {
|
|
|
39
40
|
paused?: boolean;
|
|
40
41
|
reasonerName?: string;
|
|
41
42
|
nextTickAt?: number;
|
|
43
|
+
pendingCount?: number;
|
|
42
44
|
}): void;
|
|
43
45
|
log(tag: string, text: string): void;
|
|
44
46
|
replayHistory(entries: HistoryEntry[]): void;
|
|
@@ -70,10 +72,11 @@ declare function truncatePlain(str: string, max: number): string;
|
|
|
70
72
|
* Kept for backward compatibility — used by non-TUI output paths.
|
|
71
73
|
*/
|
|
72
74
|
export declare function formatSessionSentence(s: DaemonSessionState, maxCols: number): string;
|
|
75
|
+
declare function formatPrompt(phase: DaemonPhase, paused: boolean, pendingCount: number): string;
|
|
73
76
|
declare function computeScrollSlice(bufferLen: number, visibleLines: number, scrollOffset: number): {
|
|
74
77
|
start: number;
|
|
75
78
|
end: number;
|
|
76
79
|
};
|
|
77
80
|
declare function formatScrollIndicator(offset: number, totalEntries: number, visibleLines: number, newCount: number): string;
|
|
78
|
-
export { formatActivity, formatSessionCard, truncateAnsi, truncatePlain, padBoxLine, padToWidth, stripAnsiForLen, phaseDisplay, computeScrollSlice, formatScrollIndicator };
|
|
81
|
+
export { formatActivity, formatSessionCard, truncateAnsi, truncatePlain, padBoxLine, padToWidth, stripAnsiForLen, phaseDisplay, computeScrollSlice, formatScrollIndicator, formatPrompt };
|
|
79
82
|
//# sourceMappingURL=tui.d.ts.map
|
package/dist/tui.js
CHANGED
|
@@ -57,6 +57,7 @@ export class TUI {
|
|
|
57
57
|
spinnerFrame = 0; // current spinner animation frame
|
|
58
58
|
scrollOffset = 0; // 0 = live (bottom), >0 = scrolled back N entries
|
|
59
59
|
newWhileScrolled = 0; // entries added while user is scrolled back
|
|
60
|
+
pendingCount = 0; // queued user messages awaiting next tick
|
|
60
61
|
// current state for repaints
|
|
61
62
|
phase = "sleeping";
|
|
62
63
|
pollCount = 0;
|
|
@@ -115,6 +116,8 @@ export class TUI {
|
|
|
115
116
|
this.reasonerName = opts.reasonerName;
|
|
116
117
|
if (opts.nextTickAt !== undefined)
|
|
117
118
|
this.nextTickAt = opts.nextTickAt;
|
|
119
|
+
if (opts.pendingCount !== undefined)
|
|
120
|
+
this.pendingCount = opts.pendingCount;
|
|
118
121
|
if (opts.sessions !== undefined) {
|
|
119
122
|
const sessionCountChanged = opts.sessions.length !== this.sessions.length;
|
|
120
123
|
this.sessions = opts.sessions;
|
|
@@ -337,12 +340,7 @@ export class TUI {
|
|
|
337
340
|
}
|
|
338
341
|
}
|
|
339
342
|
paintInputLine() {
|
|
340
|
-
|
|
341
|
-
const prompt = this.paused
|
|
342
|
-
? `${AMBER}${BOLD}paused >${RESET} `
|
|
343
|
-
: this.phase === "reasoning"
|
|
344
|
-
? `${SKY}thinking >${RESET} `
|
|
345
|
-
: `${LIME}>${RESET} `;
|
|
343
|
+
const prompt = formatPrompt(this.phase, this.paused, this.pendingCount);
|
|
346
344
|
process.stderr.write(SAVE_CURSOR +
|
|
347
345
|
moveTo(this.inputRow, 1) + CLEAR_LINE + prompt +
|
|
348
346
|
RESTORE_CURSOR);
|
|
@@ -490,6 +488,16 @@ export function formatSessionSentence(s, maxCols) {
|
|
|
490
488
|
}
|
|
491
489
|
return truncateAnsi(`${dot} ${BOLD}${name}${RESET} ${tool} ${SLATE}—${RESET} ${statusDesc}`, maxCols);
|
|
492
490
|
}
|
|
491
|
+
// ── Prompt helpers (pure, exported for testing) ─────────────────────────────
|
|
492
|
+
// format the input prompt based on phase, pause state, and pending queue count
|
|
493
|
+
function formatPrompt(phase, paused, pendingCount) {
|
|
494
|
+
const queueTag = pendingCount > 0 ? `${AMBER}${pendingCount} queued${RESET} ` : "";
|
|
495
|
+
if (paused)
|
|
496
|
+
return `${queueTag}${AMBER}${BOLD}paused >${RESET} `;
|
|
497
|
+
if (phase === "reasoning")
|
|
498
|
+
return `${queueTag}${SKY}thinking >${RESET} `;
|
|
499
|
+
return `${queueTag}${LIME}>${RESET} `;
|
|
500
|
+
}
|
|
493
501
|
// ── Scroll helpers (pure, exported for testing) ─────────────────────────────
|
|
494
502
|
// compute the slice indices for the activity buffer given scroll state
|
|
495
503
|
function computeScrollSlice(bufferLen, visibleLines, scrollOffset) {
|
|
@@ -504,5 +512,5 @@ function formatScrollIndicator(offset, totalEntries, visibleLines, newCount) {
|
|
|
504
512
|
return ` ↑ ${offset} older │ ${position}/${totalEntries} │ PgUp/PgDn End=live${newTag} `;
|
|
505
513
|
}
|
|
506
514
|
// ── Exported pure helpers (for testing) ─────────────────────────────────────
|
|
507
|
-
export { formatActivity, formatSessionCard, truncateAnsi, truncatePlain, padBoxLine, padToWidth, stripAnsiForLen, phaseDisplay, computeScrollSlice, formatScrollIndicator };
|
|
515
|
+
export { formatActivity, formatSessionCard, truncateAnsi, truncatePlain, padBoxLine, padToWidth, stripAnsiForLen, phaseDisplay, computeScrollSlice, formatScrollIndicator, formatPrompt };
|
|
508
516
|
//# sourceMappingURL=tui.js.map
|