office-core 0.1.3 → 0.1.4
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.
|
@@ -187,6 +187,7 @@ async function processRoomMessages(runtime) {
|
|
|
187
187
|
hooks.onRoomMessage?.(message, runtime);
|
|
188
188
|
if (message.author_type === "system") {
|
|
189
189
|
runtime.roomCursorSeq = Math.max(runtime.roomCursorSeq, message.seq);
|
|
190
|
+
await persistRoomCursor(runtime);
|
|
190
191
|
continue;
|
|
191
192
|
}
|
|
192
193
|
const runnableSessions = Array.from(sessions.values()).filter((session) => session.status === "running");
|
|
@@ -7,14 +7,14 @@ import { getHostConfigPath, getStartupLauncherPath, upsertHostConfig, } from "./
|
|
|
7
7
|
const args = parseArgs(process.argv.slice(2));
|
|
8
8
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
9
9
|
void main().catch((error) => {
|
|
10
|
-
console.error(error);
|
|
10
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
11
11
|
process.exitCode = 1;
|
|
12
12
|
});
|
|
13
13
|
async function main() {
|
|
14
|
-
const baseUrl = (args.baseUrl
|
|
15
|
-
const projectId = args.project
|
|
14
|
+
const baseUrl = normalizeBaseUrlArg(args.baseUrl);
|
|
15
|
+
const projectId = normalizeStringArg(args.project, "--project", "prj_local");
|
|
16
16
|
const requestedHostId = args.hostId;
|
|
17
|
-
const displayName = args.displayName
|
|
17
|
+
const displayName = normalizeStringArg(args.displayName, "--displayName", `${os.hostname()} host`);
|
|
18
18
|
const machineName = os.hostname();
|
|
19
19
|
const enrollSecret = args.enrollSecret ??
|
|
20
20
|
process.env.OFFICE_HOST_ENROLL_SECRET ??
|
|
@@ -37,7 +37,7 @@ async function main() {
|
|
|
37
37
|
host_id: registration.host_id,
|
|
38
38
|
base_url: baseUrl,
|
|
39
39
|
project_id: registration.project_id,
|
|
40
|
-
workdir: args.workdir,
|
|
40
|
+
workdir: normalizeOptionalPathArg(args.workdir, "--workdir"),
|
|
41
41
|
display_name: displayName,
|
|
42
42
|
token: registration.host_token,
|
|
43
43
|
room_cursor_seq: Number(registration.room_cursor_seq ?? 0),
|
|
@@ -55,14 +55,21 @@ async function main() {
|
|
|
55
55
|
console.log(`Auto-start: ${config.auto_start ? "enabled" : "disabled"}`);
|
|
56
56
|
}
|
|
57
57
|
async function registerHost(baseUrl, projectId, body, enrollSecret) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
58
|
+
let response;
|
|
59
|
+
try {
|
|
60
|
+
response = await fetch(`${baseUrl}/api/projects/${projectId}/local-host/register`, {
|
|
61
|
+
method: "POST",
|
|
62
|
+
headers: {
|
|
63
|
+
"content-type": "application/json",
|
|
64
|
+
"x-enroll-secret": enrollSecret,
|
|
65
|
+
},
|
|
66
|
+
body: JSON.stringify(body),
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
71
|
+
throw new Error(`Host registration failed: could not reach ${baseUrl} (${message})`);
|
|
72
|
+
}
|
|
66
73
|
if (!response.ok) {
|
|
67
74
|
throw new Error(`Host registration failed: ${response.status} ${await response.text()}`);
|
|
68
75
|
}
|
|
@@ -106,3 +113,42 @@ function parseArgs(argv) {
|
|
|
106
113
|
function escapeBatch(value) {
|
|
107
114
|
return value.replace(/"/g, '""');
|
|
108
115
|
}
|
|
116
|
+
function normalizeBaseUrlArg(value) {
|
|
117
|
+
if (value === "true") {
|
|
118
|
+
throw new Error("Missing value for --baseUrl");
|
|
119
|
+
}
|
|
120
|
+
const raw = (value ?? "http://127.0.0.1:8787").trim().replace(/\/$/, "");
|
|
121
|
+
try {
|
|
122
|
+
const url = new URL(raw);
|
|
123
|
+
if (!/^https?:$/.test(url.protocol)) {
|
|
124
|
+
throw new Error("protocol");
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
throw new Error(`Invalid --baseUrl: ${raw}`);
|
|
129
|
+
}
|
|
130
|
+
return raw;
|
|
131
|
+
}
|
|
132
|
+
function normalizeStringArg(value, flagName, fallback) {
|
|
133
|
+
if (value === "true") {
|
|
134
|
+
throw new Error(`Missing value for ${flagName}`);
|
|
135
|
+
}
|
|
136
|
+
const normalized = String(value ?? fallback).trim();
|
|
137
|
+
if (!normalized) {
|
|
138
|
+
throw new Error(`Missing value for ${flagName}`);
|
|
139
|
+
}
|
|
140
|
+
return normalized;
|
|
141
|
+
}
|
|
142
|
+
function normalizeOptionalPathArg(value, flagName) {
|
|
143
|
+
if (value === undefined) {
|
|
144
|
+
return undefined;
|
|
145
|
+
}
|
|
146
|
+
if (value === "true") {
|
|
147
|
+
throw new Error(`Missing value for ${flagName}`);
|
|
148
|
+
}
|
|
149
|
+
const normalized = value.trim();
|
|
150
|
+
if (!normalized) {
|
|
151
|
+
throw new Error(`Missing value for ${flagName}`);
|
|
152
|
+
}
|
|
153
|
+
return normalized;
|
|
154
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
|
-
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
2
|
+
import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
const HOST_CONFIG_VERSION = 1;
|
|
@@ -28,8 +28,14 @@ export async function loadHostConfig() {
|
|
|
28
28
|
if (!filePath) {
|
|
29
29
|
return null;
|
|
30
30
|
}
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
let parsed;
|
|
32
|
+
try {
|
|
33
|
+
const raw = await readFile(filePath, "utf8");
|
|
34
|
+
parsed = parseLooseJson(raw);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
33
39
|
if (!parsed || parsed.version !== HOST_CONFIG_VERSION) {
|
|
34
40
|
return null;
|
|
35
41
|
}
|
|
@@ -163,8 +169,19 @@ function parseLooseJson(raw) {
|
|
|
163
169
|
}
|
|
164
170
|
async function writeAtomicJson(filePath, value) {
|
|
165
171
|
const tempPath = `${filePath}.tmp`;
|
|
166
|
-
|
|
167
|
-
|
|
172
|
+
try {
|
|
173
|
+
await writeFile(tempPath, JSON.stringify(value, null, 2), "utf8");
|
|
174
|
+
await rename(tempPath, filePath);
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
await rm(tempPath, { force: true }).catch(() => undefined);
|
|
178
|
+
const code = error && typeof error === "object" && "code" in error ? String(error.code ?? "") : "";
|
|
179
|
+
if (code === "EPERM" || code === "EACCES" || code === "EBUSY") {
|
|
180
|
+
const fileLabel = path.basename(filePath);
|
|
181
|
+
throw new Error(`Unable to write ${fileLabel} at ${filePath}. Remove read-only protection or close any program using that file.`);
|
|
182
|
+
}
|
|
183
|
+
throw error;
|
|
184
|
+
}
|
|
168
185
|
}
|
|
169
186
|
function baseAppDataDir() {
|
|
170
187
|
return process.env.APPDATA ?? path.join(process.env.USERPROFILE ?? os.homedir(), "AppData", "Roaming");
|
|
@@ -7,20 +7,39 @@ import { loadHostConfig, upsertHostConfig, getHostConfigPath } from "./lib/host-
|
|
|
7
7
|
import { probeRunnerAvailability, resolveRunnerCommand } from "./lib/local-runner.js";
|
|
8
8
|
import { sessions, hooks, buildRuntimeConfig, startDaemonLoop, stopDaemonLoop, stopSession, buildSpawnContext, spawnInteractiveSession, persistSessions, postRoomMessageUpsert, postJson, getJson, resolveProjectWorkdir, } from "./home-agent-host.js";
|
|
9
9
|
// ─── ANSI Codes ─────────────────────────────────────────────────────────────
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
10
|
+
let R = "\x1b[0m";
|
|
11
|
+
let BOLD = "\x1b[1m";
|
|
12
|
+
let DIM = "\x1b[2m";
|
|
13
|
+
let BMAG = "\x1b[95m";
|
|
14
|
+
let MAG = "\x1b[35m";
|
|
15
|
+
let BBLU = "\x1b[94m";
|
|
16
|
+
let BLU = "\x1b[34m";
|
|
17
|
+
let BCYN = "\x1b[96m";
|
|
18
|
+
let CYN = "\x1b[36m";
|
|
19
|
+
let GRN = "\x1b[32m";
|
|
20
|
+
let YEL = "\x1b[33m";
|
|
21
|
+
let RED = "\x1b[31m";
|
|
22
|
+
let WHT = "\x1b[97m";
|
|
23
|
+
let GRY = "\x1b[90m";
|
|
24
|
+
let USE_UNICODE = true;
|
|
25
|
+
function configureOutputMode(isInteractiveTerminal) {
|
|
26
|
+
const styled = isInteractiveTerminal && !("NO_COLOR" in process.env) && process.env.TERM !== "dumb";
|
|
27
|
+
USE_UNICODE = isInteractiveTerminal && process.env.OFFICE_CORE_ASCII !== "1";
|
|
28
|
+
R = styled ? "\x1b[0m" : "";
|
|
29
|
+
BOLD = styled ? "\x1b[1m" : "";
|
|
30
|
+
DIM = styled ? "\x1b[2m" : "";
|
|
31
|
+
BMAG = styled ? "\x1b[95m" : "";
|
|
32
|
+
MAG = styled ? "\x1b[35m" : "";
|
|
33
|
+
BBLU = styled ? "\x1b[94m" : "";
|
|
34
|
+
BLU = styled ? "\x1b[34m" : "";
|
|
35
|
+
BCYN = styled ? "\x1b[96m" : "";
|
|
36
|
+
CYN = styled ? "\x1b[36m" : "";
|
|
37
|
+
GRN = styled ? "\x1b[32m" : "";
|
|
38
|
+
YEL = styled ? "\x1b[33m" : "";
|
|
39
|
+
RED = styled ? "\x1b[31m" : "";
|
|
40
|
+
WHT = styled ? "\x1b[97m" : "";
|
|
41
|
+
GRY = styled ? "\x1b[90m" : "";
|
|
42
|
+
}
|
|
24
43
|
// ─── ASCII Banner ───────────────────────────────────────────────────────────
|
|
25
44
|
const BANNER = [
|
|
26
45
|
[" ██████ ███████ ███████ ██ ██████ ███████ ", BMAG],
|
|
@@ -78,9 +97,15 @@ function drawAgentBox(label, text, maxW = 74) {
|
|
|
78
97
|
const lines = wordWrap(text.trim(), maxW - 4);
|
|
79
98
|
const inner = Math.max(label.length + 2, ...lines.map((l) => l.length), 20);
|
|
80
99
|
const w = Math.min(inner, maxW - 4);
|
|
81
|
-
const
|
|
82
|
-
const
|
|
83
|
-
const
|
|
100
|
+
const tl = USE_UNICODE ? "┌" : "+";
|
|
101
|
+
const tr = USE_UNICODE ? "┐" : "+";
|
|
102
|
+
const bl = USE_UNICODE ? "└" : "+";
|
|
103
|
+
const br = USE_UNICODE ? "┘" : "+";
|
|
104
|
+
const h = USE_UNICODE ? "─" : "-";
|
|
105
|
+
const v = USE_UNICODE ? "│" : "|";
|
|
106
|
+
const top = `${CYN}${tl}${h} ${R}${BOLD}${BCYN}${label}${R}${CYN} ${h.repeat(Math.max(0, w - label.length - 1))}${tr}${R}`;
|
|
107
|
+
const bot = `${CYN}${bl}${h.repeat(w + 2)}${br}${R}`;
|
|
108
|
+
const body = lines.map((l) => `${CYN}${v}${R} ${WHT}${l.padEnd(w)}${R} ${CYN}${v}${R}`);
|
|
84
109
|
return [top, ...body, bot].join("\n");
|
|
85
110
|
}
|
|
86
111
|
// ─── Timestamp ──────────────────────────────────────────────────────────────
|
|
@@ -232,6 +257,26 @@ function completer(line) {
|
|
|
232
257
|
function ask(rl, prompt) {
|
|
233
258
|
return new Promise((resolve) => rl.question(prompt, (a) => resolve(a?.trim() ?? "")));
|
|
234
259
|
}
|
|
260
|
+
async function readAllStdin(stream) {
|
|
261
|
+
let buffer = "";
|
|
262
|
+
stream.setEncoding?.("utf8");
|
|
263
|
+
for await (const chunk of stream) {
|
|
264
|
+
buffer += String(chunk);
|
|
265
|
+
}
|
|
266
|
+
return buffer;
|
|
267
|
+
}
|
|
268
|
+
function createNonInteractiveReadline() {
|
|
269
|
+
const noop = () => undefined;
|
|
270
|
+
return {
|
|
271
|
+
close: noop,
|
|
272
|
+
question: (_query, callback) => {
|
|
273
|
+
callback("");
|
|
274
|
+
return undefined;
|
|
275
|
+
},
|
|
276
|
+
prompt: noop,
|
|
277
|
+
setPrompt: noop,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
235
280
|
function safePrompt(ctx, preserveCursor = false) {
|
|
236
281
|
if (!ctx.running || !ctx.isInteractiveTerminal) {
|
|
237
282
|
return;
|
|
@@ -243,23 +288,11 @@ function safePrompt(ctx, preserveCursor = false) {
|
|
|
243
288
|
// Ignore prompt attempts after readline closes during shutdown/tests.
|
|
244
289
|
}
|
|
245
290
|
}
|
|
246
|
-
function
|
|
247
|
-
const internal = rl;
|
|
248
|
-
internal.line = value;
|
|
249
|
-
internal.cursor = value.length;
|
|
250
|
-
internal._refreshLine?.();
|
|
251
|
-
}
|
|
252
|
-
function startDaemonInBackground(ctx) {
|
|
291
|
+
async function startDaemon(ctx) {
|
|
253
292
|
if (!ctx.runtime) {
|
|
254
293
|
return;
|
|
255
294
|
}
|
|
256
|
-
|
|
257
|
-
if (!ctx.running) {
|
|
258
|
-
return;
|
|
259
|
-
}
|
|
260
|
-
console.log(`${RED}Daemon failed: ${error instanceof Error ? error.message : String(error)}${R}`);
|
|
261
|
-
safePrompt(ctx);
|
|
262
|
-
});
|
|
295
|
+
await startDaemonLoop(ctx.runtime);
|
|
263
296
|
}
|
|
264
297
|
function fmtAge(ms) {
|
|
265
298
|
if (ms < 60_000)
|
|
@@ -509,7 +542,7 @@ async function cmdSetup(_a, ctx) {
|
|
|
509
542
|
// Connect immediately
|
|
510
543
|
stopDaemonLoop();
|
|
511
544
|
ctx.runtime = await buildRuntimeConfig();
|
|
512
|
-
|
|
545
|
+
await startDaemon(ctx);
|
|
513
546
|
console.log(`\n ${GRN}Host online.${R}\n`);
|
|
514
547
|
}
|
|
515
548
|
catch (e) {
|
|
@@ -578,9 +611,10 @@ async function cmdClear(_a, _ctx) {
|
|
|
578
611
|
printBanner();
|
|
579
612
|
}
|
|
580
613
|
async function cmdHelp(_a, _ctx) {
|
|
614
|
+
const rule = (USE_UNICODE ? "─" : "-").repeat(56);
|
|
581
615
|
console.log("");
|
|
582
616
|
console.log(` ${BOLD}${WHT}Commands${R}`);
|
|
583
|
-
console.log(` ${
|
|
617
|
+
console.log(` ${rule}`);
|
|
584
618
|
for (const c of CMDS) {
|
|
585
619
|
const a = c.args ? ` ${CYN}${c.args}${R}` : "";
|
|
586
620
|
const al = c.alias ? ` ${GRY}(${c.alias.map((x) => `/${x}`).join(", ")})${R}` : "";
|
|
@@ -591,80 +625,81 @@ async function cmdHelp(_a, _ctx) {
|
|
|
591
625
|
console.log(` ${DIM}Tab to autocomplete slash commands${R}\n`);
|
|
592
626
|
}
|
|
593
627
|
async function cmdQuit(_a, ctx) {
|
|
594
|
-
ctx.running = false;
|
|
595
|
-
stopDaemonLoop();
|
|
596
628
|
ctx.rl.close();
|
|
597
629
|
}
|
|
598
630
|
// ─── Main ───────────────────────────────────────────────────────────────────
|
|
599
631
|
void main().catch((err) => {
|
|
600
|
-
console.error(err);
|
|
632
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
601
633
|
process.exitCode = 1;
|
|
602
634
|
});
|
|
603
635
|
async function main() {
|
|
604
|
-
console.clear();
|
|
605
|
-
printBanner();
|
|
606
636
|
const isInteractiveTerminal = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
}
|
|
637
|
+
configureOutputMode(isInteractiveTerminal);
|
|
638
|
+
const pipedInputPromise = !isInteractiveTerminal ? readAllStdin(process.stdin) : Promise.resolve("");
|
|
639
|
+
if (isInteractiveTerminal) {
|
|
640
|
+
console.clear();
|
|
641
|
+
printBanner();
|
|
642
|
+
}
|
|
643
|
+
const rl = isInteractiveTerminal
|
|
644
|
+
? createInterface({
|
|
645
|
+
input: process.stdin,
|
|
646
|
+
output: process.stdout,
|
|
647
|
+
terminal: true,
|
|
648
|
+
historySize: 0,
|
|
649
|
+
completer,
|
|
650
|
+
})
|
|
651
|
+
: createNonInteractiveReadline();
|
|
613
652
|
const ctx = { runtime: null, rl, running: true, isInteractiveTerminal };
|
|
614
653
|
const sentIds = new Set();
|
|
654
|
+
const startupMessages = [];
|
|
655
|
+
let startupComplete = false;
|
|
656
|
+
let pendingInputs = 0;
|
|
657
|
+
let closeRequested = false;
|
|
658
|
+
let closeFinalized = false;
|
|
615
659
|
let finishStartup;
|
|
616
660
|
const startupReady = new Promise((resolve) => {
|
|
617
661
|
finishStartup = resolve;
|
|
618
662
|
});
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
safePrompt(ctx);
|
|
623
|
-
}
|
|
624
|
-
else {
|
|
625
|
-
try {
|
|
626
|
-
rl.resume();
|
|
663
|
+
const flushStartupMessages = () => {
|
|
664
|
+
if (!startupMessages.length) {
|
|
665
|
+
return;
|
|
627
666
|
}
|
|
628
|
-
|
|
629
|
-
|
|
667
|
+
for (const message of startupMessages.splice(0)) {
|
|
668
|
+
process.stdout.write(`\r\x1b[K`);
|
|
669
|
+
displayMessage(message);
|
|
630
670
|
}
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
scheduleSlashMenuRefresh(rl, true);
|
|
648
|
-
return;
|
|
671
|
+
};
|
|
672
|
+
const finalizeClose = () => {
|
|
673
|
+
if (closeFinalized) {
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
closeFinalized = true;
|
|
677
|
+
ctx.running = false;
|
|
678
|
+
stopDaemonLoop();
|
|
679
|
+
if (ctx.isInteractiveTerminal) {
|
|
680
|
+
process.stdin.setRawMode?.(false);
|
|
681
|
+
}
|
|
682
|
+
if (ctx.isInteractiveTerminal) {
|
|
683
|
+
const internalRl = rl;
|
|
684
|
+
if (!internalRl.closed) {
|
|
685
|
+
try {
|
|
686
|
+
rl.close();
|
|
649
687
|
}
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
replaceInputLine(rl, `/${selected.name} `);
|
|
653
|
-
scheduleSlashMenuRefresh(rl, false);
|
|
654
|
-
return;
|
|
688
|
+
catch {
|
|
689
|
+
// Ignore close races during shutdown.
|
|
655
690
|
}
|
|
656
|
-
scheduleSlashMenuRefresh(rl, false);
|
|
657
691
|
}
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
rl.on("line", async (raw) => {
|
|
692
|
+
}
|
|
693
|
+
console.log(`\n${DIM}Goodbye.${R}`);
|
|
694
|
+
process.exitCode = 0;
|
|
695
|
+
};
|
|
696
|
+
const handleInput = async (raw) => {
|
|
664
697
|
clearSlashMenu();
|
|
665
698
|
const input = raw.trim();
|
|
666
699
|
if (!input) {
|
|
667
|
-
|
|
700
|
+
if (startupComplete) {
|
|
701
|
+
safePrompt(ctx);
|
|
702
|
+
}
|
|
668
703
|
return;
|
|
669
704
|
}
|
|
670
705
|
await startupReady;
|
|
@@ -693,44 +728,103 @@ async function main() {
|
|
|
693
728
|
}
|
|
694
729
|
}
|
|
695
730
|
}
|
|
731
|
+
else if (!ctx.runtime) {
|
|
732
|
+
console.log(`${RED}Not connected. Run /setup${R}`);
|
|
733
|
+
}
|
|
696
734
|
else {
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
735
|
+
const messageId = `msg_${crypto.randomUUID()}`;
|
|
736
|
+
sentIds.add(messageId);
|
|
737
|
+
console.log(`${GRN}[${ts()}]${R} ${BOLD}You${R}: ${input}`);
|
|
738
|
+
try {
|
|
739
|
+
const result = await postRoomMessageUpsert(ctx.runtime, {
|
|
740
|
+
message_id: messageId,
|
|
741
|
+
author_type: "user",
|
|
742
|
+
author_id: ctx.runtime.hostId,
|
|
743
|
+
author_label: "You",
|
|
744
|
+
text: input,
|
|
745
|
+
});
|
|
746
|
+
if (result?.message_id && result.message_id !== messageId) {
|
|
747
|
+
sentIds.add(String(result.message_id));
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
catch (e) {
|
|
751
|
+
sentIds.delete(messageId);
|
|
752
|
+
console.log(`${RED}Send failed: ${e instanceof Error ? e.message : String(e)}${R}`);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
safePrompt(ctx);
|
|
756
|
+
};
|
|
757
|
+
// ── REPL ──
|
|
758
|
+
if (ctx.isInteractiveTerminal) {
|
|
759
|
+
rl.setPrompt(`${BMAG}>${R} `);
|
|
760
|
+
}
|
|
761
|
+
// Live slash menu on keypress
|
|
762
|
+
if (ctx.isInteractiveTerminal) {
|
|
763
|
+
emitKeypressEvents(process.stdin, rl);
|
|
764
|
+
process.stdin.setRawMode?.(true);
|
|
765
|
+
process.stdin.on("keypress", (chunk, key) => {
|
|
766
|
+
if (!ctx.running || !startupComplete)
|
|
767
|
+
return;
|
|
768
|
+
const line = rl.line ?? "";
|
|
769
|
+
if (line.startsWith("/") && line.length >= 1) {
|
|
770
|
+
const hits = getSlashMatches(line.slice(1));
|
|
771
|
+
const isTab = key?.name === "tab" || chunk === "\t";
|
|
772
|
+
if (hits.length > 0 && (key?.name === "up" || key?.name === "down")) {
|
|
773
|
+
_menuSelection =
|
|
774
|
+
key.name === "up"
|
|
775
|
+
? (_menuSelection + hits.length - 1) % hits.length
|
|
776
|
+
: (_menuSelection + 1) % hits.length;
|
|
777
|
+
scheduleSlashMenuRefresh(rl, true);
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
if (!isTab) {
|
|
781
|
+
scheduleSlashMenuRefresh(rl, false);
|
|
782
|
+
}
|
|
700
783
|
}
|
|
701
784
|
else {
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
785
|
+
clearSlashMenu();
|
|
786
|
+
}
|
|
787
|
+
});
|
|
788
|
+
}
|
|
789
|
+
if (ctx.isInteractiveTerminal) {
|
|
790
|
+
rl.on("line", async (raw) => {
|
|
791
|
+
pendingInputs += 1;
|
|
792
|
+
try {
|
|
793
|
+
await handleInput(raw);
|
|
794
|
+
}
|
|
795
|
+
finally {
|
|
796
|
+
pendingInputs = Math.max(0, pendingInputs - 1);
|
|
797
|
+
if (closeRequested && pendingInputs === 0) {
|
|
798
|
+
finalizeClose();
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
});
|
|
802
|
+
rl.on("close", () => {
|
|
803
|
+
closeRequested = true;
|
|
804
|
+
if (pendingInputs === 0) {
|
|
805
|
+
finalizeClose();
|
|
806
|
+
}
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
if (!ctx.isInteractiveTerminal) {
|
|
810
|
+
void (async () => {
|
|
811
|
+
const pipedInput = await pipedInputPromise;
|
|
812
|
+
const lines = pipedInput.split(/\r?\n/).filter((line) => line.trim().length > 0);
|
|
813
|
+
for (const line of lines) {
|
|
814
|
+
pendingInputs += 1;
|
|
705
815
|
try {
|
|
706
|
-
|
|
707
|
-
message_id: messageId,
|
|
708
|
-
author_type: "user",
|
|
709
|
-
author_id: ctx.runtime.hostId,
|
|
710
|
-
author_label: "You",
|
|
711
|
-
text: input,
|
|
712
|
-
});
|
|
713
|
-
if (result?.message_id && result.message_id !== messageId) {
|
|
714
|
-
sentIds.add(String(result.message_id));
|
|
715
|
-
}
|
|
816
|
+
await handleInput(line);
|
|
716
817
|
}
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
console.log(`${RED}Send failed: ${e instanceof Error ? e.message : String(e)}${R}`);
|
|
818
|
+
finally {
|
|
819
|
+
pendingInputs = Math.max(0, pendingInputs - 1);
|
|
720
820
|
}
|
|
721
821
|
}
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
if (ctx.isInteractiveTerminal) {
|
|
729
|
-
process.stdin.setRawMode?.(false);
|
|
730
|
-
}
|
|
731
|
-
console.log(`\n${DIM}Goodbye.${R}`);
|
|
732
|
-
process.exit(0);
|
|
733
|
-
});
|
|
822
|
+
closeRequested = true;
|
|
823
|
+
if (pendingInputs === 0) {
|
|
824
|
+
finalizeClose();
|
|
825
|
+
}
|
|
826
|
+
})();
|
|
827
|
+
}
|
|
734
828
|
// ── Try to connect ──
|
|
735
829
|
const cfg = await loadHostConfig();
|
|
736
830
|
if (cfg) {
|
|
@@ -748,6 +842,10 @@ async function main() {
|
|
|
748
842
|
sentIds.delete(msg.message_id);
|
|
749
843
|
return;
|
|
750
844
|
}
|
|
845
|
+
if (!startupComplete) {
|
|
846
|
+
startupMessages.push(msg);
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
751
849
|
process.stdout.write(`\r\x1b[K`);
|
|
752
850
|
displayMessage(msg);
|
|
753
851
|
safePrompt(ctx, true);
|
|
@@ -755,16 +853,17 @@ async function main() {
|
|
|
755
853
|
hooks.onSessionChange = () => {
|
|
756
854
|
// Session list changed - could update status bar
|
|
757
855
|
};
|
|
758
|
-
|
|
856
|
+
await startDaemon(ctx);
|
|
759
857
|
console.log(` ${GRN}Host online.${R} Type ${GRN}/help${R} for commands.\n`);
|
|
760
|
-
|
|
858
|
+
flushStartupMessages();
|
|
761
859
|
}
|
|
762
860
|
catch (e) {
|
|
763
861
|
console.log(` ${RED}Connection failed: ${e instanceof Error ? e.message : String(e)}${R}`);
|
|
764
862
|
console.log(` ${DIM}Run /setup to configure or /doctor to diagnose.${R}\n`);
|
|
765
|
-
safePrompt(ctx);
|
|
766
863
|
}
|
|
767
864
|
finally {
|
|
865
|
+
startupComplete = true;
|
|
866
|
+
safePrompt(ctx);
|
|
768
867
|
finishStartup();
|
|
769
868
|
}
|
|
770
869
|
}
|
|
@@ -772,6 +871,7 @@ async function main() {
|
|
|
772
871
|
printHeader(os.hostname());
|
|
773
872
|
console.log(` ${YEL}No host config found.${R}`);
|
|
774
873
|
console.log(` ${DIM}Run /setup to get started or /doctor to check prerequisites.${R}\n`);
|
|
874
|
+
startupComplete = true;
|
|
775
875
|
safePrompt(ctx);
|
|
776
876
|
finishStartup();
|
|
777
877
|
}
|