airloom 0.1.3 → 0.1.5
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 +259 -49
- package/dist/viewer/assets/{browser-Dl0EROMD.js → browser-CHRQqYPW.js} +1 -1
- package/dist/viewer/assets/{index-BoCit9Kx.js → index-DLLnAccl.js} +3 -3
- package/dist/viewer/assets/{index-B-Rf55SG.js → index-m5PBOzJQ.js} +1 -1
- package/dist/viewer/index.html +1 -1
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -267,7 +267,7 @@ var Channel = class extends EventEmitter2 {
|
|
|
267
267
|
}
|
|
268
268
|
waitForReady(timeoutMs = 3e4) {
|
|
269
269
|
if (this._ready) return Promise.resolve();
|
|
270
|
-
return new Promise((
|
|
270
|
+
return new Promise((resolve3, reject) => {
|
|
271
271
|
const cleanup = () => {
|
|
272
272
|
clearTimeout(timer);
|
|
273
273
|
this.removeListener("ready", onReady);
|
|
@@ -276,7 +276,7 @@ var Channel = class extends EventEmitter2 {
|
|
|
276
276
|
};
|
|
277
277
|
const onReady = () => {
|
|
278
278
|
cleanup();
|
|
279
|
-
|
|
279
|
+
resolve3();
|
|
280
280
|
};
|
|
281
281
|
const onError = (err) => {
|
|
282
282
|
cleanup();
|
|
@@ -373,14 +373,14 @@ var WebSocketAdapter = class {
|
|
|
373
373
|
}
|
|
374
374
|
async doConnect() {
|
|
375
375
|
const ws = await this.createWebSocket(this.url);
|
|
376
|
-
return new Promise((
|
|
376
|
+
return new Promise((resolve3, reject) => {
|
|
377
377
|
ws.onopen = () => {
|
|
378
378
|
this._connected = true;
|
|
379
379
|
this.shouldReconnect = true;
|
|
380
380
|
this.reconnectAttempts = 0;
|
|
381
381
|
const msg = this.role === "host" ? { type: "create", sessionToken: this.sessionToken } : { type: "join", sessionToken: this.sessionToken };
|
|
382
382
|
ws.send(JSON.stringify(msg));
|
|
383
|
-
|
|
383
|
+
resolve3();
|
|
384
384
|
};
|
|
385
385
|
ws.onmessage = (event) => {
|
|
386
386
|
const raw = event.data;
|
|
@@ -503,15 +503,19 @@ var AblyAdapter = class {
|
|
|
503
503
|
}
|
|
504
504
|
async connect(sessionToken, role) {
|
|
505
505
|
this.clientId = `airloom-${role}-${Date.now()}`;
|
|
506
|
-
const clientOpts = {
|
|
506
|
+
const clientOpts = {
|
|
507
|
+
clientId: this.clientId,
|
|
508
|
+
// Enable server time sync to avoid "Timestamp not current" errors on machines with clock skew
|
|
509
|
+
queryTime: true
|
|
510
|
+
};
|
|
507
511
|
if (this.opts.key) {
|
|
508
512
|
clientOpts.key = this.opts.key;
|
|
509
513
|
} else {
|
|
510
514
|
clientOpts.token = this.opts.token;
|
|
511
515
|
}
|
|
512
516
|
this.ably = new Realtime(clientOpts);
|
|
513
|
-
await new Promise((
|
|
514
|
-
this.ably.connection.once("connected", () =>
|
|
517
|
+
await new Promise((resolve3, reject) => {
|
|
518
|
+
this.ably.connection.once("connected", () => resolve3());
|
|
515
519
|
this.ably.connection.once("failed", (stateChange) => {
|
|
516
520
|
reject(new Error(stateChange?.reason?.message ?? "Ably connection failed"));
|
|
517
521
|
});
|
|
@@ -598,14 +602,14 @@ var AblyAdapter = class {
|
|
|
598
602
|
import { sha256 as sha2562 } from "@noble/hashes/sha256";
|
|
599
603
|
import { networkInterfaces } from "os";
|
|
600
604
|
import { fileURLToPath } from "url";
|
|
601
|
-
import { dirname, resolve } from "path";
|
|
602
|
-
import { existsSync as
|
|
605
|
+
import { dirname, resolve as resolve2 } from "path";
|
|
606
|
+
import { existsSync as existsSync3 } from "fs";
|
|
603
607
|
import QRCode from "qrcode";
|
|
604
608
|
|
|
605
609
|
// src/server.ts
|
|
606
610
|
import express from "express";
|
|
607
611
|
import { createServer } from "http";
|
|
608
|
-
import { existsSync } from "fs";
|
|
612
|
+
import { existsSync as existsSync2 } from "fs";
|
|
609
613
|
import { WebSocketServer, WebSocket } from "ws";
|
|
610
614
|
|
|
611
615
|
// src/adapters/anthropic.ts
|
|
@@ -739,85 +743,266 @@ var OpenAIAdapter = class {
|
|
|
739
743
|
|
|
740
744
|
// src/adapters/cli.ts
|
|
741
745
|
import { spawn } from "child_process";
|
|
746
|
+
import { existsSync } from "fs";
|
|
747
|
+
import { delimiter, isAbsolute, join, resolve } from "path";
|
|
742
748
|
var CLI_PRESETS = [
|
|
743
749
|
{
|
|
744
750
|
id: "devin",
|
|
745
751
|
name: "Devin",
|
|
746
|
-
command: "devin
|
|
747
|
-
description: "Devin CLI
|
|
752
|
+
command: "devin",
|
|
753
|
+
description: "Devin CLI (persistent REPL session)",
|
|
754
|
+
mode: "repl",
|
|
755
|
+
silenceTimeout: 8e3
|
|
748
756
|
},
|
|
749
757
|
{
|
|
750
758
|
id: "claude-code",
|
|
751
759
|
name: "Claude Code",
|
|
760
|
+
command: "claude",
|
|
761
|
+
description: "Claude Code (persistent REPL session)",
|
|
762
|
+
mode: "repl",
|
|
763
|
+
silenceTimeout: 8e3
|
|
764
|
+
},
|
|
765
|
+
{
|
|
766
|
+
id: "claude-code-oneshot",
|
|
767
|
+
name: "Claude Code (one-shot)",
|
|
752
768
|
command: "claude -p --output-format text",
|
|
753
|
-
description: "Claude Code in print mode"
|
|
769
|
+
description: "Claude Code in print mode (new process per prompt)",
|
|
770
|
+
mode: "oneshot"
|
|
754
771
|
},
|
|
755
772
|
{
|
|
756
773
|
id: "codex",
|
|
757
774
|
name: "Codex",
|
|
758
775
|
command: "codex exec --full-auto",
|
|
759
|
-
description: "OpenAI Codex CLI in non-interactive exec mode"
|
|
776
|
+
description: "OpenAI Codex CLI in non-interactive exec mode",
|
|
777
|
+
mode: "oneshot"
|
|
760
778
|
},
|
|
761
779
|
{
|
|
762
780
|
id: "aider",
|
|
763
781
|
name: "Aider",
|
|
764
782
|
command: "aider --yes --no-auto-commits --message",
|
|
765
|
-
description: "Aider in scripting mode (prompt via --message)"
|
|
783
|
+
description: "Aider in scripting mode (prompt via --message)",
|
|
784
|
+
mode: "oneshot"
|
|
766
785
|
},
|
|
767
786
|
{
|
|
768
787
|
id: "custom",
|
|
769
788
|
name: "Custom",
|
|
770
789
|
command: "",
|
|
771
|
-
description: "Custom command
|
|
790
|
+
description: "Custom command (default: one-shot, prompt appended as last arg)",
|
|
791
|
+
mode: "oneshot"
|
|
772
792
|
}
|
|
773
793
|
];
|
|
794
|
+
var ANSI_RE = /[\x1b\x9b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nq-uy=><~]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[^[\]()#;?][^\x1b]?|\r/g;
|
|
795
|
+
var stripAnsi = (s) => s.replace(ANSI_RE, "");
|
|
796
|
+
var isDecorativeLine = (line) => {
|
|
797
|
+
const t = line.trim();
|
|
798
|
+
if (!t) return false;
|
|
799
|
+
return /^[╭╰╮╯│├┤┬┴┼─━═┄┈┌┐└┘║╔╗╚╝╠╣╦╩╬▔▁░▒▓█\-+|=*~_\s]+$/.test(t);
|
|
800
|
+
};
|
|
801
|
+
var isNoiseLine = (line) => {
|
|
802
|
+
const t = line.trim();
|
|
803
|
+
if (!t) return false;
|
|
804
|
+
return t === "#" || t.includes("Devin for Terminal") || /^v\d{4}\./.test(t) || t.startsWith("Mode: ") || t.includes("Tip: Use shift+tab") || t.startsWith("Update v") || t.includes("\u2219 Pro");
|
|
805
|
+
};
|
|
806
|
+
function resolveExecutable(command, envPath = process.env.PATH ?? "") {
|
|
807
|
+
if (!command) return null;
|
|
808
|
+
if (isAbsolute(command) && existsSync(command)) return command;
|
|
809
|
+
if (command.includes("/")) {
|
|
810
|
+
const candidate = resolve(process.cwd(), command);
|
|
811
|
+
return existsSync(candidate) ? candidate : null;
|
|
812
|
+
}
|
|
813
|
+
for (const dir of envPath.split(delimiter)) {
|
|
814
|
+
if (!dir) continue;
|
|
815
|
+
const candidate = join(dir.replace(/^~(?=$|\/)/, process.env.HOME ?? "~"), command);
|
|
816
|
+
if (existsSync(candidate)) return candidate;
|
|
817
|
+
}
|
|
818
|
+
return null;
|
|
819
|
+
}
|
|
774
820
|
var CLIAdapter = class {
|
|
775
821
|
name = "cli";
|
|
776
822
|
model;
|
|
777
823
|
command;
|
|
778
824
|
args;
|
|
825
|
+
mode;
|
|
826
|
+
silenceTimeout;
|
|
827
|
+
pty = null;
|
|
828
|
+
ptyState = "starting";
|
|
829
|
+
startupResolve = null;
|
|
830
|
+
startupTimer = null;
|
|
831
|
+
activeStream = null;
|
|
832
|
+
activeResolve = null;
|
|
833
|
+
silenceTimer = null;
|
|
834
|
+
lastInput = "";
|
|
779
835
|
constructor(config) {
|
|
780
836
|
if (!config.command) throw new Error("Command is required for CLI adapter");
|
|
781
|
-
const parts = config.command.
|
|
837
|
+
const parts = config.command.match(/(?:[^\s"]+|"[^"]*")+/g) ?? [config.command];
|
|
782
838
|
this.command = parts[0];
|
|
783
|
-
this.args = parts.slice(1);
|
|
784
|
-
this.
|
|
839
|
+
this.args = parts.slice(1).map((a) => a.replace(/^"|"$/g, ""));
|
|
840
|
+
this.mode = config.mode ?? "oneshot";
|
|
841
|
+
this.silenceTimeout = config.silenceTimeout ?? 5e3;
|
|
842
|
+
this.model = config.model || `${config.command} (${this.mode})`;
|
|
785
843
|
}
|
|
786
844
|
async streamResponse(messages, stream) {
|
|
845
|
+
if (this.mode === "repl") return this.replResponse(messages, stream);
|
|
846
|
+
return this.oneshotResponse(messages, stream);
|
|
847
|
+
}
|
|
848
|
+
destroy() {
|
|
849
|
+
if (this.silenceTimer) clearTimeout(this.silenceTimer);
|
|
850
|
+
if (this.startupTimer) clearTimeout(this.startupTimer);
|
|
851
|
+
try {
|
|
852
|
+
this.pty?.kill();
|
|
853
|
+
} catch {
|
|
854
|
+
}
|
|
855
|
+
this.pty = null;
|
|
856
|
+
this.finishResponse();
|
|
857
|
+
}
|
|
858
|
+
// --- oneshot ----------------------------------------------------------------
|
|
859
|
+
async oneshotResponse(messages, stream) {
|
|
787
860
|
const lastUserMsg = [...messages].reverse().find((m) => m.role === "user");
|
|
788
861
|
if (!lastUserMsg) {
|
|
789
862
|
stream.write("[No user message provided]");
|
|
790
863
|
stream.end();
|
|
791
864
|
return;
|
|
792
865
|
}
|
|
793
|
-
return new Promise((
|
|
866
|
+
return new Promise((resolvePromise) => {
|
|
794
867
|
const args = [...this.args, lastUserMsg.content];
|
|
795
|
-
const proc = spawn(this.command, args, {
|
|
796
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
797
|
-
});
|
|
798
|
-
const stripAnsi = (s) => s.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "");
|
|
868
|
+
const proc = spawn(this.command, args, { stdio: ["pipe", "pipe", "pipe"] });
|
|
799
869
|
proc.stdin.end();
|
|
800
870
|
proc.stdout.on("data", (data) => stream.write(stripAnsi(data.toString())));
|
|
801
871
|
proc.stderr.on("data", (data) => stream.write(stripAnsi(data.toString())));
|
|
802
872
|
proc.on("close", () => {
|
|
803
873
|
stream.end();
|
|
804
|
-
|
|
874
|
+
resolvePromise();
|
|
805
875
|
});
|
|
806
876
|
proc.on("error", (err) => {
|
|
807
877
|
stream.write(`[Error: ${err.message}]`);
|
|
808
878
|
stream.end();
|
|
809
|
-
|
|
879
|
+
resolvePromise();
|
|
880
|
+
});
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
// --- repl -------------------------------------------------------------------
|
|
884
|
+
async ensurePty() {
|
|
885
|
+
if (this.pty) return this.pty;
|
|
886
|
+
const nodePty = await import("node-pty");
|
|
887
|
+
const executable = resolveExecutable(this.command) ?? this.command;
|
|
888
|
+
console.log(`[cli-repl] Spawning PTY: ${executable} ${this.args.join(" ")}`);
|
|
889
|
+
const pty = nodePty.spawn(executable, this.args, {
|
|
890
|
+
name: "xterm-256color",
|
|
891
|
+
cols: 120,
|
|
892
|
+
rows: 40,
|
|
893
|
+
cwd: process.cwd(),
|
|
894
|
+
env: { ...process.env, NO_COLOR: "1" }
|
|
895
|
+
});
|
|
896
|
+
pty.onData((data) => this.onData(data));
|
|
897
|
+
pty.onExit(({ exitCode }) => {
|
|
898
|
+
console.log(`[cli-repl] PTY exited (code ${exitCode})`);
|
|
899
|
+
this.pty = null;
|
|
900
|
+
this.ptyState = "idle";
|
|
901
|
+
this.finishResponse();
|
|
902
|
+
});
|
|
903
|
+
this.pty = pty;
|
|
904
|
+
this.ptyState = "starting";
|
|
905
|
+
await new Promise((resolvePromise) => {
|
|
906
|
+
this.startupResolve = resolvePromise;
|
|
907
|
+
const maxTimer = setTimeout(() => {
|
|
908
|
+
if (this.ptyState === "starting") {
|
|
909
|
+
this.ptyState = "idle";
|
|
910
|
+
this.startupResolve?.();
|
|
911
|
+
this.startupResolve = null;
|
|
912
|
+
}
|
|
913
|
+
if (this.startupTimer) {
|
|
914
|
+
clearTimeout(this.startupTimer);
|
|
915
|
+
this.startupTimer = null;
|
|
916
|
+
}
|
|
917
|
+
}, 5e3);
|
|
918
|
+
pty.onExit(() => {
|
|
919
|
+
clearTimeout(maxTimer);
|
|
920
|
+
resolvePromise();
|
|
810
921
|
});
|
|
811
922
|
});
|
|
923
|
+
return pty;
|
|
924
|
+
}
|
|
925
|
+
onData(raw) {
|
|
926
|
+
const clean = stripAnsi(raw);
|
|
927
|
+
switch (this.ptyState) {
|
|
928
|
+
case "starting":
|
|
929
|
+
if (this.startupTimer) clearTimeout(this.startupTimer);
|
|
930
|
+
this.startupTimer = setTimeout(() => {
|
|
931
|
+
this.ptyState = "idle";
|
|
932
|
+
this.startupResolve?.();
|
|
933
|
+
this.startupResolve = null;
|
|
934
|
+
}, 2e3);
|
|
935
|
+
break;
|
|
936
|
+
case "idle":
|
|
937
|
+
break;
|
|
938
|
+
case "responding": {
|
|
939
|
+
if (!this.activeStream || !clean) break;
|
|
940
|
+
const filtered = this.filterOutput(clean);
|
|
941
|
+
if (filtered) this.activeStream.write(filtered);
|
|
942
|
+
this.resetSilenceTimer();
|
|
943
|
+
break;
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
filterOutput(text) {
|
|
948
|
+
const lines = text.split("\n");
|
|
949
|
+
const kept = [];
|
|
950
|
+
for (const line of lines) {
|
|
951
|
+
const trimmed = line.trim();
|
|
952
|
+
if (!trimmed) {
|
|
953
|
+
kept.push(line);
|
|
954
|
+
continue;
|
|
955
|
+
}
|
|
956
|
+
if (this.lastInput && (trimmed === this.lastInput.trim() || trimmed === `> ${this.lastInput.trim()}`)) continue;
|
|
957
|
+
if (isDecorativeLine(line) || isNoiseLine(line)) continue;
|
|
958
|
+
kept.push(line);
|
|
959
|
+
}
|
|
960
|
+
return kept.join("\n").replace(/^\n+/, "");
|
|
961
|
+
}
|
|
962
|
+
resetSilenceTimer() {
|
|
963
|
+
if (this.silenceTimer) clearTimeout(this.silenceTimer);
|
|
964
|
+
this.silenceTimer = setTimeout(() => {
|
|
965
|
+
this.ptyState = "idle";
|
|
966
|
+
this.finishResponse();
|
|
967
|
+
}, this.silenceTimeout);
|
|
968
|
+
}
|
|
969
|
+
finishResponse() {
|
|
970
|
+
if (this.silenceTimer) {
|
|
971
|
+
clearTimeout(this.silenceTimer);
|
|
972
|
+
this.silenceTimer = null;
|
|
973
|
+
}
|
|
974
|
+
const stream = this.activeStream;
|
|
975
|
+
const resolvePromise = this.activeResolve;
|
|
976
|
+
this.activeStream = null;
|
|
977
|
+
this.activeResolve = null;
|
|
978
|
+
if (stream && !stream.ended) stream.end();
|
|
979
|
+
resolvePromise?.();
|
|
980
|
+
}
|
|
981
|
+
async replResponse(messages, stream) {
|
|
982
|
+
const lastUserMsg = [...messages].reverse().find((m) => m.role === "user");
|
|
983
|
+
if (!lastUserMsg) {
|
|
984
|
+
stream.write("[No user message provided]");
|
|
985
|
+
stream.end();
|
|
986
|
+
return;
|
|
987
|
+
}
|
|
988
|
+
const pty = await this.ensurePty();
|
|
989
|
+
return new Promise((resolvePromise) => {
|
|
990
|
+
this.activeStream = stream;
|
|
991
|
+
this.activeResolve = resolvePromise;
|
|
992
|
+
this.ptyState = "responding";
|
|
993
|
+
this.lastInput = lastUserMsg.content;
|
|
994
|
+
pty.write(lastUserMsg.content + "\r");
|
|
995
|
+
this.resetSilenceTimer();
|
|
996
|
+
});
|
|
812
997
|
}
|
|
813
998
|
};
|
|
814
999
|
|
|
815
1000
|
// src/config.ts
|
|
816
1001
|
import { readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
817
1002
|
import { homedir } from "os";
|
|
818
|
-
import { join } from "path";
|
|
819
|
-
var CONFIG_DIR =
|
|
820
|
-
var CONFIG_PATH =
|
|
1003
|
+
import { join as join2 } from "path";
|
|
1004
|
+
var CONFIG_DIR = join2(homedir(), ".config", "airloom");
|
|
1005
|
+
var CONFIG_PATH = join2(CONFIG_DIR, "config.json");
|
|
821
1006
|
function loadConfig() {
|
|
822
1007
|
try {
|
|
823
1008
|
const raw = readFileSync(CONFIG_PATH, "utf-8");
|
|
@@ -858,7 +1043,7 @@ function createHostServer(opts) {
|
|
|
858
1043
|
app.get("/", (_req, res) => {
|
|
859
1044
|
res.type("html").send(HOST_HTML);
|
|
860
1045
|
});
|
|
861
|
-
if (opts.viewerDir &&
|
|
1046
|
+
if (opts.viewerDir && existsSync2(opts.viewerDir)) {
|
|
862
1047
|
app.use("/viewer", express.static(opts.viewerDir));
|
|
863
1048
|
}
|
|
864
1049
|
app.get("/api/status", (_req, res) => {
|
|
@@ -912,7 +1097,13 @@ function createHostServer(opts) {
|
|
|
912
1097
|
res.status(400).json({ error: "CLI adapter requires a command (or set AIRLOOM_CLI_COMMAND env var)" });
|
|
913
1098
|
return;
|
|
914
1099
|
}
|
|
915
|
-
|
|
1100
|
+
const presetInfo = preset ? CLI_PRESETS.find((p) => p.id === preset) : void 0;
|
|
1101
|
+
opts.state.adapter = new CLIAdapter({
|
|
1102
|
+
command: cmd,
|
|
1103
|
+
model,
|
|
1104
|
+
mode: presetInfo?.mode,
|
|
1105
|
+
silenceTimeout: presetInfo?.silenceTimeout
|
|
1106
|
+
});
|
|
916
1107
|
break;
|
|
917
1108
|
}
|
|
918
1109
|
default:
|
|
@@ -957,11 +1148,11 @@ function createHostServer(opts) {
|
|
|
957
1148
|
if (ws.readyState === WebSocket.OPEN) ws.send(msg);
|
|
958
1149
|
}
|
|
959
1150
|
}
|
|
960
|
-
return new Promise((
|
|
1151
|
+
return new Promise((resolve3) => {
|
|
961
1152
|
server.listen(opts.port, "0.0.0.0", () => {
|
|
962
1153
|
const addr = server.address();
|
|
963
1154
|
const actualPort = typeof addr === "object" && addr ? addr.port : opts.port;
|
|
964
|
-
|
|
1155
|
+
resolve3({ server, broadcast, port: actualPort });
|
|
965
1156
|
});
|
|
966
1157
|
server.on("error", (err) => {
|
|
967
1158
|
console.error(`[host] Server error: ${err.message}`);
|
|
@@ -972,19 +1163,25 @@ function createHostServer(opts) {
|
|
|
972
1163
|
async function handleAIResponse(channel, adapter, state, broadcast) {
|
|
973
1164
|
const stream = channel.createStream({ model: adapter.model });
|
|
974
1165
|
let fullResponse = "";
|
|
975
|
-
const
|
|
1166
|
+
const uiBatcher = new Batcher({
|
|
976
1167
|
interval: 100,
|
|
977
1168
|
onFlush: (data) => broadcast({ type: "stream_chunk", data })
|
|
978
1169
|
});
|
|
979
1170
|
const origWrite = stream.write.bind(stream);
|
|
1171
|
+
const relayBatcher = new Batcher({
|
|
1172
|
+
interval: 500,
|
|
1173
|
+
maxBytes: 4096,
|
|
1174
|
+
onFlush: (data) => origWrite(data)
|
|
1175
|
+
});
|
|
980
1176
|
stream.write = (data) => {
|
|
981
1177
|
fullResponse += data;
|
|
982
|
-
|
|
983
|
-
|
|
1178
|
+
uiBatcher.write(data);
|
|
1179
|
+
relayBatcher.write(data);
|
|
984
1180
|
};
|
|
985
1181
|
const origEnd = stream.end.bind(stream);
|
|
986
1182
|
stream.end = () => {
|
|
987
|
-
|
|
1183
|
+
uiBatcher.flush();
|
|
1184
|
+
relayBatcher.flush();
|
|
988
1185
|
broadcast({ type: "stream_end" });
|
|
989
1186
|
state.messages.push({ role: "assistant", content: fullResponse, timestamp: Date.now() });
|
|
990
1187
|
trimMessages(state.messages);
|
|
@@ -998,7 +1195,8 @@ async function handleAIResponse(channel, adapter, state, broadcast) {
|
|
|
998
1195
|
stream.write(`[Error: ${message}]`);
|
|
999
1196
|
stream.end();
|
|
1000
1197
|
}
|
|
1001
|
-
|
|
1198
|
+
uiBatcher.destroy();
|
|
1199
|
+
relayBatcher.destroy();
|
|
1002
1200
|
}
|
|
1003
1201
|
}
|
|
1004
1202
|
var HOST_HTML = `<!DOCTYPE html>
|
|
@@ -1239,10 +1437,10 @@ function getLanIP() {
|
|
|
1239
1437
|
return void 0;
|
|
1240
1438
|
}
|
|
1241
1439
|
function resolveViewerDir() {
|
|
1242
|
-
const prod =
|
|
1243
|
-
if (
|
|
1244
|
-
const dev =
|
|
1245
|
-
if (
|
|
1440
|
+
const prod = resolve2(__dirname, "viewer");
|
|
1441
|
+
if (existsSync3(prod)) return prod;
|
|
1442
|
+
const dev = resolve2(__dirname, "../../viewer/dist");
|
|
1443
|
+
if (existsSync3(dev)) return dev;
|
|
1246
1444
|
return void 0;
|
|
1247
1445
|
}
|
|
1248
1446
|
async function main() {
|
|
@@ -1308,17 +1506,22 @@ async function main() {
|
|
|
1308
1506
|
};
|
|
1309
1507
|
if (cliArgs.cli || cliArgs.preset) {
|
|
1310
1508
|
let command = cliArgs.cli;
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1509
|
+
let presetInfo;
|
|
1510
|
+
if (cliArgs.preset) {
|
|
1511
|
+
presetInfo = CLI_PRESETS.find((p) => p.id === cliArgs.preset);
|
|
1512
|
+
if (!presetInfo) {
|
|
1314
1513
|
console.error(`[host] Unknown preset "${cliArgs.preset}". Available: ${CLI_PRESETS.map((p) => p.id).join(", ")}`);
|
|
1315
1514
|
process.exit(1);
|
|
1316
1515
|
}
|
|
1317
|
-
command =
|
|
1516
|
+
if (!command) command = presetInfo.command;
|
|
1318
1517
|
}
|
|
1319
1518
|
if (command) {
|
|
1320
|
-
state.adapter = new CLIAdapter({
|
|
1321
|
-
|
|
1519
|
+
state.adapter = new CLIAdapter({
|
|
1520
|
+
command,
|
|
1521
|
+
mode: presetInfo?.mode,
|
|
1522
|
+
silenceTimeout: presetInfo?.silenceTimeout
|
|
1523
|
+
});
|
|
1524
|
+
console.log(`[host] CLI adapter: ${command} (${presetInfo?.mode ?? "oneshot"})`);
|
|
1322
1525
|
}
|
|
1323
1526
|
} else {
|
|
1324
1527
|
const saved = loadConfig();
|
|
@@ -1341,8 +1544,14 @@ async function main() {
|
|
|
1341
1544
|
}
|
|
1342
1545
|
case "cli": {
|
|
1343
1546
|
const cmd = saved.command || process.env.AIRLOOM_CLI_COMMAND;
|
|
1547
|
+
const savedPreset = saved.preset ? CLI_PRESETS.find((p) => p.id === saved.preset) : void 0;
|
|
1344
1548
|
if (cmd) {
|
|
1345
|
-
state.adapter = new CLIAdapter({
|
|
1549
|
+
state.adapter = new CLIAdapter({
|
|
1550
|
+
command: cmd,
|
|
1551
|
+
model: saved.model,
|
|
1552
|
+
mode: savedPreset?.mode,
|
|
1553
|
+
silenceTimeout: savedPreset?.silenceTimeout
|
|
1554
|
+
});
|
|
1346
1555
|
}
|
|
1347
1556
|
break;
|
|
1348
1557
|
}
|
|
@@ -1418,6 +1627,7 @@ async function main() {
|
|
|
1418
1627
|
if (shuttingDown) return;
|
|
1419
1628
|
shuttingDown = true;
|
|
1420
1629
|
console.log("\n[host] Shutting down...");
|
|
1630
|
+
state.adapter?.destroy?.();
|
|
1421
1631
|
try {
|
|
1422
1632
|
channel.close();
|
|
1423
1633
|
} catch {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{g as c}from"./index-
|
|
1
|
+
import{g as c}from"./index-DLLnAccl.js";function f(t,i){for(var o=0;o<i.length;o++){const e=i[o];if(typeof e!="string"&&!Array.isArray(e)){for(const r in e)if(r!=="default"&&!(r in t)){const s=Object.getOwnPropertyDescriptor(e,r);s&&Object.defineProperty(t,r,s.get?s:{enumerable:!0,get:()=>e[r]})}}}return Object.freeze(Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}))}var n,a;function b(){return a||(a=1,n=function(){throw new Error("ws does not work in the browser. Browser clients must use the native WebSocket object")}),n}var u=b();const w=c(u),p=f({__proto__:null,default:w},[u]);export{p as b};
|