omnius 1.0.253 → 1.0.255
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 +2099 -2042
- package/npm-shrinkwrap.json +7 -7
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -564799,7 +564799,7 @@ ${parts.join("\n")}
|
|
|
564799
564799
|
disablePersistentMemory: options2?.disablePersistentMemory ?? false,
|
|
564800
564800
|
disableCodebaseMap: options2?.disableCodebaseMap ?? false,
|
|
564801
564801
|
sessionId: options2?.sessionId ?? "",
|
|
564802
|
-
streamEnabled: options2?.streamEnabled ??
|
|
564802
|
+
streamEnabled: options2?.streamEnabled ?? true,
|
|
564803
564803
|
thinking: options2?.thinking ?? false,
|
|
564804
564804
|
toolProfile: options2?.toolProfile ?? void 0,
|
|
564805
564805
|
bruteForce: options2?.bruteForce ?? true,
|
|
@@ -669686,413 +669686,1309 @@ var init_chat_session = __esm({
|
|
|
669686
669686
|
}
|
|
669687
669687
|
});
|
|
669688
669688
|
|
|
669689
|
-
// packages/cli/src/
|
|
669690
|
-
var
|
|
669691
|
-
__export(
|
|
669692
|
-
|
|
669689
|
+
// packages/cli/src/tui/voicechat.ts
|
|
669690
|
+
var voicechat_exports = {};
|
|
669691
|
+
__export(voicechat_exports, {
|
|
669692
|
+
VoiceChatSession: () => VoiceChatSession
|
|
669693
669693
|
});
|
|
669694
|
-
|
|
669695
|
-
|
|
669696
|
-
|
|
669697
|
-
async function runCommand(input, opts) {
|
|
669698
|
-
const start2 = Date.now();
|
|
669699
|
-
const trimmed = input.trim();
|
|
669700
|
-
const slashCmd = trimmed.startsWith("/") ? trimmed : "/" + trimmed;
|
|
669701
|
-
const [rawCmd, ...rest] = slashCmd.slice(1).split(/\s+/);
|
|
669702
|
-
const cmdName = rawCmd ?? "";
|
|
669703
|
-
const argsStr = rest.join(" ");
|
|
669704
|
-
const release = await acquireLock2();
|
|
669705
|
-
try {
|
|
669706
|
-
const quick2 = buildNonInteractiveSummary(cmdName, argsStr, opts?.config);
|
|
669707
|
-
if (quick2) {
|
|
669708
|
-
return {
|
|
669709
|
-
ok: true,
|
|
669710
|
-
command: cmdName,
|
|
669711
|
-
args: argsStr,
|
|
669712
|
-
kind: "handled",
|
|
669713
|
-
output: quick2,
|
|
669714
|
-
ansi: quick2,
|
|
669715
|
-
durationMs: Date.now() - start2
|
|
669716
|
-
};
|
|
669717
|
-
}
|
|
669718
|
-
const buf = [];
|
|
669719
|
-
setContentWriteHook({
|
|
669720
|
-
begin: () => {
|
|
669721
|
-
},
|
|
669722
|
-
end: () => {
|
|
669723
|
-
},
|
|
669724
|
-
redirect: () => (text2) => {
|
|
669725
|
-
buf.push(text2);
|
|
669726
|
-
}
|
|
669727
|
-
});
|
|
669728
|
-
const origWrite = process.stdout.write.bind(process.stdout);
|
|
669729
|
-
process.stdout.write = function(chunk, ...rest2) {
|
|
669730
|
-
if (typeof chunk === "string") buf.push(chunk);
|
|
669731
|
-
else if (chunk instanceof Buffer) buf.push(chunk.toString("utf-8"));
|
|
669732
|
-
const cb = rest2.find((r2) => typeof r2 === "function");
|
|
669733
|
-
if (cb) cb();
|
|
669734
|
-
return true;
|
|
669735
|
-
};
|
|
669736
|
-
let kind = "handled";
|
|
669737
|
-
let errMsg;
|
|
669738
|
-
try {
|
|
669739
|
-
const ctx3 = buildSyntheticContext(opts?.config, opts?.repoRoot);
|
|
669740
|
-
kind = await handleSlashCommand(slashCmd, ctx3);
|
|
669741
|
-
} catch (e2) {
|
|
669742
|
-
kind = "error";
|
|
669743
|
-
errMsg = e2 instanceof Error ? e2.message : String(e2);
|
|
669744
|
-
buf.push(`
|
|
669745
|
-
[error] ${errMsg}
|
|
669746
|
-
`);
|
|
669747
|
-
} finally {
|
|
669748
|
-
process.stdout.write = origWrite;
|
|
669749
|
-
setContentWriteHook({ begin: () => {
|
|
669750
|
-
}, end: () => {
|
|
669751
|
-
} });
|
|
669752
|
-
}
|
|
669753
|
-
const ansi5 = buf.join("");
|
|
669754
|
-
return {
|
|
669755
|
-
ok: kind !== "error" && kind !== "not_a_command",
|
|
669756
|
-
command: cmdName,
|
|
669757
|
-
args: argsStr,
|
|
669758
|
-
kind,
|
|
669759
|
-
output: stripAnsi5(ansi5).trim(),
|
|
669760
|
-
ansi: ansi5,
|
|
669761
|
-
durationMs: Date.now() - start2,
|
|
669762
|
-
error: errMsg
|
|
669763
|
-
};
|
|
669764
|
-
} finally {
|
|
669765
|
-
release();
|
|
669766
|
-
}
|
|
669694
|
+
import { EventEmitter as EventEmitter12 } from "node:events";
|
|
669695
|
+
function clamp0114(x) {
|
|
669696
|
+
return x < 0 ? 0 : x > 1 ? 1 : x;
|
|
669767
669697
|
}
|
|
669768
|
-
function
|
|
669769
|
-
|
|
669770
|
-
|
|
669771
|
-
|
|
669772
|
-
"omnius setup",
|
|
669773
|
-
"",
|
|
669774
|
-
"The setup wizard is an interactive terminal flow. In the GUI command bridge it is summarized instead of opening prompts, installing software, starting Ollama, or pulling models.",
|
|
669775
|
-
"",
|
|
669776
|
-
`Current backend: ${cfg.backendType ?? "ollama"}`,
|
|
669777
|
-
`Current endpoint: ${cfg.backendUrl ?? "http://127.0.0.1:11434"}`,
|
|
669778
|
-
`Current model: ${cfg.model ?? "qwen3.5:latest"}`,
|
|
669779
|
-
"",
|
|
669780
|
-
"Available non-interactive setup actions:",
|
|
669781
|
-
" /endpoint <url> Set or inspect the inference endpoint.",
|
|
669782
|
-
" /model <name> Set the active model directly.",
|
|
669783
|
-
" /models Show model-selection guidance.",
|
|
669784
|
-
" /config Inspect persisted configuration.",
|
|
669785
|
-
" /doctor Run diagnostics from the terminal if deeper repair is needed.",
|
|
669786
|
-
"",
|
|
669787
|
-
"Open a terminal and run `omnius`, then use /setup for the full guided wizard."
|
|
669788
|
-
].join("\n");
|
|
669789
|
-
}
|
|
669790
|
-
if (cmdName === "models") {
|
|
669791
|
-
return [
|
|
669792
|
-
"omnius models",
|
|
669793
|
-
"",
|
|
669794
|
-
"The model picker is interactive in the TUI. The GUI bridge does not probe remote model endpoints here, so it cannot hang on a stale backend.",
|
|
669795
|
-
"",
|
|
669796
|
-
`Active model: ${cfg.model ?? "qwen3.5:latest"}`,
|
|
669797
|
-
`Endpoint: ${cfg.backendUrl ?? "http://127.0.0.1:11434"}`,
|
|
669798
|
-
`Backend: ${cfg.backendType ?? "ollama"}`,
|
|
669799
|
-
"",
|
|
669800
|
-
"Use /model <name> to set a model directly, /endpoint to switch providers, or open the TUI for the searchable model picker."
|
|
669801
|
-
].join("\n");
|
|
669802
|
-
}
|
|
669803
|
-
return null;
|
|
669698
|
+
function alnumRatio(s2) {
|
|
669699
|
+
if (!s2) return 0;
|
|
669700
|
+
const al = (s2.match(/[\p{L}\p{N}]/gu) || []).length;
|
|
669701
|
+
return al / s2.length;
|
|
669804
669702
|
}
|
|
669805
|
-
function
|
|
669806
|
-
|
|
669807
|
-
|
|
669808
|
-
release = res;
|
|
669809
|
-
});
|
|
669810
|
-
const prev = _passthroughLock;
|
|
669811
|
-
_passthroughLock = next;
|
|
669812
|
-
return prev.then(() => release);
|
|
669703
|
+
function wordCount(s2) {
|
|
669704
|
+
const words = s2.trim().match(/[\p{L}\p{N}][\p{L}\p{N}'’_-]*/gu);
|
|
669705
|
+
return words ? words.length : 0;
|
|
669813
669706
|
}
|
|
669814
|
-
function
|
|
669815
|
-
|
|
669816
|
-
|
|
669817
|
-
|
|
669818
|
-
|
|
669819
|
-
|
|
669820
|
-
|
|
669821
|
-
repoRoot: root,
|
|
669822
|
-
rl: makeRejectingReadline(),
|
|
669823
|
-
setModel: (_model) => {
|
|
669824
|
-
},
|
|
669825
|
-
setVerbose: (_verbose) => {
|
|
669826
|
-
},
|
|
669827
|
-
setEndpoint: (_url, _t, _k) => {
|
|
669828
|
-
},
|
|
669829
|
-
deactivateStatusBar: () => {
|
|
669830
|
-
},
|
|
669831
|
-
disableMouse: () => {
|
|
669832
|
-
},
|
|
669833
|
-
enableMouse: () => {
|
|
669834
|
-
},
|
|
669835
|
-
isMouseEnabled: () => false,
|
|
669836
|
-
lockFooter: () => {
|
|
669837
|
-
},
|
|
669838
|
-
unlockFooter: () => {
|
|
669839
|
-
},
|
|
669840
|
-
stopBanner: () => {
|
|
669841
|
-
},
|
|
669842
|
-
killEphemeral: () => {
|
|
669843
|
-
},
|
|
669844
|
-
setKeyPool: (_keys) => {
|
|
669845
|
-
},
|
|
669846
|
-
clearScreen: () => {
|
|
669847
|
-
},
|
|
669848
|
-
newSession: () => {
|
|
669849
|
-
},
|
|
669850
|
-
refreshBanner: () => {
|
|
669851
|
-
},
|
|
669852
|
-
exit: () => {
|
|
669853
|
-
renderError("/quit and /exit are TUI-only — close the browser tab to end the GUI session.");
|
|
669854
|
-
},
|
|
669855
|
-
voiceToggle: async () => {
|
|
669856
|
-
renderError(`voice ${TUI_ONLY_HINT} — use the GUI voice button instead.`);
|
|
669857
|
-
return "voice not available in GUI";
|
|
669858
|
-
},
|
|
669859
|
-
voiceSetModel: async (_id2) => {
|
|
669860
|
-
renderError(`voice model ${TUI_ONLY_HINT}`);
|
|
669861
|
-
return "";
|
|
669862
|
-
},
|
|
669863
|
-
getColors: () => colorsEnabled,
|
|
669864
|
-
setColors: (enabled2) => {
|
|
669865
|
-
colorsEnabled = enabled2;
|
|
669866
|
-
},
|
|
669867
|
-
getSelfModify: () => selfModifyEnabled,
|
|
669868
|
-
setSelfModify: (enabled2) => {
|
|
669869
|
-
selfModifyEnabled = enabled2;
|
|
669870
|
-
},
|
|
669871
|
-
saveSettings: (settings) => {
|
|
669872
|
-
saveProjectSettings(root, settings);
|
|
669873
|
-
saveGlobalSettings(settings);
|
|
669874
|
-
},
|
|
669875
|
-
saveLocalSettings: (settings) => {
|
|
669876
|
-
saveProjectSettings(root, settings);
|
|
669707
|
+
function repeatingCharPenalty(s2) {
|
|
669708
|
+
let maxRun = 1, cur = 1;
|
|
669709
|
+
for (let i2 = 1; i2 < s2.length; i2++) {
|
|
669710
|
+
if (s2[i2] === s2[i2 - 1]) cur++;
|
|
669711
|
+
else {
|
|
669712
|
+
if (cur > maxRun) maxRun = cur;
|
|
669713
|
+
cur = 1;
|
|
669877
669714
|
}
|
|
669878
|
-
};
|
|
669879
|
-
}
|
|
669880
|
-
function makeRejectingReadline() {
|
|
669881
|
-
const reject = () => {
|
|
669882
|
-
throw new Error(
|
|
669883
|
-
"interactive prompts are not supported via the GUI command bridge — run this command from the TUI (open a terminal and type `omnius`)."
|
|
669884
|
-
);
|
|
669885
|
-
};
|
|
669886
|
-
const noop2 = () => {
|
|
669887
|
-
};
|
|
669888
|
-
return {
|
|
669889
|
-
question: (_q, _cb) => reject(),
|
|
669890
|
-
close: noop2,
|
|
669891
|
-
write: noop2,
|
|
669892
|
-
on: () => noop2,
|
|
669893
|
-
once: () => noop2,
|
|
669894
|
-
off: () => noop2,
|
|
669895
|
-
removeListener: () => noop2,
|
|
669896
|
-
pause: noop2,
|
|
669897
|
-
resume: noop2,
|
|
669898
|
-
setPrompt: noop2,
|
|
669899
|
-
prompt: noop2,
|
|
669900
|
-
line: "",
|
|
669901
|
-
cursor: 0,
|
|
669902
|
-
terminal: false,
|
|
669903
|
-
input: { isTTY: false, on: noop2, once: noop2, removeListener: noop2, pause: noop2, resume: noop2 },
|
|
669904
|
-
output: { write: noop2, columns: 80, rows: 24, isTTY: false }
|
|
669905
|
-
};
|
|
669906
|
-
}
|
|
669907
|
-
var _passthroughLock, TUI_ONLY_HINT;
|
|
669908
|
-
var init_command_passthrough = __esm({
|
|
669909
|
-
"packages/cli/src/api/command-passthrough.ts"() {
|
|
669910
|
-
"use strict";
|
|
669911
|
-
init_render();
|
|
669912
|
-
init_commands();
|
|
669913
|
-
init_config();
|
|
669914
|
-
init_omnius_directory();
|
|
669915
|
-
_passthroughLock = Promise.resolve();
|
|
669916
|
-
TUI_ONLY_HINT = "(this command is TUI-only — no-op in GUI)";
|
|
669917
669715
|
}
|
|
669918
|
-
|
|
669919
|
-
|
|
669920
|
-
|
|
669921
|
-
|
|
669922
|
-
|
|
669923
|
-
|
|
669924
|
-
|
|
669925
|
-
|
|
669926
|
-
|
|
669927
|
-
|
|
669928
|
-
|
|
669929
|
-
|
|
669930
|
-
|
|
669931
|
-
|
|
669932
|
-
|
|
669933
|
-
|
|
669934
|
-
|
|
669935
|
-
|
|
669936
|
-
|
|
669937
|
-
if (!existsSync135(PROJECTS_FILE)) return { projects: [], schemaVersion: 1 };
|
|
669938
|
-
const raw = readFileSync112(PROJECTS_FILE, "utf8");
|
|
669939
|
-
const parsed = JSON.parse(raw);
|
|
669940
|
-
if (!parsed || !Array.isArray(parsed.projects)) return { projects: [], schemaVersion: 1 };
|
|
669941
|
-
return { projects: parsed.projects, schemaVersion: 1 };
|
|
669942
|
-
} catch {
|
|
669943
|
-
return { projects: [], schemaVersion: 1 };
|
|
669716
|
+
if (cur > maxRun) maxRun = cur;
|
|
669717
|
+
return Math.min(1, Math.max(0, (maxRun - 3) / 10));
|
|
669718
|
+
}
|
|
669719
|
+
function computeSignalFromText(text2, confidence2) {
|
|
669720
|
+
const t2 = text2.trim();
|
|
669721
|
+
if (!t2) return 0;
|
|
669722
|
+
if (NOISE_ONLY_RE.test(t2)) return 0.05;
|
|
669723
|
+
const len = t2.length;
|
|
669724
|
+
const wc = wordCount(t2);
|
|
669725
|
+
const alpha = alnumRatio(t2);
|
|
669726
|
+
let score = 0;
|
|
669727
|
+
if (wc >= 6 && alpha >= 0.6) score = 0.85;
|
|
669728
|
+
else if (wc >= 3 && alpha >= 0.5) score = 0.7;
|
|
669729
|
+
else if (wc >= 2 && alpha >= 0.4) score = 0.5;
|
|
669730
|
+
else if (wc >= 1 && alpha >= 0.3 && len >= 4) score = 0.35;
|
|
669731
|
+
else score = 0.15;
|
|
669732
|
+
score -= repeatingCharPenalty(t2) * 0.4;
|
|
669733
|
+
if (typeof confidence2 === "number" && !Number.isNaN(confidence2)) {
|
|
669734
|
+
score = 0.7 * score + 0.3 * clamp0114(confidence2);
|
|
669944
669735
|
}
|
|
669736
|
+
return clamp0114(score);
|
|
669945
669737
|
}
|
|
669946
|
-
function
|
|
669947
|
-
|
|
669948
|
-
const tmp = `${PROJECTS_FILE}.${randomUUID18().slice(0, 8)}.tmp`;
|
|
669949
|
-
writeFileSync74(tmp, JSON.stringify(file, null, 2), "utf8");
|
|
669950
|
-
renameSync11(tmp, PROJECTS_FILE);
|
|
669738
|
+
function truncateForLog(s2, n2) {
|
|
669739
|
+
return s2.length <= n2 ? s2 : s2.slice(0, n2 - 1) + "…";
|
|
669951
669740
|
}
|
|
669952
|
-
function
|
|
669953
|
-
const
|
|
669954
|
-
const
|
|
669955
|
-
|
|
669741
|
+
function extractToolJson(text2) {
|
|
669742
|
+
const lines = text2.split(/\r?\n/);
|
|
669743
|
+
for (const line of lines) {
|
|
669744
|
+
const t2 = line.trim();
|
|
669745
|
+
if (!t2.startsWith("{") || !t2.endsWith("}")) continue;
|
|
669956
669746
|
try {
|
|
669957
|
-
|
|
669747
|
+
const obj = JSON.parse(t2);
|
|
669748
|
+
if (typeof obj.tool === "string") {
|
|
669749
|
+
const name10 = obj.tool;
|
|
669750
|
+
const args = obj.args && typeof obj.args === "object" ? obj.args : {};
|
|
669751
|
+
return { name: name10, args };
|
|
669752
|
+
}
|
|
669958
669753
|
} catch {
|
|
669959
669754
|
}
|
|
669960
669755
|
}
|
|
669961
|
-
|
|
669962
|
-
return alive;
|
|
669963
|
-
}
|
|
669964
|
-
function registerProject(root, pid) {
|
|
669965
|
-
const canonical = resolve59(root);
|
|
669966
|
-
const now = Date.now();
|
|
669967
|
-
const file = readAll2();
|
|
669968
|
-
const existing = file.projects.find((p2) => p2.root === canonical);
|
|
669969
|
-
let entry;
|
|
669970
|
-
if (existing) {
|
|
669971
|
-
entry = {
|
|
669972
|
-
...existing,
|
|
669973
|
-
lastSeen: now,
|
|
669974
|
-
pid: pid ?? existing.pid,
|
|
669975
|
-
omniusDir: join148(canonical, ".omnius")
|
|
669976
|
-
};
|
|
669977
|
-
file.projects = file.projects.map((p2) => p2.root === canonical ? entry : p2);
|
|
669978
|
-
} else {
|
|
669979
|
-
entry = {
|
|
669980
|
-
root: canonical,
|
|
669981
|
-
name: basename36(canonical) || canonical,
|
|
669982
|
-
firstSeen: now,
|
|
669983
|
-
lastSeen: now,
|
|
669984
|
-
pid: pid ?? null,
|
|
669985
|
-
omniusDir: join148(canonical, ".omnius")
|
|
669986
|
-
};
|
|
669987
|
-
file.projects.push(entry);
|
|
669988
|
-
}
|
|
669989
|
-
writeAll(file);
|
|
669990
|
-
return entry;
|
|
669991
|
-
}
|
|
669992
|
-
function unregisterProject(root) {
|
|
669993
|
-
const canonical = resolve59(root);
|
|
669994
|
-
const file = readAll2();
|
|
669995
|
-
const before = file.projects.length;
|
|
669996
|
-
file.projects = file.projects.filter((p2) => p2.root !== canonical);
|
|
669997
|
-
if (file.projects.length === before) return false;
|
|
669998
|
-
writeAll(file);
|
|
669999
|
-
return true;
|
|
670000
|
-
}
|
|
670001
|
-
function renameProject(root, name10) {
|
|
670002
|
-
const canonical = resolve59(root);
|
|
670003
|
-
const file = readAll2();
|
|
670004
|
-
const idx = file.projects.findIndex((p2) => p2.root === canonical);
|
|
670005
|
-
if (idx < 0) return null;
|
|
670006
|
-
const next = { ...file.projects[idx], name: name10.trim() || file.projects[idx].name };
|
|
670007
|
-
file.projects[idx] = next;
|
|
670008
|
-
writeAll(file);
|
|
670009
|
-
return next;
|
|
669756
|
+
return null;
|
|
670010
669757
|
}
|
|
670011
|
-
function
|
|
670012
|
-
|
|
669758
|
+
function extractToolJsonLoose(text2) {
|
|
669759
|
+
const stripped = text2.replace(/```[a-zA-Z]*|```/g, "\n");
|
|
669760
|
+
const exact = extractToolJson(stripped);
|
|
669761
|
+
if (exact) return exact;
|
|
669762
|
+
const match = stripped.match(/[\{][\s\S]*[\}]/);
|
|
669763
|
+
if (match) {
|
|
670013
669764
|
try {
|
|
670014
|
-
|
|
670015
|
-
|
|
670016
|
-
|
|
669765
|
+
const obj = JSON.parse(match[0]);
|
|
669766
|
+
if (typeof obj.tool === "string") {
|
|
669767
|
+
const args = obj.args && typeof obj.args === "object" ? obj.args : {};
|
|
669768
|
+
return { name: obj.tool, args };
|
|
670017
669769
|
}
|
|
670018
669770
|
} catch {
|
|
670019
669771
|
}
|
|
670020
669772
|
}
|
|
670021
|
-
|
|
670022
|
-
const all2 = listProjects();
|
|
670023
|
-
return all2.find((p2) => p2.root === currentRoot) ?? null;
|
|
670024
|
-
}
|
|
670025
|
-
function setCurrentProject(root) {
|
|
670026
|
-
const canonical = resolve59(root);
|
|
670027
|
-
const entry = listProjects().find((p2) => p2.root === canonical);
|
|
670028
|
-
if (!entry) return null;
|
|
670029
|
-
currentRoot = canonical;
|
|
670030
|
-
try {
|
|
670031
|
-
mkdirSync84(OMNIUS_DIR3, { recursive: true });
|
|
670032
|
-
writeFileSync74(CURRENT_FILE, `${canonical}
|
|
670033
|
-
`, "utf8");
|
|
670034
|
-
} catch {
|
|
670035
|
-
}
|
|
670036
|
-
return entry;
|
|
669773
|
+
return null;
|
|
670037
669774
|
}
|
|
670038
|
-
function
|
|
670039
|
-
|
|
669775
|
+
function stripToolJsonLines(text2) {
|
|
669776
|
+
const lines = text2.split(/\r?\n/);
|
|
669777
|
+
const kept = lines.filter((l2) => {
|
|
669778
|
+
const t2 = l2.trim();
|
|
669779
|
+
if (!t2.startsWith("{") || !t2.endsWith("}")) return true;
|
|
669780
|
+
try {
|
|
669781
|
+
const obj = JSON.parse(t2);
|
|
669782
|
+
return !(typeof obj.tool === "string");
|
|
669783
|
+
} catch {
|
|
669784
|
+
return true;
|
|
669785
|
+
}
|
|
669786
|
+
});
|
|
669787
|
+
return kept.join("\n").trim();
|
|
670040
669788
|
}
|
|
670041
|
-
var
|
|
670042
|
-
var
|
|
670043
|
-
"packages/cli/src/
|
|
669789
|
+
var VAD_SILENCE_MS, MAX_SEGMENT_MS, MAX_CONTEXT_TURNS, SYSTEM_PROMPT2, MIN_SIGNAL_SCORE, NOISE_ONLY_RE, VoiceChatSession;
|
|
669790
|
+
var init_voicechat = __esm({
|
|
669791
|
+
"packages/cli/src/tui/voicechat.ts"() {
|
|
670044
669792
|
"use strict";
|
|
670045
|
-
|
|
670046
|
-
|
|
670047
|
-
|
|
670048
|
-
|
|
670049
|
-
}
|
|
670050
|
-
});
|
|
669793
|
+
VAD_SILENCE_MS = 3e3;
|
|
669794
|
+
MAX_SEGMENT_MS = 6500;
|
|
669795
|
+
MAX_CONTEXT_TURNS = 20;
|
|
669796
|
+
SYSTEM_PROMPT2 = `You are a voice assistant having a live spoken conversation. Keep responses extremely brief — 1-2 sentences max. You're speaking aloud, not writing. Be conversational, direct, and helpful. Don't use markdown or formatting — just natural speech.
|
|
670051
669797
|
|
|
670052
|
-
|
|
670053
|
-
|
|
670054
|
-
|
|
670055
|
-
|
|
670056
|
-
|
|
670057
|
-
|
|
670058
|
-
|
|
670059
|
-
|
|
670060
|
-
|
|
670061
|
-
|
|
670062
|
-
|
|
670063
|
-
|
|
670064
|
-
|
|
670065
|
-
|
|
670066
|
-
|
|
670067
|
-
|
|
670068
|
-
|
|
670069
|
-
|
|
670070
|
-
|
|
669798
|
+
Rules:
|
|
669799
|
+
- Never invent environment facts (cwd, OS, specs, repo state). If you need a precise fact from the main agent, request a tool by outputting on a single line EXACTLY one JSON object: {"tool": string, "args": object} and nothing else. Then wait for the tool result before answering.
|
|
669800
|
+
- You may also request to relay a user task to the main agent by emitting {"tool":"voice_to_main","args":{"message":"...","start":true}}.
|
|
669801
|
+
- Prefer tools for factual queries; otherwise, answer directly with a short reply.`;
|
|
669802
|
+
MIN_SIGNAL_SCORE = 0.4;
|
|
669803
|
+
NOISE_ONLY_RE = /^(?:[.·…\s,;:!?\-–—_()\[\]{}"'`]+|(?:uh|um|erm|hmm|mm+|uhh+|umm+)[\s.!?]*)+$/i;
|
|
669804
|
+
VoiceChatSession = class extends EventEmitter12 {
|
|
669805
|
+
voice;
|
|
669806
|
+
listen;
|
|
669807
|
+
backendUrl;
|
|
669808
|
+
model;
|
|
669809
|
+
apiKey;
|
|
669810
|
+
runner;
|
|
669811
|
+
toolRelay = null;
|
|
669812
|
+
verbose = false;
|
|
669813
|
+
debugSnr = false;
|
|
669814
|
+
heuristicsEnabled = true;
|
|
669815
|
+
toolCatalogNote = null;
|
|
669816
|
+
// State machine
|
|
669817
|
+
_state = "IDLE";
|
|
669818
|
+
active = false;
|
|
669819
|
+
// Conversation context — own turns, separate from main agent
|
|
669820
|
+
context = [];
|
|
669821
|
+
turnCount = 0;
|
|
669822
|
+
// Transcripts — separate logs for user<->voice and relay voice<->main
|
|
669823
|
+
voiceTranscript = [];
|
|
669824
|
+
relayTranscript = [];
|
|
669825
|
+
// VAD segment capture
|
|
669826
|
+
captureBuffer = "";
|
|
669827
|
+
captureStartTime = 0;
|
|
669828
|
+
silenceTimer = null;
|
|
669829
|
+
maxSegmentTimer = null;
|
|
669830
|
+
lastSignalScore = null;
|
|
669831
|
+
// Abort control for inference
|
|
669832
|
+
abortController = null;
|
|
669833
|
+
// Callbacks
|
|
669834
|
+
onStatus;
|
|
669835
|
+
onUserSpeech;
|
|
669836
|
+
onPartialTranscript;
|
|
669837
|
+
onAgentSpeech;
|
|
669838
|
+
onStateChange;
|
|
669839
|
+
// Bound handlers for cleanup
|
|
669840
|
+
_onTranscript = null;
|
|
669841
|
+
_onError = null;
|
|
669842
|
+
_retryMicTimer = null;
|
|
669843
|
+
constructor(opts) {
|
|
670071
669844
|
super();
|
|
670072
|
-
this.
|
|
670073
|
-
this.
|
|
670074
|
-
this.
|
|
670075
|
-
this.
|
|
670076
|
-
|
|
670077
|
-
|
|
670078
|
-
this.
|
|
670079
|
-
this.
|
|
670080
|
-
|
|
670081
|
-
|
|
670082
|
-
|
|
670083
|
-
|
|
670084
|
-
|
|
670085
|
-
|
|
670086
|
-
|
|
670087
|
-
|
|
670088
|
-
|
|
670089
|
-
|
|
670090
|
-
|
|
670091
|
-
|
|
670092
|
-
|
|
670093
|
-
|
|
670094
|
-
|
|
670095
|
-
|
|
669845
|
+
this.voice = opts.voice;
|
|
669846
|
+
this.listen = opts.listen;
|
|
669847
|
+
this.backendUrl = opts.backendUrl.replace(/\/+$/, "");
|
|
669848
|
+
this.model = opts.model;
|
|
669849
|
+
this.apiKey = opts.apiKey ?? "";
|
|
669850
|
+
this.runner = opts.runner ?? null;
|
|
669851
|
+
this.toolRelay = opts.toolRelay ?? null;
|
|
669852
|
+
this.verbose = Boolean(opts.verbose);
|
|
669853
|
+
this.debugSnr = Boolean(opts.debugSnr);
|
|
669854
|
+
this.heuristicsEnabled = opts.heuristicsEnabled !== false;
|
|
669855
|
+
if (typeof opts.vadSilenceMs === "number" && opts.vadSilenceMs > 0) {
|
|
669856
|
+
this._vadSilenceMs = Math.floor(opts.vadSilenceMs);
|
|
669857
|
+
}
|
|
669858
|
+
this.onStatus = opts.onStatus ?? (() => {
|
|
669859
|
+
});
|
|
669860
|
+
this.onUserSpeech = opts.onUserSpeech ?? (() => {
|
|
669861
|
+
});
|
|
669862
|
+
this.onPartialTranscript = opts.onPartialTranscript ?? (() => {
|
|
669863
|
+
});
|
|
669864
|
+
this.onAgentSpeech = opts.onAgentSpeech ?? (() => {
|
|
669865
|
+
});
|
|
669866
|
+
this.onStateChange = opts.onStateChange ?? (() => {
|
|
669867
|
+
});
|
|
669868
|
+
}
|
|
669869
|
+
get state() {
|
|
669870
|
+
return this._state;
|
|
669871
|
+
}
|
|
669872
|
+
get isActive() {
|
|
669873
|
+
return this.active;
|
|
669874
|
+
}
|
|
669875
|
+
// ---------------------------------------------------------------------------
|
|
669876
|
+
// State transitions
|
|
669877
|
+
// ---------------------------------------------------------------------------
|
|
669878
|
+
setState(next) {
|
|
669879
|
+
if (this._state === next) return;
|
|
669880
|
+
const prev = this._state;
|
|
669881
|
+
this._state = next;
|
|
669882
|
+
this.onStateChange(next);
|
|
669883
|
+
this.emit("stateChange", { from: prev, to: next });
|
|
669884
|
+
}
|
|
669885
|
+
// ---------------------------------------------------------------------------
|
|
669886
|
+
// Start / Stop
|
|
669887
|
+
// ---------------------------------------------------------------------------
|
|
669888
|
+
async start() {
|
|
669889
|
+
if (this.active) return;
|
|
669890
|
+
if (!this.voice.enabled || !this.voice.ready) {
|
|
669891
|
+
this.onStatus("Enabling voice engine...");
|
|
669892
|
+
await this.voice.toggle();
|
|
669893
|
+
}
|
|
669894
|
+
this.active = true;
|
|
669895
|
+
this.context = [{ role: "system", content: SYSTEM_PROMPT2 }];
|
|
669896
|
+
if (this.toolRelay) {
|
|
669897
|
+
this.toolCatalogNote = `Available tools (emit one-line JSON: {"tool":string,"args":object}):
|
|
669898
|
+
- voice_env{} → environment facts (cwd, os, cpu, mem)
|
|
669899
|
+
- voice_status{} → main-agent status
|
|
669900
|
+
- voice_list_files{dir?: string='.'} → list directory (bounded)
|
|
669901
|
+
- voice_read_file{path: string, max?: number=2048} → read file snippet
|
|
669902
|
+
- voice_to_main{message: string, start?: boolean=true} → relay/start task`;
|
|
669903
|
+
this.context.push({ role: "system", content: this.toolCatalogNote });
|
|
669904
|
+
}
|
|
669905
|
+
this.turnCount = 0;
|
|
669906
|
+
if (this.verbose) this.onStatus("VoiceChat active — LISTENING");
|
|
669907
|
+
this._onTranscript = (...args) => {
|
|
669908
|
+
let text2;
|
|
669909
|
+
let isFinal;
|
|
669910
|
+
let snr;
|
|
669911
|
+
let confidence2;
|
|
669912
|
+
if (typeof args[0] === "object" && args[0] !== null) {
|
|
669913
|
+
const evt = args[0];
|
|
669914
|
+
text2 = evt.text ?? "";
|
|
669915
|
+
isFinal = evt.isFinal ?? false;
|
|
669916
|
+
snr = evt.snr;
|
|
669917
|
+
confidence2 = evt.confidence;
|
|
669918
|
+
} else {
|
|
669919
|
+
text2 = String(args[0] ?? "");
|
|
669920
|
+
isFinal = Boolean(args[1]);
|
|
669921
|
+
}
|
|
669922
|
+
if (!text2.trim()) return;
|
|
669923
|
+
this.handleTranscript(text2.trim(), isFinal, snr, confidence2);
|
|
669924
|
+
};
|
|
669925
|
+
this._onError = (err) => {
|
|
669926
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
669927
|
+
this.onStatus(`ASR error (voicechat continues without mic): ${msg.slice(0, 80)}`);
|
|
669928
|
+
if (this.active && !this._retryMicTimer) {
|
|
669929
|
+
this._retryMicTimer = setTimeout(async () => {
|
|
669930
|
+
this._retryMicTimer = null;
|
|
669931
|
+
if (!this.active) return;
|
|
669932
|
+
try {
|
|
669933
|
+
await this.listen.stop().catch(() => {
|
|
669934
|
+
});
|
|
669935
|
+
await this.listen.start();
|
|
669936
|
+
if (this.verbose) this.onStatus("Mic auto-recovered — LISTENING");
|
|
669937
|
+
} catch {
|
|
669938
|
+
}
|
|
669939
|
+
}, 1e3);
|
|
669940
|
+
}
|
|
669941
|
+
};
|
|
669942
|
+
this.listen.on("transcript", this._onTranscript);
|
|
669943
|
+
this.listen.on("error", this._onError);
|
|
669944
|
+
try {
|
|
669945
|
+
await this.listen.start();
|
|
669946
|
+
this.setState("LISTENING");
|
|
669947
|
+
if (this.verbose) this.onStatus("Mic active — LISTENING for speech...");
|
|
669948
|
+
} catch (err) {
|
|
669949
|
+
this.onStatus(`Mic failed: ${err instanceof Error ? err.message : String(err)}. VoiceChat active without mic.`);
|
|
669950
|
+
this.setState("LISTENING");
|
|
669951
|
+
}
|
|
669952
|
+
}
|
|
669953
|
+
async stop() {
|
|
669954
|
+
if (!this.active) return;
|
|
669955
|
+
this.active = false;
|
|
669956
|
+
if (this.abortController) {
|
|
669957
|
+
this.abortController.abort();
|
|
669958
|
+
this.abortController = null;
|
|
669959
|
+
}
|
|
669960
|
+
if (this.silenceTimer) {
|
|
669961
|
+
clearTimeout(this.silenceTimer);
|
|
669962
|
+
this.silenceTimer = null;
|
|
669963
|
+
}
|
|
669964
|
+
if (this.maxSegmentTimer) {
|
|
669965
|
+
clearTimeout(this.maxSegmentTimer);
|
|
669966
|
+
this.maxSegmentTimer = null;
|
|
669967
|
+
}
|
|
669968
|
+
if (this.captureBuffer.trim() && (this._state === "CAPTURING" || this._state === "TRANSCRIBING")) {
|
|
669969
|
+
this.finalizeSegment();
|
|
669970
|
+
}
|
|
669971
|
+
if (this._onTranscript) {
|
|
669972
|
+
this.listen.removeAllListeners("transcript");
|
|
669973
|
+
this._onTranscript = null;
|
|
669974
|
+
}
|
|
669975
|
+
if (this._onError) {
|
|
669976
|
+
this.listen.removeAllListeners("error");
|
|
669977
|
+
this._onError = null;
|
|
669978
|
+
}
|
|
669979
|
+
try {
|
|
669980
|
+
await this.listen.stop();
|
|
669981
|
+
} catch {
|
|
669982
|
+
}
|
|
669983
|
+
this.setState("IDLE");
|
|
669984
|
+
if (this.verbose) this.onStatus("VoiceChat ended");
|
|
669985
|
+
this.emit("stopped");
|
|
669986
|
+
}
|
|
669987
|
+
// ---------------------------------------------------------------------------
|
|
669988
|
+
// Transcript handling — VAD-style segment capture (Voryn pattern)
|
|
669989
|
+
// ---------------------------------------------------------------------------
|
|
669990
|
+
handleTranscript(text2, isFinal, snr, confidence2) {
|
|
669991
|
+
if (!this.active) return;
|
|
669992
|
+
if (this._state !== "LISTENING" && this._state !== "CAPTURING") {
|
|
669993
|
+
return;
|
|
669994
|
+
}
|
|
669995
|
+
if (this._state === "LISTENING") {
|
|
669996
|
+
this.setState("CAPTURING");
|
|
669997
|
+
this.captureBuffer = "";
|
|
669998
|
+
this.captureStartTime = Date.now();
|
|
669999
|
+
this.maxSegmentTimer = setTimeout(() => {
|
|
670000
|
+
if (this._state === "CAPTURING") {
|
|
670001
|
+
this.finalizeSegment();
|
|
670002
|
+
}
|
|
670003
|
+
}, MAX_SEGMENT_MS);
|
|
670004
|
+
}
|
|
670005
|
+
this.captureBuffer = text2;
|
|
670006
|
+
this.lastSignalScore = typeof snr === "number" && !Number.isNaN(snr) ? clamp0114(snr) : computeSignalFromText(text2, confidence2);
|
|
670007
|
+
this.emit("snr", { score: this.lastSignalScore });
|
|
670008
|
+
this.onPartialTranscript(text2);
|
|
670009
|
+
if (this.silenceTimer) clearTimeout(this.silenceTimer);
|
|
670010
|
+
const waitMs = this._vadSilenceMs ?? VAD_SILENCE_MS;
|
|
670011
|
+
this.silenceTimer = setTimeout(() => {
|
|
670012
|
+
if (this._state === "CAPTURING") {
|
|
670013
|
+
this.finalizeSegment();
|
|
670014
|
+
}
|
|
670015
|
+
}, waitMs);
|
|
670016
|
+
}
|
|
670017
|
+
// ---------------------------------------------------------------------------
|
|
670018
|
+
// Segment finalization → Transcribing → Thinking → Speaking
|
|
670019
|
+
// ---------------------------------------------------------------------------
|
|
670020
|
+
finalizeSegment() {
|
|
670021
|
+
const text2 = this.captureBuffer.trim();
|
|
670022
|
+
if (this.silenceTimer) {
|
|
670023
|
+
clearTimeout(this.silenceTimer);
|
|
670024
|
+
this.silenceTimer = null;
|
|
670025
|
+
}
|
|
670026
|
+
if (this.maxSegmentTimer) {
|
|
670027
|
+
clearTimeout(this.maxSegmentTimer);
|
|
670028
|
+
this.maxSegmentTimer = null;
|
|
670029
|
+
}
|
|
670030
|
+
this.captureBuffer = "";
|
|
670031
|
+
if (!text2) {
|
|
670032
|
+
this.setState("LISTENING");
|
|
670033
|
+
return;
|
|
670034
|
+
}
|
|
670035
|
+
const score = this.lastSignalScore ?? computeSignalFromText(text2);
|
|
670036
|
+
if (score < MIN_SIGNAL_SCORE || NOISE_ONLY_RE.test(text2)) {
|
|
670037
|
+
if (this.debugSnr) this.onStatus(`Ignoring low-signal utterance (SNR:${score.toFixed(2)}): ${truncateForLog(text2, 48)}`);
|
|
670038
|
+
this.emit("snrFiltered", { score, text: text2 });
|
|
670039
|
+
this.setState("LISTENING");
|
|
670040
|
+
this.captureBuffer = "";
|
|
670041
|
+
this.lastSignalScore = null;
|
|
670042
|
+
return;
|
|
670043
|
+
}
|
|
670044
|
+
this.setState("TRANSCRIBING");
|
|
670045
|
+
this.onUserSpeech(text2);
|
|
670046
|
+
this.voiceTranscript.push({ role: "user", content: text2, ts: Date.now() });
|
|
670047
|
+
this.context.push({ role: "user", content: text2 });
|
|
670048
|
+
this.turnCount++;
|
|
670049
|
+
if (this.runner) {
|
|
670050
|
+
try {
|
|
670051
|
+
this.runner.injectUserMessage(`[VOICECHAT] ${text2}`);
|
|
670052
|
+
} catch {
|
|
670053
|
+
}
|
|
670054
|
+
}
|
|
670055
|
+
while (this.context.length > MAX_CONTEXT_TURNS + 1) {
|
|
670056
|
+
this.context.splice(1, 1);
|
|
670057
|
+
}
|
|
670058
|
+
this.think();
|
|
670059
|
+
}
|
|
670060
|
+
// ---------------------------------------------------------------------------
|
|
670061
|
+
// Direct Ollama inference (not through main agent runner)
|
|
670062
|
+
// ---------------------------------------------------------------------------
|
|
670063
|
+
async think() {
|
|
670064
|
+
if (!this.active) return;
|
|
670065
|
+
this.setState("THINKING");
|
|
670066
|
+
if (this.verbose) this.onStatus("Thinking...");
|
|
670067
|
+
this.abortController = new AbortController();
|
|
670068
|
+
try {
|
|
670069
|
+
if (this.toolRelay?.contextSnapshot) {
|
|
670070
|
+
try {
|
|
670071
|
+
const snap = await Promise.resolve(this.toolRelay.contextSnapshot());
|
|
670072
|
+
if (snap && snap.trim()) {
|
|
670073
|
+
this.context.push({ role: "system", content: `Context snapshot (read-only):
|
|
670074
|
+
${snap.trim()}` });
|
|
670075
|
+
}
|
|
670076
|
+
} catch {
|
|
670077
|
+
}
|
|
670078
|
+
}
|
|
670079
|
+
const lastUser = [...this.context].reverse().find((m2) => m2.role === "user")?.content || "";
|
|
670080
|
+
let preAnswered = false;
|
|
670081
|
+
if (this.heuristicsEnabled && this.toolRelay && lastUser) {
|
|
670082
|
+
const lower = lastUser.toLowerCase();
|
|
670083
|
+
const wantList = /(list|show|explore|browse|what's in|whats in|contents).*(dir|directory|folder|files)/.test(lower);
|
|
670084
|
+
const wantEnv = /(what\s+dir|cwd|current\s+dir|working\s+directory|where\s+are\s+you)/.test(lower);
|
|
670085
|
+
const readMatch = lastUser.match(/(?:read|open|show)\s+file\s+([\w./\\-]+)\b/i);
|
|
670086
|
+
const toMainMatch = lastUser.match(/^(?:start|run|do)\s+(.{5,})$/i);
|
|
670087
|
+
try {
|
|
670088
|
+
if (wantEnv) {
|
|
670089
|
+
const out = await this.toolRelay.call("voice_env", {});
|
|
670090
|
+
this.context.push({ role: "system", content: `Tool voice_env result (authoritative):
|
|
670091
|
+
${out}` });
|
|
670092
|
+
preAnswered = true;
|
|
670093
|
+
} else if (wantList) {
|
|
670094
|
+
const out = await this.toolRelay.call("voice_list_files", { dir: "." });
|
|
670095
|
+
this.context.push({ role: "system", content: `Tool voice_list_files result (authoritative):
|
|
670096
|
+
${out}` });
|
|
670097
|
+
preAnswered = true;
|
|
670098
|
+
} else if (readMatch) {
|
|
670099
|
+
const out = await this.toolRelay.call("voice_read_file", { path: readMatch[1], max: 1024 });
|
|
670100
|
+
this.context.push({ role: "system", content: `Tool voice_read_file result (authoritative):
|
|
670101
|
+
${out}` });
|
|
670102
|
+
preAnswered = true;
|
|
670103
|
+
} else if (toMainMatch) {
|
|
670104
|
+
const msg = toMainMatch[1].trim();
|
|
670105
|
+
const out = await this.toolRelay.call("voice_to_main", { message: msg, start: true });
|
|
670106
|
+
this.relayTranscript.push({ dir: "toMain", content: msg, ts: Date.now() });
|
|
670107
|
+
this.context.push({ role: "system", content: `Tool voice_to_main result (authoritative):
|
|
670108
|
+
${out}` });
|
|
670109
|
+
preAnswered = true;
|
|
670110
|
+
}
|
|
670111
|
+
} catch {
|
|
670112
|
+
}
|
|
670113
|
+
}
|
|
670114
|
+
let response = "";
|
|
670115
|
+
for (let i2 = 0; i2 < 3; i2++) {
|
|
670116
|
+
response = await this.streamOllamaInference(this.abortController.signal);
|
|
670117
|
+
if (!this.toolRelay) break;
|
|
670118
|
+
const toolReq = extractToolJsonLoose(response);
|
|
670119
|
+
if (!toolReq) break;
|
|
670120
|
+
const { name: name10, args } = toolReq;
|
|
670121
|
+
let toolOutput = "";
|
|
670122
|
+
try {
|
|
670123
|
+
toolOutput = await this.toolRelay.call(name10, args);
|
|
670124
|
+
} catch (e2) {
|
|
670125
|
+
toolOutput = `Tool ${name10} failed: ${e2 instanceof Error ? e2.message : String(e2)}`;
|
|
670126
|
+
}
|
|
670127
|
+
if (name10 === "voice_to_main") {
|
|
670128
|
+
const msg = typeof args?.message === "string" ? String(args.message) : "";
|
|
670129
|
+
if (msg) this.relayTranscript.push({ dir: "toMain", content: msg, ts: Date.now() });
|
|
670130
|
+
}
|
|
670131
|
+
this.context.push({ role: "system", content: `Tool ${name10} result (authoritative):
|
|
670132
|
+
${toolOutput}` });
|
|
670133
|
+
}
|
|
670134
|
+
if (!this.active) return;
|
|
670135
|
+
if (this.heuristicsEnabled && this.toolRelay && /\b(can't|cannot)\b/i.test(response) && this.toolCatalogNote) {
|
|
670136
|
+
this.context.push({ role: "system", content: `You have tools. Use them. ${this.toolCatalogNote}` });
|
|
670137
|
+
response = await this.streamOllamaInference(this.abortController.signal);
|
|
670138
|
+
}
|
|
670139
|
+
if (response.trim()) {
|
|
670140
|
+
const finalSpoken = stripToolJsonLines(response.trim());
|
|
670141
|
+
this.context.push({ role: "assistant", content: finalSpoken });
|
|
670142
|
+
this.setState("SPEAKING");
|
|
670143
|
+
this.onAgentSpeech(finalSpoken);
|
|
670144
|
+
try {
|
|
670145
|
+
this.listen.pause();
|
|
670146
|
+
} catch {
|
|
670147
|
+
}
|
|
670148
|
+
this.voice.speak(finalSpoken);
|
|
670149
|
+
this.voiceTranscript.push({ role: "assistant", content: finalSpoken, ts: Date.now() });
|
|
670150
|
+
this.voiceTranscript.push({ role: "assistant", content: response.trim(), ts: Date.now() });
|
|
670151
|
+
if (this.runner) {
|
|
670152
|
+
this.injectSummary();
|
|
670153
|
+
}
|
|
670154
|
+
if (typeof this.voice.waitUntilIdle === "function") {
|
|
670155
|
+
try {
|
|
670156
|
+
await this.voice.waitUntilIdle();
|
|
670157
|
+
} catch {
|
|
670158
|
+
}
|
|
670159
|
+
} else {
|
|
670160
|
+
const estimatedMs = Math.max(1500, response.length / 5 * (6e4 / 150));
|
|
670161
|
+
await new Promise((r2) => setTimeout(r2, estimatedMs));
|
|
670162
|
+
}
|
|
670163
|
+
}
|
|
670164
|
+
} catch (err) {
|
|
670165
|
+
if (!this.active) return;
|
|
670166
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
670167
|
+
if (!msg.includes("abort")) {
|
|
670168
|
+
this.onStatus(`Inference error: ${msg.slice(0, 100)}`);
|
|
670169
|
+
}
|
|
670170
|
+
} finally {
|
|
670171
|
+
this.abortController = null;
|
|
670172
|
+
}
|
|
670173
|
+
if (this.active) {
|
|
670174
|
+
try {
|
|
670175
|
+
await this.listen.resume();
|
|
670176
|
+
} catch {
|
|
670177
|
+
}
|
|
670178
|
+
this.setState("LISTENING");
|
|
670179
|
+
if (this.verbose) this.onStatus("LISTENING...");
|
|
670180
|
+
}
|
|
670181
|
+
}
|
|
670182
|
+
/**
|
|
670183
|
+
* Stream inference. Tries native Ollama /api/chat first (supports think:false
|
|
670184
|
+
* for reasoning models), falls back to OpenAI-compat /v1/chat/completions.
|
|
670185
|
+
*/
|
|
670186
|
+
async streamOllamaInference(signal) {
|
|
670187
|
+
const baseUrl = this.backendUrl.replace(/\/v1\/?$/, "");
|
|
670188
|
+
const headers = { "Content-Type": "application/json" };
|
|
670189
|
+
if (this.apiKey) headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
670190
|
+
try {
|
|
670191
|
+
const nativeBody = JSON.stringify({
|
|
670192
|
+
model: this.model,
|
|
670193
|
+
messages: this.context,
|
|
670194
|
+
stream: true,
|
|
670195
|
+
think: false,
|
|
670196
|
+
// Disable reasoning — voice chat needs fast, direct responses
|
|
670197
|
+
options: { temperature: 0.7, num_predict: 256 }
|
|
670198
|
+
});
|
|
670199
|
+
const res2 = await fetch(`${baseUrl}/api/chat`, {
|
|
670200
|
+
method: "POST",
|
|
670201
|
+
headers,
|
|
670202
|
+
body: nativeBody,
|
|
670203
|
+
signal
|
|
670204
|
+
});
|
|
670205
|
+
if (res2.ok) {
|
|
670206
|
+
return await this.parseOllamaNativeStream(res2, signal);
|
|
670207
|
+
}
|
|
670208
|
+
} catch (err) {
|
|
670209
|
+
const msg = err instanceof Error ? err.message : "";
|
|
670210
|
+
if (msg.includes("abort")) throw err;
|
|
670211
|
+
}
|
|
670212
|
+
const openaiBody = JSON.stringify({
|
|
670213
|
+
model: this.model,
|
|
670214
|
+
messages: this.context,
|
|
670215
|
+
stream: true,
|
|
670216
|
+
temperature: 0.7,
|
|
670217
|
+
max_tokens: 1024
|
|
670218
|
+
});
|
|
670219
|
+
const endpoint = baseUrl.includes("/v1") ? `${baseUrl}/chat/completions` : `${baseUrl}/v1/chat/completions`;
|
|
670220
|
+
const res = await fetch(endpoint, { method: "POST", headers, body: openaiBody, signal });
|
|
670221
|
+
if (!res.ok) {
|
|
670222
|
+
const errText = await res.text().catch(() => "unknown");
|
|
670223
|
+
throw new Error(`Inference ${res.status}: ${errText.slice(0, 200)}`);
|
|
670224
|
+
}
|
|
670225
|
+
return await this.parseOpenAIStream(res);
|
|
670226
|
+
}
|
|
670227
|
+
/** Parse native Ollama /api/chat streaming response (NDJSON, not SSE) */
|
|
670228
|
+
async parseOllamaNativeStream(res, _signal) {
|
|
670229
|
+
const reader = res.body?.getReader();
|
|
670230
|
+
if (!reader) throw new Error("No response body");
|
|
670231
|
+
const decoder = new TextDecoder();
|
|
670232
|
+
let fullText = "";
|
|
670233
|
+
let buffer2 = "";
|
|
670234
|
+
while (true) {
|
|
670235
|
+
const { done, value: value2 } = await reader.read();
|
|
670236
|
+
if (done) break;
|
|
670237
|
+
buffer2 += decoder.decode(value2, { stream: true });
|
|
670238
|
+
const lines = buffer2.split("\n");
|
|
670239
|
+
buffer2 = lines.pop() ?? "";
|
|
670240
|
+
for (const line of lines) {
|
|
670241
|
+
if (!line.trim()) continue;
|
|
670242
|
+
try {
|
|
670243
|
+
const parsed = JSON.parse(line);
|
|
670244
|
+
const content = parsed.message?.content;
|
|
670245
|
+
const thinking = parsed.message?.thinking;
|
|
670246
|
+
if (content && thinking === void 0) {
|
|
670247
|
+
fullText += content;
|
|
670248
|
+
}
|
|
670249
|
+
if (parsed.done) return fullText;
|
|
670250
|
+
} catch {
|
|
670251
|
+
}
|
|
670252
|
+
}
|
|
670253
|
+
}
|
|
670254
|
+
return fullText;
|
|
670255
|
+
}
|
|
670256
|
+
/** Parse OpenAI-compat SSE streaming response */
|
|
670257
|
+
async parseOpenAIStream(res) {
|
|
670258
|
+
const reader = res.body?.getReader();
|
|
670259
|
+
if (!reader) throw new Error("No response body");
|
|
670260
|
+
const decoder = new TextDecoder();
|
|
670261
|
+
let fullText = "";
|
|
670262
|
+
let buffer2 = "";
|
|
670263
|
+
while (true) {
|
|
670264
|
+
const { done, value: value2 } = await reader.read();
|
|
670265
|
+
if (done) break;
|
|
670266
|
+
buffer2 += decoder.decode(value2, { stream: true });
|
|
670267
|
+
const lines = buffer2.split("\n");
|
|
670268
|
+
buffer2 = lines.pop() ?? "";
|
|
670269
|
+
for (const line of lines) {
|
|
670270
|
+
const trimmed = line.trim();
|
|
670271
|
+
if (!trimmed || !trimmed.startsWith("data: ")) continue;
|
|
670272
|
+
const data = trimmed.slice(6);
|
|
670273
|
+
if (data === "[DONE]") continue;
|
|
670274
|
+
try {
|
|
670275
|
+
const parsed = JSON.parse(data);
|
|
670276
|
+
const delta = parsed.choices?.[0]?.delta?.content;
|
|
670277
|
+
if (delta) fullText += delta;
|
|
670278
|
+
} catch {
|
|
670279
|
+
}
|
|
670280
|
+
}
|
|
670281
|
+
}
|
|
670282
|
+
return fullText;
|
|
670283
|
+
}
|
|
670284
|
+
// ---------------------------------------------------------------------------
|
|
670285
|
+
// Summary injection to main agent
|
|
670286
|
+
// ---------------------------------------------------------------------------
|
|
670287
|
+
injectSummary() {
|
|
670288
|
+
if (!this.runner) return;
|
|
670289
|
+
const recentTurns = this.context.filter((t2) => t2.role !== "system").slice(-8).map((t2) => `${t2.role === "user" ? "User" : "Assistant"}: ${t2.content}`).join("\n");
|
|
670290
|
+
this.runner.injectUserMessage(
|
|
670291
|
+
`[VOICECHAT SUMMARY] Parallel voice liaison update (for awareness only). Continue your current task; do not respond to this directly.
|
|
670292
|
+
|
|
670293
|
+
${recentTurns}`
|
|
670294
|
+
);
|
|
670295
|
+
}
|
|
670296
|
+
/** Enqueue narration from main agent events into the voice channel */
|
|
670297
|
+
enqueueAgentNarration(text2, subordinate = true) {
|
|
670298
|
+
if (!text2 || !this.active) return;
|
|
670299
|
+
this.relayTranscript.push({ dir: "fromMain", content: text2, ts: Date.now() });
|
|
670300
|
+
if (subordinate) this.voice.speakSubordinate(text2);
|
|
670301
|
+
else this.voice.speak(text2);
|
|
670302
|
+
}
|
|
670303
|
+
/** Get copies of transcripts for UI/debugging */
|
|
670304
|
+
getTranscripts() {
|
|
670305
|
+
return {
|
|
670306
|
+
voice: this.voiceTranscript.slice(-200),
|
|
670307
|
+
relay: this.relayTranscript.slice(-200)
|
|
670308
|
+
};
|
|
670309
|
+
}
|
|
670310
|
+
};
|
|
670311
|
+
}
|
|
670312
|
+
});
|
|
670313
|
+
|
|
670314
|
+
// packages/cli/src/api/voice-runtime.ts
|
|
670315
|
+
var voice_runtime_exports = {};
|
|
670316
|
+
__export(voice_runtime_exports, {
|
|
670317
|
+
_resetForTests: () => _resetForTests,
|
|
670318
|
+
ensureRuntime: () => ensureRuntime,
|
|
670319
|
+
feedAudioFromClient: () => feedAudioFromClient,
|
|
670320
|
+
getDaemonListenEngine: () => getDaemonListenEngine,
|
|
670321
|
+
getRuntimeStatus: () => getRuntimeStatus,
|
|
670322
|
+
getVoiceBus: () => getVoiceBus,
|
|
670323
|
+
getVoiceEngine: () => getVoiceEngine,
|
|
670324
|
+
isVoiceChatActive: () => isVoiceChatActive,
|
|
670325
|
+
listClients: () => listClients,
|
|
670326
|
+
registerClient: () => registerClient,
|
|
670327
|
+
startVoiceChat: () => startVoiceChat,
|
|
670328
|
+
stopVoiceChat: () => stopVoiceChat,
|
|
670329
|
+
synthesizeAndBroadcast: () => synthesizeAndBroadcast,
|
|
670330
|
+
synthesizeToWav: () => synthesizeToWav,
|
|
670331
|
+
unregisterClient: () => unregisterClient
|
|
670332
|
+
});
|
|
670333
|
+
import { EventEmitter as EventEmitter13 } from "node:events";
|
|
670334
|
+
function getVoiceEngine() {
|
|
670335
|
+
if (!_voiceEngine) {
|
|
670336
|
+
_voiceEngine = new VoiceEngine();
|
|
670337
|
+
}
|
|
670338
|
+
return _voiceEngine;
|
|
670339
|
+
}
|
|
670340
|
+
function getDaemonListenEngine() {
|
|
670341
|
+
if (!_listenEngine) _listenEngine = getListenEngine();
|
|
670342
|
+
return _listenEngine;
|
|
670343
|
+
}
|
|
670344
|
+
function getVoiceBus() {
|
|
670345
|
+
if (!_bus) _bus = new EventEmitter13();
|
|
670346
|
+
return _bus;
|
|
670347
|
+
}
|
|
670348
|
+
function getRuntimeStatus() {
|
|
670349
|
+
return {
|
|
670350
|
+
state: _state3,
|
|
670351
|
+
voiceEnabled: _voiceEngine?.enabled ?? false,
|
|
670352
|
+
voiceReady: _voiceEngine?.ready ?? false,
|
|
670353
|
+
voiceModelId: _voiceEngine?.modelId ?? null,
|
|
670354
|
+
cloneRef: _voiceEngine?.luxttsCloneRef ?? null,
|
|
670355
|
+
listenActive: _listenEngine?.isActive ?? false,
|
|
670356
|
+
listenPaused: _listenEngine?.isPaused ?? false,
|
|
670357
|
+
clientCount: _clients2.size,
|
|
670358
|
+
loadedAt: _loadedAt,
|
|
670359
|
+
lastError: _lastError
|
|
670360
|
+
};
|
|
670361
|
+
}
|
|
670362
|
+
async function ensureRuntime() {
|
|
670363
|
+
if (_state3 === "loading" || _state3 === "listening" || _state3 === "speaking") return;
|
|
670364
|
+
setState("loading");
|
|
670365
|
+
try {
|
|
670366
|
+
const voice = getVoiceEngine();
|
|
670367
|
+
const listen = getDaemonListenEngine();
|
|
670368
|
+
if (!voice.enabled) {
|
|
670369
|
+
await voice.toggle();
|
|
670370
|
+
}
|
|
670371
|
+
if (!listen.isActive) {
|
|
670372
|
+
try {
|
|
670373
|
+
await listen.start();
|
|
670374
|
+
} catch (err) {
|
|
670375
|
+
const m2 = err instanceof Error ? err.message : String(err);
|
|
670376
|
+
_lastError = `listen.start() failed: ${m2}`;
|
|
670377
|
+
getVoiceBus().emit("error", _lastError);
|
|
670378
|
+
}
|
|
670379
|
+
}
|
|
670380
|
+
_loadedAt = Date.now();
|
|
670381
|
+
setState("listening");
|
|
670382
|
+
wireListenToBus();
|
|
670383
|
+
} catch (err) {
|
|
670384
|
+
const m2 = err instanceof Error ? err.message : String(err);
|
|
670385
|
+
_lastError = m2;
|
|
670386
|
+
setState("error");
|
|
670387
|
+
throw err;
|
|
670388
|
+
}
|
|
670389
|
+
}
|
|
670390
|
+
async function registerClient(handle2) {
|
|
670391
|
+
if (_shutdownTimer) {
|
|
670392
|
+
clearTimeout(_shutdownTimer);
|
|
670393
|
+
_shutdownTimer = null;
|
|
670394
|
+
}
|
|
670395
|
+
_clients2.set(handle2.id, handle2);
|
|
670396
|
+
if (_clients2.size === 1 && (_state3 === "idle" || _state3 === "error")) {
|
|
670397
|
+
try {
|
|
670398
|
+
await ensureRuntime();
|
|
670399
|
+
} catch (err) {
|
|
670400
|
+
_clients2.delete(handle2.id);
|
|
670401
|
+
throw err;
|
|
670402
|
+
}
|
|
670403
|
+
}
|
|
670404
|
+
}
|
|
670405
|
+
function unregisterClient(id) {
|
|
670406
|
+
_clients2.delete(id);
|
|
670407
|
+
if (_clients2.size === 0 && _shutdownTimer === null) {
|
|
670408
|
+
_shutdownTimer = setTimeout(() => {
|
|
670409
|
+
_shutdownTimer = null;
|
|
670410
|
+
try {
|
|
670411
|
+
_listenEngine?.pause?.();
|
|
670412
|
+
} catch {
|
|
670413
|
+
}
|
|
670414
|
+
}, IDLE_SHUTDOWN_MS);
|
|
670415
|
+
}
|
|
670416
|
+
}
|
|
670417
|
+
function feedAudioFromClient(clientId, pcmChunk) {
|
|
670418
|
+
if (_ttsSpeaking) return;
|
|
670419
|
+
const listen = _listenEngine;
|
|
670420
|
+
if (!listen || !listen.isActive) return;
|
|
670421
|
+
try {
|
|
670422
|
+
const transcriber = listen.liveTranscriber;
|
|
670423
|
+
if (transcriber?.write) transcriber.write(pcmChunk);
|
|
670424
|
+
} catch {
|
|
670425
|
+
}
|
|
670426
|
+
}
|
|
670427
|
+
async function synthesizeToWav(text2, format3 = "wav") {
|
|
670428
|
+
await ensureRuntime();
|
|
670429
|
+
const voice = _voiceEngine;
|
|
670430
|
+
if (!voice || !voice.ready || !voice.synthesizeToPCM) return null;
|
|
670431
|
+
const result = await voice.synthesizeToPCM(text2);
|
|
670432
|
+
if (!result || !result.pcm || result.pcm.length === 0) return null;
|
|
670433
|
+
const sampleRate = result.sampleRate;
|
|
670434
|
+
const pcm = result.pcm;
|
|
670435
|
+
if (format3 === "pcm") return { bytes: pcm, sampleRate, format: format3 };
|
|
670436
|
+
const header = Buffer.alloc(44);
|
|
670437
|
+
header.write("RIFF", 0);
|
|
670438
|
+
header.writeUInt32LE(36 + pcm.length, 4);
|
|
670439
|
+
header.write("WAVE", 8);
|
|
670440
|
+
header.write("fmt ", 12);
|
|
670441
|
+
header.writeUInt32LE(16, 16);
|
|
670442
|
+
header.writeUInt16LE(1, 20);
|
|
670443
|
+
header.writeUInt16LE(1, 22);
|
|
670444
|
+
header.writeUInt32LE(sampleRate, 24);
|
|
670445
|
+
header.writeUInt32LE(sampleRate * 2, 28);
|
|
670446
|
+
header.writeUInt16LE(2, 32);
|
|
670447
|
+
header.writeUInt16LE(16, 34);
|
|
670448
|
+
header.write("data", 36);
|
|
670449
|
+
header.writeUInt32LE(pcm.length, 40);
|
|
670450
|
+
return { bytes: Buffer.concat([header, pcm]), sampleRate, format: format3 };
|
|
670451
|
+
}
|
|
670452
|
+
async function synthesizeAndBroadcast(text2) {
|
|
670453
|
+
const voice = _voiceEngine;
|
|
670454
|
+
if (!voice || !voice.ready) return;
|
|
670455
|
+
if (!voice.synthesizeToPCM) {
|
|
670456
|
+
getVoiceBus().emit("error", "voice engine has no synthesizeToPCM");
|
|
670457
|
+
return;
|
|
670458
|
+
}
|
|
670459
|
+
setSpeaking(true);
|
|
670460
|
+
try {
|
|
670461
|
+
const result = await voice.synthesizeToPCM(text2);
|
|
670462
|
+
if (!result || !result.pcm || result.pcm.length === 0) return;
|
|
670463
|
+
getVoiceBus().emit("agent_text", { text: text2 });
|
|
670464
|
+
getVoiceBus().emit("tts_pcm", result.pcm, result.sampleRate);
|
|
670465
|
+
} catch (err) {
|
|
670466
|
+
const m2 = err instanceof Error ? err.message : String(err);
|
|
670467
|
+
getVoiceBus().emit("error", `tts: ${m2}`);
|
|
670468
|
+
} finally {
|
|
670469
|
+
setSpeaking(false);
|
|
670470
|
+
}
|
|
670471
|
+
}
|
|
670472
|
+
function listClients() {
|
|
670473
|
+
return Array.from(_clients2.values());
|
|
670474
|
+
}
|
|
670475
|
+
async function startVoiceChat(opts) {
|
|
670476
|
+
if (_voiceChatSession?.isActive) {
|
|
670477
|
+
return { ok: true, message: "VoiceChat already running" };
|
|
670478
|
+
}
|
|
670479
|
+
await ensureRuntime();
|
|
670480
|
+
const voice = getVoiceEngine();
|
|
670481
|
+
const listen = getDaemonListenEngine();
|
|
670482
|
+
if (!voice.ready) return { ok: false, message: "Voice engine not ready" };
|
|
670483
|
+
_voiceChatSession = new VoiceChatSession({
|
|
670484
|
+
voice,
|
|
670485
|
+
listen,
|
|
670486
|
+
backendUrl: opts.backendUrl,
|
|
670487
|
+
model: opts.model,
|
|
670488
|
+
apiKey: opts.apiKey,
|
|
670489
|
+
verbose: opts.verbose === true,
|
|
670490
|
+
onStatus: (msg) => getVoiceBus().emit("status", msg),
|
|
670491
|
+
onUserSpeech: (text2) => getVoiceBus().emit("transcript", { text: text2, final: true }),
|
|
670492
|
+
onPartialTranscript: (text2) => getVoiceBus().emit("transcript", { text: text2, final: false }),
|
|
670493
|
+
onAgentSpeech: (text2) => getVoiceBus().emit("agent_text", { text: text2 }),
|
|
670494
|
+
onStateChange: (s2) => getVoiceBus().emit("session_state", s2)
|
|
670495
|
+
});
|
|
670496
|
+
await _voiceChatSession.start();
|
|
670497
|
+
setState("listening");
|
|
670498
|
+
return { ok: true, message: "VoiceChat started" };
|
|
670499
|
+
}
|
|
670500
|
+
async function stopVoiceChat() {
|
|
670501
|
+
if (!_voiceChatSession) return { ok: true, message: "No active session" };
|
|
670502
|
+
try {
|
|
670503
|
+
if (_voiceChatSession.stop) {
|
|
670504
|
+
await _voiceChatSession.stop();
|
|
670505
|
+
}
|
|
670506
|
+
} catch {
|
|
670507
|
+
}
|
|
670508
|
+
_voiceChatSession = null;
|
|
670509
|
+
setState(_listenEngine?.isActive ? "listening" : "idle");
|
|
670510
|
+
return { ok: true, message: "VoiceChat stopped" };
|
|
670511
|
+
}
|
|
670512
|
+
function isVoiceChatActive() {
|
|
670513
|
+
return _voiceChatSession?.isActive ?? false;
|
|
670514
|
+
}
|
|
670515
|
+
function setState(s2) {
|
|
670516
|
+
if (_state3 === s2) return;
|
|
670517
|
+
_state3 = s2;
|
|
670518
|
+
getVoiceBus().emit("state", s2);
|
|
670519
|
+
}
|
|
670520
|
+
function setSpeaking(speaking) {
|
|
670521
|
+
_ttsSpeaking = speaking;
|
|
670522
|
+
if (speaking) {
|
|
670523
|
+
setState("speaking");
|
|
670524
|
+
getVoiceBus().emit("tts_start");
|
|
670525
|
+
} else {
|
|
670526
|
+
setState(_listenEngine?.isActive ? "listening" : "idle");
|
|
670527
|
+
getVoiceBus().emit("tts_end");
|
|
670528
|
+
}
|
|
670529
|
+
}
|
|
670530
|
+
function wireListenToBus() {
|
|
670531
|
+
if (_wired) return;
|
|
670532
|
+
if (!_listenEngine) return;
|
|
670533
|
+
_wired = true;
|
|
670534
|
+
_listenEngine.on("transcript", (...args) => {
|
|
670535
|
+
const payload = args[0];
|
|
670536
|
+
if (!payload || typeof payload.text !== "string") return;
|
|
670537
|
+
getVoiceBus().emit("transcript", payload);
|
|
670538
|
+
});
|
|
670539
|
+
}
|
|
670540
|
+
function _resetForTests() {
|
|
670541
|
+
_state3 = "idle";
|
|
670542
|
+
_loadedAt = null;
|
|
670543
|
+
_lastError = null;
|
|
670544
|
+
_clients2.clear();
|
|
670545
|
+
_ttsSpeaking = false;
|
|
670546
|
+
if (_shutdownTimer) {
|
|
670547
|
+
clearTimeout(_shutdownTimer);
|
|
670548
|
+
_shutdownTimer = null;
|
|
670549
|
+
}
|
|
670550
|
+
}
|
|
670551
|
+
var _voiceEngine, _listenEngine, _voiceChatSession, _bus, _state3, _loadedAt, _lastError, _clients2, _ttsSpeaking, _shutdownTimer, IDLE_SHUTDOWN_MS, _wired;
|
|
670552
|
+
var init_voice_runtime = __esm({
|
|
670553
|
+
"packages/cli/src/api/voice-runtime.ts"() {
|
|
670554
|
+
"use strict";
|
|
670555
|
+
init_voice();
|
|
670556
|
+
init_listen();
|
|
670557
|
+
init_voicechat();
|
|
670558
|
+
_voiceEngine = null;
|
|
670559
|
+
_listenEngine = null;
|
|
670560
|
+
_voiceChatSession = null;
|
|
670561
|
+
_bus = null;
|
|
670562
|
+
_state3 = "idle";
|
|
670563
|
+
_loadedAt = null;
|
|
670564
|
+
_lastError = null;
|
|
670565
|
+
_clients2 = /* @__PURE__ */ new Map();
|
|
670566
|
+
_ttsSpeaking = false;
|
|
670567
|
+
_shutdownTimer = null;
|
|
670568
|
+
IDLE_SHUTDOWN_MS = 6e4;
|
|
670569
|
+
_wired = false;
|
|
670570
|
+
}
|
|
670571
|
+
});
|
|
670572
|
+
|
|
670573
|
+
// packages/cli/src/api/command-passthrough.ts
|
|
670574
|
+
var command_passthrough_exports = {};
|
|
670575
|
+
__export(command_passthrough_exports, {
|
|
670576
|
+
runCommand: () => runCommand
|
|
670577
|
+
});
|
|
670578
|
+
function stripAnsi5(s2) {
|
|
670579
|
+
return s2.replace(/\x1B(?:\[[\d;?]*[a-zA-Z]|\][^\x07\x1B]*[\x07\x1B]?|[@-Z\\-_])/g, "");
|
|
670580
|
+
}
|
|
670581
|
+
async function runCommand(input, opts) {
|
|
670582
|
+
const start2 = Date.now();
|
|
670583
|
+
const trimmed = input.trim();
|
|
670584
|
+
const slashCmd = trimmed.startsWith("/") ? trimmed : "/" + trimmed;
|
|
670585
|
+
const [rawCmd, ...rest] = slashCmd.slice(1).split(/\s+/);
|
|
670586
|
+
const cmdName = rawCmd ?? "";
|
|
670587
|
+
const argsStr = rest.join(" ");
|
|
670588
|
+
const release = await acquireLock2();
|
|
670589
|
+
try {
|
|
670590
|
+
const quick2 = buildNonInteractiveSummary(cmdName, argsStr, opts?.config);
|
|
670591
|
+
if (quick2) {
|
|
670592
|
+
return {
|
|
670593
|
+
ok: true,
|
|
670594
|
+
command: cmdName,
|
|
670595
|
+
args: argsStr,
|
|
670596
|
+
kind: "handled",
|
|
670597
|
+
output: quick2,
|
|
670598
|
+
ansi: quick2,
|
|
670599
|
+
durationMs: Date.now() - start2
|
|
670600
|
+
};
|
|
670601
|
+
}
|
|
670602
|
+
const buf = [];
|
|
670603
|
+
setContentWriteHook({
|
|
670604
|
+
begin: () => {
|
|
670605
|
+
},
|
|
670606
|
+
end: () => {
|
|
670607
|
+
},
|
|
670608
|
+
redirect: () => (text2) => {
|
|
670609
|
+
buf.push(text2);
|
|
670610
|
+
}
|
|
670611
|
+
});
|
|
670612
|
+
const origWrite = process.stdout.write.bind(process.stdout);
|
|
670613
|
+
process.stdout.write = function(chunk, ...rest2) {
|
|
670614
|
+
if (typeof chunk === "string") buf.push(chunk);
|
|
670615
|
+
else if (chunk instanceof Buffer) buf.push(chunk.toString("utf-8"));
|
|
670616
|
+
const cb = rest2.find((r2) => typeof r2 === "function");
|
|
670617
|
+
if (cb) cb();
|
|
670618
|
+
return true;
|
|
670619
|
+
};
|
|
670620
|
+
let kind = "handled";
|
|
670621
|
+
let errMsg;
|
|
670622
|
+
try {
|
|
670623
|
+
const ctx3 = buildSyntheticContext(opts?.config, opts?.repoRoot);
|
|
670624
|
+
kind = await handleSlashCommand(slashCmd, ctx3);
|
|
670625
|
+
} catch (e2) {
|
|
670626
|
+
kind = "error";
|
|
670627
|
+
errMsg = e2 instanceof Error ? e2.message : String(e2);
|
|
670628
|
+
buf.push(`
|
|
670629
|
+
[error] ${errMsg}
|
|
670630
|
+
`);
|
|
670631
|
+
} finally {
|
|
670632
|
+
process.stdout.write = origWrite;
|
|
670633
|
+
setContentWriteHook({ begin: () => {
|
|
670634
|
+
}, end: () => {
|
|
670635
|
+
} });
|
|
670636
|
+
}
|
|
670637
|
+
const ansi5 = buf.join("");
|
|
670638
|
+
return {
|
|
670639
|
+
ok: kind !== "error" && kind !== "not_a_command",
|
|
670640
|
+
command: cmdName,
|
|
670641
|
+
args: argsStr,
|
|
670642
|
+
kind,
|
|
670643
|
+
output: stripAnsi5(ansi5).trim(),
|
|
670644
|
+
ansi: ansi5,
|
|
670645
|
+
durationMs: Date.now() - start2,
|
|
670646
|
+
error: errMsg
|
|
670647
|
+
};
|
|
670648
|
+
} finally {
|
|
670649
|
+
release();
|
|
670650
|
+
}
|
|
670651
|
+
}
|
|
670652
|
+
function buildNonInteractiveSummary(cmdName, _args, config) {
|
|
670653
|
+
const cfg = config ?? loadConfig();
|
|
670654
|
+
if (cmdName === "setup" || cmdName === "wizard") {
|
|
670655
|
+
return [
|
|
670656
|
+
"omnius setup",
|
|
670657
|
+
"",
|
|
670658
|
+
"The setup wizard is an interactive terminal flow. In the GUI command bridge it is summarized instead of opening prompts, installing software, starting Ollama, or pulling models.",
|
|
670659
|
+
"",
|
|
670660
|
+
`Current backend: ${cfg.backendType ?? "ollama"}`,
|
|
670661
|
+
`Current endpoint: ${cfg.backendUrl ?? "http://127.0.0.1:11434"}`,
|
|
670662
|
+
`Current model: ${cfg.model ?? "qwen3.5:latest"}`,
|
|
670663
|
+
"",
|
|
670664
|
+
"Available non-interactive setup actions:",
|
|
670665
|
+
" /endpoint <url> Set or inspect the inference endpoint.",
|
|
670666
|
+
" /model <name> Set the active model directly.",
|
|
670667
|
+
" /models Show model-selection guidance.",
|
|
670668
|
+
" /config Inspect persisted configuration.",
|
|
670669
|
+
" /doctor Run diagnostics from the terminal if deeper repair is needed.",
|
|
670670
|
+
"",
|
|
670671
|
+
"Open a terminal and run `omnius`, then use /setup for the full guided wizard."
|
|
670672
|
+
].join("\n");
|
|
670673
|
+
}
|
|
670674
|
+
if (cmdName === "models") {
|
|
670675
|
+
return [
|
|
670676
|
+
"omnius models",
|
|
670677
|
+
"",
|
|
670678
|
+
"The model picker is interactive in the TUI. The GUI bridge does not probe remote model endpoints here, so it cannot hang on a stale backend.",
|
|
670679
|
+
"",
|
|
670680
|
+
`Active model: ${cfg.model ?? "qwen3.5:latest"}`,
|
|
670681
|
+
`Endpoint: ${cfg.backendUrl ?? "http://127.0.0.1:11434"}`,
|
|
670682
|
+
`Backend: ${cfg.backendType ?? "ollama"}`,
|
|
670683
|
+
"",
|
|
670684
|
+
"Use /model <name> to set a model directly, /endpoint to switch providers, or open the TUI for the searchable model picker."
|
|
670685
|
+
].join("\n");
|
|
670686
|
+
}
|
|
670687
|
+
return null;
|
|
670688
|
+
}
|
|
670689
|
+
function acquireLock2() {
|
|
670690
|
+
let release;
|
|
670691
|
+
const next = new Promise((res) => {
|
|
670692
|
+
release = res;
|
|
670693
|
+
});
|
|
670694
|
+
const prev = _passthroughLock;
|
|
670695
|
+
_passthroughLock = next;
|
|
670696
|
+
return prev.then(() => release);
|
|
670697
|
+
}
|
|
670698
|
+
function buildSyntheticContext(config, repoRoot) {
|
|
670699
|
+
const cfg = config ?? loadConfig();
|
|
670700
|
+
const root = repoRoot ?? process.cwd();
|
|
670701
|
+
let colorsEnabled = loadProjectSettings(root).colors ?? loadGlobalSettings().colors ?? true;
|
|
670702
|
+
let selfModifyEnabled = loadProjectSettings(root).selfModify ?? loadGlobalSettings().selfModify ?? false;
|
|
670703
|
+
return {
|
|
670704
|
+
config: cfg,
|
|
670705
|
+
repoRoot: root,
|
|
670706
|
+
rl: makeRejectingReadline(),
|
|
670707
|
+
setModel: async (model) => {
|
|
670708
|
+
try {
|
|
670709
|
+
const engine = getDaemonListenEngine();
|
|
670710
|
+
await engine.setModel(model);
|
|
670711
|
+
renderInfo(`Model switched to: ${model}`);
|
|
670712
|
+
return `model set to ${model}`;
|
|
670713
|
+
} catch (err) {
|
|
670714
|
+
renderError(`Failed to set model: ${err instanceof Error ? err.message : String(err)}`);
|
|
670715
|
+
return "";
|
|
670716
|
+
}
|
|
670717
|
+
},
|
|
670718
|
+
setVerbose: (_verbose) => {
|
|
670719
|
+
},
|
|
670720
|
+
setEndpoint: async (_url, _t, _k) => {
|
|
670721
|
+
renderError(`/endpoint is TUI-only — use the GUI endpoint picker instead.`);
|
|
670722
|
+
return "";
|
|
670723
|
+
},
|
|
670724
|
+
deactivateStatusBar: () => {
|
|
670725
|
+
},
|
|
670726
|
+
disableMouse: () => {
|
|
670727
|
+
},
|
|
670728
|
+
enableMouse: () => {
|
|
670729
|
+
},
|
|
670730
|
+
isMouseEnabled: () => false,
|
|
670731
|
+
lockFooter: () => {
|
|
670732
|
+
},
|
|
670733
|
+
unlockFooter: () => {
|
|
670734
|
+
},
|
|
670735
|
+
stopBanner: () => {
|
|
670736
|
+
},
|
|
670737
|
+
killEphemeral: () => {
|
|
670738
|
+
},
|
|
670739
|
+
setKeyPool: (_keys) => {
|
|
670740
|
+
},
|
|
670741
|
+
clearScreen: () => {
|
|
670742
|
+
},
|
|
670743
|
+
newSession: () => {
|
|
670744
|
+
},
|
|
670745
|
+
refreshBanner: () => {
|
|
670746
|
+
},
|
|
670747
|
+
exit: () => {
|
|
670748
|
+
renderError("/quit and /exit are TUI-only — close the browser tab to end the GUI session.");
|
|
670749
|
+
},
|
|
670750
|
+
voiceToggle: async () => {
|
|
670751
|
+
renderError(`voice ${TUI_ONLY_HINT} — use the GUI voice button instead.`);
|
|
670752
|
+
return "voice not available in GUI";
|
|
670753
|
+
},
|
|
670754
|
+
voiceSetModel: async (_id2) => {
|
|
670755
|
+
renderError(`voice model ${TUI_ONLY_HINT}`);
|
|
670756
|
+
return "";
|
|
670757
|
+
},
|
|
670758
|
+
getColors: () => colorsEnabled,
|
|
670759
|
+
setColors: (enabled2) => {
|
|
670760
|
+
colorsEnabled = enabled2;
|
|
670761
|
+
},
|
|
670762
|
+
getSelfModify: () => selfModifyEnabled,
|
|
670763
|
+
setSelfModify: (enabled2) => {
|
|
670764
|
+
selfModifyEnabled = enabled2;
|
|
670765
|
+
},
|
|
670766
|
+
saveSettings: (settings) => {
|
|
670767
|
+
saveProjectSettings(root, settings);
|
|
670768
|
+
saveGlobalSettings(settings);
|
|
670769
|
+
},
|
|
670770
|
+
saveLocalSettings: (settings) => {
|
|
670771
|
+
saveProjectSettings(root, settings);
|
|
670772
|
+
}
|
|
670773
|
+
};
|
|
670774
|
+
}
|
|
670775
|
+
function makeRejectingReadline() {
|
|
670776
|
+
const reject = () => {
|
|
670777
|
+
throw new Error(
|
|
670778
|
+
"interactive prompts are not supported via the GUI command bridge — run this command from the TUI (open a terminal and type `omnius`)."
|
|
670779
|
+
);
|
|
670780
|
+
};
|
|
670781
|
+
const noop2 = () => {
|
|
670782
|
+
};
|
|
670783
|
+
return {
|
|
670784
|
+
question: (_q, _cb) => reject(),
|
|
670785
|
+
close: noop2,
|
|
670786
|
+
write: noop2,
|
|
670787
|
+
on: () => noop2,
|
|
670788
|
+
once: () => noop2,
|
|
670789
|
+
off: () => noop2,
|
|
670790
|
+
removeListener: () => noop2,
|
|
670791
|
+
pause: noop2,
|
|
670792
|
+
resume: noop2,
|
|
670793
|
+
setPrompt: noop2,
|
|
670794
|
+
prompt: noop2,
|
|
670795
|
+
line: "",
|
|
670796
|
+
cursor: 0,
|
|
670797
|
+
terminal: false,
|
|
670798
|
+
input: { isTTY: false, on: noop2, once: noop2, removeListener: noop2, pause: noop2, resume: noop2 },
|
|
670799
|
+
output: { write: noop2, columns: 80, rows: 24, isTTY: false }
|
|
670800
|
+
};
|
|
670801
|
+
}
|
|
670802
|
+
var _passthroughLock, TUI_ONLY_HINT;
|
|
670803
|
+
var init_command_passthrough = __esm({
|
|
670804
|
+
"packages/cli/src/api/command-passthrough.ts"() {
|
|
670805
|
+
"use strict";
|
|
670806
|
+
init_render();
|
|
670807
|
+
init_commands();
|
|
670808
|
+
init_config();
|
|
670809
|
+
init_voice_runtime();
|
|
670810
|
+
init_omnius_directory();
|
|
670811
|
+
_passthroughLock = Promise.resolve();
|
|
670812
|
+
TUI_ONLY_HINT = "(this command is TUI-only — no-op in GUI)";
|
|
670813
|
+
}
|
|
670814
|
+
});
|
|
670815
|
+
|
|
670816
|
+
// packages/cli/src/api/projects.ts
|
|
670817
|
+
var projects_exports = {};
|
|
670818
|
+
__export(projects_exports, {
|
|
670819
|
+
_resetCurrentProject: () => _resetCurrentProject,
|
|
670820
|
+
getCurrentProject: () => getCurrentProject,
|
|
670821
|
+
listProjects: () => listProjects,
|
|
670822
|
+
registerProject: () => registerProject,
|
|
670823
|
+
renameProject: () => renameProject,
|
|
670824
|
+
setCurrentProject: () => setCurrentProject,
|
|
670825
|
+
unregisterProject: () => unregisterProject
|
|
670826
|
+
});
|
|
670827
|
+
import { readFileSync as readFileSync112, writeFileSync as writeFileSync74, mkdirSync as mkdirSync84, existsSync as existsSync135, statSync as statSync48, renameSync as renameSync11 } from "node:fs";
|
|
670828
|
+
import { homedir as homedir47 } from "node:os";
|
|
670829
|
+
import { basename as basename36, join as join148, resolve as resolve59 } from "node:path";
|
|
670830
|
+
import { randomUUID as randomUUID18 } from "node:crypto";
|
|
670831
|
+
function readAll2() {
|
|
670832
|
+
try {
|
|
670833
|
+
if (!existsSync135(PROJECTS_FILE)) return { projects: [], schemaVersion: 1 };
|
|
670834
|
+
const raw = readFileSync112(PROJECTS_FILE, "utf8");
|
|
670835
|
+
const parsed = JSON.parse(raw);
|
|
670836
|
+
if (!parsed || !Array.isArray(parsed.projects)) return { projects: [], schemaVersion: 1 };
|
|
670837
|
+
return { projects: parsed.projects, schemaVersion: 1 };
|
|
670838
|
+
} catch {
|
|
670839
|
+
return { projects: [], schemaVersion: 1 };
|
|
670840
|
+
}
|
|
670841
|
+
}
|
|
670842
|
+
function writeAll(file) {
|
|
670843
|
+
mkdirSync84(OMNIUS_DIR3, { recursive: true });
|
|
670844
|
+
const tmp = `${PROJECTS_FILE}.${randomUUID18().slice(0, 8)}.tmp`;
|
|
670845
|
+
writeFileSync74(tmp, JSON.stringify(file, null, 2), "utf8");
|
|
670846
|
+
renameSync11(tmp, PROJECTS_FILE);
|
|
670847
|
+
}
|
|
670848
|
+
function listProjects() {
|
|
670849
|
+
const { projects } = readAll2();
|
|
670850
|
+
const alive = [];
|
|
670851
|
+
for (const p2 of projects) {
|
|
670852
|
+
try {
|
|
670853
|
+
if (statSync48(p2.root).isDirectory()) alive.push(p2);
|
|
670854
|
+
} catch {
|
|
670855
|
+
}
|
|
670856
|
+
}
|
|
670857
|
+
alive.sort((a2, b) => b.lastSeen - a2.lastSeen);
|
|
670858
|
+
return alive;
|
|
670859
|
+
}
|
|
670860
|
+
function registerProject(root, pid) {
|
|
670861
|
+
const canonical = resolve59(root);
|
|
670862
|
+
const now = Date.now();
|
|
670863
|
+
const file = readAll2();
|
|
670864
|
+
const existing = file.projects.find((p2) => p2.root === canonical);
|
|
670865
|
+
let entry;
|
|
670866
|
+
if (existing) {
|
|
670867
|
+
entry = {
|
|
670868
|
+
...existing,
|
|
670869
|
+
lastSeen: now,
|
|
670870
|
+
pid: pid ?? existing.pid,
|
|
670871
|
+
omniusDir: join148(canonical, ".omnius")
|
|
670872
|
+
};
|
|
670873
|
+
file.projects = file.projects.map((p2) => p2.root === canonical ? entry : p2);
|
|
670874
|
+
} else {
|
|
670875
|
+
entry = {
|
|
670876
|
+
root: canonical,
|
|
670877
|
+
name: basename36(canonical) || canonical,
|
|
670878
|
+
firstSeen: now,
|
|
670879
|
+
lastSeen: now,
|
|
670880
|
+
pid: pid ?? null,
|
|
670881
|
+
omniusDir: join148(canonical, ".omnius")
|
|
670882
|
+
};
|
|
670883
|
+
file.projects.push(entry);
|
|
670884
|
+
}
|
|
670885
|
+
writeAll(file);
|
|
670886
|
+
return entry;
|
|
670887
|
+
}
|
|
670888
|
+
function unregisterProject(root) {
|
|
670889
|
+
const canonical = resolve59(root);
|
|
670890
|
+
const file = readAll2();
|
|
670891
|
+
const before = file.projects.length;
|
|
670892
|
+
file.projects = file.projects.filter((p2) => p2.root !== canonical);
|
|
670893
|
+
if (file.projects.length === before) return false;
|
|
670894
|
+
writeAll(file);
|
|
670895
|
+
return true;
|
|
670896
|
+
}
|
|
670897
|
+
function renameProject(root, name10) {
|
|
670898
|
+
const canonical = resolve59(root);
|
|
670899
|
+
const file = readAll2();
|
|
670900
|
+
const idx = file.projects.findIndex((p2) => p2.root === canonical);
|
|
670901
|
+
if (idx < 0) return null;
|
|
670902
|
+
const next = { ...file.projects[idx], name: name10.trim() || file.projects[idx].name };
|
|
670903
|
+
file.projects[idx] = next;
|
|
670904
|
+
writeAll(file);
|
|
670905
|
+
return next;
|
|
670906
|
+
}
|
|
670907
|
+
function getCurrentProject() {
|
|
670908
|
+
if (!currentRoot) {
|
|
670909
|
+
try {
|
|
670910
|
+
if (existsSync135(CURRENT_FILE)) {
|
|
670911
|
+
const persisted = readFileSync112(CURRENT_FILE, "utf8").trim();
|
|
670912
|
+
if (persisted) currentRoot = persisted;
|
|
670913
|
+
}
|
|
670914
|
+
} catch {
|
|
670915
|
+
}
|
|
670916
|
+
}
|
|
670917
|
+
if (!currentRoot) return null;
|
|
670918
|
+
const all2 = listProjects();
|
|
670919
|
+
return all2.find((p2) => p2.root === currentRoot) ?? null;
|
|
670920
|
+
}
|
|
670921
|
+
function setCurrentProject(root) {
|
|
670922
|
+
const canonical = resolve59(root);
|
|
670923
|
+
const entry = listProjects().find((p2) => p2.root === canonical);
|
|
670924
|
+
if (!entry) return null;
|
|
670925
|
+
currentRoot = canonical;
|
|
670926
|
+
try {
|
|
670927
|
+
mkdirSync84(OMNIUS_DIR3, { recursive: true });
|
|
670928
|
+
writeFileSync74(CURRENT_FILE, `${canonical}
|
|
670929
|
+
`, "utf8");
|
|
670930
|
+
} catch {
|
|
670931
|
+
}
|
|
670932
|
+
return entry;
|
|
670933
|
+
}
|
|
670934
|
+
function _resetCurrentProject() {
|
|
670935
|
+
currentRoot = null;
|
|
670936
|
+
}
|
|
670937
|
+
var OMNIUS_DIR3, PROJECTS_FILE, CURRENT_FILE, currentRoot;
|
|
670938
|
+
var init_projects = __esm({
|
|
670939
|
+
"packages/cli/src/api/projects.ts"() {
|
|
670940
|
+
"use strict";
|
|
670941
|
+
OMNIUS_DIR3 = join148(homedir47(), ".omnius");
|
|
670942
|
+
PROJECTS_FILE = join148(OMNIUS_DIR3, "projects.json");
|
|
670943
|
+
CURRENT_FILE = join148(OMNIUS_DIR3, "current-project");
|
|
670944
|
+
currentRoot = null;
|
|
670945
|
+
}
|
|
670946
|
+
});
|
|
670947
|
+
|
|
670948
|
+
// packages/cli/src/tui/mouse-filter.ts
|
|
670949
|
+
var mouse_filter_exports = {};
|
|
670950
|
+
__export(mouse_filter_exports, {
|
|
670951
|
+
MouseFilterStream: () => MouseFilterStream
|
|
670952
|
+
});
|
|
670953
|
+
import { Transform } from "node:stream";
|
|
670954
|
+
var MouseFilterStream;
|
|
670955
|
+
var init_mouse_filter = __esm({
|
|
670956
|
+
"packages/cli/src/tui/mouse-filter.ts"() {
|
|
670957
|
+
"use strict";
|
|
670958
|
+
MouseFilterStream = class extends Transform {
|
|
670959
|
+
buffer = "";
|
|
670960
|
+
onScroll = null;
|
|
670961
|
+
onActivity = null;
|
|
670962
|
+
onPointer = null;
|
|
670963
|
+
onKeyboard = null;
|
|
670964
|
+
flushTimer = null;
|
|
670965
|
+
expectPrefixlessMouseUntil = 0;
|
|
670966
|
+
constructor(scrollHandler, activityHandler, pointerHandler, keyboardHandler) {
|
|
670967
|
+
super();
|
|
670968
|
+
this.onScroll = scrollHandler;
|
|
670969
|
+
this.onActivity = activityHandler ?? null;
|
|
670970
|
+
this.onPointer = pointerHandler ?? null;
|
|
670971
|
+
this.onKeyboard = keyboardHandler ?? null;
|
|
670972
|
+
}
|
|
670973
|
+
_transform(chunk, _encoding, callback) {
|
|
670974
|
+
this.buffer += chunk.toString();
|
|
670975
|
+
this.processBuffer(callback);
|
|
670976
|
+
}
|
|
670977
|
+
processBuffer(callback) {
|
|
670978
|
+
let output = "";
|
|
670979
|
+
let i2 = 0;
|
|
670980
|
+
while (i2 < this.buffer.length) {
|
|
670981
|
+
const remaining = this.buffer.slice(i2);
|
|
670982
|
+
const prefixlessMouse = this.matchPrefixlessSgrMouse(remaining);
|
|
670983
|
+
if (prefixlessMouse) {
|
|
670984
|
+
this.handleSgrMouse(prefixlessMouse);
|
|
670985
|
+
i2 += prefixlessMouse.raw.length;
|
|
670986
|
+
continue;
|
|
670987
|
+
}
|
|
670988
|
+
if (this.looksLikePartialPrefixlessSgrMouse(remaining)) {
|
|
670989
|
+
break;
|
|
670990
|
+
}
|
|
670991
|
+
if (this.buffer[i2] === "\x1B") {
|
|
670096
670992
|
const mouseMatch = remaining.match(/^\x1B\[<(\d+);(\d+);(\d+)([Mm])/);
|
|
670097
670993
|
if (mouseMatch) {
|
|
670098
670994
|
this.handleSgrMouse({
|
|
@@ -670119,1782 +671015,898 @@ var init_mouse_filter = __esm({
|
|
|
670119
671015
|
if (remaining.startsWith("\x1B[") && remaining.length === 2) {
|
|
670120
671016
|
break;
|
|
670121
671017
|
}
|
|
670122
|
-
if (remaining.length === 1) {
|
|
670123
|
-
break;
|
|
670124
|
-
}
|
|
670125
|
-
}
|
|
670126
|
-
output += this.buffer[i2];
|
|
670127
|
-
i2++;
|
|
670128
|
-
}
|
|
670129
|
-
this.buffer = this.buffer.slice(i2);
|
|
670130
|
-
if (output.length > 0) {
|
|
670131
|
-
if (this.onKeyboard) this.onKeyboard();
|
|
670132
|
-
this.push(output);
|
|
670133
|
-
}
|
|
670134
|
-
if (this.buffer.length > 0) {
|
|
670135
|
-
if (this.flushTimer) clearTimeout(this.flushTimer);
|
|
670136
|
-
this.flushTimer = setTimeout(() => {
|
|
670137
|
-
if (this.buffer.length > 0) {
|
|
670138
|
-
if (this.buffer.startsWith("\x1B[<") || this.buffer.startsWith("\x1B[M")) {
|
|
670139
|
-
this.expectPrefixlessMouseUntil = Date.now() + 1e3;
|
|
670140
|
-
this.buffer = "";
|
|
670141
|
-
} else if (this.buffer === "\x1B[") {
|
|
670142
|
-
this.expectPrefixlessMouseUntil = Date.now() + 1e3;
|
|
670143
|
-
this.buffer = "";
|
|
670144
|
-
} else if (this.looksLikePartialPrefixlessSgrMouse(this.buffer)) {
|
|
670145
|
-
this.buffer = "";
|
|
670146
|
-
} else if (this.buffer === "\x1B") {
|
|
670147
|
-
this.push(this.buffer);
|
|
670148
|
-
this.buffer = "";
|
|
670149
|
-
} else {
|
|
670150
|
-
this.push(this.buffer);
|
|
670151
|
-
this.buffer = "";
|
|
670152
|
-
}
|
|
670153
|
-
}
|
|
670154
|
-
}, 50);
|
|
670155
|
-
}
|
|
670156
|
-
callback();
|
|
670157
|
-
}
|
|
670158
|
-
_flush(callback) {
|
|
670159
|
-
if (this.flushTimer) {
|
|
670160
|
-
clearTimeout(this.flushTimer);
|
|
670161
|
-
this.flushTimer = null;
|
|
670162
|
-
}
|
|
670163
|
-
if (this.buffer.length > 0) {
|
|
670164
|
-
if (this.buffer.startsWith("\x1B[<") || this.buffer.startsWith("\x1B[M") || this.buffer === "\x1B[" || this.looksLikePartialPrefixlessSgrMouse(this.buffer)) {
|
|
670165
|
-
this.buffer = "";
|
|
670166
|
-
callback();
|
|
670167
|
-
return;
|
|
670168
|
-
}
|
|
670169
|
-
this.push(this.buffer);
|
|
670170
|
-
this.buffer = "";
|
|
670171
|
-
}
|
|
670172
|
-
callback();
|
|
670173
|
-
}
|
|
670174
|
-
matchPrefixlessSgrMouse(input) {
|
|
670175
|
-
const match = input.match(/^(<)?(\d{1,3});(\d{1,5});(\d{1,5})([Mm])/);
|
|
670176
|
-
if (!match) return null;
|
|
670177
|
-
const hasMarker = Boolean(match[1]);
|
|
670178
|
-
const btn = parseInt(match[2], 10);
|
|
670179
|
-
const col = parseInt(match[3], 10);
|
|
670180
|
-
const row = parseInt(match[4], 10);
|
|
670181
|
-
const suffix = match[5];
|
|
670182
|
-
if (!Number.isFinite(btn) || !Number.isFinite(col) || !Number.isFinite(row))
|
|
670183
|
-
return null;
|
|
670184
|
-
if (col <= 0 || row <= 0) return null;
|
|
670185
|
-
if (!hasMarker && Date.now() > this.expectPrefixlessMouseUntil && !this.isKnownMouseButton(btn))
|
|
670186
|
-
return null;
|
|
670187
|
-
if (btn < 0 || btn > 255) return null;
|
|
670188
|
-
return { raw: match[0], btn, col, row, suffix };
|
|
670189
|
-
}
|
|
670190
|
-
looksLikePartialPrefixlessSgrMouse(input) {
|
|
670191
|
-
if (input.length < 2) return false;
|
|
670192
|
-
if (/^<\d{1,3};\d{0,5}(?:;\d{0,5})?$/.test(input)) return true;
|
|
670193
|
-
if (Date.now() <= this.expectPrefixlessMouseUntil && /^\d{1,3};\d{0,5}(?:;\d{0,5})?$/.test(input)) {
|
|
670194
|
-
return true;
|
|
670195
|
-
}
|
|
670196
|
-
return false;
|
|
670197
|
-
}
|
|
670198
|
-
isKnownMouseButton(btn) {
|
|
670199
|
-
if (btn >= 0 && btn <= 6) return true;
|
|
670200
|
-
if (btn >= 32 && btn <= 39) return true;
|
|
670201
|
-
if (btn >= 64 && btn <= 71) return true;
|
|
670202
|
-
if (btn >= 96 && btn <= 103) return true;
|
|
670203
|
-
return false;
|
|
670204
|
-
}
|
|
670205
|
-
handleSgrMouse(mouse) {
|
|
670206
|
-
const { btn, col, row, suffix } = mouse;
|
|
670207
|
-
if ((btn === 64 || btn === 96) && this.onScroll)
|
|
670208
|
-
this.onScroll("up", 3, row);
|
|
670209
|
-
else if ((btn === 65 || btn === 97) && this.onScroll)
|
|
670210
|
-
this.onScroll("down", 3, row);
|
|
670211
|
-
else if (this.onPointer) {
|
|
670212
|
-
const hasShift = (btn & 4) !== 0 && btn < 32;
|
|
670213
|
-
if (btn === 2) {
|
|
670214
|
-
} else if (hasShift) {
|
|
670215
|
-
} else if ((btn === 0 || btn === 1) && suffix === "M") {
|
|
670216
|
-
this.onPointer("press", col, row);
|
|
670217
|
-
} else if (btn >= 32 && btn <= 35 && suffix === "M") {
|
|
670218
|
-
this.onPointer("drag", col, row);
|
|
670219
|
-
} else if (suffix === "m") {
|
|
670220
|
-
this.onPointer("release", col, row);
|
|
670221
|
-
}
|
|
670222
|
-
}
|
|
670223
|
-
if (this.onActivity) this.onActivity();
|
|
670224
|
-
}
|
|
670225
|
-
};
|
|
670226
|
-
}
|
|
670227
|
-
});
|
|
670228
|
-
|
|
670229
|
-
// packages/cli/src/tui/direct-input.ts
|
|
670230
|
-
var direct_input_exports = {};
|
|
670231
|
-
__export(direct_input_exports, {
|
|
670232
|
-
DirectInput: () => DirectInput
|
|
670233
|
-
});
|
|
670234
|
-
import { EventEmitter as EventEmitter12 } from "node:events";
|
|
670235
|
-
var DirectInput;
|
|
670236
|
-
var init_direct_input = __esm({
|
|
670237
|
-
"packages/cli/src/tui/direct-input.ts"() {
|
|
670238
|
-
"use strict";
|
|
670239
|
-
DirectInput = class extends EventEmitter12 {
|
|
670240
|
-
/** Current input line text */
|
|
670241
|
-
line = "";
|
|
670242
|
-
/** Cursor position within .line (0-based) */
|
|
670243
|
-
cursor = 0;
|
|
670244
|
-
_history;
|
|
670245
|
-
_historySize;
|
|
670246
|
-
_historyIndex = -1;
|
|
670247
|
-
_savedLine = "";
|
|
670248
|
-
// saved current input when navigating history
|
|
670249
|
-
_completer = null;
|
|
670250
|
-
_paused = false;
|
|
670251
|
-
_closed = false;
|
|
670252
|
-
_input;
|
|
670253
|
-
_buffer = "";
|
|
670254
|
-
// partial escape sequence buffer
|
|
670255
|
-
_flushTimer = null;
|
|
670256
|
-
_expectPrefixlessMouseUntil = 0;
|
|
670257
|
-
constructor(input, options2) {
|
|
670258
|
-
super();
|
|
670259
|
-
this._input = input;
|
|
670260
|
-
this._history = [...options2?.history ?? []];
|
|
670261
|
-
this._historySize = options2?.historySize ?? 500;
|
|
670262
|
-
this._completer = options2?.completer ?? null;
|
|
670263
|
-
input.on("data", (chunk) => {
|
|
670264
|
-
if (this._paused || this._closed) return;
|
|
670265
|
-
this.feed(chunk.toString("utf8"));
|
|
670266
|
-
});
|
|
670267
|
-
input.on("end", () => {
|
|
670268
|
-
if (!this._closed) this.close();
|
|
670269
|
-
});
|
|
670270
|
-
}
|
|
670271
|
-
/** Process raw input data — parse escape sequences and printable chars */
|
|
670272
|
-
feed(data) {
|
|
670273
|
-
const beforeLine = this.line;
|
|
670274
|
-
const beforeCursor = this.cursor;
|
|
670275
|
-
this._buffer += data;
|
|
670276
|
-
this._processBuffer();
|
|
670277
|
-
this._emitChangeIfNeeded(beforeLine, beforeCursor);
|
|
670278
|
-
}
|
|
670279
|
-
/** Pause input processing (for overlay transitions) */
|
|
670280
|
-
pause() {
|
|
670281
|
-
this._paused = true;
|
|
670282
|
-
}
|
|
670283
|
-
/** Resume input processing */
|
|
670284
|
-
resume() {
|
|
670285
|
-
this._paused = false;
|
|
670286
|
-
}
|
|
670287
|
-
/** Close the input handler */
|
|
670288
|
-
close() {
|
|
670289
|
-
if (this._closed) return;
|
|
670290
|
-
this._closed = true;
|
|
670291
|
-
if (this._flushTimer) {
|
|
670292
|
-
clearTimeout(this._flushTimer);
|
|
670293
|
-
this._flushTimer = null;
|
|
670294
|
-
}
|
|
670295
|
-
this.emit("close");
|
|
670296
|
-
}
|
|
670297
|
-
/** No-op — readline compat (StatusBar renders the prompt, not us) */
|
|
670298
|
-
setPrompt(_prompt) {
|
|
670299
|
-
}
|
|
670300
|
-
/** No-op — readline compat */
|
|
670301
|
-
prompt(_preserveCursor) {
|
|
670302
|
-
}
|
|
670303
|
-
/** Set the line content and cursor position (for Esc-to-recall or suggestion apply) */
|
|
670304
|
-
setLine(text2, cursorPos) {
|
|
670305
|
-
const beforeLine = this.line;
|
|
670306
|
-
const beforeCursor = this.cursor;
|
|
670307
|
-
this.line = text2;
|
|
670308
|
-
this.cursor = cursorPos ?? text2.length;
|
|
670309
|
-
this._emitChangeIfNeeded(beforeLine, beforeCursor);
|
|
670310
|
-
}
|
|
670311
|
-
/** Pre-submit hook — called before Enter submits. Return true to consume Enter. */
|
|
670312
|
-
_preSubmit = null;
|
|
670313
|
-
setPreSubmit(hook) {
|
|
670314
|
-
this._preSubmit = hook;
|
|
670315
|
-
}
|
|
670316
|
-
/** Navigate history up (older) */
|
|
670317
|
-
historyUp() {
|
|
670318
|
-
if (this._history.length === 0) return;
|
|
670319
|
-
if (this._historyIndex === -1) {
|
|
670320
|
-
this._savedLine = this.line;
|
|
670321
|
-
}
|
|
670322
|
-
if (this._historyIndex < this._history.length - 1) {
|
|
670323
|
-
this._historyIndex++;
|
|
670324
|
-
this.line = this._history[this._historyIndex];
|
|
670325
|
-
this.cursor = this.line.length;
|
|
670326
|
-
}
|
|
670327
|
-
}
|
|
670328
|
-
/** Navigate history down (newer) */
|
|
670329
|
-
historyDown() {
|
|
670330
|
-
if (this._historyIndex <= -1) return;
|
|
670331
|
-
this._historyIndex--;
|
|
670332
|
-
if (this._historyIndex === -1) {
|
|
670333
|
-
this.line = this._savedLine;
|
|
670334
|
-
} else {
|
|
670335
|
-
this.line = this._history[this._historyIndex];
|
|
670336
|
-
}
|
|
670337
|
-
this.cursor = this.line.length;
|
|
670338
|
-
}
|
|
670339
|
-
/**
|
|
670340
|
-
* Move cursor up one wrapped line. Returns true if moved, false if at top line.
|
|
670341
|
-
* Used by arrow-up to navigate within wrapped input before falling through to history.
|
|
670342
|
-
*/
|
|
670343
|
-
cursorUpWrapped(availWidth) {
|
|
670344
|
-
if (this.line.length <= availWidth) return false;
|
|
670345
|
-
const { charPositions, rawLines } = this._computeWrappedLines(availWidth);
|
|
670346
|
-
if (rawLines.length <= 1) return false;
|
|
670347
|
-
let currentLineIdx = rawLines.length - 1;
|
|
670348
|
-
for (let i2 = 0; i2 < charPositions.length; i2++) {
|
|
670349
|
-
const lineStart = charPositions[i2];
|
|
670350
|
-
const lineEnd = lineStart + rawLines[i2].length;
|
|
670351
|
-
if (this.cursor >= lineStart && this.cursor <= lineEnd) {
|
|
670352
|
-
currentLineIdx = i2;
|
|
670353
|
-
break;
|
|
670354
|
-
}
|
|
670355
|
-
}
|
|
670356
|
-
if (currentLineIdx === 0) return false;
|
|
670357
|
-
const prevLineIdx = currentLineIdx - 1;
|
|
670358
|
-
const currentColInLine = this.cursor - charPositions[currentLineIdx];
|
|
670359
|
-
const prevLineLength = rawLines[prevLineIdx].length;
|
|
670360
|
-
const newColInLine = Math.min(currentColInLine, prevLineLength);
|
|
670361
|
-
this.cursor = charPositions[prevLineIdx] + newColInLine;
|
|
670362
|
-
return true;
|
|
670363
|
-
}
|
|
670364
|
-
/**
|
|
670365
|
-
* Move cursor down one wrapped line. Returns true if moved, false if at bottom line.
|
|
670366
|
-
* Used by arrow-down to navigate within wrapped input before falling through to history.
|
|
670367
|
-
*/
|
|
670368
|
-
cursorDownWrapped(availWidth) {
|
|
670369
|
-
if (this.line.length <= availWidth) return false;
|
|
670370
|
-
const { charPositions, rawLines } = this._computeWrappedLines(availWidth);
|
|
670371
|
-
if (rawLines.length <= 1) return false;
|
|
670372
|
-
let currentLineIdx = rawLines.length - 1;
|
|
670373
|
-
for (let i2 = 0; i2 < charPositions.length; i2++) {
|
|
670374
|
-
const lineStart = charPositions[i2];
|
|
670375
|
-
const lineEnd = lineStart + rawLines[i2].length;
|
|
670376
|
-
if (this.cursor >= lineStart && this.cursor <= lineEnd) {
|
|
670377
|
-
currentLineIdx = i2;
|
|
670378
|
-
break;
|
|
670379
|
-
}
|
|
670380
|
-
}
|
|
670381
|
-
if (currentLineIdx === rawLines.length - 1) return false;
|
|
670382
|
-
const nextLineIdx = currentLineIdx + 1;
|
|
670383
|
-
const currentColInLine = this.cursor - charPositions[currentLineIdx];
|
|
670384
|
-
const nextLineLength = rawLines[nextLineIdx].length;
|
|
670385
|
-
const newColInLine = Math.min(currentColInLine, nextLineLength);
|
|
670386
|
-
this.cursor = charPositions[nextLineIdx] + newColInLine;
|
|
670387
|
-
return true;
|
|
670388
|
-
}
|
|
670389
|
-
/**
|
|
670390
|
-
* Compute wrapped lines (word-aware). Returns charPositions (start index of each line)
|
|
670391
|
-
* and rawLines (text of each line). Matches wrapInput logic in status-bar.ts.
|
|
670392
|
-
*/
|
|
670393
|
-
_computeWrappedLines(availWidth) {
|
|
670394
|
-
const width = Math.max(1, availWidth);
|
|
670395
|
-
const rawLines = [];
|
|
670396
|
-
const charPositions = [];
|
|
670397
|
-
const pushWrappedSegment = (segment, segmentStart2) => {
|
|
670398
|
-
if (segment.length === 0) {
|
|
670399
|
-
charPositions.push(segmentStart2);
|
|
670400
|
-
rawLines.push("");
|
|
670401
|
-
return;
|
|
670402
|
-
}
|
|
670403
|
-
let offset = 0;
|
|
670404
|
-
while (offset < segment.length) {
|
|
670405
|
-
const remaining = segment.slice(offset);
|
|
670406
|
-
if (remaining.length <= width) {
|
|
670407
|
-
charPositions.push(segmentStart2 + offset);
|
|
670408
|
-
rawLines.push(remaining);
|
|
670409
|
-
break;
|
|
670410
|
-
}
|
|
670411
|
-
let breakAt = width;
|
|
670412
|
-
const lastSpace = remaining.lastIndexOf(" ", width);
|
|
670413
|
-
if (lastSpace > 0 && lastSpace >= width * 0.3) {
|
|
670414
|
-
breakAt = lastSpace + 1;
|
|
670415
|
-
}
|
|
670416
|
-
charPositions.push(segmentStart2 + offset);
|
|
670417
|
-
rawLines.push(remaining.slice(0, breakAt));
|
|
670418
|
-
offset += breakAt;
|
|
670419
|
-
}
|
|
670420
|
-
};
|
|
670421
|
-
if (this.line.length === 0) {
|
|
670422
|
-
pushWrappedSegment("", 0);
|
|
670423
|
-
return { charPositions, rawLines };
|
|
670424
|
-
}
|
|
670425
|
-
let segmentStart = 0;
|
|
670426
|
-
while (segmentStart <= this.line.length) {
|
|
670427
|
-
const newlineAt = this.line.indexOf("\n", segmentStart);
|
|
670428
|
-
const segmentEnd = newlineAt === -1 ? this.line.length : newlineAt;
|
|
670429
|
-
pushWrappedSegment(this.line.slice(segmentStart, segmentEnd), segmentStart);
|
|
670430
|
-
if (newlineAt === -1) break;
|
|
670431
|
-
segmentStart = newlineAt + 1;
|
|
670432
|
-
if (segmentStart === this.line.length) {
|
|
670433
|
-
pushWrappedSegment("", segmentStart);
|
|
670434
|
-
break;
|
|
670435
|
-
}
|
|
670436
|
-
}
|
|
670437
|
-
return { charPositions, rawLines };
|
|
670438
|
-
}
|
|
670439
|
-
// ---------------------------------------------------------------------------
|
|
670440
|
-
// Private: buffer processing and escape sequence parsing
|
|
670441
|
-
// ---------------------------------------------------------------------------
|
|
670442
|
-
_processBuffer() {
|
|
670443
|
-
let i2 = 0;
|
|
670444
|
-
while (i2 < this._buffer.length) {
|
|
670445
|
-
const remaining = this._buffer.slice(i2);
|
|
670446
|
-
const prefixlessMouse = this._matchPrefixlessSgrMouse(remaining);
|
|
670447
|
-
if (prefixlessMouse) {
|
|
670448
|
-
i2 += prefixlessMouse.length;
|
|
670449
|
-
continue;
|
|
670450
|
-
}
|
|
670451
|
-
if (this._looksLikePartialPrefixlessSgrMouse(remaining)) {
|
|
670452
|
-
break;
|
|
670453
|
-
}
|
|
670454
|
-
const ch = this._buffer[i2];
|
|
670455
|
-
const code8 = ch.charCodeAt(0);
|
|
670456
|
-
if (code8 === 27) {
|
|
670457
|
-
if (remaining.length >= 2 && remaining[1] === "[") {
|
|
670458
|
-
const mouseMatch = remaining.match(/^\x1B\[<(\d+);(\d+);(\d+)([Mm])/);
|
|
670459
|
-
if (mouseMatch) {
|
|
670460
|
-
i2 += mouseMatch[0].length;
|
|
670461
|
-
continue;
|
|
670462
|
-
}
|
|
670463
|
-
if (remaining.startsWith("\x1B[<") && remaining.length < 15) {
|
|
670464
|
-
break;
|
|
670465
|
-
}
|
|
670466
|
-
if (remaining.startsWith("\x1B[M") && remaining.length >= 6) {
|
|
670467
|
-
i2 += 6;
|
|
670468
|
-
continue;
|
|
670469
|
-
}
|
|
670470
|
-
if (remaining.startsWith("\x1B[M") && remaining.length < 6) {
|
|
670471
|
-
break;
|
|
670472
|
-
}
|
|
670473
|
-
if (remaining.startsWith("\x1B[<")) {
|
|
670474
|
-
this._expectPrefixlessMouseUntil = Date.now() + 1e3;
|
|
670475
|
-
let end = 3;
|
|
670476
|
-
while (end < remaining.length && remaining[end] !== "M" && remaining[end] !== "m") end++;
|
|
670477
|
-
if (end < remaining.length) end++;
|
|
670478
|
-
i2 += end;
|
|
670479
|
-
continue;
|
|
670480
|
-
}
|
|
670481
|
-
const csiMatch = remaining.match(/^\x1B\[([0-9;]*)([A-Za-z~])/);
|
|
670482
|
-
if (csiMatch) {
|
|
670483
|
-
this._handleCSI(csiMatch[1], csiMatch[2]);
|
|
670484
|
-
i2 += csiMatch[0].length;
|
|
670485
|
-
continue;
|
|
670486
|
-
}
|
|
670487
|
-
if (remaining.length < 10) {
|
|
670488
|
-
break;
|
|
670489
|
-
}
|
|
670490
|
-
i2 += 2;
|
|
670491
|
-
continue;
|
|
670492
|
-
}
|
|
670493
|
-
if (remaining.length >= 2 && remaining[1] === "O") {
|
|
670494
|
-
if (remaining.length >= 3) {
|
|
670495
|
-
this._handleSS3(remaining[2]);
|
|
670496
|
-
i2 += 3;
|
|
670497
|
-
continue;
|
|
670498
|
-
}
|
|
670499
|
-
break;
|
|
670500
|
-
}
|
|
670501
|
-
if (remaining.length >= 2 && (remaining[1] === "\r" || remaining[1] === "\n")) {
|
|
670502
|
-
this._insertText("\n");
|
|
670503
|
-
i2 += 2;
|
|
670504
|
-
if (remaining[1] === "\r" && remaining[2] === "\n") i2++;
|
|
670505
|
-
continue;
|
|
670506
|
-
}
|
|
670507
|
-
if (remaining.length === 1) {
|
|
670508
|
-
break;
|
|
670509
|
-
}
|
|
670510
|
-
i2++;
|
|
670511
|
-
continue;
|
|
670512
|
-
}
|
|
670513
|
-
if (code8 < 32 || code8 === 127) {
|
|
670514
|
-
if (code8 === 13) {
|
|
670515
|
-
if (this._preSubmit?.()) {
|
|
670516
|
-
i2++;
|
|
670517
|
-
continue;
|
|
670518
|
-
}
|
|
670519
|
-
this._submit();
|
|
670520
|
-
i2++;
|
|
670521
|
-
if (this._buffer[i2] === "\n") i2++;
|
|
670522
|
-
continue;
|
|
670523
|
-
}
|
|
670524
|
-
if (code8 === 10) {
|
|
670525
|
-
this._insertText("\n");
|
|
670526
|
-
i2++;
|
|
670527
|
-
continue;
|
|
670528
|
-
}
|
|
670529
|
-
this._handleControl(code8);
|
|
670530
|
-
i2++;
|
|
670531
|
-
continue;
|
|
671018
|
+
if (remaining.length === 1) {
|
|
671019
|
+
break;
|
|
671020
|
+
}
|
|
670532
671021
|
}
|
|
670533
|
-
|
|
670534
|
-
this.line = this.line.slice(0, this.cursor) + char + this.line.slice(this.cursor);
|
|
670535
|
-
this.cursor++;
|
|
671022
|
+
output += this.buffer[i2];
|
|
670536
671023
|
i2++;
|
|
670537
671024
|
}
|
|
670538
|
-
this.
|
|
670539
|
-
if (
|
|
670540
|
-
if (this.
|
|
670541
|
-
this.
|
|
670542
|
-
|
|
670543
|
-
|
|
670544
|
-
|
|
670545
|
-
|
|
670546
|
-
|
|
670547
|
-
|
|
670548
|
-
|
|
670549
|
-
|
|
670550
|
-
|
|
670551
|
-
this.
|
|
670552
|
-
|
|
670553
|
-
}
|
|
670554
|
-
|
|
670555
|
-
|
|
670556
|
-
|
|
670557
|
-
|
|
670558
|
-
|
|
670559
|
-
this.
|
|
671025
|
+
this.buffer = this.buffer.slice(i2);
|
|
671026
|
+
if (output.length > 0) {
|
|
671027
|
+
if (this.onKeyboard) this.onKeyboard();
|
|
671028
|
+
this.push(output);
|
|
671029
|
+
}
|
|
671030
|
+
if (this.buffer.length > 0) {
|
|
671031
|
+
if (this.flushTimer) clearTimeout(this.flushTimer);
|
|
671032
|
+
this.flushTimer = setTimeout(() => {
|
|
671033
|
+
if (this.buffer.length > 0) {
|
|
671034
|
+
if (this.buffer.startsWith("\x1B[<") || this.buffer.startsWith("\x1B[M")) {
|
|
671035
|
+
this.expectPrefixlessMouseUntil = Date.now() + 1e3;
|
|
671036
|
+
this.buffer = "";
|
|
671037
|
+
} else if (this.buffer === "\x1B[") {
|
|
671038
|
+
this.expectPrefixlessMouseUntil = Date.now() + 1e3;
|
|
671039
|
+
this.buffer = "";
|
|
671040
|
+
} else if (this.looksLikePartialPrefixlessSgrMouse(this.buffer)) {
|
|
671041
|
+
this.buffer = "";
|
|
671042
|
+
} else if (this.buffer === "\x1B") {
|
|
671043
|
+
this.push(this.buffer);
|
|
671044
|
+
this.buffer = "";
|
|
671045
|
+
} else {
|
|
671046
|
+
this.push(this.buffer);
|
|
671047
|
+
this.buffer = "";
|
|
670560
671048
|
}
|
|
670561
|
-
this._buffer = "";
|
|
670562
671049
|
}
|
|
670563
671050
|
}, 50);
|
|
670564
|
-
}
|
|
670565
|
-
|
|
670566
|
-
|
|
670567
|
-
|
|
671051
|
+
}
|
|
671052
|
+
callback();
|
|
671053
|
+
}
|
|
671054
|
+
_flush(callback) {
|
|
671055
|
+
if (this.flushTimer) {
|
|
671056
|
+
clearTimeout(this.flushTimer);
|
|
671057
|
+
this.flushTimer = null;
|
|
671058
|
+
}
|
|
671059
|
+
if (this.buffer.length > 0) {
|
|
671060
|
+
if (this.buffer.startsWith("\x1B[<") || this.buffer.startsWith("\x1B[M") || this.buffer === "\x1B[" || this.looksLikePartialPrefixlessSgrMouse(this.buffer)) {
|
|
671061
|
+
this.buffer = "";
|
|
671062
|
+
callback();
|
|
671063
|
+
return;
|
|
670568
671064
|
}
|
|
671065
|
+
this.push(this.buffer);
|
|
671066
|
+
this.buffer = "";
|
|
670569
671067
|
}
|
|
671068
|
+
callback();
|
|
670570
671069
|
}
|
|
670571
|
-
|
|
671070
|
+
matchPrefixlessSgrMouse(input) {
|
|
670572
671071
|
const match = input.match(/^(<)?(\d{1,3});(\d{1,5});(\d{1,5})([Mm])/);
|
|
670573
671072
|
if (!match) return null;
|
|
670574
671073
|
const hasMarker = Boolean(match[1]);
|
|
670575
671074
|
const btn = parseInt(match[2], 10);
|
|
670576
671075
|
const col = parseInt(match[3], 10);
|
|
670577
671076
|
const row = parseInt(match[4], 10);
|
|
671077
|
+
const suffix = match[5];
|
|
670578
671078
|
if (!Number.isFinite(btn) || !Number.isFinite(col) || !Number.isFinite(row))
|
|
670579
671079
|
return null;
|
|
670580
|
-
if (
|
|
670581
|
-
if (!hasMarker && Date.now() > this.
|
|
671080
|
+
if (col <= 0 || row <= 0) return null;
|
|
671081
|
+
if (!hasMarker && Date.now() > this.expectPrefixlessMouseUntil && !this.isKnownMouseButton(btn))
|
|
670582
671082
|
return null;
|
|
670583
|
-
|
|
671083
|
+
if (btn < 0 || btn > 255) return null;
|
|
671084
|
+
return { raw: match[0], btn, col, row, suffix };
|
|
670584
671085
|
}
|
|
670585
|
-
|
|
671086
|
+
looksLikePartialPrefixlessSgrMouse(input) {
|
|
670586
671087
|
if (input.length < 2) return false;
|
|
670587
671088
|
if (/^<\d{1,3};\d{0,5}(?:;\d{0,5})?$/.test(input)) return true;
|
|
670588
|
-
if (Date.now() <= this.
|
|
671089
|
+
if (Date.now() <= this.expectPrefixlessMouseUntil && /^\d{1,3};\d{0,5}(?:;\d{0,5})?$/.test(input)) {
|
|
670589
671090
|
return true;
|
|
670590
671091
|
}
|
|
670591
671092
|
return false;
|
|
670592
671093
|
}
|
|
670593
|
-
|
|
671094
|
+
isKnownMouseButton(btn) {
|
|
670594
671095
|
if (btn >= 0 && btn <= 6) return true;
|
|
670595
671096
|
if (btn >= 32 && btn <= 39) return true;
|
|
670596
671097
|
if (btn >= 64 && btn <= 71) return true;
|
|
670597
671098
|
if (btn >= 96 && btn <= 103) return true;
|
|
670598
671099
|
return false;
|
|
670599
671100
|
}
|
|
670600
|
-
|
|
670601
|
-
|
|
670602
|
-
|
|
670603
|
-
|
|
670604
|
-
|
|
670605
|
-
|
|
670606
|
-
|
|
670607
|
-
|
|
670608
|
-
|
|
670609
|
-
|
|
670610
|
-
|
|
670611
|
-
|
|
670612
|
-
|
|
670613
|
-
|
|
670614
|
-
|
|
670615
|
-
|
|
670616
|
-
|
|
670617
|
-
|
|
670618
|
-
|
|
670619
|
-
|
|
670620
|
-
return;
|
|
670621
|
-
case "C":
|
|
670622
|
-
if (params === "1;5") {
|
|
670623
|
-
this.emit("ctrl-right");
|
|
670624
|
-
return;
|
|
670625
|
-
}
|
|
670626
|
-
if (this.cursor < this.line.length) this.cursor++;
|
|
670627
|
-
return;
|
|
670628
|
-
case "D":
|
|
670629
|
-
if (params === "1;5") {
|
|
670630
|
-
this.emit("ctrl-left");
|
|
670631
|
-
return;
|
|
670632
|
-
}
|
|
670633
|
-
if (this.cursor > 0) this.cursor--;
|
|
670634
|
-
return;
|
|
670635
|
-
case "H":
|
|
670636
|
-
this.cursor = 0;
|
|
670637
|
-
return;
|
|
670638
|
-
case "F":
|
|
670639
|
-
this.cursor = this.line.length;
|
|
670640
|
-
return;
|
|
670641
|
-
case "~":
|
|
670642
|
-
if (this._isShiftEnterCSI(params)) {
|
|
670643
|
-
this._insertText("\n");
|
|
670644
|
-
return;
|
|
670645
|
-
}
|
|
670646
|
-
if (params === "3") {
|
|
670647
|
-
if (this.cursor < this.line.length) {
|
|
670648
|
-
this.line = this.line.slice(0, this.cursor) + this.line.slice(this.cursor + 1);
|
|
670649
|
-
}
|
|
670650
|
-
return;
|
|
670651
|
-
}
|
|
670652
|
-
if (params === "5") {
|
|
670653
|
-
this.emit("pageup");
|
|
670654
|
-
return;
|
|
670655
|
-
}
|
|
670656
|
-
if (params === "6") {
|
|
670657
|
-
this.emit("pagedown");
|
|
670658
|
-
return;
|
|
670659
|
-
}
|
|
670660
|
-
return;
|
|
670661
|
-
case "u": {
|
|
670662
|
-
const parts = params.split(";");
|
|
670663
|
-
const codepoint = parseInt(parts[0] ?? "0");
|
|
670664
|
-
const modifiers = parseInt(parts[1] ?? "1");
|
|
670665
|
-
const hasCtrl = modifiers - 1 & 4;
|
|
670666
|
-
const hasShift = modifiers - 1 & 1;
|
|
670667
|
-
if (hasShift && (codepoint === 10 || codepoint === 13)) {
|
|
670668
|
-
this._insertText("\n");
|
|
670669
|
-
return;
|
|
670670
|
-
}
|
|
670671
|
-
if (hasCtrl && hasShift) {
|
|
670672
|
-
if (codepoint === 67) {
|
|
670673
|
-
this.emit("ctrl-shift-c");
|
|
670674
|
-
return;
|
|
670675
|
-
}
|
|
670676
|
-
if (codepoint === 66) {
|
|
670677
|
-
this.emit("ctrl-shift-b");
|
|
670678
|
-
return;
|
|
670679
|
-
}
|
|
670680
|
-
if (codepoint === 86) {
|
|
670681
|
-
this.emit("ctrl-shift-v");
|
|
670682
|
-
return;
|
|
670683
|
-
}
|
|
670684
|
-
}
|
|
670685
|
-
if (hasCtrl && !hasShift && (codepoint === 73 || codepoint === 105)) {
|
|
670686
|
-
this.emit("ctrl-i");
|
|
670687
|
-
return;
|
|
670688
|
-
}
|
|
670689
|
-
return;
|
|
670690
|
-
}
|
|
670691
|
-
}
|
|
670692
|
-
}
|
|
670693
|
-
_isShiftEnterCSI(params) {
|
|
670694
|
-
const parts = params.split(";").map((part) => parseInt(part, 10));
|
|
670695
|
-
if (parts.length === 2) {
|
|
670696
|
-
const [codepoint, modifiers] = parts;
|
|
670697
|
-
return (codepoint === 10 || codepoint === 13) && modifiers === 2;
|
|
670698
|
-
}
|
|
670699
|
-
if (parts.length === 3) {
|
|
670700
|
-
const [prefix, modifiers, codepoint] = parts;
|
|
670701
|
-
return prefix === 27 && modifiers === 2 && (codepoint === 10 || codepoint === 13);
|
|
670702
|
-
}
|
|
670703
|
-
return false;
|
|
670704
|
-
}
|
|
670705
|
-
_insertText(text2) {
|
|
670706
|
-
this.line = this.line.slice(0, this.cursor) + text2 + this.line.slice(this.cursor);
|
|
670707
|
-
this.cursor += text2.length;
|
|
670708
|
-
}
|
|
670709
|
-
/** Handle SS3 sequence: \x1BO {final} (some terminals use this for arrows/Home/End) */
|
|
670710
|
-
_handleSS3(final2) {
|
|
670711
|
-
switch (final2) {
|
|
670712
|
-
case "A":
|
|
670713
|
-
this.emit("up");
|
|
670714
|
-
return;
|
|
670715
|
-
case "B":
|
|
670716
|
-
this.emit("down");
|
|
670717
|
-
return;
|
|
670718
|
-
case "C":
|
|
670719
|
-
if (this.cursor < this.line.length) this.cursor++;
|
|
670720
|
-
return;
|
|
670721
|
-
case "D":
|
|
670722
|
-
if (this.cursor > 0) this.cursor--;
|
|
670723
|
-
return;
|
|
670724
|
-
case "H":
|
|
670725
|
-
this.cursor = 0;
|
|
670726
|
-
return;
|
|
670727
|
-
case "F":
|
|
670728
|
-
this.cursor = this.line.length;
|
|
670729
|
-
return;
|
|
670730
|
-
}
|
|
670731
|
-
}
|
|
670732
|
-
/** Handle control characters (ASCII < 32 and DEL) */
|
|
670733
|
-
_handleControl(code8) {
|
|
670734
|
-
switch (code8) {
|
|
670735
|
-
case 127:
|
|
670736
|
-
// Backspace (DEL)
|
|
670737
|
-
case 8:
|
|
670738
|
-
if (this.cursor > 0) {
|
|
670739
|
-
this.line = this.line.slice(0, this.cursor - 1) + this.line.slice(this.cursor);
|
|
670740
|
-
this.cursor--;
|
|
670741
|
-
}
|
|
670742
|
-
return;
|
|
670743
|
-
case 3:
|
|
670744
|
-
this.emit("SIGINT");
|
|
670745
|
-
return;
|
|
670746
|
-
case 4:
|
|
670747
|
-
if (this.line.length === 0) this.close();
|
|
670748
|
-
return;
|
|
670749
|
-
case 9:
|
|
670750
|
-
this._handleTab();
|
|
670751
|
-
return;
|
|
670752
|
-
case 23:
|
|
670753
|
-
this._deleteWordLeft();
|
|
670754
|
-
return;
|
|
670755
|
-
case 21:
|
|
670756
|
-
this.line = this.line.slice(this.cursor);
|
|
670757
|
-
this.cursor = 0;
|
|
670758
|
-
return;
|
|
670759
|
-
case 11:
|
|
670760
|
-
this.line = this.line.slice(0, this.cursor);
|
|
670761
|
-
return;
|
|
670762
|
-
case 1:
|
|
670763
|
-
this.cursor = 0;
|
|
670764
|
-
return;
|
|
670765
|
-
case 5:
|
|
670766
|
-
this.cursor = this.line.length;
|
|
670767
|
-
return;
|
|
670768
|
-
case 15:
|
|
670769
|
-
this.emit("ctrl-o");
|
|
670770
|
-
return;
|
|
670771
|
-
case 12:
|
|
670772
|
-
this.emit("ctrl-l");
|
|
670773
|
-
return;
|
|
670774
|
-
case 22:
|
|
670775
|
-
this.emit("ctrl-v");
|
|
670776
|
-
return;
|
|
670777
|
-
case 28:
|
|
670778
|
-
this.emit("ctrl-backslash");
|
|
670779
|
-
return;
|
|
670780
|
-
}
|
|
670781
|
-
}
|
|
670782
|
-
/** Submit the current line */
|
|
670783
|
-
_submit() {
|
|
670784
|
-
const line = this.line;
|
|
670785
|
-
if (line.trim() && (this._history.length === 0 || this._history[0] !== line)) {
|
|
670786
|
-
this._history.unshift(line);
|
|
670787
|
-
if (this._history.length > this._historySize) {
|
|
670788
|
-
this._history.length = this._historySize;
|
|
670789
|
-
}
|
|
670790
|
-
}
|
|
670791
|
-
this._historyIndex = -1;
|
|
670792
|
-
this._savedLine = "";
|
|
670793
|
-
this.line = "";
|
|
670794
|
-
this.cursor = 0;
|
|
670795
|
-
this.emit("line", line);
|
|
670796
|
-
}
|
|
670797
|
-
/** Handle Tab completion */
|
|
670798
|
-
_handleTab() {
|
|
670799
|
-
if (this.line.length === 0) {
|
|
670800
|
-
this.emit("ctrl-i");
|
|
670801
|
-
return;
|
|
670802
|
-
}
|
|
670803
|
-
if (!this._completer) return;
|
|
670804
|
-
this._completer(this.line, (err, result) => {
|
|
670805
|
-
if (err || !result) return;
|
|
670806
|
-
const [completions, substring] = result;
|
|
670807
|
-
if (completions.length === 0) return;
|
|
670808
|
-
if (completions.length === 1) {
|
|
670809
|
-
const completion = completions[0];
|
|
670810
|
-
const before = this.line.slice(0, this.cursor);
|
|
670811
|
-
const idx = before.lastIndexOf(substring);
|
|
670812
|
-
if (idx >= 0) {
|
|
670813
|
-
this.line = before.slice(0, idx) + completion + this.line.slice(this.cursor);
|
|
670814
|
-
this.cursor = idx + completion.length;
|
|
670815
|
-
}
|
|
670816
|
-
} else if (completions.length > 1) {
|
|
670817
|
-
let common = completions[0];
|
|
670818
|
-
for (let i2 = 1; i2 < completions.length; i2++) {
|
|
670819
|
-
const other = completions[i2];
|
|
670820
|
-
let j = 0;
|
|
670821
|
-
while (j < common.length && j < other.length && common[j] === other[j]) j++;
|
|
670822
|
-
common = common.slice(0, j);
|
|
670823
|
-
}
|
|
670824
|
-
if (common.length > substring.length) {
|
|
670825
|
-
const before = this.line.slice(0, this.cursor);
|
|
670826
|
-
const idx = before.lastIndexOf(substring);
|
|
670827
|
-
if (idx >= 0) {
|
|
670828
|
-
this.line = before.slice(0, idx) + common + this.line.slice(this.cursor);
|
|
670829
|
-
this.cursor = idx + common.length;
|
|
670830
|
-
}
|
|
670831
|
-
}
|
|
670832
|
-
}
|
|
670833
|
-
});
|
|
670834
|
-
}
|
|
670835
|
-
/** Move cursor left by one word */
|
|
670836
|
-
_wordLeft() {
|
|
670837
|
-
if (this.cursor === 0) return;
|
|
670838
|
-
let i2 = this.cursor - 1;
|
|
670839
|
-
while (i2 > 0 && /\s/.test(this.line[i2])) i2--;
|
|
670840
|
-
while (i2 > 0 && /\S/.test(this.line[i2 - 1])) i2--;
|
|
670841
|
-
this.cursor = i2;
|
|
670842
|
-
}
|
|
670843
|
-
/** Move cursor right by one word */
|
|
670844
|
-
_wordRight() {
|
|
670845
|
-
if (this.cursor >= this.line.length) return;
|
|
670846
|
-
let i2 = this.cursor;
|
|
670847
|
-
while (i2 < this.line.length && /\S/.test(this.line[i2])) i2++;
|
|
670848
|
-
while (i2 < this.line.length && /\s/.test(this.line[i2])) i2++;
|
|
670849
|
-
this.cursor = i2;
|
|
670850
|
-
}
|
|
670851
|
-
/** Delete word left of cursor (Ctrl+W) */
|
|
670852
|
-
_deleteWordLeft() {
|
|
670853
|
-
if (this.cursor === 0) return;
|
|
670854
|
-
let i2 = this.cursor - 1;
|
|
670855
|
-
while (i2 > 0 && /\s/.test(this.line[i2])) i2--;
|
|
670856
|
-
while (i2 > 0 && /\S/.test(this.line[i2 - 1])) i2--;
|
|
670857
|
-
this.line = this.line.slice(0, i2) + this.line.slice(this.cursor);
|
|
670858
|
-
this.cursor = i2;
|
|
670859
|
-
}
|
|
670860
|
-
};
|
|
670861
|
-
}
|
|
670862
|
-
});
|
|
670863
|
-
|
|
670864
|
-
// packages/cli/src/api/access-policy.ts
|
|
670865
|
-
function defaultAccessMode(bindHost) {
|
|
670866
|
-
if (bindHost === "0.0.0.0" || bindHost === "::" || bindHost === "::0") {
|
|
670867
|
-
return "lan";
|
|
670868
|
-
}
|
|
670869
|
-
return "loopback";
|
|
670870
|
-
}
|
|
670871
|
-
function resolveAccessMode(envValue, bindHost) {
|
|
670872
|
-
const v = (envValue || "").toLowerCase().trim();
|
|
670873
|
-
if (v === "loopback" || v === "lan" || v === "any") return v;
|
|
670874
|
-
return defaultAccessMode(bindHost);
|
|
670875
|
-
}
|
|
670876
|
-
function stripMappedPrefix(ip) {
|
|
670877
|
-
return ip.replace(/^::ffff:/i, "");
|
|
670878
|
-
}
|
|
670879
|
-
function isLoopbackIP(ip) {
|
|
670880
|
-
if (!ip) return false;
|
|
670881
|
-
const clean5 = stripMappedPrefix(ip);
|
|
670882
|
-
if (clean5 === "::1") return true;
|
|
670883
|
-
if (/^127\./.test(clean5)) return true;
|
|
670884
|
-
return false;
|
|
670885
|
-
}
|
|
670886
|
-
function isPrivateIP(ip) {
|
|
670887
|
-
if (!ip) return false;
|
|
670888
|
-
const clean5 = stripMappedPrefix(ip);
|
|
670889
|
-
if (/^10\./.test(clean5)) return true;
|
|
670890
|
-
if (/^192\.168\./.test(clean5)) return true;
|
|
670891
|
-
const m2 = /^172\.(\d{1,3})\./.exec(clean5);
|
|
670892
|
-
if (m2) {
|
|
670893
|
-
const second3 = parseInt(m2[1], 10);
|
|
670894
|
-
if (second3 >= 16 && second3 <= 31) return true;
|
|
670895
|
-
}
|
|
670896
|
-
if (/^169\.254\./.test(clean5)) return true;
|
|
670897
|
-
if (/^f[cd][0-9a-f]{2}:/i.test(clean5)) return true;
|
|
670898
|
-
if (/^fe[89ab][0-9a-f]:/i.test(clean5)) return true;
|
|
670899
|
-
return false;
|
|
670900
|
-
}
|
|
670901
|
-
function isAllowedIP(ip, mode) {
|
|
670902
|
-
if (mode === "any") return true;
|
|
670903
|
-
if (isLoopbackIP(ip)) return true;
|
|
670904
|
-
if (mode === "lan" && isPrivateIP(ip)) return true;
|
|
670905
|
-
return false;
|
|
670906
|
-
}
|
|
670907
|
-
function describeAccessMode(mode) {
|
|
670908
|
-
switch (mode) {
|
|
670909
|
-
case "loopback":
|
|
670910
|
-
return "loopback only";
|
|
670911
|
-
case "lan":
|
|
670912
|
-
return "loopback + RFC 1918";
|
|
670913
|
-
case "any":
|
|
670914
|
-
return "any — WIDE OPEN";
|
|
670915
|
-
}
|
|
670916
|
-
}
|
|
670917
|
-
var init_access_policy = __esm({
|
|
670918
|
-
"packages/cli/src/api/access-policy.ts"() {
|
|
670919
|
-
"use strict";
|
|
670920
|
-
}
|
|
670921
|
-
});
|
|
670922
|
-
|
|
670923
|
-
// packages/cli/src/api/project-preferences.ts
|
|
670924
|
-
import { createHash as createHash40 } from "node:crypto";
|
|
670925
|
-
import { existsSync as existsSync136, mkdirSync as mkdirSync85, readFileSync as readFileSync113, renameSync as renameSync12, writeFileSync as writeFileSync75, unlinkSync as unlinkSync31 } from "node:fs";
|
|
670926
|
-
import { homedir as homedir48 } from "node:os";
|
|
670927
|
-
import { join as join149, resolve as resolve60 } from "node:path";
|
|
670928
|
-
import { randomUUID as randomUUID19 } from "node:crypto";
|
|
670929
|
-
function projectKey(root) {
|
|
670930
|
-
const canonical = resolve60(root);
|
|
670931
|
-
return createHash40("sha256").update(canonical).digest("hex").slice(0, 16);
|
|
670932
|
-
}
|
|
670933
|
-
function projectDir(root) {
|
|
670934
|
-
return join149(PROJECTS_DIR, projectKey(root));
|
|
670935
|
-
}
|
|
670936
|
-
function prefsPath(root) {
|
|
670937
|
-
return join149(projectDir(root), "preferences.json");
|
|
670938
|
-
}
|
|
670939
|
-
function rootSentinelPath(root) {
|
|
670940
|
-
return join149(projectDir(root), ".root");
|
|
670941
|
-
}
|
|
670942
|
-
function ensureDir(root) {
|
|
670943
|
-
const dir = projectDir(root);
|
|
670944
|
-
mkdirSync85(dir, { recursive: true });
|
|
670945
|
-
const sentinel = rootSentinelPath(root);
|
|
670946
|
-
try {
|
|
670947
|
-
if (!existsSync136(sentinel)) {
|
|
670948
|
-
writeFileSync75(sentinel, `${resolve60(root)}
|
|
670949
|
-
`, "utf8");
|
|
670950
|
-
}
|
|
670951
|
-
} catch {
|
|
670952
|
-
}
|
|
670953
|
-
}
|
|
670954
|
-
function readProjectPreferences(root) {
|
|
670955
|
-
try {
|
|
670956
|
-
const file = prefsPath(root);
|
|
670957
|
-
if (!existsSync136(file)) return { ...DEFAULT_PREFS };
|
|
670958
|
-
const raw = readFileSync113(file, "utf8");
|
|
670959
|
-
const parsed = JSON.parse(raw);
|
|
670960
|
-
if (!parsed || parsed.v !== SCHEMA_VERSION) return { ...DEFAULT_PREFS };
|
|
670961
|
-
return { ...DEFAULT_PREFS, ...parsed, v: SCHEMA_VERSION };
|
|
670962
|
-
} catch {
|
|
670963
|
-
return { ...DEFAULT_PREFS };
|
|
670964
|
-
}
|
|
670965
|
-
}
|
|
670966
|
-
function writeProjectPreferences(root, partial) {
|
|
670967
|
-
ensureDir(root);
|
|
670968
|
-
const current = readProjectPreferences(root);
|
|
670969
|
-
const merged = {
|
|
670970
|
-
...current,
|
|
670971
|
-
...partial,
|
|
670972
|
-
v: SCHEMA_VERSION,
|
|
670973
|
-
updatedAt: Date.now()
|
|
670974
|
-
};
|
|
670975
|
-
const file = prefsPath(root);
|
|
670976
|
-
const tmp = `${file}.${randomUUID19().slice(0, 8)}.tmp`;
|
|
670977
|
-
writeFileSync75(tmp, JSON.stringify(merged, null, 2), "utf8");
|
|
670978
|
-
try {
|
|
670979
|
-
renameSync12(tmp, file);
|
|
670980
|
-
} catch (err) {
|
|
670981
|
-
try {
|
|
670982
|
-
writeFileSync75(file, JSON.stringify(merged, null, 2), "utf8");
|
|
670983
|
-
} catch {
|
|
670984
|
-
}
|
|
670985
|
-
try {
|
|
670986
|
-
unlinkSync31(tmp);
|
|
670987
|
-
} catch {
|
|
670988
|
-
}
|
|
670989
|
-
throw err;
|
|
670990
|
-
}
|
|
670991
|
-
return merged;
|
|
670992
|
-
}
|
|
670993
|
-
function deleteProjectPreferences(root) {
|
|
670994
|
-
try {
|
|
670995
|
-
const file = prefsPath(root);
|
|
670996
|
-
if (!existsSync136(file)) return false;
|
|
670997
|
-
unlinkSync31(file);
|
|
670998
|
-
return true;
|
|
670999
|
-
} catch {
|
|
671000
|
-
return false;
|
|
671001
|
-
}
|
|
671002
|
-
}
|
|
671003
|
-
var OMNIUS_DIR4, PROJECTS_DIR, SCHEMA_VERSION, DEFAULT_PREFS;
|
|
671004
|
-
var init_project_preferences = __esm({
|
|
671005
|
-
"packages/cli/src/api/project-preferences.ts"() {
|
|
671006
|
-
"use strict";
|
|
671007
|
-
OMNIUS_DIR4 = join149(homedir48(), ".omnius");
|
|
671008
|
-
PROJECTS_DIR = join149(OMNIUS_DIR4, "projects");
|
|
671009
|
-
SCHEMA_VERSION = 1;
|
|
671010
|
-
DEFAULT_PREFS = {
|
|
671011
|
-
v: SCHEMA_VERSION,
|
|
671012
|
-
updatedAt: 0
|
|
671101
|
+
handleSgrMouse(mouse) {
|
|
671102
|
+
const { btn, col, row, suffix } = mouse;
|
|
671103
|
+
if ((btn === 64 || btn === 96) && this.onScroll)
|
|
671104
|
+
this.onScroll("up", 3, row);
|
|
671105
|
+
else if ((btn === 65 || btn === 97) && this.onScroll)
|
|
671106
|
+
this.onScroll("down", 3, row);
|
|
671107
|
+
else if (this.onPointer) {
|
|
671108
|
+
const hasShift = (btn & 4) !== 0 && btn < 32;
|
|
671109
|
+
if (btn === 2) {
|
|
671110
|
+
} else if (hasShift) {
|
|
671111
|
+
} else if ((btn === 0 || btn === 1) && suffix === "M") {
|
|
671112
|
+
this.onPointer("press", col, row);
|
|
671113
|
+
} else if (btn >= 32 && btn <= 35 && suffix === "M") {
|
|
671114
|
+
this.onPointer("drag", col, row);
|
|
671115
|
+
} else if (suffix === "m") {
|
|
671116
|
+
this.onPointer("release", col, row);
|
|
671117
|
+
}
|
|
671118
|
+
}
|
|
671119
|
+
if (this.onActivity) this.onActivity();
|
|
671120
|
+
}
|
|
671013
671121
|
};
|
|
671014
671122
|
}
|
|
671015
671123
|
});
|
|
671016
671124
|
|
|
671017
|
-
// packages/cli/src/tui/
|
|
671018
|
-
var
|
|
671019
|
-
__export(
|
|
671020
|
-
|
|
671125
|
+
// packages/cli/src/tui/direct-input.ts
|
|
671126
|
+
var direct_input_exports = {};
|
|
671127
|
+
__export(direct_input_exports, {
|
|
671128
|
+
DirectInput: () => DirectInput
|
|
671021
671129
|
});
|
|
671022
|
-
import { EventEmitter as
|
|
671023
|
-
|
|
671024
|
-
|
|
671025
|
-
|
|
671026
|
-
function alnumRatio(s2) {
|
|
671027
|
-
if (!s2) return 0;
|
|
671028
|
-
const al = (s2.match(/[\p{L}\p{N}]/gu) || []).length;
|
|
671029
|
-
return al / s2.length;
|
|
671030
|
-
}
|
|
671031
|
-
function wordCount(s2) {
|
|
671032
|
-
const words = s2.trim().match(/[\p{L}\p{N}][\p{L}\p{N}'’_-]*/gu);
|
|
671033
|
-
return words ? words.length : 0;
|
|
671034
|
-
}
|
|
671035
|
-
function repeatingCharPenalty(s2) {
|
|
671036
|
-
let maxRun = 1, cur = 1;
|
|
671037
|
-
for (let i2 = 1; i2 < s2.length; i2++) {
|
|
671038
|
-
if (s2[i2] === s2[i2 - 1]) cur++;
|
|
671039
|
-
else {
|
|
671040
|
-
if (cur > maxRun) maxRun = cur;
|
|
671041
|
-
cur = 1;
|
|
671042
|
-
}
|
|
671043
|
-
}
|
|
671044
|
-
if (cur > maxRun) maxRun = cur;
|
|
671045
|
-
return Math.min(1, Math.max(0, (maxRun - 3) / 10));
|
|
671046
|
-
}
|
|
671047
|
-
function computeSignalFromText(text2, confidence2) {
|
|
671048
|
-
const t2 = text2.trim();
|
|
671049
|
-
if (!t2) return 0;
|
|
671050
|
-
if (NOISE_ONLY_RE.test(t2)) return 0.05;
|
|
671051
|
-
const len = t2.length;
|
|
671052
|
-
const wc = wordCount(t2);
|
|
671053
|
-
const alpha = alnumRatio(t2);
|
|
671054
|
-
let score = 0;
|
|
671055
|
-
if (wc >= 6 && alpha >= 0.6) score = 0.85;
|
|
671056
|
-
else if (wc >= 3 && alpha >= 0.5) score = 0.7;
|
|
671057
|
-
else if (wc >= 2 && alpha >= 0.4) score = 0.5;
|
|
671058
|
-
else if (wc >= 1 && alpha >= 0.3 && len >= 4) score = 0.35;
|
|
671059
|
-
else score = 0.15;
|
|
671060
|
-
score -= repeatingCharPenalty(t2) * 0.4;
|
|
671061
|
-
if (typeof confidence2 === "number" && !Number.isNaN(confidence2)) {
|
|
671062
|
-
score = 0.7 * score + 0.3 * clamp0114(confidence2);
|
|
671063
|
-
}
|
|
671064
|
-
return clamp0114(score);
|
|
671065
|
-
}
|
|
671066
|
-
function truncateForLog(s2, n2) {
|
|
671067
|
-
return s2.length <= n2 ? s2 : s2.slice(0, n2 - 1) + "…";
|
|
671068
|
-
}
|
|
671069
|
-
function extractToolJson(text2) {
|
|
671070
|
-
const lines = text2.split(/\r?\n/);
|
|
671071
|
-
for (const line of lines) {
|
|
671072
|
-
const t2 = line.trim();
|
|
671073
|
-
if (!t2.startsWith("{") || !t2.endsWith("}")) continue;
|
|
671074
|
-
try {
|
|
671075
|
-
const obj = JSON.parse(t2);
|
|
671076
|
-
if (typeof obj.tool === "string") {
|
|
671077
|
-
const name10 = obj.tool;
|
|
671078
|
-
const args = obj.args && typeof obj.args === "object" ? obj.args : {};
|
|
671079
|
-
return { name: name10, args };
|
|
671080
|
-
}
|
|
671081
|
-
} catch {
|
|
671082
|
-
}
|
|
671083
|
-
}
|
|
671084
|
-
return null;
|
|
671085
|
-
}
|
|
671086
|
-
function extractToolJsonLoose(text2) {
|
|
671087
|
-
const stripped = text2.replace(/```[a-zA-Z]*|```/g, "\n");
|
|
671088
|
-
const exact = extractToolJson(stripped);
|
|
671089
|
-
if (exact) return exact;
|
|
671090
|
-
const match = stripped.match(/[\{][\s\S]*[\}]/);
|
|
671091
|
-
if (match) {
|
|
671092
|
-
try {
|
|
671093
|
-
const obj = JSON.parse(match[0]);
|
|
671094
|
-
if (typeof obj.tool === "string") {
|
|
671095
|
-
const args = obj.args && typeof obj.args === "object" ? obj.args : {};
|
|
671096
|
-
return { name: obj.tool, args };
|
|
671097
|
-
}
|
|
671098
|
-
} catch {
|
|
671099
|
-
}
|
|
671100
|
-
}
|
|
671101
|
-
return null;
|
|
671102
|
-
}
|
|
671103
|
-
function stripToolJsonLines(text2) {
|
|
671104
|
-
const lines = text2.split(/\r?\n/);
|
|
671105
|
-
const kept = lines.filter((l2) => {
|
|
671106
|
-
const t2 = l2.trim();
|
|
671107
|
-
if (!t2.startsWith("{") || !t2.endsWith("}")) return true;
|
|
671108
|
-
try {
|
|
671109
|
-
const obj = JSON.parse(t2);
|
|
671110
|
-
return !(typeof obj.tool === "string");
|
|
671111
|
-
} catch {
|
|
671112
|
-
return true;
|
|
671113
|
-
}
|
|
671114
|
-
});
|
|
671115
|
-
return kept.join("\n").trim();
|
|
671116
|
-
}
|
|
671117
|
-
var VAD_SILENCE_MS, MAX_SEGMENT_MS, MAX_CONTEXT_TURNS, SYSTEM_PROMPT2, MIN_SIGNAL_SCORE, NOISE_ONLY_RE, VoiceChatSession;
|
|
671118
|
-
var init_voicechat = __esm({
|
|
671119
|
-
"packages/cli/src/tui/voicechat.ts"() {
|
|
671130
|
+
import { EventEmitter as EventEmitter14 } from "node:events";
|
|
671131
|
+
var DirectInput;
|
|
671132
|
+
var init_direct_input = __esm({
|
|
671133
|
+
"packages/cli/src/tui/direct-input.ts"() {
|
|
671120
671134
|
"use strict";
|
|
671121
|
-
|
|
671122
|
-
|
|
671123
|
-
|
|
671124
|
-
|
|
671125
|
-
|
|
671126
|
-
|
|
671127
|
-
|
|
671128
|
-
|
|
671129
|
-
|
|
671130
|
-
|
|
671131
|
-
|
|
671132
|
-
|
|
671133
|
-
|
|
671134
|
-
|
|
671135
|
-
|
|
671136
|
-
|
|
671137
|
-
|
|
671138
|
-
|
|
671139
|
-
|
|
671140
|
-
verbose = false;
|
|
671141
|
-
debugSnr = false;
|
|
671142
|
-
heuristicsEnabled = true;
|
|
671143
|
-
toolCatalogNote = null;
|
|
671144
|
-
// State machine
|
|
671145
|
-
_state = "IDLE";
|
|
671146
|
-
active = false;
|
|
671147
|
-
// Conversation context — own turns, separate from main agent
|
|
671148
|
-
context = [];
|
|
671149
|
-
turnCount = 0;
|
|
671150
|
-
// Transcripts — separate logs for user<->voice and relay voice<->main
|
|
671151
|
-
voiceTranscript = [];
|
|
671152
|
-
relayTranscript = [];
|
|
671153
|
-
// VAD segment capture
|
|
671154
|
-
captureBuffer = "";
|
|
671155
|
-
captureStartTime = 0;
|
|
671156
|
-
silenceTimer = null;
|
|
671157
|
-
maxSegmentTimer = null;
|
|
671158
|
-
lastSignalScore = null;
|
|
671159
|
-
// Abort control for inference
|
|
671160
|
-
abortController = null;
|
|
671161
|
-
// Callbacks
|
|
671162
|
-
onStatus;
|
|
671163
|
-
onUserSpeech;
|
|
671164
|
-
onPartialTranscript;
|
|
671165
|
-
onAgentSpeech;
|
|
671166
|
-
onStateChange;
|
|
671167
|
-
// Bound handlers for cleanup
|
|
671168
|
-
_onTranscript = null;
|
|
671169
|
-
_onError = null;
|
|
671170
|
-
_retryMicTimer = null;
|
|
671171
|
-
constructor(opts) {
|
|
671135
|
+
DirectInput = class extends EventEmitter14 {
|
|
671136
|
+
/** Current input line text */
|
|
671137
|
+
line = "";
|
|
671138
|
+
/** Cursor position within .line (0-based) */
|
|
671139
|
+
cursor = 0;
|
|
671140
|
+
_history;
|
|
671141
|
+
_historySize;
|
|
671142
|
+
_historyIndex = -1;
|
|
671143
|
+
_savedLine = "";
|
|
671144
|
+
// saved current input when navigating history
|
|
671145
|
+
_completer = null;
|
|
671146
|
+
_paused = false;
|
|
671147
|
+
_closed = false;
|
|
671148
|
+
_input;
|
|
671149
|
+
_buffer = "";
|
|
671150
|
+
// partial escape sequence buffer
|
|
671151
|
+
_flushTimer = null;
|
|
671152
|
+
_expectPrefixlessMouseUntil = 0;
|
|
671153
|
+
constructor(input, options2) {
|
|
671172
671154
|
super();
|
|
671173
|
-
this.
|
|
671174
|
-
this.
|
|
671175
|
-
this.
|
|
671176
|
-
this.
|
|
671177
|
-
|
|
671178
|
-
|
|
671179
|
-
|
|
671180
|
-
this.verbose = Boolean(opts.verbose);
|
|
671181
|
-
this.debugSnr = Boolean(opts.debugSnr);
|
|
671182
|
-
this.heuristicsEnabled = opts.heuristicsEnabled !== false;
|
|
671183
|
-
if (typeof opts.vadSilenceMs === "number" && opts.vadSilenceMs > 0) {
|
|
671184
|
-
this._vadSilenceMs = Math.floor(opts.vadSilenceMs);
|
|
671185
|
-
}
|
|
671186
|
-
this.onStatus = opts.onStatus ?? (() => {
|
|
671187
|
-
});
|
|
671188
|
-
this.onUserSpeech = opts.onUserSpeech ?? (() => {
|
|
671189
|
-
});
|
|
671190
|
-
this.onPartialTranscript = opts.onPartialTranscript ?? (() => {
|
|
671191
|
-
});
|
|
671192
|
-
this.onAgentSpeech = opts.onAgentSpeech ?? (() => {
|
|
671155
|
+
this._input = input;
|
|
671156
|
+
this._history = [...options2?.history ?? []];
|
|
671157
|
+
this._historySize = options2?.historySize ?? 500;
|
|
671158
|
+
this._completer = options2?.completer ?? null;
|
|
671159
|
+
input.on("data", (chunk) => {
|
|
671160
|
+
if (this._paused || this._closed) return;
|
|
671161
|
+
this.feed(chunk.toString("utf8"));
|
|
671193
671162
|
});
|
|
671194
|
-
|
|
671163
|
+
input.on("end", () => {
|
|
671164
|
+
if (!this._closed) this.close();
|
|
671195
671165
|
});
|
|
671196
671166
|
}
|
|
671197
|
-
|
|
671198
|
-
|
|
671167
|
+
/** Process raw input data — parse escape sequences and printable chars */
|
|
671168
|
+
feed(data) {
|
|
671169
|
+
const beforeLine = this.line;
|
|
671170
|
+
const beforeCursor = this.cursor;
|
|
671171
|
+
this._buffer += data;
|
|
671172
|
+
this._processBuffer();
|
|
671173
|
+
this._emitChangeIfNeeded(beforeLine, beforeCursor);
|
|
671199
671174
|
}
|
|
671200
|
-
|
|
671201
|
-
|
|
671175
|
+
/** Pause input processing (for overlay transitions) */
|
|
671176
|
+
pause() {
|
|
671177
|
+
this._paused = true;
|
|
671202
671178
|
}
|
|
671203
|
-
|
|
671204
|
-
|
|
671205
|
-
|
|
671206
|
-
setState(next) {
|
|
671207
|
-
if (this._state === next) return;
|
|
671208
|
-
const prev = this._state;
|
|
671209
|
-
this._state = next;
|
|
671210
|
-
this.onStateChange(next);
|
|
671211
|
-
this.emit("stateChange", { from: prev, to: next });
|
|
671179
|
+
/** Resume input processing */
|
|
671180
|
+
resume() {
|
|
671181
|
+
this._paused = false;
|
|
671212
671182
|
}
|
|
671213
|
-
|
|
671214
|
-
|
|
671215
|
-
|
|
671216
|
-
|
|
671217
|
-
if (this.
|
|
671218
|
-
|
|
671219
|
-
this.
|
|
671220
|
-
await this.voice.toggle();
|
|
671221
|
-
}
|
|
671222
|
-
this.active = true;
|
|
671223
|
-
this.context = [{ role: "system", content: SYSTEM_PROMPT2 }];
|
|
671224
|
-
if (this.toolRelay) {
|
|
671225
|
-
this.toolCatalogNote = `Available tools (emit one-line JSON: {"tool":string,"args":object}):
|
|
671226
|
-
- voice_env{} → environment facts (cwd, os, cpu, mem)
|
|
671227
|
-
- voice_status{} → main-agent status
|
|
671228
|
-
- voice_list_files{dir?: string='.'} → list directory (bounded)
|
|
671229
|
-
- voice_read_file{path: string, max?: number=2048} → read file snippet
|
|
671230
|
-
- voice_to_main{message: string, start?: boolean=true} → relay/start task`;
|
|
671231
|
-
this.context.push({ role: "system", content: this.toolCatalogNote });
|
|
671232
|
-
}
|
|
671233
|
-
this.turnCount = 0;
|
|
671234
|
-
if (this.verbose) this.onStatus("VoiceChat active — LISTENING");
|
|
671235
|
-
this._onTranscript = (...args) => {
|
|
671236
|
-
let text2;
|
|
671237
|
-
let isFinal;
|
|
671238
|
-
let snr;
|
|
671239
|
-
let confidence2;
|
|
671240
|
-
if (typeof args[0] === "object" && args[0] !== null) {
|
|
671241
|
-
const evt = args[0];
|
|
671242
|
-
text2 = evt.text ?? "";
|
|
671243
|
-
isFinal = evt.isFinal ?? false;
|
|
671244
|
-
snr = evt.snr;
|
|
671245
|
-
confidence2 = evt.confidence;
|
|
671246
|
-
} else {
|
|
671247
|
-
text2 = String(args[0] ?? "");
|
|
671248
|
-
isFinal = Boolean(args[1]);
|
|
671249
|
-
}
|
|
671250
|
-
if (!text2.trim()) return;
|
|
671251
|
-
this.handleTranscript(text2.trim(), isFinal, snr, confidence2);
|
|
671252
|
-
};
|
|
671253
|
-
this._onError = (err) => {
|
|
671254
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
671255
|
-
this.onStatus(`ASR error (voicechat continues without mic): ${msg.slice(0, 80)}`);
|
|
671256
|
-
if (this.active && !this._retryMicTimer) {
|
|
671257
|
-
this._retryMicTimer = setTimeout(async () => {
|
|
671258
|
-
this._retryMicTimer = null;
|
|
671259
|
-
if (!this.active) return;
|
|
671260
|
-
try {
|
|
671261
|
-
await this.listen.stop().catch(() => {
|
|
671262
|
-
});
|
|
671263
|
-
await this.listen.start();
|
|
671264
|
-
if (this.verbose) this.onStatus("Mic auto-recovered — LISTENING");
|
|
671265
|
-
} catch {
|
|
671266
|
-
}
|
|
671267
|
-
}, 1e3);
|
|
671268
|
-
}
|
|
671269
|
-
};
|
|
671270
|
-
this.listen.on("transcript", this._onTranscript);
|
|
671271
|
-
this.listen.on("error", this._onError);
|
|
671272
|
-
try {
|
|
671273
|
-
await this.listen.start();
|
|
671274
|
-
this.setState("LISTENING");
|
|
671275
|
-
if (this.verbose) this.onStatus("Mic active — LISTENING for speech...");
|
|
671276
|
-
} catch (err) {
|
|
671277
|
-
this.onStatus(`Mic failed: ${err instanceof Error ? err.message : String(err)}. VoiceChat active without mic.`);
|
|
671278
|
-
this.setState("LISTENING");
|
|
671183
|
+
/** Close the input handler */
|
|
671184
|
+
close() {
|
|
671185
|
+
if (this._closed) return;
|
|
671186
|
+
this._closed = true;
|
|
671187
|
+
if (this._flushTimer) {
|
|
671188
|
+
clearTimeout(this._flushTimer);
|
|
671189
|
+
this._flushTimer = null;
|
|
671279
671190
|
}
|
|
671191
|
+
this.emit("close");
|
|
671280
671192
|
}
|
|
671281
|
-
|
|
671282
|
-
|
|
671283
|
-
this.active = false;
|
|
671284
|
-
if (this.abortController) {
|
|
671285
|
-
this.abortController.abort();
|
|
671286
|
-
this.abortController = null;
|
|
671287
|
-
}
|
|
671288
|
-
if (this.silenceTimer) {
|
|
671289
|
-
clearTimeout(this.silenceTimer);
|
|
671290
|
-
this.silenceTimer = null;
|
|
671291
|
-
}
|
|
671292
|
-
if (this.maxSegmentTimer) {
|
|
671293
|
-
clearTimeout(this.maxSegmentTimer);
|
|
671294
|
-
this.maxSegmentTimer = null;
|
|
671295
|
-
}
|
|
671296
|
-
if (this.captureBuffer.trim() && (this._state === "CAPTURING" || this._state === "TRANSCRIBING")) {
|
|
671297
|
-
this.finalizeSegment();
|
|
671298
|
-
}
|
|
671299
|
-
if (this._onTranscript) {
|
|
671300
|
-
this.listen.removeAllListeners("transcript");
|
|
671301
|
-
this._onTranscript = null;
|
|
671302
|
-
}
|
|
671303
|
-
if (this._onError) {
|
|
671304
|
-
this.listen.removeAllListeners("error");
|
|
671305
|
-
this._onError = null;
|
|
671306
|
-
}
|
|
671307
|
-
try {
|
|
671308
|
-
await this.listen.stop();
|
|
671309
|
-
} catch {
|
|
671310
|
-
}
|
|
671311
|
-
this.setState("IDLE");
|
|
671312
|
-
if (this.verbose) this.onStatus("VoiceChat ended");
|
|
671313
|
-
this.emit("stopped");
|
|
671193
|
+
/** No-op — readline compat (StatusBar renders the prompt, not us) */
|
|
671194
|
+
setPrompt(_prompt) {
|
|
671314
671195
|
}
|
|
671315
|
-
|
|
671316
|
-
|
|
671317
|
-
|
|
671318
|
-
|
|
671319
|
-
|
|
671320
|
-
|
|
671321
|
-
|
|
671196
|
+
/** No-op — readline compat */
|
|
671197
|
+
prompt(_preserveCursor) {
|
|
671198
|
+
}
|
|
671199
|
+
/** Set the line content and cursor position (for Esc-to-recall or suggestion apply) */
|
|
671200
|
+
setLine(text2, cursorPos) {
|
|
671201
|
+
const beforeLine = this.line;
|
|
671202
|
+
const beforeCursor = this.cursor;
|
|
671203
|
+
this.line = text2;
|
|
671204
|
+
this.cursor = cursorPos ?? text2.length;
|
|
671205
|
+
this._emitChangeIfNeeded(beforeLine, beforeCursor);
|
|
671206
|
+
}
|
|
671207
|
+
/** Pre-submit hook — called before Enter submits. Return true to consume Enter. */
|
|
671208
|
+
_preSubmit = null;
|
|
671209
|
+
setPreSubmit(hook) {
|
|
671210
|
+
this._preSubmit = hook;
|
|
671211
|
+
}
|
|
671212
|
+
/** Navigate history up (older) */
|
|
671213
|
+
historyUp() {
|
|
671214
|
+
if (this._history.length === 0) return;
|
|
671215
|
+
if (this._historyIndex === -1) {
|
|
671216
|
+
this._savedLine = this.line;
|
|
671322
671217
|
}
|
|
671323
|
-
if (this.
|
|
671324
|
-
this.
|
|
671325
|
-
this.
|
|
671326
|
-
this.
|
|
671327
|
-
this.maxSegmentTimer = setTimeout(() => {
|
|
671328
|
-
if (this._state === "CAPTURING") {
|
|
671329
|
-
this.finalizeSegment();
|
|
671330
|
-
}
|
|
671331
|
-
}, MAX_SEGMENT_MS);
|
|
671218
|
+
if (this._historyIndex < this._history.length - 1) {
|
|
671219
|
+
this._historyIndex++;
|
|
671220
|
+
this.line = this._history[this._historyIndex];
|
|
671221
|
+
this.cursor = this.line.length;
|
|
671332
671222
|
}
|
|
671333
|
-
this.captureBuffer = text2;
|
|
671334
|
-
this.lastSignalScore = typeof snr === "number" && !Number.isNaN(snr) ? clamp0114(snr) : computeSignalFromText(text2, confidence2);
|
|
671335
|
-
this.emit("snr", { score: this.lastSignalScore });
|
|
671336
|
-
this.onPartialTranscript(text2);
|
|
671337
|
-
if (this.silenceTimer) clearTimeout(this.silenceTimer);
|
|
671338
|
-
const waitMs = this._vadSilenceMs ?? VAD_SILENCE_MS;
|
|
671339
|
-
this.silenceTimer = setTimeout(() => {
|
|
671340
|
-
if (this._state === "CAPTURING") {
|
|
671341
|
-
this.finalizeSegment();
|
|
671342
|
-
}
|
|
671343
|
-
}, waitMs);
|
|
671344
671223
|
}
|
|
671345
|
-
|
|
671346
|
-
|
|
671347
|
-
|
|
671348
|
-
|
|
671349
|
-
|
|
671350
|
-
|
|
671351
|
-
|
|
671352
|
-
this.
|
|
671353
|
-
}
|
|
671354
|
-
if (this.maxSegmentTimer) {
|
|
671355
|
-
clearTimeout(this.maxSegmentTimer);
|
|
671356
|
-
this.maxSegmentTimer = null;
|
|
671224
|
+
/** Navigate history down (newer) */
|
|
671225
|
+
historyDown() {
|
|
671226
|
+
if (this._historyIndex <= -1) return;
|
|
671227
|
+
this._historyIndex--;
|
|
671228
|
+
if (this._historyIndex === -1) {
|
|
671229
|
+
this.line = this._savedLine;
|
|
671230
|
+
} else {
|
|
671231
|
+
this.line = this._history[this._historyIndex];
|
|
671357
671232
|
}
|
|
671358
|
-
this.
|
|
671359
|
-
|
|
671360
|
-
|
|
671361
|
-
|
|
671233
|
+
this.cursor = this.line.length;
|
|
671234
|
+
}
|
|
671235
|
+
/**
|
|
671236
|
+
* Move cursor up one wrapped line. Returns true if moved, false if at top line.
|
|
671237
|
+
* Used by arrow-up to navigate within wrapped input before falling through to history.
|
|
671238
|
+
*/
|
|
671239
|
+
cursorUpWrapped(availWidth) {
|
|
671240
|
+
if (this.line.length <= availWidth) return false;
|
|
671241
|
+
const { charPositions, rawLines } = this._computeWrappedLines(availWidth);
|
|
671242
|
+
if (rawLines.length <= 1) return false;
|
|
671243
|
+
let currentLineIdx = rawLines.length - 1;
|
|
671244
|
+
for (let i2 = 0; i2 < charPositions.length; i2++) {
|
|
671245
|
+
const lineStart = charPositions[i2];
|
|
671246
|
+
const lineEnd = lineStart + rawLines[i2].length;
|
|
671247
|
+
if (this.cursor >= lineStart && this.cursor <= lineEnd) {
|
|
671248
|
+
currentLineIdx = i2;
|
|
671249
|
+
break;
|
|
671250
|
+
}
|
|
671362
671251
|
}
|
|
671363
|
-
|
|
671364
|
-
|
|
671365
|
-
|
|
671366
|
-
|
|
671367
|
-
|
|
671368
|
-
|
|
671369
|
-
|
|
671370
|
-
|
|
671252
|
+
if (currentLineIdx === 0) return false;
|
|
671253
|
+
const prevLineIdx = currentLineIdx - 1;
|
|
671254
|
+
const currentColInLine = this.cursor - charPositions[currentLineIdx];
|
|
671255
|
+
const prevLineLength = rawLines[prevLineIdx].length;
|
|
671256
|
+
const newColInLine = Math.min(currentColInLine, prevLineLength);
|
|
671257
|
+
this.cursor = charPositions[prevLineIdx] + newColInLine;
|
|
671258
|
+
return true;
|
|
671259
|
+
}
|
|
671260
|
+
/**
|
|
671261
|
+
* Move cursor down one wrapped line. Returns true if moved, false if at bottom line.
|
|
671262
|
+
* Used by arrow-down to navigate within wrapped input before falling through to history.
|
|
671263
|
+
*/
|
|
671264
|
+
cursorDownWrapped(availWidth) {
|
|
671265
|
+
if (this.line.length <= availWidth) return false;
|
|
671266
|
+
const { charPositions, rawLines } = this._computeWrappedLines(availWidth);
|
|
671267
|
+
if (rawLines.length <= 1) return false;
|
|
671268
|
+
let currentLineIdx = rawLines.length - 1;
|
|
671269
|
+
for (let i2 = 0; i2 < charPositions.length; i2++) {
|
|
671270
|
+
const lineStart = charPositions[i2];
|
|
671271
|
+
const lineEnd = lineStart + rawLines[i2].length;
|
|
671272
|
+
if (this.cursor >= lineStart && this.cursor <= lineEnd) {
|
|
671273
|
+
currentLineIdx = i2;
|
|
671274
|
+
break;
|
|
671275
|
+
}
|
|
671371
671276
|
}
|
|
671372
|
-
|
|
671373
|
-
|
|
671374
|
-
|
|
671375
|
-
|
|
671376
|
-
|
|
671377
|
-
|
|
671378
|
-
|
|
671379
|
-
|
|
671380
|
-
|
|
671277
|
+
if (currentLineIdx === rawLines.length - 1) return false;
|
|
671278
|
+
const nextLineIdx = currentLineIdx + 1;
|
|
671279
|
+
const currentColInLine = this.cursor - charPositions[currentLineIdx];
|
|
671280
|
+
const nextLineLength = rawLines[nextLineIdx].length;
|
|
671281
|
+
const newColInLine = Math.min(currentColInLine, nextLineLength);
|
|
671282
|
+
this.cursor = charPositions[nextLineIdx] + newColInLine;
|
|
671283
|
+
return true;
|
|
671284
|
+
}
|
|
671285
|
+
/**
|
|
671286
|
+
* Compute wrapped lines (word-aware). Returns charPositions (start index of each line)
|
|
671287
|
+
* and rawLines (text of each line). Matches wrapInput logic in status-bar.ts.
|
|
671288
|
+
*/
|
|
671289
|
+
_computeWrappedLines(availWidth) {
|
|
671290
|
+
const width = Math.max(1, availWidth);
|
|
671291
|
+
const rawLines = [];
|
|
671292
|
+
const charPositions = [];
|
|
671293
|
+
const pushWrappedSegment = (segment, segmentStart2) => {
|
|
671294
|
+
if (segment.length === 0) {
|
|
671295
|
+
charPositions.push(segmentStart2);
|
|
671296
|
+
rawLines.push("");
|
|
671297
|
+
return;
|
|
671381
671298
|
}
|
|
671299
|
+
let offset = 0;
|
|
671300
|
+
while (offset < segment.length) {
|
|
671301
|
+
const remaining = segment.slice(offset);
|
|
671302
|
+
if (remaining.length <= width) {
|
|
671303
|
+
charPositions.push(segmentStart2 + offset);
|
|
671304
|
+
rawLines.push(remaining);
|
|
671305
|
+
break;
|
|
671306
|
+
}
|
|
671307
|
+
let breakAt = width;
|
|
671308
|
+
const lastSpace = remaining.lastIndexOf(" ", width);
|
|
671309
|
+
if (lastSpace > 0 && lastSpace >= width * 0.3) {
|
|
671310
|
+
breakAt = lastSpace + 1;
|
|
671311
|
+
}
|
|
671312
|
+
charPositions.push(segmentStart2 + offset);
|
|
671313
|
+
rawLines.push(remaining.slice(0, breakAt));
|
|
671314
|
+
offset += breakAt;
|
|
671315
|
+
}
|
|
671316
|
+
};
|
|
671317
|
+
if (this.line.length === 0) {
|
|
671318
|
+
pushWrappedSegment("", 0);
|
|
671319
|
+
return { charPositions, rawLines };
|
|
671382
671320
|
}
|
|
671383
|
-
|
|
671384
|
-
|
|
671321
|
+
let segmentStart = 0;
|
|
671322
|
+
while (segmentStart <= this.line.length) {
|
|
671323
|
+
const newlineAt = this.line.indexOf("\n", segmentStart);
|
|
671324
|
+
const segmentEnd = newlineAt === -1 ? this.line.length : newlineAt;
|
|
671325
|
+
pushWrappedSegment(this.line.slice(segmentStart, segmentEnd), segmentStart);
|
|
671326
|
+
if (newlineAt === -1) break;
|
|
671327
|
+
segmentStart = newlineAt + 1;
|
|
671328
|
+
if (segmentStart === this.line.length) {
|
|
671329
|
+
pushWrappedSegment("", segmentStart);
|
|
671330
|
+
break;
|
|
671331
|
+
}
|
|
671385
671332
|
}
|
|
671386
|
-
|
|
671333
|
+
return { charPositions, rawLines };
|
|
671387
671334
|
}
|
|
671388
671335
|
// ---------------------------------------------------------------------------
|
|
671389
|
-
//
|
|
671336
|
+
// Private: buffer processing and escape sequence parsing
|
|
671390
671337
|
// ---------------------------------------------------------------------------
|
|
671391
|
-
|
|
671392
|
-
|
|
671393
|
-
this.
|
|
671394
|
-
|
|
671395
|
-
|
|
671396
|
-
|
|
671397
|
-
|
|
671398
|
-
|
|
671399
|
-
|
|
671400
|
-
|
|
671401
|
-
|
|
671402
|
-
|
|
671338
|
+
_processBuffer() {
|
|
671339
|
+
let i2 = 0;
|
|
671340
|
+
while (i2 < this._buffer.length) {
|
|
671341
|
+
const remaining = this._buffer.slice(i2);
|
|
671342
|
+
const prefixlessMouse = this._matchPrefixlessSgrMouse(remaining);
|
|
671343
|
+
if (prefixlessMouse) {
|
|
671344
|
+
i2 += prefixlessMouse.length;
|
|
671345
|
+
continue;
|
|
671346
|
+
}
|
|
671347
|
+
if (this._looksLikePartialPrefixlessSgrMouse(remaining)) {
|
|
671348
|
+
break;
|
|
671349
|
+
}
|
|
671350
|
+
const ch = this._buffer[i2];
|
|
671351
|
+
const code8 = ch.charCodeAt(0);
|
|
671352
|
+
if (code8 === 27) {
|
|
671353
|
+
if (remaining.length >= 2 && remaining[1] === "[") {
|
|
671354
|
+
const mouseMatch = remaining.match(/^\x1B\[<(\d+);(\d+);(\d+)([Mm])/);
|
|
671355
|
+
if (mouseMatch) {
|
|
671356
|
+
i2 += mouseMatch[0].length;
|
|
671357
|
+
continue;
|
|
671403
671358
|
}
|
|
671404
|
-
|
|
671359
|
+
if (remaining.startsWith("\x1B[<") && remaining.length < 15) {
|
|
671360
|
+
break;
|
|
671361
|
+
}
|
|
671362
|
+
if (remaining.startsWith("\x1B[M") && remaining.length >= 6) {
|
|
671363
|
+
i2 += 6;
|
|
671364
|
+
continue;
|
|
671365
|
+
}
|
|
671366
|
+
if (remaining.startsWith("\x1B[M") && remaining.length < 6) {
|
|
671367
|
+
break;
|
|
671368
|
+
}
|
|
671369
|
+
if (remaining.startsWith("\x1B[<")) {
|
|
671370
|
+
this._expectPrefixlessMouseUntil = Date.now() + 1e3;
|
|
671371
|
+
let end = 3;
|
|
671372
|
+
while (end < remaining.length && remaining[end] !== "M" && remaining[end] !== "m") end++;
|
|
671373
|
+
if (end < remaining.length) end++;
|
|
671374
|
+
i2 += end;
|
|
671375
|
+
continue;
|
|
671376
|
+
}
|
|
671377
|
+
const csiMatch = remaining.match(/^\x1B\[([0-9;]*)([A-Za-z~])/);
|
|
671378
|
+
if (csiMatch) {
|
|
671379
|
+
this._handleCSI(csiMatch[1], csiMatch[2]);
|
|
671380
|
+
i2 += csiMatch[0].length;
|
|
671381
|
+
continue;
|
|
671382
|
+
}
|
|
671383
|
+
if (remaining.length < 10) {
|
|
671384
|
+
break;
|
|
671385
|
+
}
|
|
671386
|
+
i2 += 2;
|
|
671387
|
+
continue;
|
|
671405
671388
|
}
|
|
671406
|
-
|
|
671407
|
-
|
|
671408
|
-
|
|
671409
|
-
|
|
671410
|
-
|
|
671411
|
-
const wantList = /(list|show|explore|browse|what's in|whats in|contents).*(dir|directory|folder|files)/.test(lower);
|
|
671412
|
-
const wantEnv = /(what\s+dir|cwd|current\s+dir|working\s+directory|where\s+are\s+you)/.test(lower);
|
|
671413
|
-
const readMatch = lastUser.match(/(?:read|open|show)\s+file\s+([\w./\\-]+)\b/i);
|
|
671414
|
-
const toMainMatch = lastUser.match(/^(?:start|run|do)\s+(.{5,})$/i);
|
|
671415
|
-
try {
|
|
671416
|
-
if (wantEnv) {
|
|
671417
|
-
const out = await this.toolRelay.call("voice_env", {});
|
|
671418
|
-
this.context.push({ role: "system", content: `Tool voice_env result (authoritative):
|
|
671419
|
-
${out}` });
|
|
671420
|
-
preAnswered = true;
|
|
671421
|
-
} else if (wantList) {
|
|
671422
|
-
const out = await this.toolRelay.call("voice_list_files", { dir: "." });
|
|
671423
|
-
this.context.push({ role: "system", content: `Tool voice_list_files result (authoritative):
|
|
671424
|
-
${out}` });
|
|
671425
|
-
preAnswered = true;
|
|
671426
|
-
} else if (readMatch) {
|
|
671427
|
-
const out = await this.toolRelay.call("voice_read_file", { path: readMatch[1], max: 1024 });
|
|
671428
|
-
this.context.push({ role: "system", content: `Tool voice_read_file result (authoritative):
|
|
671429
|
-
${out}` });
|
|
671430
|
-
preAnswered = true;
|
|
671431
|
-
} else if (toMainMatch) {
|
|
671432
|
-
const msg = toMainMatch[1].trim();
|
|
671433
|
-
const out = await this.toolRelay.call("voice_to_main", { message: msg, start: true });
|
|
671434
|
-
this.relayTranscript.push({ dir: "toMain", content: msg, ts: Date.now() });
|
|
671435
|
-
this.context.push({ role: "system", content: `Tool voice_to_main result (authoritative):
|
|
671436
|
-
${out}` });
|
|
671437
|
-
preAnswered = true;
|
|
671389
|
+
if (remaining.length >= 2 && remaining[1] === "O") {
|
|
671390
|
+
if (remaining.length >= 3) {
|
|
671391
|
+
this._handleSS3(remaining[2]);
|
|
671392
|
+
i2 += 3;
|
|
671393
|
+
continue;
|
|
671438
671394
|
}
|
|
671439
|
-
|
|
671395
|
+
break;
|
|
671440
671396
|
}
|
|
671441
|
-
|
|
671442
|
-
|
|
671443
|
-
|
|
671444
|
-
|
|
671445
|
-
|
|
671446
|
-
const toolReq = extractToolJsonLoose(response);
|
|
671447
|
-
if (!toolReq) break;
|
|
671448
|
-
const { name: name10, args } = toolReq;
|
|
671449
|
-
let toolOutput = "";
|
|
671450
|
-
try {
|
|
671451
|
-
toolOutput = await this.toolRelay.call(name10, args);
|
|
671452
|
-
} catch (e2) {
|
|
671453
|
-
toolOutput = `Tool ${name10} failed: ${e2 instanceof Error ? e2.message : String(e2)}`;
|
|
671397
|
+
if (remaining.length >= 2 && (remaining[1] === "\r" || remaining[1] === "\n")) {
|
|
671398
|
+
this._insertText("\n");
|
|
671399
|
+
i2 += 2;
|
|
671400
|
+
if (remaining[1] === "\r" && remaining[2] === "\n") i2++;
|
|
671401
|
+
continue;
|
|
671454
671402
|
}
|
|
671455
|
-
if (
|
|
671456
|
-
|
|
671457
|
-
if (msg) this.relayTranscript.push({ dir: "toMain", content: msg, ts: Date.now() });
|
|
671403
|
+
if (remaining.length === 1) {
|
|
671404
|
+
break;
|
|
671458
671405
|
}
|
|
671459
|
-
|
|
671460
|
-
|
|
671461
|
-
}
|
|
671462
|
-
if (!this.active) return;
|
|
671463
|
-
if (this.heuristicsEnabled && this.toolRelay && /\b(can't|cannot)\b/i.test(response) && this.toolCatalogNote) {
|
|
671464
|
-
this.context.push({ role: "system", content: `You have tools. Use them. ${this.toolCatalogNote}` });
|
|
671465
|
-
response = await this.streamOllamaInference(this.abortController.signal);
|
|
671406
|
+
i2++;
|
|
671407
|
+
continue;
|
|
671466
671408
|
}
|
|
671467
|
-
if (
|
|
671468
|
-
|
|
671469
|
-
|
|
671470
|
-
|
|
671471
|
-
|
|
671472
|
-
|
|
671473
|
-
this.
|
|
671474
|
-
|
|
671409
|
+
if (code8 < 32 || code8 === 127) {
|
|
671410
|
+
if (code8 === 13) {
|
|
671411
|
+
if (this._preSubmit?.()) {
|
|
671412
|
+
i2++;
|
|
671413
|
+
continue;
|
|
671414
|
+
}
|
|
671415
|
+
this._submit();
|
|
671416
|
+
i2++;
|
|
671417
|
+
if (this._buffer[i2] === "\n") i2++;
|
|
671418
|
+
continue;
|
|
671475
671419
|
}
|
|
671476
|
-
|
|
671477
|
-
|
|
671478
|
-
|
|
671479
|
-
|
|
671480
|
-
this.injectSummary();
|
|
671420
|
+
if (code8 === 10) {
|
|
671421
|
+
this._insertText("\n");
|
|
671422
|
+
i2++;
|
|
671423
|
+
continue;
|
|
671481
671424
|
}
|
|
671482
|
-
|
|
671483
|
-
|
|
671484
|
-
|
|
671485
|
-
|
|
671425
|
+
this._handleControl(code8);
|
|
671426
|
+
i2++;
|
|
671427
|
+
continue;
|
|
671428
|
+
}
|
|
671429
|
+
const char = this._buffer[i2];
|
|
671430
|
+
this.line = this.line.slice(0, this.cursor) + char + this.line.slice(this.cursor);
|
|
671431
|
+
this.cursor++;
|
|
671432
|
+
i2++;
|
|
671433
|
+
}
|
|
671434
|
+
this._buffer = this._buffer.slice(i2);
|
|
671435
|
+
if (this._buffer.length > 0) {
|
|
671436
|
+
if (this._flushTimer) clearTimeout(this._flushTimer);
|
|
671437
|
+
this._flushTimer = setTimeout(() => {
|
|
671438
|
+
this._flushTimer = null;
|
|
671439
|
+
if (this._buffer.length > 0) {
|
|
671440
|
+
if (this._buffer.startsWith("\x1B[<") || this._buffer.startsWith("\x1B[M")) {
|
|
671441
|
+
this._expectPrefixlessMouseUntil = Date.now() + 1e3;
|
|
671442
|
+
this._buffer = "";
|
|
671443
|
+
return;
|
|
671486
671444
|
}
|
|
671487
|
-
|
|
671488
|
-
|
|
671489
|
-
|
|
671445
|
+
if (this._buffer === "\x1B[") {
|
|
671446
|
+
this._expectPrefixlessMouseUntil = Date.now() + 1e3;
|
|
671447
|
+
this._buffer = "";
|
|
671448
|
+
return;
|
|
671449
|
+
}
|
|
671450
|
+
if (this._looksLikePartialPrefixlessSgrMouse(this._buffer)) {
|
|
671451
|
+
this._buffer = "";
|
|
671452
|
+
return;
|
|
671453
|
+
}
|
|
671454
|
+
if (this._buffer === "\x1B") {
|
|
671455
|
+
this.emit("escape");
|
|
671456
|
+
}
|
|
671457
|
+
this._buffer = "";
|
|
671490
671458
|
}
|
|
671459
|
+
}, 50);
|
|
671460
|
+
} else {
|
|
671461
|
+
if (this._flushTimer) {
|
|
671462
|
+
clearTimeout(this._flushTimer);
|
|
671463
|
+
this._flushTimer = null;
|
|
671491
671464
|
}
|
|
671492
|
-
} catch (err) {
|
|
671493
|
-
if (!this.active) return;
|
|
671494
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
671495
|
-
if (!msg.includes("abort")) {
|
|
671496
|
-
this.onStatus(`Inference error: ${msg.slice(0, 100)}`);
|
|
671497
|
-
}
|
|
671498
|
-
} finally {
|
|
671499
|
-
this.abortController = null;
|
|
671500
671465
|
}
|
|
671501
|
-
|
|
671502
|
-
|
|
671503
|
-
|
|
671504
|
-
|
|
671505
|
-
|
|
671506
|
-
|
|
671507
|
-
|
|
671466
|
+
}
|
|
671467
|
+
_matchPrefixlessSgrMouse(input) {
|
|
671468
|
+
const match = input.match(/^(<)?(\d{1,3});(\d{1,5});(\d{1,5})([Mm])/);
|
|
671469
|
+
if (!match) return null;
|
|
671470
|
+
const hasMarker = Boolean(match[1]);
|
|
671471
|
+
const btn = parseInt(match[2], 10);
|
|
671472
|
+
const col = parseInt(match[3], 10);
|
|
671473
|
+
const row = parseInt(match[4], 10);
|
|
671474
|
+
if (!Number.isFinite(btn) || !Number.isFinite(col) || !Number.isFinite(row))
|
|
671475
|
+
return null;
|
|
671476
|
+
if (btn < 0 || btn > 255 || col <= 0 || row <= 0) return null;
|
|
671477
|
+
if (!hasMarker && Date.now() > this._expectPrefixlessMouseUntil && !this._isKnownMouseButton(btn))
|
|
671478
|
+
return null;
|
|
671479
|
+
return { length: match[0].length };
|
|
671480
|
+
}
|
|
671481
|
+
_looksLikePartialPrefixlessSgrMouse(input) {
|
|
671482
|
+
if (input.length < 2) return false;
|
|
671483
|
+
if (/^<\d{1,3};\d{0,5}(?:;\d{0,5})?$/.test(input)) return true;
|
|
671484
|
+
if (Date.now() <= this._expectPrefixlessMouseUntil && /^\d{1,3};\d{0,5}(?:;\d{0,5})?$/.test(input)) {
|
|
671485
|
+
return true;
|
|
671508
671486
|
}
|
|
671487
|
+
return false;
|
|
671509
671488
|
}
|
|
671510
|
-
|
|
671511
|
-
|
|
671512
|
-
|
|
671513
|
-
|
|
671514
|
-
|
|
671515
|
-
|
|
671516
|
-
|
|
671517
|
-
|
|
671518
|
-
|
|
671519
|
-
|
|
671520
|
-
|
|
671521
|
-
|
|
671522
|
-
|
|
671523
|
-
|
|
671524
|
-
|
|
671525
|
-
|
|
671526
|
-
|
|
671527
|
-
|
|
671528
|
-
|
|
671529
|
-
|
|
671530
|
-
|
|
671531
|
-
|
|
671532
|
-
|
|
671533
|
-
|
|
671534
|
-
|
|
671489
|
+
_isKnownMouseButton(btn) {
|
|
671490
|
+
if (btn >= 0 && btn <= 6) return true;
|
|
671491
|
+
if (btn >= 32 && btn <= 39) return true;
|
|
671492
|
+
if (btn >= 64 && btn <= 71) return true;
|
|
671493
|
+
if (btn >= 96 && btn <= 103) return true;
|
|
671494
|
+
return false;
|
|
671495
|
+
}
|
|
671496
|
+
_emitChangeIfNeeded(beforeLine, beforeCursor) {
|
|
671497
|
+
if (this.line === beforeLine && this.cursor === beforeCursor) return;
|
|
671498
|
+
this.emit("change", { line: this.line, cursor: this.cursor });
|
|
671499
|
+
}
|
|
671500
|
+
/** Handle CSI escape sequence: \x1B[ {params} {final} */
|
|
671501
|
+
_handleCSI(params, final2) {
|
|
671502
|
+
switch (final2) {
|
|
671503
|
+
case "A":
|
|
671504
|
+
if (params === "1;2") {
|
|
671505
|
+
this.emit("shiftup");
|
|
671506
|
+
return;
|
|
671507
|
+
}
|
|
671508
|
+
this.emit("up");
|
|
671509
|
+
return;
|
|
671510
|
+
case "B":
|
|
671511
|
+
if (params === "1;2") {
|
|
671512
|
+
this.emit("shiftdown");
|
|
671513
|
+
return;
|
|
671514
|
+
}
|
|
671515
|
+
this.emit("down");
|
|
671516
|
+
return;
|
|
671517
|
+
case "C":
|
|
671518
|
+
if (params === "1;5") {
|
|
671519
|
+
this.emit("ctrl-right");
|
|
671520
|
+
return;
|
|
671521
|
+
}
|
|
671522
|
+
if (this.cursor < this.line.length) this.cursor++;
|
|
671523
|
+
return;
|
|
671524
|
+
case "D":
|
|
671525
|
+
if (params === "1;5") {
|
|
671526
|
+
this.emit("ctrl-left");
|
|
671527
|
+
return;
|
|
671528
|
+
}
|
|
671529
|
+
if (this.cursor > 0) this.cursor--;
|
|
671530
|
+
return;
|
|
671531
|
+
case "H":
|
|
671532
|
+
this.cursor = 0;
|
|
671533
|
+
return;
|
|
671534
|
+
case "F":
|
|
671535
|
+
this.cursor = this.line.length;
|
|
671536
|
+
return;
|
|
671537
|
+
case "~":
|
|
671538
|
+
if (this._isShiftEnterCSI(params)) {
|
|
671539
|
+
this._insertText("\n");
|
|
671540
|
+
return;
|
|
671541
|
+
}
|
|
671542
|
+
if (params === "3") {
|
|
671543
|
+
if (this.cursor < this.line.length) {
|
|
671544
|
+
this.line = this.line.slice(0, this.cursor) + this.line.slice(this.cursor + 1);
|
|
671545
|
+
}
|
|
671546
|
+
return;
|
|
671547
|
+
}
|
|
671548
|
+
if (params === "5") {
|
|
671549
|
+
this.emit("pageup");
|
|
671550
|
+
return;
|
|
671551
|
+
}
|
|
671552
|
+
if (params === "6") {
|
|
671553
|
+
this.emit("pagedown");
|
|
671554
|
+
return;
|
|
671555
|
+
}
|
|
671556
|
+
return;
|
|
671557
|
+
case "u": {
|
|
671558
|
+
const parts = params.split(";");
|
|
671559
|
+
const codepoint = parseInt(parts[0] ?? "0");
|
|
671560
|
+
const modifiers = parseInt(parts[1] ?? "1");
|
|
671561
|
+
const hasCtrl = modifiers - 1 & 4;
|
|
671562
|
+
const hasShift = modifiers - 1 & 1;
|
|
671563
|
+
if (hasShift && (codepoint === 10 || codepoint === 13)) {
|
|
671564
|
+
this._insertText("\n");
|
|
671565
|
+
return;
|
|
671566
|
+
}
|
|
671567
|
+
if (hasCtrl && hasShift) {
|
|
671568
|
+
if (codepoint === 67) {
|
|
671569
|
+
this.emit("ctrl-shift-c");
|
|
671570
|
+
return;
|
|
671571
|
+
}
|
|
671572
|
+
if (codepoint === 66) {
|
|
671573
|
+
this.emit("ctrl-shift-b");
|
|
671574
|
+
return;
|
|
671575
|
+
}
|
|
671576
|
+
if (codepoint === 86) {
|
|
671577
|
+
this.emit("ctrl-shift-v");
|
|
671578
|
+
return;
|
|
671579
|
+
}
|
|
671580
|
+
}
|
|
671581
|
+
if (hasCtrl && !hasShift && (codepoint === 73 || codepoint === 105)) {
|
|
671582
|
+
this.emit("ctrl-i");
|
|
671583
|
+
return;
|
|
671584
|
+
}
|
|
671585
|
+
return;
|
|
671535
671586
|
}
|
|
671536
|
-
} catch (err) {
|
|
671537
|
-
const msg = err instanceof Error ? err.message : "";
|
|
671538
|
-
if (msg.includes("abort")) throw err;
|
|
671539
671587
|
}
|
|
671540
|
-
|
|
671541
|
-
|
|
671542
|
-
|
|
671543
|
-
|
|
671544
|
-
|
|
671545
|
-
|
|
671546
|
-
});
|
|
671547
|
-
const endpoint = baseUrl.includes("/v1") ? `${baseUrl}/chat/completions` : `${baseUrl}/v1/chat/completions`;
|
|
671548
|
-
const res = await fetch(endpoint, { method: "POST", headers, body: openaiBody, signal });
|
|
671549
|
-
if (!res.ok) {
|
|
671550
|
-
const errText = await res.text().catch(() => "unknown");
|
|
671551
|
-
throw new Error(`Inference ${res.status}: ${errText.slice(0, 200)}`);
|
|
671588
|
+
}
|
|
671589
|
+
_isShiftEnterCSI(params) {
|
|
671590
|
+
const parts = params.split(";").map((part) => parseInt(part, 10));
|
|
671591
|
+
if (parts.length === 2) {
|
|
671592
|
+
const [codepoint, modifiers] = parts;
|
|
671593
|
+
return (codepoint === 10 || codepoint === 13) && modifiers === 2;
|
|
671552
671594
|
}
|
|
671553
|
-
|
|
671595
|
+
if (parts.length === 3) {
|
|
671596
|
+
const [prefix, modifiers, codepoint] = parts;
|
|
671597
|
+
return prefix === 27 && modifiers === 2 && (codepoint === 10 || codepoint === 13);
|
|
671598
|
+
}
|
|
671599
|
+
return false;
|
|
671554
671600
|
}
|
|
671555
|
-
|
|
671556
|
-
|
|
671557
|
-
|
|
671558
|
-
|
|
671559
|
-
|
|
671560
|
-
|
|
671561
|
-
|
|
671562
|
-
|
|
671563
|
-
|
|
671564
|
-
|
|
671565
|
-
|
|
671566
|
-
|
|
671567
|
-
|
|
671568
|
-
|
|
671569
|
-
if (
|
|
671570
|
-
|
|
671571
|
-
|
|
671572
|
-
|
|
671573
|
-
|
|
671574
|
-
|
|
671575
|
-
|
|
671576
|
-
|
|
671577
|
-
|
|
671578
|
-
|
|
671601
|
+
_insertText(text2) {
|
|
671602
|
+
this.line = this.line.slice(0, this.cursor) + text2 + this.line.slice(this.cursor);
|
|
671603
|
+
this.cursor += text2.length;
|
|
671604
|
+
}
|
|
671605
|
+
/** Handle SS3 sequence: \x1BO {final} (some terminals use this for arrows/Home/End) */
|
|
671606
|
+
_handleSS3(final2) {
|
|
671607
|
+
switch (final2) {
|
|
671608
|
+
case "A":
|
|
671609
|
+
this.emit("up");
|
|
671610
|
+
return;
|
|
671611
|
+
case "B":
|
|
671612
|
+
this.emit("down");
|
|
671613
|
+
return;
|
|
671614
|
+
case "C":
|
|
671615
|
+
if (this.cursor < this.line.length) this.cursor++;
|
|
671616
|
+
return;
|
|
671617
|
+
case "D":
|
|
671618
|
+
if (this.cursor > 0) this.cursor--;
|
|
671619
|
+
return;
|
|
671620
|
+
case "H":
|
|
671621
|
+
this.cursor = 0;
|
|
671622
|
+
return;
|
|
671623
|
+
case "F":
|
|
671624
|
+
this.cursor = this.line.length;
|
|
671625
|
+
return;
|
|
671626
|
+
}
|
|
671627
|
+
}
|
|
671628
|
+
/** Handle control characters (ASCII < 32 and DEL) */
|
|
671629
|
+
_handleControl(code8) {
|
|
671630
|
+
switch (code8) {
|
|
671631
|
+
case 127:
|
|
671632
|
+
// Backspace (DEL)
|
|
671633
|
+
case 8:
|
|
671634
|
+
if (this.cursor > 0) {
|
|
671635
|
+
this.line = this.line.slice(0, this.cursor - 1) + this.line.slice(this.cursor);
|
|
671636
|
+
this.cursor--;
|
|
671579
671637
|
}
|
|
671638
|
+
return;
|
|
671639
|
+
case 3:
|
|
671640
|
+
this.emit("SIGINT");
|
|
671641
|
+
return;
|
|
671642
|
+
case 4:
|
|
671643
|
+
if (this.line.length === 0) this.close();
|
|
671644
|
+
return;
|
|
671645
|
+
case 9:
|
|
671646
|
+
this._handleTab();
|
|
671647
|
+
return;
|
|
671648
|
+
case 23:
|
|
671649
|
+
this._deleteWordLeft();
|
|
671650
|
+
return;
|
|
671651
|
+
case 21:
|
|
671652
|
+
this.line = this.line.slice(this.cursor);
|
|
671653
|
+
this.cursor = 0;
|
|
671654
|
+
return;
|
|
671655
|
+
case 11:
|
|
671656
|
+
this.line = this.line.slice(0, this.cursor);
|
|
671657
|
+
return;
|
|
671658
|
+
case 1:
|
|
671659
|
+
this.cursor = 0;
|
|
671660
|
+
return;
|
|
671661
|
+
case 5:
|
|
671662
|
+
this.cursor = this.line.length;
|
|
671663
|
+
return;
|
|
671664
|
+
case 15:
|
|
671665
|
+
this.emit("ctrl-o");
|
|
671666
|
+
return;
|
|
671667
|
+
case 12:
|
|
671668
|
+
this.emit("ctrl-l");
|
|
671669
|
+
return;
|
|
671670
|
+
case 22:
|
|
671671
|
+
this.emit("ctrl-v");
|
|
671672
|
+
return;
|
|
671673
|
+
case 28:
|
|
671674
|
+
this.emit("ctrl-backslash");
|
|
671675
|
+
return;
|
|
671676
|
+
}
|
|
671677
|
+
}
|
|
671678
|
+
/** Submit the current line */
|
|
671679
|
+
_submit() {
|
|
671680
|
+
const line = this.line;
|
|
671681
|
+
if (line.trim() && (this._history.length === 0 || this._history[0] !== line)) {
|
|
671682
|
+
this._history.unshift(line);
|
|
671683
|
+
if (this._history.length > this._historySize) {
|
|
671684
|
+
this._history.length = this._historySize;
|
|
671580
671685
|
}
|
|
671581
671686
|
}
|
|
671582
|
-
|
|
671687
|
+
this._historyIndex = -1;
|
|
671688
|
+
this._savedLine = "";
|
|
671689
|
+
this.line = "";
|
|
671690
|
+
this.cursor = 0;
|
|
671691
|
+
this.emit("line", line);
|
|
671583
671692
|
}
|
|
671584
|
-
/**
|
|
671585
|
-
|
|
671586
|
-
|
|
671587
|
-
|
|
671588
|
-
|
|
671589
|
-
|
|
671590
|
-
|
|
671591
|
-
|
|
671592
|
-
|
|
671593
|
-
|
|
671594
|
-
|
|
671595
|
-
|
|
671596
|
-
|
|
671597
|
-
|
|
671598
|
-
const
|
|
671599
|
-
if (
|
|
671600
|
-
|
|
671601
|
-
|
|
671602
|
-
|
|
671603
|
-
|
|
671604
|
-
|
|
671605
|
-
|
|
671606
|
-
|
|
671693
|
+
/** Handle Tab completion */
|
|
671694
|
+
_handleTab() {
|
|
671695
|
+
if (this.line.length === 0) {
|
|
671696
|
+
this.emit("ctrl-i");
|
|
671697
|
+
return;
|
|
671698
|
+
}
|
|
671699
|
+
if (!this._completer) return;
|
|
671700
|
+
this._completer(this.line, (err, result) => {
|
|
671701
|
+
if (err || !result) return;
|
|
671702
|
+
const [completions, substring] = result;
|
|
671703
|
+
if (completions.length === 0) return;
|
|
671704
|
+
if (completions.length === 1) {
|
|
671705
|
+
const completion = completions[0];
|
|
671706
|
+
const before = this.line.slice(0, this.cursor);
|
|
671707
|
+
const idx = before.lastIndexOf(substring);
|
|
671708
|
+
if (idx >= 0) {
|
|
671709
|
+
this.line = before.slice(0, idx) + completion + this.line.slice(this.cursor);
|
|
671710
|
+
this.cursor = idx + completion.length;
|
|
671711
|
+
}
|
|
671712
|
+
} else if (completions.length > 1) {
|
|
671713
|
+
let common = completions[0];
|
|
671714
|
+
for (let i2 = 1; i2 < completions.length; i2++) {
|
|
671715
|
+
const other = completions[i2];
|
|
671716
|
+
let j = 0;
|
|
671717
|
+
while (j < common.length && j < other.length && common[j] === other[j]) j++;
|
|
671718
|
+
common = common.slice(0, j);
|
|
671719
|
+
}
|
|
671720
|
+
if (common.length > substring.length) {
|
|
671721
|
+
const before = this.line.slice(0, this.cursor);
|
|
671722
|
+
const idx = before.lastIndexOf(substring);
|
|
671723
|
+
if (idx >= 0) {
|
|
671724
|
+
this.line = before.slice(0, idx) + common + this.line.slice(this.cursor);
|
|
671725
|
+
this.cursor = idx + common.length;
|
|
671726
|
+
}
|
|
671607
671727
|
}
|
|
671608
671728
|
}
|
|
671609
|
-
}
|
|
671610
|
-
return fullText;
|
|
671729
|
+
});
|
|
671611
671730
|
}
|
|
671612
|
-
|
|
671613
|
-
|
|
671614
|
-
|
|
671615
|
-
|
|
671616
|
-
|
|
671617
|
-
|
|
671618
|
-
this.
|
|
671619
|
-
`[VOICECHAT SUMMARY] Parallel voice liaison update (for awareness only). Continue your current task; do not respond to this directly.
|
|
671620
|
-
|
|
671621
|
-
${recentTurns}`
|
|
671622
|
-
);
|
|
671731
|
+
/** Move cursor left by one word */
|
|
671732
|
+
_wordLeft() {
|
|
671733
|
+
if (this.cursor === 0) return;
|
|
671734
|
+
let i2 = this.cursor - 1;
|
|
671735
|
+
while (i2 > 0 && /\s/.test(this.line[i2])) i2--;
|
|
671736
|
+
while (i2 > 0 && /\S/.test(this.line[i2 - 1])) i2--;
|
|
671737
|
+
this.cursor = i2;
|
|
671623
671738
|
}
|
|
671624
|
-
/**
|
|
671625
|
-
|
|
671626
|
-
if (
|
|
671627
|
-
|
|
671628
|
-
|
|
671629
|
-
|
|
671739
|
+
/** Move cursor right by one word */
|
|
671740
|
+
_wordRight() {
|
|
671741
|
+
if (this.cursor >= this.line.length) return;
|
|
671742
|
+
let i2 = this.cursor;
|
|
671743
|
+
while (i2 < this.line.length && /\S/.test(this.line[i2])) i2++;
|
|
671744
|
+
while (i2 < this.line.length && /\s/.test(this.line[i2])) i2++;
|
|
671745
|
+
this.cursor = i2;
|
|
671630
671746
|
}
|
|
671631
|
-
/**
|
|
671632
|
-
|
|
671633
|
-
return
|
|
671634
|
-
|
|
671635
|
-
|
|
671636
|
-
|
|
671747
|
+
/** Delete word left of cursor (Ctrl+W) */
|
|
671748
|
+
_deleteWordLeft() {
|
|
671749
|
+
if (this.cursor === 0) return;
|
|
671750
|
+
let i2 = this.cursor - 1;
|
|
671751
|
+
while (i2 > 0 && /\s/.test(this.line[i2])) i2--;
|
|
671752
|
+
while (i2 > 0 && /\S/.test(this.line[i2 - 1])) i2--;
|
|
671753
|
+
this.line = this.line.slice(0, i2) + this.line.slice(this.cursor);
|
|
671754
|
+
this.cursor = i2;
|
|
671637
671755
|
}
|
|
671638
671756
|
};
|
|
671639
671757
|
}
|
|
671640
671758
|
});
|
|
671641
671759
|
|
|
671642
|
-
// packages/cli/src/api/
|
|
671643
|
-
|
|
671644
|
-
|
|
671645
|
-
|
|
671646
|
-
ensureRuntime: () => ensureRuntime,
|
|
671647
|
-
feedAudioFromClient: () => feedAudioFromClient,
|
|
671648
|
-
getDaemonListenEngine: () => getDaemonListenEngine,
|
|
671649
|
-
getRuntimeStatus: () => getRuntimeStatus,
|
|
671650
|
-
getVoiceBus: () => getVoiceBus,
|
|
671651
|
-
getVoiceEngine: () => getVoiceEngine,
|
|
671652
|
-
isVoiceChatActive: () => isVoiceChatActive,
|
|
671653
|
-
listClients: () => listClients,
|
|
671654
|
-
registerClient: () => registerClient,
|
|
671655
|
-
startVoiceChat: () => startVoiceChat,
|
|
671656
|
-
stopVoiceChat: () => stopVoiceChat,
|
|
671657
|
-
synthesizeAndBroadcast: () => synthesizeAndBroadcast,
|
|
671658
|
-
synthesizeToWav: () => synthesizeToWav,
|
|
671659
|
-
unregisterClient: () => unregisterClient
|
|
671660
|
-
});
|
|
671661
|
-
import { EventEmitter as EventEmitter14 } from "node:events";
|
|
671662
|
-
function getVoiceEngine() {
|
|
671663
|
-
if (!_voiceEngine) {
|
|
671664
|
-
_voiceEngine = new VoiceEngine();
|
|
671760
|
+
// packages/cli/src/api/access-policy.ts
|
|
671761
|
+
function defaultAccessMode(bindHost) {
|
|
671762
|
+
if (bindHost === "0.0.0.0" || bindHost === "::" || bindHost === "::0") {
|
|
671763
|
+
return "lan";
|
|
671665
671764
|
}
|
|
671666
|
-
return
|
|
671765
|
+
return "loopback";
|
|
671667
671766
|
}
|
|
671668
|
-
function
|
|
671669
|
-
|
|
671670
|
-
return
|
|
671767
|
+
function resolveAccessMode(envValue, bindHost) {
|
|
671768
|
+
const v = (envValue || "").toLowerCase().trim();
|
|
671769
|
+
if (v === "loopback" || v === "lan" || v === "any") return v;
|
|
671770
|
+
return defaultAccessMode(bindHost);
|
|
671671
671771
|
}
|
|
671672
|
-
function
|
|
671673
|
-
|
|
671674
|
-
return _bus;
|
|
671772
|
+
function stripMappedPrefix(ip) {
|
|
671773
|
+
return ip.replace(/^::ffff:/i, "");
|
|
671675
671774
|
}
|
|
671676
|
-
function
|
|
671677
|
-
return
|
|
671678
|
-
|
|
671679
|
-
|
|
671680
|
-
|
|
671681
|
-
|
|
671682
|
-
cloneRef: _voiceEngine?.luxttsCloneRef ?? null,
|
|
671683
|
-
listenActive: _listenEngine?.isActive ?? false,
|
|
671684
|
-
listenPaused: _listenEngine?.isPaused ?? false,
|
|
671685
|
-
clientCount: _clients2.size,
|
|
671686
|
-
loadedAt: _loadedAt,
|
|
671687
|
-
lastError: _lastError
|
|
671688
|
-
};
|
|
671775
|
+
function isLoopbackIP(ip) {
|
|
671776
|
+
if (!ip) return false;
|
|
671777
|
+
const clean5 = stripMappedPrefix(ip);
|
|
671778
|
+
if (clean5 === "::1") return true;
|
|
671779
|
+
if (/^127\./.test(clean5)) return true;
|
|
671780
|
+
return false;
|
|
671689
671781
|
}
|
|
671690
|
-
|
|
671691
|
-
if (
|
|
671692
|
-
|
|
671693
|
-
|
|
671694
|
-
|
|
671695
|
-
|
|
671696
|
-
|
|
671697
|
-
|
|
671698
|
-
|
|
671699
|
-
if (!listen.isActive) {
|
|
671700
|
-
try {
|
|
671701
|
-
await listen.start();
|
|
671702
|
-
} catch (err) {
|
|
671703
|
-
const m2 = err instanceof Error ? err.message : String(err);
|
|
671704
|
-
_lastError = `listen.start() failed: ${m2}`;
|
|
671705
|
-
getVoiceBus().emit("error", _lastError);
|
|
671706
|
-
}
|
|
671707
|
-
}
|
|
671708
|
-
_loadedAt = Date.now();
|
|
671709
|
-
setState("listening");
|
|
671710
|
-
wireListenToBus();
|
|
671711
|
-
} catch (err) {
|
|
671712
|
-
const m2 = err instanceof Error ? err.message : String(err);
|
|
671713
|
-
_lastError = m2;
|
|
671714
|
-
setState("error");
|
|
671715
|
-
throw err;
|
|
671782
|
+
function isPrivateIP(ip) {
|
|
671783
|
+
if (!ip) return false;
|
|
671784
|
+
const clean5 = stripMappedPrefix(ip);
|
|
671785
|
+
if (/^10\./.test(clean5)) return true;
|
|
671786
|
+
if (/^192\.168\./.test(clean5)) return true;
|
|
671787
|
+
const m2 = /^172\.(\d{1,3})\./.exec(clean5);
|
|
671788
|
+
if (m2) {
|
|
671789
|
+
const second3 = parseInt(m2[1], 10);
|
|
671790
|
+
if (second3 >= 16 && second3 <= 31) return true;
|
|
671716
671791
|
}
|
|
671792
|
+
if (/^169\.254\./.test(clean5)) return true;
|
|
671793
|
+
if (/^f[cd][0-9a-f]{2}:/i.test(clean5)) return true;
|
|
671794
|
+
if (/^fe[89ab][0-9a-f]:/i.test(clean5)) return true;
|
|
671795
|
+
return false;
|
|
671717
671796
|
}
|
|
671718
|
-
|
|
671719
|
-
if (
|
|
671720
|
-
|
|
671721
|
-
|
|
671722
|
-
|
|
671723
|
-
_clients2.set(handle2.id, handle2);
|
|
671724
|
-
if (_clients2.size === 1 && (_state3 === "idle" || _state3 === "error")) {
|
|
671725
|
-
try {
|
|
671726
|
-
await ensureRuntime();
|
|
671727
|
-
} catch (err) {
|
|
671728
|
-
_clients2.delete(handle2.id);
|
|
671729
|
-
throw err;
|
|
671730
|
-
}
|
|
671731
|
-
}
|
|
671797
|
+
function isAllowedIP(ip, mode) {
|
|
671798
|
+
if (mode === "any") return true;
|
|
671799
|
+
if (isLoopbackIP(ip)) return true;
|
|
671800
|
+
if (mode === "lan" && isPrivateIP(ip)) return true;
|
|
671801
|
+
return false;
|
|
671732
671802
|
}
|
|
671733
|
-
function
|
|
671734
|
-
|
|
671735
|
-
|
|
671736
|
-
|
|
671737
|
-
|
|
671738
|
-
|
|
671739
|
-
|
|
671740
|
-
|
|
671741
|
-
}
|
|
671742
|
-
}, IDLE_SHUTDOWN_MS);
|
|
671803
|
+
function describeAccessMode(mode) {
|
|
671804
|
+
switch (mode) {
|
|
671805
|
+
case "loopback":
|
|
671806
|
+
return "loopback only";
|
|
671807
|
+
case "lan":
|
|
671808
|
+
return "loopback + RFC 1918";
|
|
671809
|
+
case "any":
|
|
671810
|
+
return "any — WIDE OPEN";
|
|
671743
671811
|
}
|
|
671744
671812
|
}
|
|
671745
|
-
|
|
671746
|
-
|
|
671747
|
-
|
|
671748
|
-
if (!listen || !listen.isActive) return;
|
|
671749
|
-
try {
|
|
671750
|
-
const transcriber = listen.liveTranscriber;
|
|
671751
|
-
if (transcriber?.write) transcriber.write(pcmChunk);
|
|
671752
|
-
} catch {
|
|
671813
|
+
var init_access_policy = __esm({
|
|
671814
|
+
"packages/cli/src/api/access-policy.ts"() {
|
|
671815
|
+
"use strict";
|
|
671753
671816
|
}
|
|
671817
|
+
});
|
|
671818
|
+
|
|
671819
|
+
// packages/cli/src/api/project-preferences.ts
|
|
671820
|
+
import { createHash as createHash40 } from "node:crypto";
|
|
671821
|
+
import { existsSync as existsSync136, mkdirSync as mkdirSync85, readFileSync as readFileSync113, renameSync as renameSync12, writeFileSync as writeFileSync75, unlinkSync as unlinkSync31 } from "node:fs";
|
|
671822
|
+
import { homedir as homedir48 } from "node:os";
|
|
671823
|
+
import { join as join149, resolve as resolve60 } from "node:path";
|
|
671824
|
+
import { randomUUID as randomUUID19 } from "node:crypto";
|
|
671825
|
+
function projectKey(root) {
|
|
671826
|
+
const canonical = resolve60(root);
|
|
671827
|
+
return createHash40("sha256").update(canonical).digest("hex").slice(0, 16);
|
|
671754
671828
|
}
|
|
671755
|
-
|
|
671756
|
-
|
|
671757
|
-
const voice = _voiceEngine;
|
|
671758
|
-
if (!voice || !voice.ready || !voice.synthesizeToPCM) return null;
|
|
671759
|
-
const result = await voice.synthesizeToPCM(text2);
|
|
671760
|
-
if (!result || !result.pcm || result.pcm.length === 0) return null;
|
|
671761
|
-
const sampleRate = result.sampleRate;
|
|
671762
|
-
const pcm = result.pcm;
|
|
671763
|
-
if (format3 === "pcm") return { bytes: pcm, sampleRate, format: format3 };
|
|
671764
|
-
const header = Buffer.alloc(44);
|
|
671765
|
-
header.write("RIFF", 0);
|
|
671766
|
-
header.writeUInt32LE(36 + pcm.length, 4);
|
|
671767
|
-
header.write("WAVE", 8);
|
|
671768
|
-
header.write("fmt ", 12);
|
|
671769
|
-
header.writeUInt32LE(16, 16);
|
|
671770
|
-
header.writeUInt16LE(1, 20);
|
|
671771
|
-
header.writeUInt16LE(1, 22);
|
|
671772
|
-
header.writeUInt32LE(sampleRate, 24);
|
|
671773
|
-
header.writeUInt32LE(sampleRate * 2, 28);
|
|
671774
|
-
header.writeUInt16LE(2, 32);
|
|
671775
|
-
header.writeUInt16LE(16, 34);
|
|
671776
|
-
header.write("data", 36);
|
|
671777
|
-
header.writeUInt32LE(pcm.length, 40);
|
|
671778
|
-
return { bytes: Buffer.concat([header, pcm]), sampleRate, format: format3 };
|
|
671779
|
-
}
|
|
671780
|
-
async function synthesizeAndBroadcast(text2) {
|
|
671781
|
-
const voice = _voiceEngine;
|
|
671782
|
-
if (!voice || !voice.ready) return;
|
|
671783
|
-
if (!voice.synthesizeToPCM) {
|
|
671784
|
-
getVoiceBus().emit("error", "voice engine has no synthesizeToPCM");
|
|
671785
|
-
return;
|
|
671786
|
-
}
|
|
671787
|
-
setSpeaking(true);
|
|
671788
|
-
try {
|
|
671789
|
-
const result = await voice.synthesizeToPCM(text2);
|
|
671790
|
-
if (!result || !result.pcm || result.pcm.length === 0) return;
|
|
671791
|
-
getVoiceBus().emit("agent_text", { text: text2 });
|
|
671792
|
-
getVoiceBus().emit("tts_pcm", result.pcm, result.sampleRate);
|
|
671793
|
-
} catch (err) {
|
|
671794
|
-
const m2 = err instanceof Error ? err.message : String(err);
|
|
671795
|
-
getVoiceBus().emit("error", `tts: ${m2}`);
|
|
671796
|
-
} finally {
|
|
671797
|
-
setSpeaking(false);
|
|
671798
|
-
}
|
|
671829
|
+
function projectDir(root) {
|
|
671830
|
+
return join149(PROJECTS_DIR, projectKey(root));
|
|
671799
671831
|
}
|
|
671800
|
-
function
|
|
671801
|
-
return
|
|
671832
|
+
function prefsPath(root) {
|
|
671833
|
+
return join149(projectDir(root), "preferences.json");
|
|
671802
671834
|
}
|
|
671803
|
-
|
|
671804
|
-
|
|
671805
|
-
return { ok: true, message: "VoiceChat already running" };
|
|
671806
|
-
}
|
|
671807
|
-
await ensureRuntime();
|
|
671808
|
-
const voice = getVoiceEngine();
|
|
671809
|
-
const listen = getDaemonListenEngine();
|
|
671810
|
-
if (!voice.ready) return { ok: false, message: "Voice engine not ready" };
|
|
671811
|
-
_voiceChatSession = new VoiceChatSession({
|
|
671812
|
-
voice,
|
|
671813
|
-
listen,
|
|
671814
|
-
backendUrl: opts.backendUrl,
|
|
671815
|
-
model: opts.model,
|
|
671816
|
-
apiKey: opts.apiKey,
|
|
671817
|
-
verbose: opts.verbose === true,
|
|
671818
|
-
onStatus: (msg) => getVoiceBus().emit("status", msg),
|
|
671819
|
-
onUserSpeech: (text2) => getVoiceBus().emit("transcript", { text: text2, final: true }),
|
|
671820
|
-
onPartialTranscript: (text2) => getVoiceBus().emit("transcript", { text: text2, final: false }),
|
|
671821
|
-
onAgentSpeech: (text2) => getVoiceBus().emit("agent_text", { text: text2 }),
|
|
671822
|
-
onStateChange: (s2) => getVoiceBus().emit("session_state", s2)
|
|
671823
|
-
});
|
|
671824
|
-
await _voiceChatSession.start();
|
|
671825
|
-
setState("listening");
|
|
671826
|
-
return { ok: true, message: "VoiceChat started" };
|
|
671835
|
+
function rootSentinelPath(root) {
|
|
671836
|
+
return join149(projectDir(root), ".root");
|
|
671827
671837
|
}
|
|
671828
|
-
|
|
671829
|
-
|
|
671838
|
+
function ensureDir(root) {
|
|
671839
|
+
const dir = projectDir(root);
|
|
671840
|
+
mkdirSync85(dir, { recursive: true });
|
|
671841
|
+
const sentinel = rootSentinelPath(root);
|
|
671830
671842
|
try {
|
|
671831
|
-
if (
|
|
671832
|
-
|
|
671843
|
+
if (!existsSync136(sentinel)) {
|
|
671844
|
+
writeFileSync75(sentinel, `${resolve60(root)}
|
|
671845
|
+
`, "utf8");
|
|
671833
671846
|
}
|
|
671834
671847
|
} catch {
|
|
671835
671848
|
}
|
|
671836
|
-
_voiceChatSession = null;
|
|
671837
|
-
setState(_listenEngine?.isActive ? "listening" : "idle");
|
|
671838
|
-
return { ok: true, message: "VoiceChat stopped" };
|
|
671839
|
-
}
|
|
671840
|
-
function isVoiceChatActive() {
|
|
671841
|
-
return _voiceChatSession?.isActive ?? false;
|
|
671842
|
-
}
|
|
671843
|
-
function setState(s2) {
|
|
671844
|
-
if (_state3 === s2) return;
|
|
671845
|
-
_state3 = s2;
|
|
671846
|
-
getVoiceBus().emit("state", s2);
|
|
671847
671849
|
}
|
|
671848
|
-
function
|
|
671849
|
-
|
|
671850
|
-
|
|
671851
|
-
|
|
671852
|
-
|
|
671853
|
-
|
|
671854
|
-
|
|
671855
|
-
|
|
671850
|
+
function readProjectPreferences(root) {
|
|
671851
|
+
try {
|
|
671852
|
+
const file = prefsPath(root);
|
|
671853
|
+
if (!existsSync136(file)) return { ...DEFAULT_PREFS };
|
|
671854
|
+
const raw = readFileSync113(file, "utf8");
|
|
671855
|
+
const parsed = JSON.parse(raw);
|
|
671856
|
+
if (!parsed || parsed.v !== SCHEMA_VERSION) return { ...DEFAULT_PREFS };
|
|
671857
|
+
return { ...DEFAULT_PREFS, ...parsed, v: SCHEMA_VERSION };
|
|
671858
|
+
} catch {
|
|
671859
|
+
return { ...DEFAULT_PREFS };
|
|
671856
671860
|
}
|
|
671857
671861
|
}
|
|
671858
|
-
function
|
|
671859
|
-
|
|
671860
|
-
|
|
671861
|
-
|
|
671862
|
-
|
|
671863
|
-
|
|
671864
|
-
|
|
671865
|
-
|
|
671866
|
-
}
|
|
671862
|
+
function writeProjectPreferences(root, partial) {
|
|
671863
|
+
ensureDir(root);
|
|
671864
|
+
const current = readProjectPreferences(root);
|
|
671865
|
+
const merged = {
|
|
671866
|
+
...current,
|
|
671867
|
+
...partial,
|
|
671868
|
+
v: SCHEMA_VERSION,
|
|
671869
|
+
updatedAt: Date.now()
|
|
671870
|
+
};
|
|
671871
|
+
const file = prefsPath(root);
|
|
671872
|
+
const tmp = `${file}.${randomUUID19().slice(0, 8)}.tmp`;
|
|
671873
|
+
writeFileSync75(tmp, JSON.stringify(merged, null, 2), "utf8");
|
|
671874
|
+
try {
|
|
671875
|
+
renameSync12(tmp, file);
|
|
671876
|
+
} catch (err) {
|
|
671877
|
+
try {
|
|
671878
|
+
writeFileSync75(file, JSON.stringify(merged, null, 2), "utf8");
|
|
671879
|
+
} catch {
|
|
671880
|
+
}
|
|
671881
|
+
try {
|
|
671882
|
+
unlinkSync31(tmp);
|
|
671883
|
+
} catch {
|
|
671884
|
+
}
|
|
671885
|
+
throw err;
|
|
671886
|
+
}
|
|
671887
|
+
return merged;
|
|
671867
671888
|
}
|
|
671868
|
-
function
|
|
671869
|
-
|
|
671870
|
-
|
|
671871
|
-
|
|
671872
|
-
|
|
671873
|
-
|
|
671874
|
-
|
|
671875
|
-
|
|
671876
|
-
_shutdownTimer = null;
|
|
671889
|
+
function deleteProjectPreferences(root) {
|
|
671890
|
+
try {
|
|
671891
|
+
const file = prefsPath(root);
|
|
671892
|
+
if (!existsSync136(file)) return false;
|
|
671893
|
+
unlinkSync31(file);
|
|
671894
|
+
return true;
|
|
671895
|
+
} catch {
|
|
671896
|
+
return false;
|
|
671877
671897
|
}
|
|
671878
671898
|
}
|
|
671879
|
-
var
|
|
671880
|
-
var
|
|
671881
|
-
"packages/cli/src/api/
|
|
671899
|
+
var OMNIUS_DIR4, PROJECTS_DIR, SCHEMA_VERSION, DEFAULT_PREFS;
|
|
671900
|
+
var init_project_preferences = __esm({
|
|
671901
|
+
"packages/cli/src/api/project-preferences.ts"() {
|
|
671882
671902
|
"use strict";
|
|
671883
|
-
|
|
671884
|
-
|
|
671885
|
-
|
|
671886
|
-
|
|
671887
|
-
|
|
671888
|
-
|
|
671889
|
-
|
|
671890
|
-
_state3 = "idle";
|
|
671891
|
-
_loadedAt = null;
|
|
671892
|
-
_lastError = null;
|
|
671893
|
-
_clients2 = /* @__PURE__ */ new Map();
|
|
671894
|
-
_ttsSpeaking = false;
|
|
671895
|
-
_shutdownTimer = null;
|
|
671896
|
-
IDLE_SHUTDOWN_MS = 6e4;
|
|
671897
|
-
_wired = false;
|
|
671903
|
+
OMNIUS_DIR4 = join149(homedir48(), ".omnius");
|
|
671904
|
+
PROJECTS_DIR = join149(OMNIUS_DIR4, "projects");
|
|
671905
|
+
SCHEMA_VERSION = 1;
|
|
671906
|
+
DEFAULT_PREFS = {
|
|
671907
|
+
v: SCHEMA_VERSION,
|
|
671908
|
+
updatedAt: 0
|
|
671909
|
+
};
|
|
671898
671910
|
}
|
|
671899
671911
|
});
|
|
671900
671912
|
|
|
@@ -692095,6 +692107,51 @@ data: ${JSON.stringify(data)}
|
|
|
692095
692107
|
}
|
|
692096
692108
|
return;
|
|
692097
692109
|
}
|
|
692110
|
+
if (pathname === "/v1/model" && method === "GET") {
|
|
692111
|
+
const listen = getDaemonListenEngine();
|
|
692112
|
+
const current = listen.currentModel ?? "base";
|
|
692113
|
+
jsonResponse(res, 200, { model: current });
|
|
692114
|
+
return;
|
|
692115
|
+
}
|
|
692116
|
+
if (pathname === "/v1/model" && method === "POST") {
|
|
692117
|
+
const body = await parseJsonBody(req3);
|
|
692118
|
+
const modelId = (body?.model ?? "").trim();
|
|
692119
|
+
if (!modelId) {
|
|
692120
|
+
jsonResponse(res, 400, { error: "missing_model" });
|
|
692121
|
+
return;
|
|
692122
|
+
}
|
|
692123
|
+
try {
|
|
692124
|
+
await getDaemonListenEngine().setModel(modelId);
|
|
692125
|
+
jsonResponse(res, 200, { ok: true, model: modelId });
|
|
692126
|
+
} catch (err) {
|
|
692127
|
+
jsonResponse(res, 500, { error: "model_switch_failed", message: err instanceof Error ? err.message : String(err) });
|
|
692128
|
+
}
|
|
692129
|
+
return;
|
|
692130
|
+
}
|
|
692131
|
+
if (pathname === "/v1/endpoint" && method === "GET") {
|
|
692132
|
+
const listen = getDaemonListenEngine();
|
|
692133
|
+
const current = listen.currentEndpoint ?? null;
|
|
692134
|
+
jsonResponse(res, 200, { endpoint: current });
|
|
692135
|
+
return;
|
|
692136
|
+
}
|
|
692137
|
+
if (pathname === "/v1/endpoint" && method === "POST") {
|
|
692138
|
+
const body = await parseJsonBody(req3);
|
|
692139
|
+
const url = (body?.url ?? "").trim();
|
|
692140
|
+
const type = body?.type ?? "ollama";
|
|
692141
|
+
const key = body?.key;
|
|
692142
|
+
if (!url) {
|
|
692143
|
+
jsonResponse(res, 400, { error: "missing_url" });
|
|
692144
|
+
return;
|
|
692145
|
+
}
|
|
692146
|
+
try {
|
|
692147
|
+
const listen = getDaemonListenEngine();
|
|
692148
|
+
await listen.setEndpoint?.(url, type, key);
|
|
692149
|
+
jsonResponse(res, 200, { ok: true, endpoint: url });
|
|
692150
|
+
} catch (err) {
|
|
692151
|
+
jsonResponse(res, 500, { error: "endpoint_switch_failed", message: err instanceof Error ? err.message : String(err) });
|
|
692152
|
+
}
|
|
692153
|
+
return;
|
|
692154
|
+
}
|
|
692098
692155
|
if (pathname === "/v1/codegraph/snapshot" && method === "GET") {
|
|
692099
692156
|
const workingDir = process.cwd();
|
|
692100
692157
|
const db = getCodeGraphDBIfReady(workingDir);
|