@addai/node 0.4.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 -0
- package/dist/agent-auth.d.ts +9 -0
- package/dist/agent-auth.js +96 -0
- package/dist/attachments.d.ts +20 -0
- package/dist/attachments.js +164 -0
- package/dist/capabilities.d.ts +64 -0
- package/dist/capabilities.js +406 -0
- package/dist/claude-binary.d.ts +16 -0
- package/dist/claude-binary.js +145 -0
- package/dist/claude-config.d.ts +11 -0
- package/dist/claude-config.js +144 -0
- package/dist/claude-print.d.ts +35 -0
- package/dist/claude-print.js +312 -0
- package/dist/claude-spawn.d.ts +40 -0
- package/dist/claude-spawn.js +220 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +137 -0
- package/dist/codex-binary.d.ts +4 -0
- package/dist/codex-binary.js +70 -0
- package/dist/codex-spawn.d.ts +47 -0
- package/dist/codex-spawn.js +320 -0
- package/dist/command-runner.d.ts +9 -0
- package/dist/command-runner.js +592 -0
- package/dist/config.d.ts +7 -0
- package/dist/config.js +38 -0
- package/dist/control-server.d.ts +5 -0
- package/dist/control-server.js +292 -0
- package/dist/diskguard.d.ts +96 -0
- package/dist/diskguard.js +435 -0
- package/dist/error-classify.d.ts +20 -0
- package/dist/error-classify.js +159 -0
- package/dist/events.d.ts +26 -0
- package/dist/events.js +97 -0
- package/dist/gemini-binary.d.ts +4 -0
- package/dist/gemini-binary.js +64 -0
- package/dist/gemini-spawn.d.ts +52 -0
- package/dist/gemini-spawn.js +369 -0
- package/dist/git-identity.d.ts +18 -0
- package/dist/git-identity.js +104 -0
- package/dist/grok-binary.d.ts +4 -0
- package/dist/grok-binary.js +70 -0
- package/dist/grok-spawn.d.ts +41 -0
- package/dist/grok-spawn.js +495 -0
- package/dist/guardrails.d.ts +32 -0
- package/dist/guardrails.js +125 -0
- package/dist/hang-watchdog.d.ts +17 -0
- package/dist/hang-watchdog.js +48 -0
- package/dist/harness-registry.d.ts +55 -0
- package/dist/harness-registry.js +180 -0
- package/dist/heartbeat.d.ts +13 -0
- package/dist/heartbeat.js +95 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +440 -0
- package/dist/install.d.ts +63 -0
- package/dist/install.js +467 -0
- package/dist/jsonl-tail.d.ts +20 -0
- package/dist/jsonl-tail.js +192 -0
- package/dist/kimi-binary.d.ts +4 -0
- package/dist/kimi-binary.js +68 -0
- package/dist/kimi-spawn.d.ts +47 -0
- package/dist/kimi-spawn.js +319 -0
- package/dist/lockfile.d.ts +30 -0
- package/dist/lockfile.js +169 -0
- package/dist/mcp-config.d.ts +23 -0
- package/dist/mcp-config.js +133 -0
- package/dist/memory-capture.d.ts +20 -0
- package/dist/memory-capture.js +45 -0
- package/dist/memory-pack.d.ts +34 -0
- package/dist/memory-pack.js +171 -0
- package/dist/model-filter.d.ts +4 -0
- package/dist/model-filter.js +34 -0
- package/dist/node-health.d.ts +18 -0
- package/dist/node-health.js +74 -0
- package/dist/pairing.d.ts +20 -0
- package/dist/pairing.js +129 -0
- package/dist/paths.d.ts +9 -0
- package/dist/paths.js +48 -0
- package/dist/precompact-hook.d.ts +27 -0
- package/dist/precompact-hook.js +136 -0
- package/dist/projects.d.ts +25 -0
- package/dist/projects.js +255 -0
- package/dist/prompt-keystrokes.d.ts +3 -0
- package/dist/prompt-keystrokes.js +56 -0
- package/dist/pty-helper.d.ts +26 -0
- package/dist/pty-helper.js +156 -0
- package/dist/request-pump.d.ts +12 -0
- package/dist/request-pump.js +249 -0
- package/dist/run-retry.d.ts +28 -0
- package/dist/run-retry.js +81 -0
- package/dist/self-update.d.ts +75 -0
- package/dist/self-update.js +192 -0
- package/dist/session-runner.d.ts +130 -0
- package/dist/session-runner.js +2271 -0
- package/dist/sleep-detector.d.ts +11 -0
- package/dist/sleep-detector.js +32 -0
- package/dist/state-migrate.d.ts +8 -0
- package/dist/state-migrate.js +91 -0
- package/dist/store.d.ts +5 -0
- package/dist/store.js +82 -0
- package/dist/supabase-client.d.ts +15 -0
- package/dist/supabase-client.js +119 -0
- package/dist/time-context.d.ts +10 -0
- package/dist/time-context.js +71 -0
- package/dist/tokens.d.ts +4 -0
- package/dist/tokens.js +33 -0
- package/dist/tui/app.d.ts +35 -0
- package/dist/tui/app.js +82 -0
- package/dist/tui/console-capture.d.ts +13 -0
- package/dist/tui/console-capture.js +62 -0
- package/dist/tui/dashboard.d.ts +42 -0
- package/dist/tui/dashboard.js +205 -0
- package/dist/tui/data.d.ts +57 -0
- package/dist/tui/data.js +62 -0
- package/dist/tui/harnesses.d.ts +16 -0
- package/dist/tui/harnesses.js +354 -0
- package/dist/tui/render.d.ts +21 -0
- package/dist/tui/render.js +116 -0
- package/dist/tui/requests.d.ts +18 -0
- package/dist/tui/requests.js +127 -0
- package/dist/tui/run.d.ts +13 -0
- package/dist/tui/run.js +178 -0
- package/dist/tui/transcript.d.ts +39 -0
- package/dist/tui/transcript.js +234 -0
- package/dist/tui.d.ts +1 -0
- package/dist/tui.js +314 -0
- package/dist/types.d.ts +65 -0
- package/dist/types.js +3 -0
- package/dist/win.d.ts +67 -0
- package/dist/win.js +317 -0
- package/package.json +60 -0
- package/scripts/fix-pty-helper.js +28 -0
- package/scripts/precompact-capture.js +292 -0
- package/scripts/smoke-test.sh +74 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// While the console UI owns the terminal, the daemon must not write to it.
|
|
3
|
+
//
|
|
4
|
+
// `ainode` with no args runs the daemon IN THIS PROCESS and then paints a
|
|
5
|
+
// full-screen UI over the same stdout. The daemon logs continuously —
|
|
6
|
+
// request-pump backoffs, sleep/wake detection, session-runner chatter — and
|
|
7
|
+
// every one of those lines lands in the middle of a frame, so the screen
|
|
8
|
+
// appears to flicker between the UI and log output.
|
|
9
|
+
//
|
|
10
|
+
// Capturing rather than silencing: the lines are kept in a ring buffer, the
|
|
11
|
+
// newest is surfaced in the UI, and the whole buffer is replayed to the real
|
|
12
|
+
// stdout on exit, so running the daemon under the dashboard loses nothing
|
|
13
|
+
// you would have seen without it.
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.captureConsole = captureConsole;
|
|
16
|
+
const MAX_LINES = 500;
|
|
17
|
+
function captureConsole() {
|
|
18
|
+
const ring = [];
|
|
19
|
+
const original = {
|
|
20
|
+
log: console.log.bind(console),
|
|
21
|
+
warn: console.warn.bind(console),
|
|
22
|
+
error: console.error.bind(console),
|
|
23
|
+
};
|
|
24
|
+
const format = (args) => args.map(a => {
|
|
25
|
+
if (typeof a === 'string')
|
|
26
|
+
return a;
|
|
27
|
+
if (a instanceof Error)
|
|
28
|
+
return a.stack ?? a.message;
|
|
29
|
+
try {
|
|
30
|
+
return JSON.stringify(a);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return String(a);
|
|
34
|
+
}
|
|
35
|
+
}).join(' ');
|
|
36
|
+
const push = (level) => (...args) => {
|
|
37
|
+
ring.push({ level, text: format(args), at: Date.now() });
|
|
38
|
+
if (ring.length > MAX_LINES)
|
|
39
|
+
ring.shift();
|
|
40
|
+
};
|
|
41
|
+
console.log = push('log');
|
|
42
|
+
console.warn = push('warn');
|
|
43
|
+
console.error = push('error');
|
|
44
|
+
return {
|
|
45
|
+
lines: () => [...ring],
|
|
46
|
+
last: () => (ring.length ? ring[ring.length - 1] : null),
|
|
47
|
+
count: () => ring.length,
|
|
48
|
+
restoreAndReplay() {
|
|
49
|
+
console.log = original.log;
|
|
50
|
+
console.warn = original.warn;
|
|
51
|
+
console.error = original.error;
|
|
52
|
+
for (const l of ring) {
|
|
53
|
+
if (l.level === 'error')
|
|
54
|
+
original.error(l.text);
|
|
55
|
+
else if (l.level === 'warn')
|
|
56
|
+
original.warn(l.text);
|
|
57
|
+
else
|
|
58
|
+
original.log(l.text);
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { DataLayer, NodeSelf, NodeStats, RequestRow } from './data';
|
|
2
|
+
import type { AppHost, Screen } from './app';
|
|
3
|
+
export interface DashboardState {
|
|
4
|
+
self: NodeSelf | null;
|
|
5
|
+
stats: NodeStats | null;
|
|
6
|
+
requests: RequestRow[];
|
|
7
|
+
sel: number;
|
|
8
|
+
spin: number;
|
|
9
|
+
pid: number | null;
|
|
10
|
+
startedAt: number | null;
|
|
11
|
+
inflight: number;
|
|
12
|
+
paired: boolean;
|
|
13
|
+
viewerMode: boolean;
|
|
14
|
+
offline: boolean;
|
|
15
|
+
now: number;
|
|
16
|
+
version: string;
|
|
17
|
+
/** Newest line the daemon logged while the UI owned the screen. */
|
|
18
|
+
lastLog: string | null;
|
|
19
|
+
logCount: number;
|
|
20
|
+
}
|
|
21
|
+
export declare const FEED_ROWS = 12;
|
|
22
|
+
/** Trigger sources are DB-shaped ('self_improve_distil'); the column is
|
|
23
|
+
* nine columns wide and "self_impr" tells you nothing. */
|
|
24
|
+
export declare function shortVia(via: string | null | undefined): string;
|
|
25
|
+
export declare function requestLine(r: RequestRow, now: number, selected: boolean, width: number): string;
|
|
26
|
+
/** The landing screen's menu. Activity and Harnesses are places you go,
|
|
27
|
+
* not things that crowd the overview. */
|
|
28
|
+
export declare const MENU: Array<{
|
|
29
|
+
key: string;
|
|
30
|
+
label: string;
|
|
31
|
+
hint: string;
|
|
32
|
+
shortcut: string;
|
|
33
|
+
}>;
|
|
34
|
+
export declare function renderDashboard(st: DashboardState, width: number): string[];
|
|
35
|
+
export declare function createDashboardScreen(deps: {
|
|
36
|
+
data: DataLayer;
|
|
37
|
+
host: AppHost;
|
|
38
|
+
state: DashboardState;
|
|
39
|
+
openTranscript(r: RequestRow): void;
|
|
40
|
+
openRequests(): void;
|
|
41
|
+
openHarnesses(): void;
|
|
42
|
+
}): Screen;
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Home screen: is this node alive, what has it been doing, what is it
|
|
3
|
+
// doing right now.
|
|
4
|
+
//
|
|
5
|
+
// Status vocabulary comes from the DB, where terminal success is
|
|
6
|
+
// 'completed' and 'canceled' is a deliberate user action — shown apart
|
|
7
|
+
// from failures so a healthy node isn't slandered by its own cancels.
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.MENU = exports.FEED_ROWS = void 0;
|
|
10
|
+
exports.shortVia = shortVia;
|
|
11
|
+
exports.requestLine = requestLine;
|
|
12
|
+
exports.renderDashboard = renderDashboard;
|
|
13
|
+
exports.createDashboardScreen = createDashboardScreen;
|
|
14
|
+
const render_1 = require("./render");
|
|
15
|
+
exports.FEED_ROWS = 12;
|
|
16
|
+
const ACTIVE = new Set(['pending', 'starting', 'running']);
|
|
17
|
+
const CANCELLED = new Set(['canceled', 'cancelled']);
|
|
18
|
+
/** Trigger sources are DB-shaped ('self_improve_distil'); the column is
|
|
19
|
+
* nine columns wide and "self_impr" tells you nothing. */
|
|
20
|
+
function shortVia(via) {
|
|
21
|
+
if (!via)
|
|
22
|
+
return '—';
|
|
23
|
+
const map = {
|
|
24
|
+
self_improve_distil: 'distil',
|
|
25
|
+
self_improve: 'improve',
|
|
26
|
+
entity_chat: 'chat',
|
|
27
|
+
chat: 'chat',
|
|
28
|
+
schedule: 'schedule',
|
|
29
|
+
flow: 'flow',
|
|
30
|
+
api: 'api',
|
|
31
|
+
studio: 'studio',
|
|
32
|
+
};
|
|
33
|
+
return map[via] ?? via;
|
|
34
|
+
}
|
|
35
|
+
function statusLabel(status) {
|
|
36
|
+
if (status === 'completed')
|
|
37
|
+
return 'DONE';
|
|
38
|
+
if (CANCELLED.has(status))
|
|
39
|
+
return 'CANCEL';
|
|
40
|
+
return status.toUpperCase();
|
|
41
|
+
}
|
|
42
|
+
function statusCell(status) {
|
|
43
|
+
const label = statusLabel(status).padEnd(8).slice(0, 8);
|
|
44
|
+
if (ACTIVE.has(status))
|
|
45
|
+
return (0, render_1.cyan)(label);
|
|
46
|
+
if (status === 'completed')
|
|
47
|
+
return (0, render_1.green)(label);
|
|
48
|
+
if (CANCELLED.has(status))
|
|
49
|
+
return (0, render_1.grey)(label);
|
|
50
|
+
return (0, render_1.red)(label);
|
|
51
|
+
}
|
|
52
|
+
function dot(status) {
|
|
53
|
+
if (ACTIVE.has(status))
|
|
54
|
+
return (0, render_1.cyan)('⏺');
|
|
55
|
+
if (status === 'completed')
|
|
56
|
+
return (0, render_1.green)('⏺');
|
|
57
|
+
if (CANCELLED.has(status))
|
|
58
|
+
return (0, render_1.grey)('⏺');
|
|
59
|
+
return (0, render_1.red)('⏺');
|
|
60
|
+
}
|
|
61
|
+
function durationOf(r, now) {
|
|
62
|
+
const start = r.picked_up_at ?? r.created_at;
|
|
63
|
+
if (!start)
|
|
64
|
+
return null;
|
|
65
|
+
const end = r.finished_at ? new Date(r.finished_at).getTime() : now;
|
|
66
|
+
return Math.max(0, end - new Date(start).getTime());
|
|
67
|
+
}
|
|
68
|
+
function requestLine(r, now, selected, width) {
|
|
69
|
+
const entity = (0, render_1.padEndVisible)((0, render_1.truncate)(r.entity_name ?? '—', 12), 12);
|
|
70
|
+
const agent = `${r.agent}/${r.mode}`.padEnd(12).slice(0, 12);
|
|
71
|
+
const model = (r.model ?? '—').replace(/^claude-/, '').padEnd(7).slice(0, 7);
|
|
72
|
+
const via = shortVia(r.issued_via).padEnd(9).slice(0, 9);
|
|
73
|
+
const dur = (0, render_1.fmtDuration)(durationOf(r, now)).padStart(7);
|
|
74
|
+
// A run that failed once and then succeeded on retry still carries the
|
|
75
|
+
// old error_code. Showing it on a DONE row reads as "this broke" when
|
|
76
|
+
// the work actually landed — so the code is only for rows that ended badly.
|
|
77
|
+
const ended_badly = r.status !== 'completed' && !ACTIVE.has(r.status);
|
|
78
|
+
const tail = r.error_code && ended_badly
|
|
79
|
+
? (0, render_1.red)(`[${r.error_code}]`)
|
|
80
|
+
: (r.prompt ?? '').replace(/\s+/g, ' ');
|
|
81
|
+
const prefix = `${dot(r.status)} ${statusCell(r.status)} ${entity} ${(0, render_1.dim)(agent)} ${(0, render_1.dim)(model)} ${(0, render_1.dim)(via)} ${dur} `;
|
|
82
|
+
const room = Math.max(8, width - 2 - (0, render_1.visibleWidth)(prefix));
|
|
83
|
+
const line = `${prefix}${(0, render_1.truncate)(tail, room)}`;
|
|
84
|
+
return selected ? `${(0, render_1.cyan)('❯')} ${line}` : ` ${line}`;
|
|
85
|
+
}
|
|
86
|
+
function headerLines(st) {
|
|
87
|
+
if (!st.paired) {
|
|
88
|
+
return [
|
|
89
|
+
(0, render_1.yellow)('not paired'),
|
|
90
|
+
(0, render_1.dim)('run `ainode` and follow the prompt to vault.add.ai/entity/connect/<CODE>'),
|
|
91
|
+
];
|
|
92
|
+
}
|
|
93
|
+
const self = st.self;
|
|
94
|
+
const name = self?.name ?? self?.hostname ?? 'this node';
|
|
95
|
+
const state = self?.effective_status === 'online'
|
|
96
|
+
? (0, render_1.green)('online')
|
|
97
|
+
: (0, render_1.yellow)(self?.effective_status ?? 'unknown');
|
|
98
|
+
const paired = self?.created_at ? `paired ${(0, render_1.fmtRelative)(self.created_at, st.now)}` : '';
|
|
99
|
+
const first = `${(0, render_1.bold)(name)} ${(0, render_1.dim)('·')} ${state} ${(0, render_1.dim)('·')} ${(0, render_1.dim)(paired)}`;
|
|
100
|
+
const up = st.startedAt ? (0, render_1.fmtDuration)(st.now - st.startedAt) : '—';
|
|
101
|
+
const daemon = st.viewerMode
|
|
102
|
+
? `${(0, render_1.cyan)('⏺')} viewer ${(0, render_1.dim)('· another ainode owns this node')} ${(0, render_1.dim)(`pid ${st.pid ?? '?'}`)}`
|
|
103
|
+
: `${(0, render_1.green)('⏺')} running ${(0, render_1.dim)(`pid ${st.pid ?? '?'}`)} ${(0, render_1.dim)(`up ${up}`)} ${(0, render_1.dim)(`${st.inflight} in flight`)}`;
|
|
104
|
+
const chip = st.offline ? ` ${(0, render_1.yellow)('⚠ offline · retrying')}` : '';
|
|
105
|
+
return [first, daemon + chip];
|
|
106
|
+
}
|
|
107
|
+
function statsLines(st) {
|
|
108
|
+
const s = st.stats;
|
|
109
|
+
if (!s)
|
|
110
|
+
return [(0, render_1.dim)('stats unavailable')];
|
|
111
|
+
const agents = Object.entries(s.by_agent)
|
|
112
|
+
.sort((a, b) => b[1] - a[1])
|
|
113
|
+
.map(([k, v]) => `${k} ${v}`)
|
|
114
|
+
.join(' · ');
|
|
115
|
+
const cancels = s.canceled_24h ? (0, render_1.dim)(` ${s.canceled_24h} cancelled`) : '';
|
|
116
|
+
// The ✓/✗/avg figures are a rolling 24h window while "Today" is the
|
|
117
|
+
// calendar day, so they are labelled — otherwise ✓3 next to Today 2
|
|
118
|
+
// looks like an arithmetic bug.
|
|
119
|
+
return [
|
|
120
|
+
`${(0, render_1.bold)(`Today ${s.today}`)} ${(0, render_1.dim)('·')} ${(0, render_1.dim)('24h')} ${(0, render_1.green)(`✓ ${s.succeeded_24h}`)} ${s.failed_24h ? (0, render_1.red)(`✗ ${s.failed_24h}`) : (0, render_1.dim)('✗ 0')}${cancels} ${(0, render_1.dim)(`avg ${(0, render_1.fmtDuration)(s.avg_duration_ms)}`)} ${(0, render_1.dim)(agents)}`,
|
|
121
|
+
(0, render_1.dim)(`Week ${s.week} Month ${s.month} Total ${s.total}`),
|
|
122
|
+
];
|
|
123
|
+
}
|
|
124
|
+
/** The landing screen's menu. Activity and Harnesses are places you go,
|
|
125
|
+
* not things that crowd the overview. */
|
|
126
|
+
exports.MENU = [
|
|
127
|
+
{ key: 'activity', label: 'Activity', hint: 'requests this node has handled', shortcut: 'a' },
|
|
128
|
+
{ key: 'harnesses', label: 'Harnesses', hint: 'install / log in agent CLIs', shortcut: 'h' },
|
|
129
|
+
];
|
|
130
|
+
function renderDashboard(st, width) {
|
|
131
|
+
const out = [];
|
|
132
|
+
out.push(...(0, render_1.panel)(`+Ai Node ${(0, render_1.dim)(`ainode v${st.version}`)}`, headerLines(st), width));
|
|
133
|
+
out.push('');
|
|
134
|
+
if (st.paired) {
|
|
135
|
+
out.push(...statsLines(st).map(l => ` ${l}`));
|
|
136
|
+
out.push('');
|
|
137
|
+
exports.MENU.forEach((m, i) => {
|
|
138
|
+
const selected = i === st.sel;
|
|
139
|
+
const label = selected ? (0, render_1.bold)(m.label.padEnd(12)) : m.label.padEnd(12);
|
|
140
|
+
const marker = selected ? (0, render_1.cyan)('❯') : ' ';
|
|
141
|
+
out.push(`${marker} ${label} ${(0, render_1.dim)(m.hint)} ${(0, render_1.dim)(m.shortcut)}`);
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
out.push('');
|
|
145
|
+
if (st.lastLog) {
|
|
146
|
+
out.push((0, render_1.dim)(` log ${st.lastLog}`));
|
|
147
|
+
if (st.logCount > 1)
|
|
148
|
+
out.push((0, render_1.dim)(` ${st.logCount} lines captured — printed on exit`));
|
|
149
|
+
}
|
|
150
|
+
out.push('');
|
|
151
|
+
out.push((0, render_1.dim)(' ↑↓ move ⏎ open r refresh q quit'));
|
|
152
|
+
return out.map(l => (0, render_1.truncate)(l, width));
|
|
153
|
+
}
|
|
154
|
+
function createDashboardScreen(deps) {
|
|
155
|
+
const st = deps.state;
|
|
156
|
+
// The landing screen shows identity and numbers only, so it fetches two
|
|
157
|
+
// rows of counters — not the request feed. Activity pages that in itself.
|
|
158
|
+
const refresh = async () => {
|
|
159
|
+
st.now = Date.now();
|
|
160
|
+
const [self, stats] = await Promise.all([deps.data.self(), deps.data.stats()]);
|
|
161
|
+
if (self)
|
|
162
|
+
st.self = self;
|
|
163
|
+
if (stats)
|
|
164
|
+
st.stats = stats;
|
|
165
|
+
st.offline = deps.data.offline();
|
|
166
|
+
deps.host.redraw();
|
|
167
|
+
};
|
|
168
|
+
return {
|
|
169
|
+
id: 'dashboard',
|
|
170
|
+
title: '+Ai Node',
|
|
171
|
+
render: (width) => renderDashboard(st, width),
|
|
172
|
+
pollMs: () => (st.stats && st.stats.active > 0 ? 2000 : 5000),
|
|
173
|
+
poll: refresh,
|
|
174
|
+
async onKey(key) {
|
|
175
|
+
if (key.name === 'up' || key.name === 'k') {
|
|
176
|
+
st.sel = Math.max(0, st.sel - 1);
|
|
177
|
+
deps.host.redraw();
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
if (key.name === 'down' || key.name === 'j') {
|
|
181
|
+
st.sel = Math.min(exports.MENU.length - 1, st.sel + 1);
|
|
182
|
+
deps.host.redraw();
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (key.name === 'a') {
|
|
186
|
+
deps.openRequests();
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
if (key.name === 'h') {
|
|
190
|
+
deps.openHarnesses();
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (key.name === 'r') {
|
|
194
|
+
await refresh();
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
if (key.name === 'return') {
|
|
198
|
+
if (exports.MENU[st.sel]?.key === 'harnesses')
|
|
199
|
+
deps.openHarnesses();
|
|
200
|
+
else
|
|
201
|
+
deps.openRequests();
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export interface RequestRow {
|
|
2
|
+
id: string;
|
|
3
|
+
entity_name: string | null;
|
|
4
|
+
agent: string;
|
|
5
|
+
mode: string;
|
|
6
|
+
model: string | null;
|
|
7
|
+
issued_via: string | null;
|
|
8
|
+
status: string;
|
|
9
|
+
prompt: string | null;
|
|
10
|
+
created_at: string;
|
|
11
|
+
picked_up_at: string | null;
|
|
12
|
+
finished_at: string | null;
|
|
13
|
+
last_activity_at: string | null;
|
|
14
|
+
retry_count: number | null;
|
|
15
|
+
error_code: string | null;
|
|
16
|
+
error_message: string | null;
|
|
17
|
+
}
|
|
18
|
+
export interface NodeStats {
|
|
19
|
+
today: number;
|
|
20
|
+
week: number;
|
|
21
|
+
month: number;
|
|
22
|
+
total: number;
|
|
23
|
+
active: number;
|
|
24
|
+
succeeded_24h: number;
|
|
25
|
+
failed_24h: number;
|
|
26
|
+
canceled_24h: number;
|
|
27
|
+
avg_duration_ms: number;
|
|
28
|
+
by_agent: Record<string, number>;
|
|
29
|
+
}
|
|
30
|
+
export interface NodeSelf {
|
|
31
|
+
id: string;
|
|
32
|
+
name: string;
|
|
33
|
+
hostname: string | null;
|
|
34
|
+
platform: string | null;
|
|
35
|
+
status: string;
|
|
36
|
+
effective_status: string;
|
|
37
|
+
last_seen_at: string | null;
|
|
38
|
+
created_at: string;
|
|
39
|
+
}
|
|
40
|
+
export interface DataLayer {
|
|
41
|
+
self(): Promise<NodeSelf | null>;
|
|
42
|
+
stats(): Promise<NodeStats | null>;
|
|
43
|
+
requests(opts?: {
|
|
44
|
+
limit?: number;
|
|
45
|
+
before?: string | null;
|
|
46
|
+
}): Promise<RequestRow[]>;
|
|
47
|
+
detail(id: string): Promise<unknown>;
|
|
48
|
+
lastError(): string | null;
|
|
49
|
+
offline(): boolean;
|
|
50
|
+
}
|
|
51
|
+
type Rpc = (name: string, args: Record<string, unknown>) => Promise<unknown>;
|
|
52
|
+
export declare function createDataLayer(deps: {
|
|
53
|
+
token: string;
|
|
54
|
+
rpc?: Rpc;
|
|
55
|
+
breakerOpen?: () => boolean;
|
|
56
|
+
}): DataLayer;
|
|
57
|
+
export {};
|
package/dist/tui/data.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// The only place the console UI talks to Supabase.
|
|
3
|
+
//
|
|
4
|
+
// Screens read cached values and never await a network call while
|
|
5
|
+
// rendering. Three rules: one in-flight request per rpc (a slow tick can
|
|
6
|
+
// never stack), the last good value survives a failure, and an open
|
|
7
|
+
// circuit breaker means we don't even try.
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.createDataLayer = createDataLayer;
|
|
10
|
+
const supabase_client_1 = require("../supabase-client");
|
|
11
|
+
function createDataLayer(deps) {
|
|
12
|
+
const call = deps.rpc ?? supabase_client_1.rpc;
|
|
13
|
+
const breakerOpen = deps.breakerOpen ?? supabase_client_1.rpcBreakerOpen;
|
|
14
|
+
const cache = new Map();
|
|
15
|
+
const inflight = new Map();
|
|
16
|
+
let lastError = null;
|
|
17
|
+
let offline = false;
|
|
18
|
+
async function fetch(key, name, args) {
|
|
19
|
+
if (breakerOpen()) {
|
|
20
|
+
offline = true;
|
|
21
|
+
return cache.get(key) ?? null;
|
|
22
|
+
}
|
|
23
|
+
const existing = inflight.get(key);
|
|
24
|
+
if (existing)
|
|
25
|
+
return existing;
|
|
26
|
+
const p = (async () => {
|
|
27
|
+
try {
|
|
28
|
+
const res = await call(name, { p_token: deps.token, ...args });
|
|
29
|
+
cache.set(key, res);
|
|
30
|
+
offline = false;
|
|
31
|
+
lastError = null;
|
|
32
|
+
return res;
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
offline = true;
|
|
36
|
+
lastError = err.message;
|
|
37
|
+
return cache.get(key) ?? null;
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
inflight.delete(key);
|
|
41
|
+
}
|
|
42
|
+
})();
|
|
43
|
+
inflight.set(key, p);
|
|
44
|
+
return p;
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
self: () => fetch('self', 'runtime_self', {}),
|
|
48
|
+
stats: () => fetch('stats', 'runtime_self_stats', {}),
|
|
49
|
+
async requests(opts = {}) {
|
|
50
|
+
const limit = opts.limit ?? 20;
|
|
51
|
+
const before = opts.before ?? null;
|
|
52
|
+
const key = `requests:${limit}:${before ?? 'head'}`;
|
|
53
|
+
const rows = await fetch(key, 'runtime_self_recent_requests', {
|
|
54
|
+
p_limit: limit, p_before: before,
|
|
55
|
+
});
|
|
56
|
+
return Array.isArray(rows) ? rows : [];
|
|
57
|
+
},
|
|
58
|
+
detail: (id) => fetch(`detail:${id}`, 'runtime_self_request_detail', { p_request_id: id }),
|
|
59
|
+
lastError: () => lastError,
|
|
60
|
+
offline: () => offline,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run the harness manager.
|
|
3
|
+
*
|
|
4
|
+
* The returned promise resolves when the USER LEAVES the screen — not when
|
|
5
|
+
* the first capability probe finishes. Resolving early is what made the
|
|
6
|
+
* embedded case flicker: the dashboard re-attached its own keypress handler
|
|
7
|
+
* and repainted while this screen was still live, and the two fought over
|
|
8
|
+
* the terminal.
|
|
9
|
+
*
|
|
10
|
+
* `embedded` distinguishes `ainode harnesses` (q exits the process) from
|
|
11
|
+
* the dashboard's `h` (q hands control back to the caller). Ctrl-C always
|
|
12
|
+
* ends the process.
|
|
13
|
+
*/
|
|
14
|
+
export declare function runHarnessesTui(opts?: {
|
|
15
|
+
embedded?: boolean;
|
|
16
|
+
}): Promise<void>;
|