omnius 1.0.254 → 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 +2089 -2032
- package/npm-shrinkwrap.json +7 -7
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -669686,2215 +669686,2227 @@ 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
|
-
|
|
669694
|
+
import { EventEmitter as EventEmitter12 } from "node:events";
|
|
669695
|
+
function clamp0114(x) {
|
|
669696
|
+
return x < 0 ? 0 : x > 1 ? 1 : x;
|
|
669696
669697
|
}
|
|
669697
|
-
|
|
669698
|
-
|
|
669699
|
-
const
|
|
669700
|
-
|
|
669701
|
-
|
|
669702
|
-
|
|
669703
|
-
const
|
|
669704
|
-
|
|
669705
|
-
|
|
669706
|
-
|
|
669707
|
-
|
|
669708
|
-
|
|
669709
|
-
|
|
669710
|
-
|
|
669711
|
-
|
|
669712
|
-
|
|
669713
|
-
output: quick2,
|
|
669714
|
-
ansi: quick2,
|
|
669715
|
-
durationMs: Date.now() - start2
|
|
669716
|
-
};
|
|
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;
|
|
669702
|
+
}
|
|
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;
|
|
669706
|
+
}
|
|
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;
|
|
669717
669714
|
}
|
|
669718
|
-
|
|
669719
|
-
|
|
669720
|
-
|
|
669721
|
-
|
|
669722
|
-
|
|
669723
|
-
|
|
669724
|
-
|
|
669725
|
-
|
|
669726
|
-
|
|
669727
|
-
|
|
669728
|
-
|
|
669729
|
-
|
|
669730
|
-
|
|
669731
|
-
|
|
669732
|
-
|
|
669733
|
-
|
|
669734
|
-
|
|
669735
|
-
|
|
669736
|
-
|
|
669737
|
-
|
|
669715
|
+
}
|
|
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);
|
|
669735
|
+
}
|
|
669736
|
+
return clamp0114(score);
|
|
669737
|
+
}
|
|
669738
|
+
function truncateForLog(s2, n2) {
|
|
669739
|
+
return s2.length <= n2 ? s2 : s2.slice(0, n2 - 1) + "…";
|
|
669740
|
+
}
|
|
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;
|
|
669738
669746
|
try {
|
|
669739
|
-
const
|
|
669740
|
-
|
|
669741
|
-
|
|
669742
|
-
|
|
669743
|
-
|
|
669744
|
-
|
|
669745
|
-
|
|
669746
|
-
`);
|
|
669747
|
-
} finally {
|
|
669748
|
-
process.stdout.write = origWrite;
|
|
669749
|
-
setContentWriteHook({ begin: () => {
|
|
669750
|
-
}, end: () => {
|
|
669751
|
-
} });
|
|
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
|
+
}
|
|
669753
|
+
} catch {
|
|
669752
669754
|
}
|
|
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
669755
|
}
|
|
669756
|
+
return null;
|
|
669767
669757
|
}
|
|
669768
|
-
function
|
|
669769
|
-
const
|
|
669770
|
-
|
|
669771
|
-
|
|
669772
|
-
|
|
669773
|
-
|
|
669774
|
-
|
|
669775
|
-
|
|
669776
|
-
|
|
669777
|
-
|
|
669778
|
-
|
|
669779
|
-
|
|
669780
|
-
|
|
669781
|
-
|
|
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");
|
|
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) {
|
|
669764
|
+
try {
|
|
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 };
|
|
669769
|
+
}
|
|
669770
|
+
} catch {
|
|
669771
|
+
}
|
|
669802
669772
|
}
|
|
669803
669773
|
return null;
|
|
669804
669774
|
}
|
|
669805
|
-
function
|
|
669806
|
-
|
|
669807
|
-
const
|
|
669808
|
-
|
|
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
|
+
}
|
|
669809
669786
|
});
|
|
669810
|
-
|
|
669811
|
-
_passthroughLock = next;
|
|
669812
|
-
return prev.then(() => release);
|
|
669787
|
+
return kept.join("\n").trim();
|
|
669813
669788
|
}
|
|
669814
|
-
|
|
669815
|
-
|
|
669816
|
-
|
|
669817
|
-
let colorsEnabled = loadProjectSettings(root).colors ?? loadGlobalSettings().colors ?? true;
|
|
669818
|
-
let selfModifyEnabled = loadProjectSettings(root).selfModify ?? loadGlobalSettings().selfModify ?? false;
|
|
669819
|
-
return {
|
|
669820
|
-
config: cfg,
|
|
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);
|
|
669877
|
-
}
|
|
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
|
-
}
|
|
669918
|
-
});
|
|
669919
|
-
|
|
669920
|
-
// packages/cli/src/api/projects.ts
|
|
669921
|
-
var projects_exports = {};
|
|
669922
|
-
__export(projects_exports, {
|
|
669923
|
-
_resetCurrentProject: () => _resetCurrentProject,
|
|
669924
|
-
getCurrentProject: () => getCurrentProject,
|
|
669925
|
-
listProjects: () => listProjects,
|
|
669926
|
-
registerProject: () => registerProject,
|
|
669927
|
-
renameProject: () => renameProject,
|
|
669928
|
-
setCurrentProject: () => setCurrentProject,
|
|
669929
|
-
unregisterProject: () => unregisterProject
|
|
669930
|
-
});
|
|
669931
|
-
import { readFileSync as readFileSync112, writeFileSync as writeFileSync74, mkdirSync as mkdirSync84, existsSync as existsSync135, statSync as statSync48, renameSync as renameSync11 } from "node:fs";
|
|
669932
|
-
import { homedir as homedir47 } from "node:os";
|
|
669933
|
-
import { basename as basename36, join as join148, resolve as resolve59 } from "node:path";
|
|
669934
|
-
import { randomUUID as randomUUID18 } from "node:crypto";
|
|
669935
|
-
function readAll2() {
|
|
669936
|
-
try {
|
|
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 };
|
|
669944
|
-
}
|
|
669945
|
-
}
|
|
669946
|
-
function writeAll(file) {
|
|
669947
|
-
mkdirSync84(OMNIUS_DIR3, { recursive: true });
|
|
669948
|
-
const tmp = `${PROJECTS_FILE}.${randomUUID18().slice(0, 8)}.tmp`;
|
|
669949
|
-
writeFileSync74(tmp, JSON.stringify(file, null, 2), "utf8");
|
|
669950
|
-
renameSync11(tmp, PROJECTS_FILE);
|
|
669951
|
-
}
|
|
669952
|
-
function listProjects() {
|
|
669953
|
-
const { projects } = readAll2();
|
|
669954
|
-
const alive = [];
|
|
669955
|
-
for (const p2 of projects) {
|
|
669956
|
-
try {
|
|
669957
|
-
if (statSync48(p2.root).isDirectory()) alive.push(p2);
|
|
669958
|
-
} catch {
|
|
669959
|
-
}
|
|
669960
|
-
}
|
|
669961
|
-
alive.sort((a2, b) => b.lastSeen - a2.lastSeen);
|
|
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;
|
|
670010
|
-
}
|
|
670011
|
-
function getCurrentProject() {
|
|
670012
|
-
if (!currentRoot) {
|
|
670013
|
-
try {
|
|
670014
|
-
if (existsSync135(CURRENT_FILE)) {
|
|
670015
|
-
const persisted = readFileSync112(CURRENT_FILE, "utf8").trim();
|
|
670016
|
-
if (persisted) currentRoot = persisted;
|
|
670017
|
-
}
|
|
670018
|
-
} catch {
|
|
670019
|
-
}
|
|
670020
|
-
}
|
|
670021
|
-
if (!currentRoot) return null;
|
|
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;
|
|
670037
|
-
}
|
|
670038
|
-
function _resetCurrentProject() {
|
|
670039
|
-
currentRoot = null;
|
|
670040
|
-
}
|
|
670041
|
-
var OMNIUS_DIR3, PROJECTS_FILE, CURRENT_FILE, currentRoot;
|
|
670042
|
-
var init_projects = __esm({
|
|
670043
|
-
"packages/cli/src/api/projects.ts"() {
|
|
670044
|
-
"use strict";
|
|
670045
|
-
OMNIUS_DIR3 = join148(homedir47(), ".omnius");
|
|
670046
|
-
PROJECTS_FILE = join148(OMNIUS_DIR3, "projects.json");
|
|
670047
|
-
CURRENT_FILE = join148(OMNIUS_DIR3, "current-project");
|
|
670048
|
-
currentRoot = null;
|
|
670049
|
-
}
|
|
670050
|
-
});
|
|
670051
|
-
|
|
670052
|
-
// packages/cli/src/tui/mouse-filter.ts
|
|
670053
|
-
var mouse_filter_exports = {};
|
|
670054
|
-
__export(mouse_filter_exports, {
|
|
670055
|
-
MouseFilterStream: () => MouseFilterStream
|
|
670056
|
-
});
|
|
670057
|
-
import { Transform } from "node:stream";
|
|
670058
|
-
var MouseFilterStream;
|
|
670059
|
-
var init_mouse_filter = __esm({
|
|
670060
|
-
"packages/cli/src/tui/mouse-filter.ts"() {
|
|
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"() {
|
|
670061
669792
|
"use strict";
|
|
670062
|
-
|
|
670063
|
-
|
|
670064
|
-
|
|
670065
|
-
|
|
670066
|
-
onPointer = null;
|
|
670067
|
-
onKeyboard = null;
|
|
670068
|
-
flushTimer = null;
|
|
670069
|
-
expectPrefixlessMouseUntil = 0;
|
|
670070
|
-
constructor(scrollHandler, activityHandler, pointerHandler, keyboardHandler) {
|
|
670071
|
-
super();
|
|
670072
|
-
this.onScroll = scrollHandler;
|
|
670073
|
-
this.onActivity = activityHandler ?? null;
|
|
670074
|
-
this.onPointer = pointerHandler ?? null;
|
|
670075
|
-
this.onKeyboard = keyboardHandler ?? null;
|
|
670076
|
-
}
|
|
670077
|
-
_transform(chunk, _encoding, callback) {
|
|
670078
|
-
this.buffer += chunk.toString();
|
|
670079
|
-
this.processBuffer(callback);
|
|
670080
|
-
}
|
|
670081
|
-
processBuffer(callback) {
|
|
670082
|
-
let output = "";
|
|
670083
|
-
let i2 = 0;
|
|
670084
|
-
while (i2 < this.buffer.length) {
|
|
670085
|
-
const remaining = this.buffer.slice(i2);
|
|
670086
|
-
const prefixlessMouse = this.matchPrefixlessSgrMouse(remaining);
|
|
670087
|
-
if (prefixlessMouse) {
|
|
670088
|
-
this.handleSgrMouse(prefixlessMouse);
|
|
670089
|
-
i2 += prefixlessMouse.raw.length;
|
|
670090
|
-
continue;
|
|
670091
|
-
}
|
|
670092
|
-
if (this.looksLikePartialPrefixlessSgrMouse(remaining)) {
|
|
670093
|
-
break;
|
|
670094
|
-
}
|
|
670095
|
-
if (this.buffer[i2] === "\x1B") {
|
|
670096
|
-
const mouseMatch = remaining.match(/^\x1B\[<(\d+);(\d+);(\d+)([Mm])/);
|
|
670097
|
-
if (mouseMatch) {
|
|
670098
|
-
this.handleSgrMouse({
|
|
670099
|
-
raw: mouseMatch[0],
|
|
670100
|
-
btn: parseInt(mouseMatch[1]),
|
|
670101
|
-
col: parseInt(mouseMatch[2]),
|
|
670102
|
-
row: parseInt(mouseMatch[3]),
|
|
670103
|
-
suffix: mouseMatch[4]
|
|
670104
|
-
});
|
|
670105
|
-
i2 += mouseMatch[0].length;
|
|
670106
|
-
continue;
|
|
670107
|
-
}
|
|
670108
|
-
if (remaining.startsWith("\x1B[M")) {
|
|
670109
|
-
if (remaining.length >= 6) {
|
|
670110
|
-
if (this.onActivity) this.onActivity();
|
|
670111
|
-
i2 += 6;
|
|
670112
|
-
continue;
|
|
670113
|
-
}
|
|
670114
|
-
break;
|
|
670115
|
-
}
|
|
670116
|
-
if (remaining.startsWith("\x1B[<") && remaining.length < 15) {
|
|
670117
|
-
break;
|
|
670118
|
-
}
|
|
670119
|
-
if (remaining.startsWith("\x1B[") && remaining.length === 2) {
|
|
670120
|
-
break;
|
|
670121
|
-
}
|
|
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
|
-
});
|
|
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.
|
|
670228
669797
|
|
|
670229
|
-
|
|
670230
|
-
|
|
670231
|
-
|
|
670232
|
-
|
|
670233
|
-
|
|
670234
|
-
|
|
670235
|
-
|
|
670236
|
-
|
|
670237
|
-
|
|
670238
|
-
|
|
670239
|
-
|
|
670240
|
-
|
|
670241
|
-
|
|
670242
|
-
|
|
670243
|
-
|
|
670244
|
-
|
|
670245
|
-
|
|
670246
|
-
|
|
670247
|
-
|
|
670248
|
-
|
|
670249
|
-
|
|
670250
|
-
|
|
670251
|
-
|
|
670252
|
-
|
|
670253
|
-
|
|
670254
|
-
|
|
670255
|
-
|
|
670256
|
-
|
|
670257
|
-
|
|
670258
|
-
|
|
670259
|
-
|
|
670260
|
-
|
|
670261
|
-
|
|
670262
|
-
|
|
670263
|
-
|
|
670264
|
-
|
|
670265
|
-
|
|
670266
|
-
|
|
670267
|
-
|
|
670268
|
-
|
|
670269
|
-
|
|
670270
|
-
|
|
670271
|
-
|
|
670272
|
-
|
|
670273
|
-
|
|
670274
|
-
|
|
670275
|
-
|
|
670276
|
-
this.
|
|
670277
|
-
this.
|
|
670278
|
-
|
|
670279
|
-
|
|
670280
|
-
|
|
670281
|
-
this.
|
|
670282
|
-
|
|
670283
|
-
|
|
670284
|
-
|
|
670285
|
-
this.
|
|
670286
|
-
|
|
670287
|
-
|
|
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];
|
|
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) {
|
|
669844
|
+
super();
|
|
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);
|
|
670336
669857
|
}
|
|
670337
|
-
this.
|
|
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
|
+
});
|
|
670338
669868
|
}
|
|
670339
|
-
|
|
670340
|
-
|
|
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;
|
|
669869
|
+
get state() {
|
|
669870
|
+
return this._state;
|
|
670363
669871
|
}
|
|
670364
|
-
|
|
670365
|
-
|
|
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;
|
|
669872
|
+
get isActive() {
|
|
669873
|
+
return this.active;
|
|
670388
669874
|
}
|
|
670389
|
-
|
|
670390
|
-
|
|
670391
|
-
|
|
670392
|
-
|
|
670393
|
-
|
|
670394
|
-
const
|
|
670395
|
-
|
|
670396
|
-
|
|
670397
|
-
|
|
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 };
|
|
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 });
|
|
670438
669884
|
}
|
|
670439
669885
|
// ---------------------------------------------------------------------------
|
|
670440
|
-
//
|
|
669886
|
+
// Start / Stop
|
|
670441
669887
|
// ---------------------------------------------------------------------------
|
|
670442
|
-
|
|
670443
|
-
|
|
670444
|
-
|
|
670445
|
-
|
|
670446
|
-
|
|
670447
|
-
|
|
670448
|
-
|
|
670449
|
-
|
|
670450
|
-
|
|
670451
|
-
|
|
670452
|
-
|
|
670453
|
-
|
|
670454
|
-
|
|
670455
|
-
|
|
670456
|
-
|
|
670457
|
-
|
|
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;
|
|
670532
|
-
}
|
|
670533
|
-
const char = this._buffer[i2];
|
|
670534
|
-
this.line = this.line.slice(0, this.cursor) + char + this.line.slice(this.cursor);
|
|
670535
|
-
this.cursor++;
|
|
670536
|
-
i2++;
|
|
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 });
|
|
670537
669904
|
}
|
|
670538
|
-
this.
|
|
670539
|
-
if (this.
|
|
670540
|
-
|
|
670541
|
-
|
|
670542
|
-
|
|
670543
|
-
|
|
670544
|
-
|
|
670545
|
-
|
|
670546
|
-
|
|
670547
|
-
|
|
670548
|
-
|
|
670549
|
-
|
|
670550
|
-
|
|
670551
|
-
|
|
670552
|
-
|
|
670553
|
-
|
|
670554
|
-
|
|
670555
|
-
|
|
670556
|
-
|
|
670557
|
-
|
|
670558
|
-
|
|
670559
|
-
|
|
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 {
|
|
670560
669938
|
}
|
|
670561
|
-
|
|
670562
|
-
}
|
|
670563
|
-
}, 50);
|
|
670564
|
-
} else {
|
|
670565
|
-
if (this._flushTimer) {
|
|
670566
|
-
clearTimeout(this._flushTimer);
|
|
670567
|
-
this._flushTimer = null;
|
|
669939
|
+
}, 1e3);
|
|
670568
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");
|
|
670569
669951
|
}
|
|
670570
669952
|
}
|
|
670571
|
-
|
|
670572
|
-
|
|
670573
|
-
|
|
670574
|
-
|
|
670575
|
-
|
|
670576
|
-
|
|
670577
|
-
const row = parseInt(match[4], 10);
|
|
670578
|
-
if (!Number.isFinite(btn) || !Number.isFinite(col) || !Number.isFinite(row))
|
|
670579
|
-
return null;
|
|
670580
|
-
if (btn < 0 || btn > 255 || col <= 0 || row <= 0) return null;
|
|
670581
|
-
if (!hasMarker && Date.now() > this._expectPrefixlessMouseUntil && !this._isKnownMouseButton(btn))
|
|
670582
|
-
return null;
|
|
670583
|
-
return { length: match[0].length };
|
|
670584
|
-
}
|
|
670585
|
-
_looksLikePartialPrefixlessSgrMouse(input) {
|
|
670586
|
-
if (input.length < 2) return false;
|
|
670587
|
-
if (/^<\d{1,3};\d{0,5}(?:;\d{0,5})?$/.test(input)) return true;
|
|
670588
|
-
if (Date.now() <= this._expectPrefixlessMouseUntil && /^\d{1,3};\d{0,5}(?:;\d{0,5})?$/.test(input)) {
|
|
670589
|
-
return true;
|
|
669953
|
+
async stop() {
|
|
669954
|
+
if (!this.active) return;
|
|
669955
|
+
this.active = false;
|
|
669956
|
+
if (this.abortController) {
|
|
669957
|
+
this.abortController.abort();
|
|
669958
|
+
this.abortController = null;
|
|
670590
669959
|
}
|
|
670591
|
-
|
|
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");
|
|
670592
669986
|
}
|
|
670593
|
-
|
|
670594
|
-
|
|
670595
|
-
|
|
670596
|
-
|
|
670597
|
-
if (
|
|
670598
|
-
|
|
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);
|
|
670599
670016
|
}
|
|
670600
|
-
|
|
670601
|
-
|
|
670602
|
-
|
|
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();
|
|
670603
670059
|
}
|
|
670604
|
-
|
|
670605
|
-
|
|
670606
|
-
|
|
670607
|
-
|
|
670608
|
-
|
|
670609
|
-
|
|
670610
|
-
|
|
670611
|
-
|
|
670612
|
-
|
|
670613
|
-
|
|
670614
|
-
|
|
670615
|
-
|
|
670616
|
-
|
|
670617
|
-
|
|
670618
|
-
|
|
670619
|
-
|
|
670620
|
-
|
|
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;
|
|
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 {
|
|
670645
670077
|
}
|
|
670646
|
-
|
|
670647
|
-
|
|
670648
|
-
|
|
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;
|
|
670649
670110
|
}
|
|
670650
|
-
|
|
670111
|
+
} catch {
|
|
670651
670112
|
}
|
|
670652
|
-
|
|
670653
|
-
|
|
670654
|
-
|
|
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)}`;
|
|
670655
670126
|
}
|
|
670656
|
-
if (
|
|
670657
|
-
|
|
670658
|
-
|
|
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() });
|
|
670659
670130
|
}
|
|
670660
|
-
|
|
670661
|
-
|
|
670662
|
-
|
|
670663
|
-
|
|
670664
|
-
|
|
670665
|
-
|
|
670666
|
-
|
|
670667
|
-
|
|
670668
|
-
|
|
670669
|
-
|
|
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 {
|
|
670670
670147
|
}
|
|
670671
|
-
|
|
670672
|
-
|
|
670673
|
-
|
|
670674
|
-
|
|
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
|
-
}
|
|
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();
|
|
670684
670153
|
}
|
|
670685
|
-
if (
|
|
670686
|
-
|
|
670687
|
-
|
|
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));
|
|
670688
670162
|
}
|
|
670689
|
-
return;
|
|
670690
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;
|
|
670691
670172
|
}
|
|
670692
|
-
|
|
670693
|
-
|
|
670694
|
-
|
|
670695
|
-
|
|
670696
|
-
|
|
670697
|
-
|
|
670698
|
-
|
|
670699
|
-
if (parts.length === 3) {
|
|
670700
|
-
const [prefix, modifiers, codepoint] = parts;
|
|
670701
|
-
return prefix === 27 && modifiers === 2 && (codepoint === 10 || codepoint === 13);
|
|
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...");
|
|
670702
670180
|
}
|
|
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
670181
|
}
|
|
670709
|
-
/**
|
|
670710
|
-
|
|
670711
|
-
|
|
670712
|
-
|
|
670713
|
-
|
|
670714
|
-
|
|
670715
|
-
|
|
670716
|
-
|
|
670717
|
-
|
|
670718
|
-
|
|
670719
|
-
|
|
670720
|
-
|
|
670721
|
-
|
|
670722
|
-
|
|
670723
|
-
|
|
670724
|
-
|
|
670725
|
-
|
|
670726
|
-
|
|
670727
|
-
|
|
670728
|
-
|
|
670729
|
-
|
|
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;
|
|
670730
670211
|
}
|
|
670731
|
-
|
|
670732
|
-
|
|
670733
|
-
|
|
670734
|
-
|
|
670735
|
-
|
|
670736
|
-
|
|
670737
|
-
|
|
670738
|
-
|
|
670739
|
-
|
|
670740
|
-
|
|
670741
|
-
|
|
670742
|
-
|
|
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;
|
|
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)}`);
|
|
670780
670224
|
}
|
|
670225
|
+
return await this.parseOpenAIStream(res);
|
|
670781
670226
|
}
|
|
670782
|
-
/**
|
|
670783
|
-
|
|
670784
|
-
const
|
|
670785
|
-
if (
|
|
670786
|
-
|
|
670787
|
-
|
|
670788
|
-
|
|
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
|
+
}
|
|
670789
670252
|
}
|
|
670790
670253
|
}
|
|
670791
|
-
|
|
670792
|
-
this._savedLine = "";
|
|
670793
|
-
this.line = "";
|
|
670794
|
-
this.cursor = 0;
|
|
670795
|
-
this.emit("line", line);
|
|
670254
|
+
return fullText;
|
|
670796
670255
|
}
|
|
670797
|
-
/**
|
|
670798
|
-
|
|
670799
|
-
|
|
670800
|
-
|
|
670801
|
-
|
|
670802
|
-
|
|
670803
|
-
|
|
670804
|
-
|
|
670805
|
-
|
|
670806
|
-
|
|
670807
|
-
|
|
670808
|
-
|
|
670809
|
-
|
|
670810
|
-
|
|
670811
|
-
const
|
|
670812
|
-
if (
|
|
670813
|
-
|
|
670814
|
-
|
|
670815
|
-
|
|
670816
|
-
|
|
670817
|
-
|
|
670818
|
-
|
|
670819
|
-
|
|
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
|
-
}
|
|
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 {
|
|
670831
670279
|
}
|
|
670832
670280
|
}
|
|
670833
|
-
}
|
|
670281
|
+
}
|
|
670282
|
+
return fullText;
|
|
670834
670283
|
}
|
|
670835
|
-
|
|
670836
|
-
|
|
670837
|
-
|
|
670838
|
-
|
|
670839
|
-
|
|
670840
|
-
|
|
670841
|
-
this.
|
|
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
|
+
);
|
|
670842
670295
|
}
|
|
670843
|
-
/**
|
|
670844
|
-
|
|
670845
|
-
if (
|
|
670846
|
-
|
|
670847
|
-
|
|
670848
|
-
|
|
670849
|
-
this.cursor = i2;
|
|
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);
|
|
670850
670302
|
}
|
|
670851
|
-
/**
|
|
670852
|
-
|
|
670853
|
-
|
|
670854
|
-
|
|
670855
|
-
|
|
670856
|
-
|
|
670857
|
-
this.line = this.line.slice(0, i2) + this.line.slice(this.cursor);
|
|
670858
|
-
this.cursor = i2;
|
|
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
|
+
};
|
|
670859
670309
|
}
|
|
670860
670310
|
};
|
|
670861
670311
|
}
|
|
670862
|
-
});
|
|
670863
|
-
|
|
670864
|
-
// packages/cli/src/api/
|
|
670865
|
-
|
|
670866
|
-
|
|
670867
|
-
|
|
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 {
|
|
670868
670425
|
}
|
|
670869
|
-
return "loopback";
|
|
670870
670426
|
}
|
|
670871
|
-
function
|
|
670872
|
-
|
|
670873
|
-
|
|
670874
|
-
|
|
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 };
|
|
670875
670451
|
}
|
|
670876
|
-
function
|
|
670877
|
-
|
|
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
|
+
}
|
|
670878
670471
|
}
|
|
670879
|
-
function
|
|
670880
|
-
|
|
670881
|
-
const clean5 = stripMappedPrefix(ip);
|
|
670882
|
-
if (clean5 === "::1") return true;
|
|
670883
|
-
if (/^127\./.test(clean5)) return true;
|
|
670884
|
-
return false;
|
|
670472
|
+
function listClients() {
|
|
670473
|
+
return Array.from(_clients2.values());
|
|
670885
670474
|
}
|
|
670886
|
-
function
|
|
670887
|
-
if (
|
|
670888
|
-
|
|
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;
|
|
670475
|
+
async function startVoiceChat(opts) {
|
|
670476
|
+
if (_voiceChatSession?.isActive) {
|
|
670477
|
+
return { ok: true, message: "VoiceChat already running" };
|
|
670895
670478
|
}
|
|
670896
|
-
|
|
670897
|
-
|
|
670898
|
-
|
|
670899
|
-
return false;
|
|
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" };
|
|
670900
670499
|
}
|
|
670901
|
-
function
|
|
670902
|
-
if (
|
|
670903
|
-
|
|
670904
|
-
|
|
670905
|
-
|
|
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" };
|
|
670906
670511
|
}
|
|
670907
|
-
function
|
|
670908
|
-
|
|
670909
|
-
|
|
670910
|
-
|
|
670911
|
-
|
|
670912
|
-
|
|
670913
|
-
|
|
670914
|
-
|
|
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();
|
|
670915
670650
|
}
|
|
670916
670651
|
}
|
|
670917
|
-
|
|
670918
|
-
|
|
670919
|
-
|
|
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");
|
|
670920
670673
|
}
|
|
670921
|
-
|
|
670922
|
-
|
|
670923
|
-
|
|
670924
|
-
|
|
670925
|
-
|
|
670926
|
-
|
|
670927
|
-
|
|
670928
|
-
|
|
670929
|
-
|
|
670930
|
-
|
|
670931
|
-
|
|
670932
|
-
|
|
670933
|
-
|
|
670934
|
-
return
|
|
670935
|
-
}
|
|
670936
|
-
function prefsPath(root) {
|
|
670937
|
-
return join149(projectDir(root), "preferences.json");
|
|
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;
|
|
670938
670688
|
}
|
|
670939
|
-
function
|
|
670940
|
-
|
|
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);
|
|
670941
670697
|
}
|
|
670942
|
-
function
|
|
670943
|
-
const
|
|
670944
|
-
|
|
670945
|
-
|
|
670946
|
-
|
|
670947
|
-
|
|
670948
|
-
|
|
670949
|
-
|
|
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);
|
|
670950
670772
|
}
|
|
670951
|
-
}
|
|
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
|
-
}
|
|
670773
|
+
};
|
|
670965
670774
|
}
|
|
670966
|
-
function
|
|
670967
|
-
|
|
670968
|
-
|
|
670969
|
-
|
|
670970
|
-
|
|
670971
|
-
|
|
670972
|
-
|
|
670973
|
-
|
|
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 }
|
|
670974
670800
|
};
|
|
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
670801
|
}
|
|
670993
|
-
|
|
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() {
|
|
670994
670832
|
try {
|
|
670995
|
-
|
|
670996
|
-
|
|
670997
|
-
|
|
670998
|
-
return
|
|
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 };
|
|
670999
670838
|
} catch {
|
|
671000
|
-
return
|
|
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
|
|
671013
|
-
};
|
|
670839
|
+
return { projects: [], schemaVersion: 1 };
|
|
671014
670840
|
}
|
|
671015
|
-
});
|
|
671016
|
-
|
|
671017
|
-
// packages/cli/src/tui/voicechat.ts
|
|
671018
|
-
var voicechat_exports = {};
|
|
671019
|
-
__export(voicechat_exports, {
|
|
671020
|
-
VoiceChatSession: () => VoiceChatSession
|
|
671021
|
-
});
|
|
671022
|
-
import { EventEmitter as EventEmitter13 } from "node:events";
|
|
671023
|
-
function clamp0114(x) {
|
|
671024
|
-
return x < 0 ? 0 : x > 1 ? 1 : x;
|
|
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
670841
|
}
|
|
671031
|
-
function
|
|
671032
|
-
|
|
671033
|
-
|
|
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);
|
|
671034
670847
|
}
|
|
671035
|
-
function
|
|
671036
|
-
|
|
671037
|
-
|
|
671038
|
-
|
|
671039
|
-
|
|
671040
|
-
if (
|
|
671041
|
-
|
|
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 {
|
|
671042
670855
|
}
|
|
671043
670856
|
}
|
|
671044
|
-
|
|
671045
|
-
return
|
|
670857
|
+
alive.sort((a2, b) => b.lastSeen - a2.lastSeen);
|
|
670858
|
+
return alive;
|
|
671046
670859
|
}
|
|
671047
|
-
function
|
|
671048
|
-
const
|
|
671049
|
-
|
|
671050
|
-
|
|
671051
|
-
const
|
|
671052
|
-
|
|
671053
|
-
|
|
671054
|
-
|
|
671055
|
-
|
|
671056
|
-
|
|
671057
|
-
|
|
671058
|
-
|
|
671059
|
-
|
|
671060
|
-
|
|
671061
|
-
|
|
671062
|
-
|
|
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);
|
|
671063
670884
|
}
|
|
671064
|
-
|
|
670885
|
+
writeAll(file);
|
|
670886
|
+
return entry;
|
|
671065
670887
|
}
|
|
671066
|
-
function
|
|
671067
|
-
|
|
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;
|
|
671068
670896
|
}
|
|
671069
|
-
function
|
|
671070
|
-
const
|
|
671071
|
-
|
|
671072
|
-
|
|
671073
|
-
|
|
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) {
|
|
671074
670909
|
try {
|
|
671075
|
-
|
|
671076
|
-
|
|
671077
|
-
|
|
671078
|
-
const args = obj.args && typeof obj.args === "object" ? obj.args : {};
|
|
671079
|
-
return { name: name10, args };
|
|
670910
|
+
if (existsSync135(CURRENT_FILE)) {
|
|
670911
|
+
const persisted = readFileSync112(CURRENT_FILE, "utf8").trim();
|
|
670912
|
+
if (persisted) currentRoot = persisted;
|
|
671080
670913
|
}
|
|
671081
670914
|
} catch {
|
|
671082
670915
|
}
|
|
671083
670916
|
}
|
|
671084
|
-
return null;
|
|
670917
|
+
if (!currentRoot) return null;
|
|
670918
|
+
const all2 = listProjects();
|
|
670919
|
+
return all2.find((p2) => p2.root === currentRoot) ?? null;
|
|
671085
670920
|
}
|
|
671086
|
-
function
|
|
671087
|
-
const
|
|
671088
|
-
const
|
|
671089
|
-
if (
|
|
671090
|
-
|
|
671091
|
-
|
|
671092
|
-
|
|
671093
|
-
|
|
671094
|
-
|
|
671095
|
-
|
|
671096
|
-
return { name: obj.tool, args };
|
|
671097
|
-
}
|
|
671098
|
-
} catch {
|
|
671099
|
-
}
|
|
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 {
|
|
671100
670931
|
}
|
|
671101
|
-
return
|
|
670932
|
+
return entry;
|
|
671102
670933
|
}
|
|
671103
|
-
function
|
|
671104
|
-
|
|
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();
|
|
670934
|
+
function _resetCurrentProject() {
|
|
670935
|
+
currentRoot = null;
|
|
671116
670936
|
}
|
|
671117
|
-
var
|
|
671118
|
-
var
|
|
671119
|
-
"packages/cli/src/
|
|
670937
|
+
var OMNIUS_DIR3, PROJECTS_FILE, CURRENT_FILE, currentRoot;
|
|
670938
|
+
var init_projects = __esm({
|
|
670939
|
+
"packages/cli/src/api/projects.ts"() {
|
|
671120
670940
|
"use strict";
|
|
671121
|
-
|
|
671122
|
-
|
|
671123
|
-
|
|
671124
|
-
|
|
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
|
+
});
|
|
671125
670947
|
|
|
671126
|
-
|
|
671127
|
-
|
|
671128
|
-
|
|
671129
|
-
|
|
671130
|
-
|
|
671131
|
-
|
|
671132
|
-
|
|
671133
|
-
|
|
671134
|
-
|
|
671135
|
-
|
|
671136
|
-
|
|
671137
|
-
|
|
671138
|
-
|
|
671139
|
-
|
|
671140
|
-
|
|
671141
|
-
|
|
671142
|
-
|
|
671143
|
-
|
|
671144
|
-
|
|
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) {
|
|
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) {
|
|
671172
670967
|
super();
|
|
671173
|
-
this.
|
|
671174
|
-
this.
|
|
671175
|
-
this.
|
|
671176
|
-
this.
|
|
671177
|
-
this.apiKey = opts.apiKey ?? "";
|
|
671178
|
-
this.runner = opts.runner ?? null;
|
|
671179
|
-
this.toolRelay = opts.toolRelay ?? null;
|
|
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 ?? (() => {
|
|
671193
|
-
});
|
|
671194
|
-
this.onStateChange = opts.onStateChange ?? (() => {
|
|
671195
|
-
});
|
|
671196
|
-
}
|
|
671197
|
-
get state() {
|
|
671198
|
-
return this._state;
|
|
671199
|
-
}
|
|
671200
|
-
get isActive() {
|
|
671201
|
-
return this.active;
|
|
670968
|
+
this.onScroll = scrollHandler;
|
|
670969
|
+
this.onActivity = activityHandler ?? null;
|
|
670970
|
+
this.onPointer = pointerHandler ?? null;
|
|
670971
|
+
this.onKeyboard = keyboardHandler ?? null;
|
|
671202
670972
|
}
|
|
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 });
|
|
670973
|
+
_transform(chunk, _encoding, callback) {
|
|
670974
|
+
this.buffer += chunk.toString();
|
|
670975
|
+
this.processBuffer(callback);
|
|
671212
670976
|
}
|
|
671213
|
-
|
|
671214
|
-
|
|
671215
|
-
|
|
671216
|
-
|
|
671217
|
-
|
|
671218
|
-
|
|
671219
|
-
|
|
671220
|
-
|
|
671221
|
-
|
|
671222
|
-
|
|
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]);
|
|
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;
|
|
671249
670987
|
}
|
|
671250
|
-
if (
|
|
671251
|
-
|
|
671252
|
-
|
|
671253
|
-
|
|
671254
|
-
|
|
671255
|
-
|
|
671256
|
-
|
|
671257
|
-
|
|
671258
|
-
|
|
671259
|
-
|
|
671260
|
-
|
|
671261
|
-
|
|
671262
|
-
|
|
671263
|
-
|
|
671264
|
-
|
|
671265
|
-
|
|
670988
|
+
if (this.looksLikePartialPrefixlessSgrMouse(remaining)) {
|
|
670989
|
+
break;
|
|
670990
|
+
}
|
|
670991
|
+
if (this.buffer[i2] === "\x1B") {
|
|
670992
|
+
const mouseMatch = remaining.match(/^\x1B\[<(\d+);(\d+);(\d+)([Mm])/);
|
|
670993
|
+
if (mouseMatch) {
|
|
670994
|
+
this.handleSgrMouse({
|
|
670995
|
+
raw: mouseMatch[0],
|
|
670996
|
+
btn: parseInt(mouseMatch[1]),
|
|
670997
|
+
col: parseInt(mouseMatch[2]),
|
|
670998
|
+
row: parseInt(mouseMatch[3]),
|
|
670999
|
+
suffix: mouseMatch[4]
|
|
671000
|
+
});
|
|
671001
|
+
i2 += mouseMatch[0].length;
|
|
671002
|
+
continue;
|
|
671003
|
+
}
|
|
671004
|
+
if (remaining.startsWith("\x1B[M")) {
|
|
671005
|
+
if (remaining.length >= 6) {
|
|
671006
|
+
if (this.onActivity) this.onActivity();
|
|
671007
|
+
i2 += 6;
|
|
671008
|
+
continue;
|
|
671266
671009
|
}
|
|
671267
|
-
|
|
671010
|
+
break;
|
|
671011
|
+
}
|
|
671012
|
+
if (remaining.startsWith("\x1B[<") && remaining.length < 15) {
|
|
671013
|
+
break;
|
|
671014
|
+
}
|
|
671015
|
+
if (remaining.startsWith("\x1B[") && remaining.length === 2) {
|
|
671016
|
+
break;
|
|
671017
|
+
}
|
|
671018
|
+
if (remaining.length === 1) {
|
|
671019
|
+
break;
|
|
671020
|
+
}
|
|
671268
671021
|
}
|
|
671269
|
-
|
|
671270
|
-
|
|
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");
|
|
671022
|
+
output += this.buffer[i2];
|
|
671023
|
+
i2++;
|
|
671279
671024
|
}
|
|
671280
|
-
|
|
671281
|
-
|
|
671282
|
-
|
|
671283
|
-
|
|
671284
|
-
if (this.abortController) {
|
|
671285
|
-
this.abortController.abort();
|
|
671286
|
-
this.abortController = null;
|
|
671025
|
+
this.buffer = this.buffer.slice(i2);
|
|
671026
|
+
if (output.length > 0) {
|
|
671027
|
+
if (this.onKeyboard) this.onKeyboard();
|
|
671028
|
+
this.push(output);
|
|
671287
671029
|
}
|
|
671288
|
-
if (this.
|
|
671289
|
-
clearTimeout(this.
|
|
671290
|
-
this.
|
|
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 = "";
|
|
671048
|
+
}
|
|
671049
|
+
}
|
|
671050
|
+
}, 50);
|
|
671291
671051
|
}
|
|
671292
|
-
|
|
671293
|
-
|
|
671294
|
-
|
|
671052
|
+
callback();
|
|
671053
|
+
}
|
|
671054
|
+
_flush(callback) {
|
|
671055
|
+
if (this.flushTimer) {
|
|
671056
|
+
clearTimeout(this.flushTimer);
|
|
671057
|
+
this.flushTimer = null;
|
|
671295
671058
|
}
|
|
671296
|
-
if (this.
|
|
671297
|
-
this.
|
|
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;
|
|
671064
|
+
}
|
|
671065
|
+
this.push(this.buffer);
|
|
671066
|
+
this.buffer = "";
|
|
671298
671067
|
}
|
|
671299
|
-
|
|
671300
|
-
|
|
671301
|
-
|
|
671068
|
+
callback();
|
|
671069
|
+
}
|
|
671070
|
+
matchPrefixlessSgrMouse(input) {
|
|
671071
|
+
const match = input.match(/^(<)?(\d{1,3});(\d{1,5});(\d{1,5})([Mm])/);
|
|
671072
|
+
if (!match) return null;
|
|
671073
|
+
const hasMarker = Boolean(match[1]);
|
|
671074
|
+
const btn = parseInt(match[2], 10);
|
|
671075
|
+
const col = parseInt(match[3], 10);
|
|
671076
|
+
const row = parseInt(match[4], 10);
|
|
671077
|
+
const suffix = match[5];
|
|
671078
|
+
if (!Number.isFinite(btn) || !Number.isFinite(col) || !Number.isFinite(row))
|
|
671079
|
+
return null;
|
|
671080
|
+
if (col <= 0 || row <= 0) return null;
|
|
671081
|
+
if (!hasMarker && Date.now() > this.expectPrefixlessMouseUntil && !this.isKnownMouseButton(btn))
|
|
671082
|
+
return null;
|
|
671083
|
+
if (btn < 0 || btn > 255) return null;
|
|
671084
|
+
return { raw: match[0], btn, col, row, suffix };
|
|
671085
|
+
}
|
|
671086
|
+
looksLikePartialPrefixlessSgrMouse(input) {
|
|
671087
|
+
if (input.length < 2) return false;
|
|
671088
|
+
if (/^<\d{1,3};\d{0,5}(?:;\d{0,5})?$/.test(input)) return true;
|
|
671089
|
+
if (Date.now() <= this.expectPrefixlessMouseUntil && /^\d{1,3};\d{0,5}(?:;\d{0,5})?$/.test(input)) {
|
|
671090
|
+
return true;
|
|
671302
671091
|
}
|
|
671303
|
-
|
|
671304
|
-
|
|
671305
|
-
|
|
671092
|
+
return false;
|
|
671093
|
+
}
|
|
671094
|
+
isKnownMouseButton(btn) {
|
|
671095
|
+
if (btn >= 0 && btn <= 6) return true;
|
|
671096
|
+
if (btn >= 32 && btn <= 39) return true;
|
|
671097
|
+
if (btn >= 64 && btn <= 71) return true;
|
|
671098
|
+
if (btn >= 96 && btn <= 103) return true;
|
|
671099
|
+
return false;
|
|
671100
|
+
}
|
|
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
|
+
}
|
|
671306
671118
|
}
|
|
671307
|
-
|
|
671308
|
-
|
|
671309
|
-
|
|
671119
|
+
if (this.onActivity) this.onActivity();
|
|
671120
|
+
}
|
|
671121
|
+
};
|
|
671122
|
+
}
|
|
671123
|
+
});
|
|
671124
|
+
|
|
671125
|
+
// packages/cli/src/tui/direct-input.ts
|
|
671126
|
+
var direct_input_exports = {};
|
|
671127
|
+
__export(direct_input_exports, {
|
|
671128
|
+
DirectInput: () => DirectInput
|
|
671129
|
+
});
|
|
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"() {
|
|
671134
|
+
"use strict";
|
|
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) {
|
|
671154
|
+
super();
|
|
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"));
|
|
671162
|
+
});
|
|
671163
|
+
input.on("end", () => {
|
|
671164
|
+
if (!this._closed) this.close();
|
|
671165
|
+
});
|
|
671166
|
+
}
|
|
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);
|
|
671174
|
+
}
|
|
671175
|
+
/** Pause input processing (for overlay transitions) */
|
|
671176
|
+
pause() {
|
|
671177
|
+
this._paused = true;
|
|
671178
|
+
}
|
|
671179
|
+
/** Resume input processing */
|
|
671180
|
+
resume() {
|
|
671181
|
+
this._paused = false;
|
|
671182
|
+
}
|
|
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;
|
|
671310
671190
|
}
|
|
671311
|
-
this.
|
|
671312
|
-
if (this.verbose) this.onStatus("VoiceChat ended");
|
|
671313
|
-
this.emit("stopped");
|
|
671191
|
+
this.emit("close");
|
|
671314
671192
|
}
|
|
671315
|
-
|
|
671316
|
-
|
|
671317
|
-
|
|
671318
|
-
|
|
671319
|
-
|
|
671320
|
-
|
|
671321
|
-
|
|
671193
|
+
/** No-op — readline compat (StatusBar renders the prompt, not us) */
|
|
671194
|
+
setPrompt(_prompt) {
|
|
671195
|
+
}
|
|
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;
|
|
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;
|
|
671381
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;
|
|
671388
|
+
}
|
|
671389
|
+
if (remaining.length >= 2 && remaining[1] === "O") {
|
|
671390
|
+
if (remaining.length >= 3) {
|
|
671391
|
+
this._handleSS3(remaining[2]);
|
|
671392
|
+
i2 += 3;
|
|
671393
|
+
continue;
|
|
671394
|
+
}
|
|
671395
|
+
break;
|
|
671396
|
+
}
|
|
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;
|
|
671402
|
+
}
|
|
671403
|
+
if (remaining.length === 1) {
|
|
671404
|
+
break;
|
|
671405
671405
|
}
|
|
671406
|
+
i2++;
|
|
671407
|
+
continue;
|
|
671406
671408
|
}
|
|
671407
|
-
|
|
671408
|
-
|
|
671409
|
-
|
|
671410
|
-
|
|
671411
|
-
|
|
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;
|
|
671409
|
+
if (code8 < 32 || code8 === 127) {
|
|
671410
|
+
if (code8 === 13) {
|
|
671411
|
+
if (this._preSubmit?.()) {
|
|
671412
|
+
i2++;
|
|
671413
|
+
continue;
|
|
671438
671414
|
}
|
|
671439
|
-
|
|
671415
|
+
this._submit();
|
|
671416
|
+
i2++;
|
|
671417
|
+
if (this._buffer[i2] === "\n") i2++;
|
|
671418
|
+
continue;
|
|
671419
|
+
}
|
|
671420
|
+
if (code8 === 10) {
|
|
671421
|
+
this._insertText("\n");
|
|
671422
|
+
i2++;
|
|
671423
|
+
continue;
|
|
671424
|
+
}
|
|
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;
|
|
671444
|
+
}
|
|
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 = "";
|
|
671458
|
+
}
|
|
671459
|
+
}, 50);
|
|
671460
|
+
} else {
|
|
671461
|
+
if (this._flushTimer) {
|
|
671462
|
+
clearTimeout(this._flushTimer);
|
|
671463
|
+
this._flushTimer = null;
|
|
671464
|
+
}
|
|
671465
|
+
}
|
|
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;
|
|
671486
|
+
}
|
|
671487
|
+
return false;
|
|
671488
|
+
}
|
|
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;
|
|
671440
671528
|
}
|
|
671441
|
-
|
|
671442
|
-
|
|
671443
|
-
|
|
671444
|
-
|
|
671445
|
-
|
|
671446
|
-
|
|
671447
|
-
|
|
671448
|
-
|
|
671449
|
-
|
|
671450
|
-
|
|
671451
|
-
|
|
671452
|
-
|
|
671453
|
-
toolOutput = `Tool ${name10} failed: ${e2 instanceof Error ? e2.message : String(e2)}`;
|
|
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;
|
|
671454
671541
|
}
|
|
671455
|
-
if (
|
|
671456
|
-
|
|
671457
|
-
|
|
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;
|
|
671458
671547
|
}
|
|
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);
|
|
671466
|
-
}
|
|
671467
|
-
if (response.trim()) {
|
|
671468
|
-
const finalSpoken = stripToolJsonLines(response.trim());
|
|
671469
|
-
this.context.push({ role: "assistant", content: finalSpoken });
|
|
671470
|
-
this.setState("SPEAKING");
|
|
671471
|
-
this.onAgentSpeech(finalSpoken);
|
|
671472
|
-
try {
|
|
671473
|
-
this.listen.pause();
|
|
671474
|
-
} catch {
|
|
671548
|
+
if (params === "5") {
|
|
671549
|
+
this.emit("pageup");
|
|
671550
|
+
return;
|
|
671475
671551
|
}
|
|
671476
|
-
|
|
671477
|
-
|
|
671478
|
-
|
|
671479
|
-
if (this.runner) {
|
|
671480
|
-
this.injectSummary();
|
|
671552
|
+
if (params === "6") {
|
|
671553
|
+
this.emit("pagedown");
|
|
671554
|
+
return;
|
|
671481
671555
|
}
|
|
671482
|
-
|
|
671483
|
-
|
|
671484
|
-
|
|
671485
|
-
|
|
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;
|
|
671486
671579
|
}
|
|
671487
|
-
} else {
|
|
671488
|
-
const estimatedMs = Math.max(1500, response.length / 5 * (6e4 / 150));
|
|
671489
|
-
await new Promise((r2) => setTimeout(r2, estimatedMs));
|
|
671490
671580
|
}
|
|
671581
|
+
if (hasCtrl && !hasShift && (codepoint === 73 || codepoint === 105)) {
|
|
671582
|
+
this.emit("ctrl-i");
|
|
671583
|
+
return;
|
|
671584
|
+
}
|
|
671585
|
+
return;
|
|
671491
671586
|
}
|
|
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
|
-
}
|
|
671501
|
-
if (this.active) {
|
|
671502
|
-
try {
|
|
671503
|
-
await this.listen.resume();
|
|
671504
|
-
} catch {
|
|
671505
|
-
}
|
|
671506
|
-
this.setState("LISTENING");
|
|
671507
|
-
if (this.verbose) this.onStatus("LISTENING...");
|
|
671508
671587
|
}
|
|
671509
671588
|
}
|
|
671510
|
-
|
|
671511
|
-
|
|
671512
|
-
|
|
671513
|
-
|
|
671514
|
-
|
|
671515
|
-
const baseUrl = this.backendUrl.replace(/\/v1\/?$/, "");
|
|
671516
|
-
const headers = { "Content-Type": "application/json" };
|
|
671517
|
-
if (this.apiKey) headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
671518
|
-
try {
|
|
671519
|
-
const nativeBody = JSON.stringify({
|
|
671520
|
-
model: this.model,
|
|
671521
|
-
messages: this.context,
|
|
671522
|
-
stream: true,
|
|
671523
|
-
think: false,
|
|
671524
|
-
// Disable reasoning — voice chat needs fast, direct responses
|
|
671525
|
-
options: { temperature: 0.7, num_predict: 256 }
|
|
671526
|
-
});
|
|
671527
|
-
const res2 = await fetch(`${baseUrl}/api/chat`, {
|
|
671528
|
-
method: "POST",
|
|
671529
|
-
headers,
|
|
671530
|
-
body: nativeBody,
|
|
671531
|
-
signal
|
|
671532
|
-
});
|
|
671533
|
-
if (res2.ok) {
|
|
671534
|
-
return await this.parseOllamaNativeStream(res2, signal);
|
|
671535
|
-
}
|
|
671536
|
-
} catch (err) {
|
|
671537
|
-
const msg = err instanceof Error ? err.message : "";
|
|
671538
|
-
if (msg.includes("abort")) throw err;
|
|
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;
|
|
671539
671594
|
}
|
|
671540
|
-
|
|
671541
|
-
|
|
671542
|
-
|
|
671543
|
-
stream: true,
|
|
671544
|
-
temperature: 0.7,
|
|
671545
|
-
max_tokens: 1024
|
|
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)}`);
|
|
671595
|
+
if (parts.length === 3) {
|
|
671596
|
+
const [prefix, modifiers, codepoint] = parts;
|
|
671597
|
+
return prefix === 27 && modifiers === 2 && (codepoint === 10 || codepoint === 13);
|
|
671552
671598
|
}
|
|
671553
|
-
return
|
|
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
|
|
671667
|
-
}
|
|
671668
|
-
function getDaemonListenEngine() {
|
|
671669
|
-
if (!_listenEngine) _listenEngine = getListenEngine();
|
|
671670
|
-
return _listenEngine;
|
|
671671
|
-
}
|
|
671672
|
-
function getVoiceBus() {
|
|
671673
|
-
if (!_bus) _bus = new EventEmitter14();
|
|
671674
|
-
return _bus;
|
|
671675
|
-
}
|
|
671676
|
-
function getRuntimeStatus() {
|
|
671677
|
-
return {
|
|
671678
|
-
state: _state3,
|
|
671679
|
-
voiceEnabled: _voiceEngine?.enabled ?? false,
|
|
671680
|
-
voiceReady: _voiceEngine?.ready ?? false,
|
|
671681
|
-
voiceModelId: _voiceEngine?.modelId ?? null,
|
|
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
|
-
};
|
|
671765
|
+
return "loopback";
|
|
671689
671766
|
}
|
|
671690
|
-
|
|
671691
|
-
|
|
671692
|
-
|
|
671693
|
-
|
|
671694
|
-
const voice = getVoiceEngine();
|
|
671695
|
-
const listen = getDaemonListenEngine();
|
|
671696
|
-
if (!voice.enabled) {
|
|
671697
|
-
await voice.toggle();
|
|
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;
|
|
671716
|
-
}
|
|
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);
|
|
671717
671771
|
}
|
|
671718
|
-
|
|
671719
|
-
|
|
671720
|
-
clearTimeout(_shutdownTimer);
|
|
671721
|
-
_shutdownTimer = null;
|
|
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
|
-
}
|
|
671772
|
+
function stripMappedPrefix(ip) {
|
|
671773
|
+
return ip.replace(/^::ffff:/i, "");
|
|
671732
671774
|
}
|
|
671733
|
-
function
|
|
671734
|
-
|
|
671735
|
-
|
|
671736
|
-
|
|
671737
|
-
|
|
671738
|
-
|
|
671739
|
-
_listenEngine?.pause?.();
|
|
671740
|
-
} catch {
|
|
671741
|
-
}
|
|
671742
|
-
}, IDLE_SHUTDOWN_MS);
|
|
671743
|
-
}
|
|
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;
|
|
671744
671781
|
}
|
|
671745
|
-
function
|
|
671746
|
-
if (
|
|
671747
|
-
const
|
|
671748
|
-
if (
|
|
671749
|
-
|
|
671750
|
-
|
|
671751
|
-
|
|
671752
|
-
|
|
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;
|
|
671753
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;
|
|
671754
671796
|
}
|
|
671755
|
-
|
|
671756
|
-
|
|
671757
|
-
|
|
671758
|
-
if (
|
|
671759
|
-
|
|
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 };
|
|
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;
|
|
671779
671802
|
}
|
|
671780
|
-
|
|
671781
|
-
|
|
671782
|
-
|
|
671783
|
-
|
|
671784
|
-
|
|
671785
|
-
|
|
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";
|
|
671786
671811
|
}
|
|
671787
|
-
|
|
671788
|
-
|
|
671789
|
-
|
|
671790
|
-
|
|
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);
|
|
671812
|
+
}
|
|
671813
|
+
var init_access_policy = __esm({
|
|
671814
|
+
"packages/cli/src/api/access-policy.ts"() {
|
|
671815
|
+
"use strict";
|
|
671798
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);
|
|
671799
671828
|
}
|
|
671800
|
-
function
|
|
671801
|
-
return
|
|
671829
|
+
function projectDir(root) {
|
|
671830
|
+
return join149(PROJECTS_DIR, projectKey(root));
|
|
671802
671831
|
}
|
|
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" };
|
|
671832
|
+
function prefsPath(root) {
|
|
671833
|
+
return join149(projectDir(root), "preferences.json");
|
|
671827
671834
|
}
|
|
671828
|
-
|
|
671829
|
-
|
|
671835
|
+
function rootSentinelPath(root) {
|
|
671836
|
+
return join149(projectDir(root), ".root");
|
|
671837
|
+
}
|
|
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);
|