omnius 1.0.254 → 1.0.256
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 +2286 -2038
- 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"() {
|
|
669789
|
+
var VAD_SILENCE_MS, MAX_SEGMENT_MS, MAX_CONTEXT_TURNS, SYSTEM_PROMPT2, MIN_SIGNAL_SCORE, NOISE_ONLY_RE, VoiceChatSession;
|
|
669790
|
+
var init_voicechat = __esm({
|
|
669791
|
+
"packages/cli/src/tui/voicechat.ts"() {
|
|
670044
669792
|
"use strict";
|
|
670045
|
-
|
|
670046
|
-
|
|
670047
|
-
|
|
670048
|
-
|
|
670049
|
-
}
|
|
670050
|
-
});
|
|
669793
|
+
VAD_SILENCE_MS = 3e3;
|
|
669794
|
+
MAX_SEGMENT_MS = 6500;
|
|
669795
|
+
MAX_CONTEXT_TURNS = 20;
|
|
669796
|
+
SYSTEM_PROMPT2 = `You are a voice assistant having a live spoken conversation. Keep responses extremely brief — 1-2 sentences max. You're speaking aloud, not writing. Be conversational, direct, and helpful. Don't use markdown or formatting — just natural speech.
|
|
670051
669797
|
|
|
670052
|
-
|
|
670053
|
-
|
|
670054
|
-
|
|
670055
|
-
|
|
670056
|
-
|
|
670057
|
-
|
|
670058
|
-
|
|
670059
|
-
|
|
670060
|
-
|
|
670061
|
-
|
|
670062
|
-
|
|
670063
|
-
|
|
670064
|
-
|
|
670065
|
-
|
|
670066
|
-
|
|
670067
|
-
|
|
670068
|
-
|
|
670069
|
-
|
|
670070
|
-
|
|
670071
|
-
|
|
670072
|
-
|
|
670073
|
-
|
|
670074
|
-
|
|
670075
|
-
|
|
670076
|
-
|
|
670077
|
-
|
|
670078
|
-
|
|
670079
|
-
|
|
670080
|
-
|
|
670081
|
-
|
|
670082
|
-
|
|
670083
|
-
|
|
670084
|
-
|
|
670085
|
-
|
|
670086
|
-
|
|
670087
|
-
|
|
670088
|
-
|
|
670089
|
-
|
|
670090
|
-
|
|
670091
|
-
|
|
670092
|
-
|
|
670093
|
-
|
|
670094
|
-
|
|
670095
|
-
|
|
670096
|
-
|
|
670097
|
-
|
|
670098
|
-
|
|
670099
|
-
|
|
670100
|
-
|
|
670101
|
-
|
|
670102
|
-
|
|
670103
|
-
|
|
670104
|
-
|
|
670105
|
-
|
|
670106
|
-
|
|
670107
|
-
|
|
670108
|
-
|
|
670109
|
-
|
|
670110
|
-
|
|
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
|
-
});
|
|
670228
|
-
|
|
670229
|
-
// packages/cli/src/tui/direct-input.ts
|
|
670230
|
-
var direct_input_exports = {};
|
|
670231
|
-
__export(direct_input_exports, {
|
|
670232
|
-
DirectInput: () => DirectInput
|
|
670233
|
-
});
|
|
670234
|
-
import { EventEmitter as EventEmitter12 } from "node:events";
|
|
670235
|
-
var DirectInput;
|
|
670236
|
-
var init_direct_input = __esm({
|
|
670237
|
-
"packages/cli/src/tui/direct-input.ts"() {
|
|
670238
|
-
"use strict";
|
|
670239
|
-
DirectInput = class extends EventEmitter12 {
|
|
670240
|
-
/** Current input line text */
|
|
670241
|
-
line = "";
|
|
670242
|
-
/** Cursor position within .line (0-based) */
|
|
670243
|
-
cursor = 0;
|
|
670244
|
-
_history;
|
|
670245
|
-
_historySize;
|
|
670246
|
-
_historyIndex = -1;
|
|
670247
|
-
_savedLine = "";
|
|
670248
|
-
// saved current input when navigating history
|
|
670249
|
-
_completer = null;
|
|
670250
|
-
_paused = false;
|
|
670251
|
-
_closed = false;
|
|
670252
|
-
_input;
|
|
670253
|
-
_buffer = "";
|
|
670254
|
-
// partial escape sequence buffer
|
|
670255
|
-
_flushTimer = null;
|
|
670256
|
-
_expectPrefixlessMouseUntil = 0;
|
|
670257
|
-
constructor(input, options2) {
|
|
670258
|
-
super();
|
|
670259
|
-
this._input = input;
|
|
670260
|
-
this._history = [...options2?.history ?? []];
|
|
670261
|
-
this._historySize = options2?.historySize ?? 500;
|
|
670262
|
-
this._completer = options2?.completer ?? null;
|
|
670263
|
-
input.on("data", (chunk) => {
|
|
670264
|
-
if (this._paused || this._closed) return;
|
|
670265
|
-
this.feed(chunk.toString("utf8"));
|
|
670266
|
-
});
|
|
670267
|
-
input.on("end", () => {
|
|
670268
|
-
if (!this._closed) this.close();
|
|
670269
|
-
});
|
|
670270
|
-
}
|
|
670271
|
-
/** Process raw input data — parse escape sequences and printable chars */
|
|
670272
|
-
feed(data) {
|
|
670273
|
-
const beforeLine = this.line;
|
|
670274
|
-
const beforeCursor = this.cursor;
|
|
670275
|
-
this._buffer += data;
|
|
670276
|
-
this._processBuffer();
|
|
670277
|
-
this._emitChangeIfNeeded(beforeLine, beforeCursor);
|
|
670278
|
-
}
|
|
670279
|
-
/** Pause input processing (for overlay transitions) */
|
|
670280
|
-
pause() {
|
|
670281
|
-
this._paused = true;
|
|
670282
|
-
}
|
|
670283
|
-
/** Resume input processing */
|
|
670284
|
-
resume() {
|
|
670285
|
-
this._paused = false;
|
|
670286
|
-
}
|
|
670287
|
-
/** Close the input handler */
|
|
670288
|
-
close() {
|
|
670289
|
-
if (this._closed) return;
|
|
670290
|
-
this._closed = true;
|
|
670291
|
-
if (this._flushTimer) {
|
|
670292
|
-
clearTimeout(this._flushTimer);
|
|
670293
|
-
this._flushTimer = null;
|
|
670294
|
-
}
|
|
670295
|
-
this.emit("close");
|
|
670296
|
-
}
|
|
670297
|
-
/** No-op — readline compat (StatusBar renders the prompt, not us) */
|
|
670298
|
-
setPrompt(_prompt) {
|
|
670299
|
-
}
|
|
670300
|
-
/** No-op — readline compat */
|
|
670301
|
-
prompt(_preserveCursor) {
|
|
670302
|
-
}
|
|
670303
|
-
/** Set the line content and cursor position (for Esc-to-recall or suggestion apply) */
|
|
670304
|
-
setLine(text2, cursorPos) {
|
|
670305
|
-
const beforeLine = this.line;
|
|
670306
|
-
const beforeCursor = this.cursor;
|
|
670307
|
-
this.line = text2;
|
|
670308
|
-
this.cursor = cursorPos ?? text2.length;
|
|
670309
|
-
this._emitChangeIfNeeded(beforeLine, beforeCursor);
|
|
670310
|
-
}
|
|
670311
|
-
/** Pre-submit hook — called before Enter submits. Return true to consume Enter. */
|
|
670312
|
-
_preSubmit = null;
|
|
670313
|
-
setPreSubmit(hook) {
|
|
670314
|
-
this._preSubmit = hook;
|
|
670315
|
-
}
|
|
670316
|
-
/** Navigate history up (older) */
|
|
670317
|
-
historyUp() {
|
|
670318
|
-
if (this._history.length === 0) return;
|
|
670319
|
-
if (this._historyIndex === -1) {
|
|
670320
|
-
this._savedLine = this.line;
|
|
670321
|
-
}
|
|
670322
|
-
if (this._historyIndex < this._history.length - 1) {
|
|
670323
|
-
this._historyIndex++;
|
|
670324
|
-
this.line = this._history[this._historyIndex];
|
|
670325
|
-
this.cursor = this.line.length;
|
|
670326
|
-
}
|
|
670327
|
-
}
|
|
670328
|
-
/** Navigate history down (newer) */
|
|
670329
|
-
historyDown() {
|
|
670330
|
-
if (this._historyIndex <= -1) return;
|
|
670331
|
-
this._historyIndex--;
|
|
670332
|
-
if (this._historyIndex === -1) {
|
|
670333
|
-
this.line = this._savedLine;
|
|
670334
|
-
} else {
|
|
670335
|
-
this.line = this._history[this._historyIndex];
|
|
670336
|
-
}
|
|
670337
|
-
this.cursor = this.line.length;
|
|
670338
|
-
}
|
|
670339
|
-
/**
|
|
670340
|
-
* Move cursor up one wrapped line. Returns true if moved, false if at top line.
|
|
670341
|
-
* Used by arrow-up to navigate within wrapped input before falling through to history.
|
|
670342
|
-
*/
|
|
670343
|
-
cursorUpWrapped(availWidth) {
|
|
670344
|
-
if (this.line.length <= availWidth) return false;
|
|
670345
|
-
const { charPositions, rawLines } = this._computeWrappedLines(availWidth);
|
|
670346
|
-
if (rawLines.length <= 1) return false;
|
|
670347
|
-
let currentLineIdx = rawLines.length - 1;
|
|
670348
|
-
for (let i2 = 0; i2 < charPositions.length; i2++) {
|
|
670349
|
-
const lineStart = charPositions[i2];
|
|
670350
|
-
const lineEnd = lineStart + rawLines[i2].length;
|
|
670351
|
-
if (this.cursor >= lineStart && this.cursor <= lineEnd) {
|
|
670352
|
-
currentLineIdx = i2;
|
|
670353
|
-
break;
|
|
670354
|
-
}
|
|
670355
|
-
}
|
|
670356
|
-
if (currentLineIdx === 0) return false;
|
|
670357
|
-
const prevLineIdx = currentLineIdx - 1;
|
|
670358
|
-
const currentColInLine = this.cursor - charPositions[currentLineIdx];
|
|
670359
|
-
const prevLineLength = rawLines[prevLineIdx].length;
|
|
670360
|
-
const newColInLine = Math.min(currentColInLine, prevLineLength);
|
|
670361
|
-
this.cursor = charPositions[prevLineIdx] + newColInLine;
|
|
670362
|
-
return true;
|
|
670363
|
-
}
|
|
670364
|
-
/**
|
|
670365
|
-
* Move cursor down one wrapped line. Returns true if moved, false if at bottom line.
|
|
670366
|
-
* Used by arrow-down to navigate within wrapped input before falling through to history.
|
|
670367
|
-
*/
|
|
670368
|
-
cursorDownWrapped(availWidth) {
|
|
670369
|
-
if (this.line.length <= availWidth) return false;
|
|
670370
|
-
const { charPositions, rawLines } = this._computeWrappedLines(availWidth);
|
|
670371
|
-
if (rawLines.length <= 1) return false;
|
|
670372
|
-
let currentLineIdx = rawLines.length - 1;
|
|
670373
|
-
for (let i2 = 0; i2 < charPositions.length; i2++) {
|
|
670374
|
-
const lineStart = charPositions[i2];
|
|
670375
|
-
const lineEnd = lineStart + rawLines[i2].length;
|
|
670376
|
-
if (this.cursor >= lineStart && this.cursor <= lineEnd) {
|
|
670377
|
-
currentLineIdx = i2;
|
|
670378
|
-
break;
|
|
670379
|
-
}
|
|
670380
|
-
}
|
|
670381
|
-
if (currentLineIdx === rawLines.length - 1) return false;
|
|
670382
|
-
const nextLineIdx = currentLineIdx + 1;
|
|
670383
|
-
const currentColInLine = this.cursor - charPositions[currentLineIdx];
|
|
670384
|
-
const nextLineLength = rawLines[nextLineIdx].length;
|
|
670385
|
-
const newColInLine = Math.min(currentColInLine, nextLineLength);
|
|
670386
|
-
this.cursor = charPositions[nextLineIdx] + newColInLine;
|
|
670387
|
-
return true;
|
|
670388
|
-
}
|
|
670389
|
-
/**
|
|
670390
|
-
* Compute wrapped lines (word-aware). Returns charPositions (start index of each line)
|
|
670391
|
-
* and rawLines (text of each line). Matches wrapInput logic in status-bar.ts.
|
|
670392
|
-
*/
|
|
670393
|
-
_computeWrappedLines(availWidth) {
|
|
670394
|
-
const width = Math.max(1, availWidth);
|
|
670395
|
-
const rawLines = [];
|
|
670396
|
-
const charPositions = [];
|
|
670397
|
-
const pushWrappedSegment = (segment, segmentStart2) => {
|
|
670398
|
-
if (segment.length === 0) {
|
|
670399
|
-
charPositions.push(segmentStart2);
|
|
670400
|
-
rawLines.push("");
|
|
670401
|
-
return;
|
|
670402
|
-
}
|
|
670403
|
-
let offset = 0;
|
|
670404
|
-
while (offset < segment.length) {
|
|
670405
|
-
const remaining = segment.slice(offset);
|
|
670406
|
-
if (remaining.length <= width) {
|
|
670407
|
-
charPositions.push(segmentStart2 + offset);
|
|
670408
|
-
rawLines.push(remaining);
|
|
670409
|
-
break;
|
|
670410
|
-
}
|
|
670411
|
-
let breakAt = width;
|
|
670412
|
-
const lastSpace = remaining.lastIndexOf(" ", width);
|
|
670413
|
-
if (lastSpace > 0 && lastSpace >= width * 0.3) {
|
|
670414
|
-
breakAt = lastSpace + 1;
|
|
670415
|
-
}
|
|
670416
|
-
charPositions.push(segmentStart2 + offset);
|
|
670417
|
-
rawLines.push(remaining.slice(0, breakAt));
|
|
670418
|
-
offset += breakAt;
|
|
670419
|
-
}
|
|
670420
|
-
};
|
|
670421
|
-
if (this.line.length === 0) {
|
|
670422
|
-
pushWrappedSegment("", 0);
|
|
670423
|
-
return { charPositions, rawLines };
|
|
670424
|
-
}
|
|
670425
|
-
let segmentStart = 0;
|
|
670426
|
-
while (segmentStart <= this.line.length) {
|
|
670427
|
-
const newlineAt = this.line.indexOf("\n", segmentStart);
|
|
670428
|
-
const segmentEnd = newlineAt === -1 ? this.line.length : newlineAt;
|
|
670429
|
-
pushWrappedSegment(this.line.slice(segmentStart, segmentEnd), segmentStart);
|
|
670430
|
-
if (newlineAt === -1) break;
|
|
670431
|
-
segmentStart = newlineAt + 1;
|
|
670432
|
-
if (segmentStart === this.line.length) {
|
|
670433
|
-
pushWrappedSegment("", segmentStart);
|
|
670434
|
-
break;
|
|
670435
|
-
}
|
|
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);
|
|
670436
669857
|
}
|
|
670437
|
-
|
|
669858
|
+
this.onStatus = opts.onStatus ?? (() => {
|
|
669859
|
+
});
|
|
669860
|
+
this.onUserSpeech = opts.onUserSpeech ?? (() => {
|
|
669861
|
+
});
|
|
669862
|
+
this.onPartialTranscript = opts.onPartialTranscript ?? (() => {
|
|
669863
|
+
});
|
|
669864
|
+
this.onAgentSpeech = opts.onAgentSpeech ?? (() => {
|
|
669865
|
+
});
|
|
669866
|
+
this.onStateChange = opts.onStateChange ?? (() => {
|
|
669867
|
+
});
|
|
669868
|
+
}
|
|
669869
|
+
get state() {
|
|
669870
|
+
return this._state;
|
|
669871
|
+
}
|
|
669872
|
+
get isActive() {
|
|
669873
|
+
return this.active;
|
|
670438
669874
|
}
|
|
670439
669875
|
// ---------------------------------------------------------------------------
|
|
670440
|
-
//
|
|
669876
|
+
// State transitions
|
|
670441
669877
|
// ---------------------------------------------------------------------------
|
|
670442
|
-
|
|
670443
|
-
|
|
670444
|
-
|
|
670445
|
-
|
|
670446
|
-
|
|
670447
|
-
|
|
670448
|
-
|
|
670449
|
-
|
|
670450
|
-
|
|
670451
|
-
|
|
670452
|
-
|
|
670453
|
-
|
|
670454
|
-
|
|
670455
|
-
|
|
670456
|
-
|
|
670457
|
-
|
|
670458
|
-
|
|
670459
|
-
|
|
670460
|
-
|
|
670461
|
-
|
|
670462
|
-
|
|
670463
|
-
|
|
670464
|
-
|
|
670465
|
-
|
|
670466
|
-
|
|
670467
|
-
|
|
670468
|
-
|
|
670469
|
-
|
|
670470
|
-
|
|
670471
|
-
|
|
670472
|
-
|
|
670473
|
-
|
|
670474
|
-
|
|
670475
|
-
|
|
670476
|
-
|
|
670477
|
-
|
|
670478
|
-
|
|
670479
|
-
|
|
670480
|
-
|
|
670481
|
-
|
|
670482
|
-
|
|
670483
|
-
|
|
670484
|
-
|
|
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;
|
|
669878
|
+
setState(next) {
|
|
669879
|
+
if (this._state === next) return;
|
|
669880
|
+
const prev = this._state;
|
|
669881
|
+
this._state = next;
|
|
669882
|
+
this.onStateChange(next);
|
|
669883
|
+
this.emit("stateChange", { from: prev, to: next });
|
|
669884
|
+
}
|
|
669885
|
+
// ---------------------------------------------------------------------------
|
|
669886
|
+
// Start / Stop
|
|
669887
|
+
// ---------------------------------------------------------------------------
|
|
669888
|
+
async start() {
|
|
669889
|
+
if (this.active) return;
|
|
669890
|
+
if (!this.voice.enabled || !this.voice.ready) {
|
|
669891
|
+
this.onStatus("Enabling voice engine...");
|
|
669892
|
+
await this.voice.toggle();
|
|
669893
|
+
}
|
|
669894
|
+
this.active = true;
|
|
669895
|
+
this.context = [{ role: "system", content: SYSTEM_PROMPT2 }];
|
|
669896
|
+
if (this.toolRelay) {
|
|
669897
|
+
this.toolCatalogNote = `Available tools (emit one-line JSON: {"tool":string,"args":object}):
|
|
669898
|
+
- voice_env{} → environment facts (cwd, os, cpu, mem)
|
|
669899
|
+
- voice_status{} → main-agent status
|
|
669900
|
+
- voice_list_files{dir?: string='.'} → list directory (bounded)
|
|
669901
|
+
- voice_read_file{path: string, max?: number=2048} → read file snippet
|
|
669902
|
+
- voice_to_main{message: string, start?: boolean=true} → relay/start task`;
|
|
669903
|
+
this.context.push({ role: "system", content: this.toolCatalogNote });
|
|
669904
|
+
}
|
|
669905
|
+
this.turnCount = 0;
|
|
669906
|
+
if (this.verbose) this.onStatus("VoiceChat active — LISTENING");
|
|
669907
|
+
this._onTranscript = (...args) => {
|
|
669908
|
+
let text2;
|
|
669909
|
+
let isFinal;
|
|
669910
|
+
let snr;
|
|
669911
|
+
let confidence2;
|
|
669912
|
+
if (typeof args[0] === "object" && args[0] !== null) {
|
|
669913
|
+
const evt = args[0];
|
|
669914
|
+
text2 = evt.text ?? "";
|
|
669915
|
+
isFinal = evt.isFinal ?? false;
|
|
669916
|
+
snr = evt.snr;
|
|
669917
|
+
confidence2 = evt.confidence;
|
|
669918
|
+
} else {
|
|
669919
|
+
text2 = String(args[0] ?? "");
|
|
669920
|
+
isFinal = Boolean(args[1]);
|
|
670512
669921
|
}
|
|
670513
|
-
if (
|
|
670514
|
-
|
|
670515
|
-
|
|
670516
|
-
|
|
670517
|
-
|
|
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 {
|
|
670518
669938
|
}
|
|
670519
|
-
|
|
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;
|
|
669939
|
+
}, 1e3);
|
|
670532
669940
|
}
|
|
670533
|
-
|
|
670534
|
-
|
|
670535
|
-
|
|
670536
|
-
|
|
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");
|
|
670537
669951
|
}
|
|
670538
|
-
|
|
670539
|
-
|
|
670540
|
-
|
|
670541
|
-
|
|
670542
|
-
|
|
670543
|
-
|
|
670544
|
-
|
|
670545
|
-
|
|
670546
|
-
|
|
670547
|
-
|
|
670548
|
-
|
|
670549
|
-
|
|
670550
|
-
|
|
670551
|
-
|
|
670552
|
-
|
|
670553
|
-
|
|
670554
|
-
|
|
670555
|
-
|
|
670556
|
-
|
|
670557
|
-
|
|
670558
|
-
|
|
670559
|
-
|
|
670560
|
-
|
|
670561
|
-
|
|
670562
|
-
|
|
670563
|
-
|
|
670564
|
-
}
|
|
670565
|
-
|
|
670566
|
-
|
|
670567
|
-
|
|
670568
|
-
}
|
|
669952
|
+
}
|
|
669953
|
+
async stop() {
|
|
669954
|
+
if (!this.active) return;
|
|
669955
|
+
this.active = false;
|
|
669956
|
+
if (this.abortController) {
|
|
669957
|
+
this.abortController.abort();
|
|
669958
|
+
this.abortController = null;
|
|
669959
|
+
}
|
|
669960
|
+
if (this.silenceTimer) {
|
|
669961
|
+
clearTimeout(this.silenceTimer);
|
|
669962
|
+
this.silenceTimer = null;
|
|
669963
|
+
}
|
|
669964
|
+
if (this.maxSegmentTimer) {
|
|
669965
|
+
clearTimeout(this.maxSegmentTimer);
|
|
669966
|
+
this.maxSegmentTimer = null;
|
|
669967
|
+
}
|
|
669968
|
+
if (this.captureBuffer.trim() && (this._state === "CAPTURING" || this._state === "TRANSCRIBING")) {
|
|
669969
|
+
this.finalizeSegment();
|
|
669970
|
+
}
|
|
669971
|
+
if (this._onTranscript) {
|
|
669972
|
+
this.listen.removeAllListeners("transcript");
|
|
669973
|
+
this._onTranscript = null;
|
|
669974
|
+
}
|
|
669975
|
+
if (this._onError) {
|
|
669976
|
+
this.listen.removeAllListeners("error");
|
|
669977
|
+
this._onError = null;
|
|
669978
|
+
}
|
|
669979
|
+
try {
|
|
669980
|
+
await this.listen.stop();
|
|
669981
|
+
} catch {
|
|
670569
669982
|
}
|
|
669983
|
+
this.setState("IDLE");
|
|
669984
|
+
if (this.verbose) this.onStatus("VoiceChat ended");
|
|
669985
|
+
this.emit("stopped");
|
|
670570
669986
|
}
|
|
670571
|
-
|
|
670572
|
-
|
|
670573
|
-
|
|
670574
|
-
|
|
670575
|
-
|
|
670576
|
-
|
|
670577
|
-
|
|
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;
|
|
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;
|
|
670590
669994
|
}
|
|
670591
|
-
|
|
670592
|
-
|
|
670593
|
-
|
|
670594
|
-
|
|
670595
|
-
|
|
670596
|
-
|
|
670597
|
-
|
|
670598
|
-
|
|
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
|
-
this.emit("down");
|
|
670620
|
-
return;
|
|
670621
|
-
case "C":
|
|
670622
|
-
if (params === "1;5") {
|
|
670623
|
-
this.emit("ctrl-right");
|
|
670624
|
-
return;
|
|
670625
|
-
}
|
|
670626
|
-
if (this.cursor < this.line.length) this.cursor++;
|
|
670627
|
-
return;
|
|
670628
|
-
case "D":
|
|
670629
|
-
if (params === "1;5") {
|
|
670630
|
-
this.emit("ctrl-left");
|
|
670631
|
-
return;
|
|
670632
|
-
}
|
|
670633
|
-
if (this.cursor > 0) this.cursor--;
|
|
670634
|
-
return;
|
|
670635
|
-
case "H":
|
|
670636
|
-
this.cursor = 0;
|
|
670637
|
-
return;
|
|
670638
|
-
case "F":
|
|
670639
|
-
this.cursor = this.line.length;
|
|
670640
|
-
return;
|
|
670641
|
-
case "~":
|
|
670642
|
-
if (this._isShiftEnterCSI(params)) {
|
|
670643
|
-
this._insertText("\n");
|
|
670644
|
-
return;
|
|
670645
|
-
}
|
|
670646
|
-
if (params === "3") {
|
|
670647
|
-
if (this.cursor < this.line.length) {
|
|
670648
|
-
this.line = this.line.slice(0, this.cursor) + this.line.slice(this.cursor + 1);
|
|
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()}` });
|
|
670649
670075
|
}
|
|
670650
|
-
|
|
670076
|
+
} catch {
|
|
670651
670077
|
}
|
|
670652
|
-
|
|
670653
|
-
|
|
670654
|
-
|
|
670078
|
+
}
|
|
670079
|
+
const lastUser = [...this.context].reverse().find((m2) => m2.role === "user")?.content || "";
|
|
670080
|
+
let preAnswered = false;
|
|
670081
|
+
if (this.heuristicsEnabled && this.toolRelay && lastUser) {
|
|
670082
|
+
const lower = lastUser.toLowerCase();
|
|
670083
|
+
const wantList = /(list|show|explore|browse|what's in|whats in|contents).*(dir|directory|folder|files)/.test(lower);
|
|
670084
|
+
const wantEnv = /(what\s+dir|cwd|current\s+dir|working\s+directory|where\s+are\s+you)/.test(lower);
|
|
670085
|
+
const readMatch = lastUser.match(/(?:read|open|show)\s+file\s+([\w./\\-]+)\b/i);
|
|
670086
|
+
const toMainMatch = lastUser.match(/^(?:start|run|do)\s+(.{5,})$/i);
|
|
670087
|
+
try {
|
|
670088
|
+
if (wantEnv) {
|
|
670089
|
+
const out = await this.toolRelay.call("voice_env", {});
|
|
670090
|
+
this.context.push({ role: "system", content: `Tool voice_env result (authoritative):
|
|
670091
|
+
${out}` });
|
|
670092
|
+
preAnswered = true;
|
|
670093
|
+
} else if (wantList) {
|
|
670094
|
+
const out = await this.toolRelay.call("voice_list_files", { dir: "." });
|
|
670095
|
+
this.context.push({ role: "system", content: `Tool voice_list_files result (authoritative):
|
|
670096
|
+
${out}` });
|
|
670097
|
+
preAnswered = true;
|
|
670098
|
+
} else if (readMatch) {
|
|
670099
|
+
const out = await this.toolRelay.call("voice_read_file", { path: readMatch[1], max: 1024 });
|
|
670100
|
+
this.context.push({ role: "system", content: `Tool voice_read_file result (authoritative):
|
|
670101
|
+
${out}` });
|
|
670102
|
+
preAnswered = true;
|
|
670103
|
+
} else if (toMainMatch) {
|
|
670104
|
+
const msg = toMainMatch[1].trim();
|
|
670105
|
+
const out = await this.toolRelay.call("voice_to_main", { message: msg, start: true });
|
|
670106
|
+
this.relayTranscript.push({ dir: "toMain", content: msg, ts: Date.now() });
|
|
670107
|
+
this.context.push({ role: "system", content: `Tool voice_to_main result (authoritative):
|
|
670108
|
+
${out}` });
|
|
670109
|
+
preAnswered = true;
|
|
670110
|
+
}
|
|
670111
|
+
} catch {
|
|
670655
670112
|
}
|
|
670656
|
-
|
|
670657
|
-
|
|
670658
|
-
|
|
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)}`;
|
|
670659
670126
|
}
|
|
670660
|
-
|
|
670661
|
-
|
|
670662
|
-
|
|
670663
|
-
const codepoint = parseInt(parts[0] ?? "0");
|
|
670664
|
-
const modifiers = parseInt(parts[1] ?? "1");
|
|
670665
|
-
const hasCtrl = modifiers - 1 & 4;
|
|
670666
|
-
const hasShift = modifiers - 1 & 1;
|
|
670667
|
-
if (hasShift && (codepoint === 10 || codepoint === 13)) {
|
|
670668
|
-
this._insertText("\n");
|
|
670669
|
-
return;
|
|
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() });
|
|
670670
670130
|
}
|
|
670671
|
-
|
|
670672
|
-
|
|
670673
|
-
|
|
670674
|
-
|
|
670675
|
-
|
|
670676
|
-
|
|
670677
|
-
|
|
670678
|
-
|
|
670679
|
-
|
|
670680
|
-
|
|
670681
|
-
|
|
670682
|
-
|
|
670683
|
-
|
|
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 {
|
|
670684
670147
|
}
|
|
670685
|
-
|
|
670686
|
-
|
|
670687
|
-
|
|
670148
|
+
this.voice.speak(finalSpoken);
|
|
670149
|
+
this.voiceTranscript.push({ role: "assistant", content: finalSpoken, ts: Date.now() });
|
|
670150
|
+
this.voiceTranscript.push({ role: "assistant", content: response.trim(), ts: Date.now() });
|
|
670151
|
+
if (this.runner) {
|
|
670152
|
+
this.injectSummary();
|
|
670153
|
+
}
|
|
670154
|
+
if (typeof this.voice.waitUntilIdle === "function") {
|
|
670155
|
+
try {
|
|
670156
|
+
await this.voice.waitUntilIdle();
|
|
670157
|
+
} catch {
|
|
670158
|
+
}
|
|
670159
|
+
} else {
|
|
670160
|
+
const estimatedMs = Math.max(1500, response.length / 5 * (6e4 / 150));
|
|
670161
|
+
await new Promise((r2) => setTimeout(r2, estimatedMs));
|
|
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
670312
|
});
|
|
670863
670313
|
|
|
670864
|
-
// packages/cli/src/api/
|
|
670865
|
-
|
|
670866
|
-
|
|
670867
|
-
|
|
670314
|
+
// packages/cli/src/api/voice-runtime.ts
|
|
670315
|
+
var voice_runtime_exports = {};
|
|
670316
|
+
__export(voice_runtime_exports, {
|
|
670317
|
+
_resetForTests: () => _resetForTests,
|
|
670318
|
+
ensureRuntime: () => ensureRuntime,
|
|
670319
|
+
feedAudioFromClient: () => feedAudioFromClient,
|
|
670320
|
+
getDaemonListenEngine: () => getDaemonListenEngine,
|
|
670321
|
+
getRuntimeStatus: () => getRuntimeStatus,
|
|
670322
|
+
getVoiceBus: () => getVoiceBus,
|
|
670323
|
+
getVoiceEngine: () => getVoiceEngine,
|
|
670324
|
+
isVoiceChatActive: () => isVoiceChatActive,
|
|
670325
|
+
listClients: () => listClients,
|
|
670326
|
+
registerClient: () => registerClient,
|
|
670327
|
+
startVoiceChat: () => startVoiceChat,
|
|
670328
|
+
stopVoiceChat: () => stopVoiceChat,
|
|
670329
|
+
synthesizeAndBroadcast: () => synthesizeAndBroadcast,
|
|
670330
|
+
synthesizeToWav: () => synthesizeToWav,
|
|
670331
|
+
unregisterClient: () => unregisterClient
|
|
670332
|
+
});
|
|
670333
|
+
import { EventEmitter as EventEmitter13 } from "node:events";
|
|
670334
|
+
function getVoiceEngine() {
|
|
670335
|
+
if (!_voiceEngine) {
|
|
670336
|
+
_voiceEngine = new VoiceEngine();
|
|
670337
|
+
}
|
|
670338
|
+
return _voiceEngine;
|
|
670339
|
+
}
|
|
670340
|
+
function getDaemonListenEngine() {
|
|
670341
|
+
if (!_listenEngine) _listenEngine = getListenEngine();
|
|
670342
|
+
return _listenEngine;
|
|
670343
|
+
}
|
|
670344
|
+
function getVoiceBus() {
|
|
670345
|
+
if (!_bus) _bus = new EventEmitter13();
|
|
670346
|
+
return _bus;
|
|
670347
|
+
}
|
|
670348
|
+
function getRuntimeStatus() {
|
|
670349
|
+
return {
|
|
670350
|
+
state: _state3,
|
|
670351
|
+
voiceEnabled: _voiceEngine?.enabled ?? false,
|
|
670352
|
+
voiceReady: _voiceEngine?.ready ?? false,
|
|
670353
|
+
voiceModelId: _voiceEngine?.modelId ?? null,
|
|
670354
|
+
cloneRef: _voiceEngine?.luxttsCloneRef ?? null,
|
|
670355
|
+
listenActive: _listenEngine?.isActive ?? false,
|
|
670356
|
+
listenPaused: _listenEngine?.isPaused ?? false,
|
|
670357
|
+
clientCount: _clients2.size,
|
|
670358
|
+
loadedAt: _loadedAt,
|
|
670359
|
+
lastError: _lastError
|
|
670360
|
+
};
|
|
670361
|
+
}
|
|
670362
|
+
async function ensureRuntime() {
|
|
670363
|
+
if (_state3 === "loading" || _state3 === "listening" || _state3 === "speaking") return;
|
|
670364
|
+
setState("loading");
|
|
670365
|
+
try {
|
|
670366
|
+
const voice = getVoiceEngine();
|
|
670367
|
+
const listen = getDaemonListenEngine();
|
|
670368
|
+
if (!voice.enabled) {
|
|
670369
|
+
await voice.toggle();
|
|
670370
|
+
}
|
|
670371
|
+
if (!listen.isActive) {
|
|
670372
|
+
try {
|
|
670373
|
+
await listen.start();
|
|
670374
|
+
} catch (err) {
|
|
670375
|
+
const m2 = err instanceof Error ? err.message : String(err);
|
|
670376
|
+
_lastError = `listen.start() failed: ${m2}`;
|
|
670377
|
+
getVoiceBus().emit("error", _lastError);
|
|
670378
|
+
}
|
|
670379
|
+
}
|
|
670380
|
+
_loadedAt = Date.now();
|
|
670381
|
+
setState("listening");
|
|
670382
|
+
wireListenToBus();
|
|
670383
|
+
} catch (err) {
|
|
670384
|
+
const m2 = err instanceof Error ? err.message : String(err);
|
|
670385
|
+
_lastError = m2;
|
|
670386
|
+
setState("error");
|
|
670387
|
+
throw err;
|
|
670388
|
+
}
|
|
670389
|
+
}
|
|
670390
|
+
async function registerClient(handle2) {
|
|
670391
|
+
if (_shutdownTimer) {
|
|
670392
|
+
clearTimeout(_shutdownTimer);
|
|
670393
|
+
_shutdownTimer = null;
|
|
670394
|
+
}
|
|
670395
|
+
_clients2.set(handle2.id, handle2);
|
|
670396
|
+
if (_clients2.size === 1 && (_state3 === "idle" || _state3 === "error")) {
|
|
670397
|
+
try {
|
|
670398
|
+
await ensureRuntime();
|
|
670399
|
+
} catch (err) {
|
|
670400
|
+
_clients2.delete(handle2.id);
|
|
670401
|
+
throw err;
|
|
670402
|
+
}
|
|
670403
|
+
}
|
|
670404
|
+
}
|
|
670405
|
+
function unregisterClient(id) {
|
|
670406
|
+
_clients2.delete(id);
|
|
670407
|
+
if (_clients2.size === 0 && _shutdownTimer === null) {
|
|
670408
|
+
_shutdownTimer = setTimeout(() => {
|
|
670409
|
+
_shutdownTimer = null;
|
|
670410
|
+
try {
|
|
670411
|
+
_listenEngine?.pause?.();
|
|
670412
|
+
} catch {
|
|
670413
|
+
}
|
|
670414
|
+
}, IDLE_SHUTDOWN_MS);
|
|
670415
|
+
}
|
|
670416
|
+
}
|
|
670417
|
+
function feedAudioFromClient(clientId, pcmChunk) {
|
|
670418
|
+
if (_ttsSpeaking) return;
|
|
670419
|
+
const listen = _listenEngine;
|
|
670420
|
+
if (!listen || !listen.isActive) return;
|
|
670421
|
+
try {
|
|
670422
|
+
const transcriber = listen.liveTranscriber;
|
|
670423
|
+
if (transcriber?.write) transcriber.write(pcmChunk);
|
|
670424
|
+
} catch {
|
|
670425
|
+
}
|
|
670426
|
+
}
|
|
670427
|
+
async function synthesizeToWav(text2, format3 = "wav") {
|
|
670428
|
+
await ensureRuntime();
|
|
670429
|
+
const voice = _voiceEngine;
|
|
670430
|
+
if (!voice || !voice.ready || !voice.synthesizeToPCM) return null;
|
|
670431
|
+
const result = await voice.synthesizeToPCM(text2);
|
|
670432
|
+
if (!result || !result.pcm || result.pcm.length === 0) return null;
|
|
670433
|
+
const sampleRate = result.sampleRate;
|
|
670434
|
+
const pcm = result.pcm;
|
|
670435
|
+
if (format3 === "pcm") return { bytes: pcm, sampleRate, format: format3 };
|
|
670436
|
+
const header = Buffer.alloc(44);
|
|
670437
|
+
header.write("RIFF", 0);
|
|
670438
|
+
header.writeUInt32LE(36 + pcm.length, 4);
|
|
670439
|
+
header.write("WAVE", 8);
|
|
670440
|
+
header.write("fmt ", 12);
|
|
670441
|
+
header.writeUInt32LE(16, 16);
|
|
670442
|
+
header.writeUInt16LE(1, 20);
|
|
670443
|
+
header.writeUInt16LE(1, 22);
|
|
670444
|
+
header.writeUInt32LE(sampleRate, 24);
|
|
670445
|
+
header.writeUInt32LE(sampleRate * 2, 28);
|
|
670446
|
+
header.writeUInt16LE(2, 32);
|
|
670447
|
+
header.writeUInt16LE(16, 34);
|
|
670448
|
+
header.write("data", 36);
|
|
670449
|
+
header.writeUInt32LE(pcm.length, 40);
|
|
670450
|
+
return { bytes: Buffer.concat([header, pcm]), sampleRate, format: format3 };
|
|
670451
|
+
}
|
|
670452
|
+
async function synthesizeAndBroadcast(text2) {
|
|
670453
|
+
const voice = _voiceEngine;
|
|
670454
|
+
if (!voice || !voice.ready) return;
|
|
670455
|
+
if (!voice.synthesizeToPCM) {
|
|
670456
|
+
getVoiceBus().emit("error", "voice engine has no synthesizeToPCM");
|
|
670457
|
+
return;
|
|
670458
|
+
}
|
|
670459
|
+
setSpeaking(true);
|
|
670460
|
+
try {
|
|
670461
|
+
const result = await voice.synthesizeToPCM(text2);
|
|
670462
|
+
if (!result || !result.pcm || result.pcm.length === 0) return;
|
|
670463
|
+
getVoiceBus().emit("agent_text", { text: text2 });
|
|
670464
|
+
getVoiceBus().emit("tts_pcm", result.pcm, result.sampleRate);
|
|
670465
|
+
} catch (err) {
|
|
670466
|
+
const m2 = err instanceof Error ? err.message : String(err);
|
|
670467
|
+
getVoiceBus().emit("error", `tts: ${m2}`);
|
|
670468
|
+
} finally {
|
|
670469
|
+
setSpeaking(false);
|
|
670470
|
+
}
|
|
670471
|
+
}
|
|
670472
|
+
function listClients() {
|
|
670473
|
+
return Array.from(_clients2.values());
|
|
670474
|
+
}
|
|
670475
|
+
async function startVoiceChat(opts) {
|
|
670476
|
+
if (_voiceChatSession?.isActive) {
|
|
670477
|
+
return { ok: true, message: "VoiceChat already running" };
|
|
670478
|
+
}
|
|
670479
|
+
await ensureRuntime();
|
|
670480
|
+
const voice = getVoiceEngine();
|
|
670481
|
+
const listen = getDaemonListenEngine();
|
|
670482
|
+
if (!voice.ready) return { ok: false, message: "Voice engine not ready" };
|
|
670483
|
+
_voiceChatSession = new VoiceChatSession({
|
|
670484
|
+
voice,
|
|
670485
|
+
listen,
|
|
670486
|
+
backendUrl: opts.backendUrl,
|
|
670487
|
+
model: opts.model,
|
|
670488
|
+
apiKey: opts.apiKey,
|
|
670489
|
+
verbose: opts.verbose === true,
|
|
670490
|
+
onStatus: (msg) => getVoiceBus().emit("status", msg),
|
|
670491
|
+
onUserSpeech: (text2) => getVoiceBus().emit("transcript", { text: text2, final: true }),
|
|
670492
|
+
onPartialTranscript: (text2) => getVoiceBus().emit("transcript", { text: text2, final: false }),
|
|
670493
|
+
onAgentSpeech: (text2) => getVoiceBus().emit("agent_text", { text: text2 }),
|
|
670494
|
+
onStateChange: (s2) => getVoiceBus().emit("session_state", s2)
|
|
670495
|
+
});
|
|
670496
|
+
await _voiceChatSession.start();
|
|
670497
|
+
setState("listening");
|
|
670498
|
+
return { ok: true, message: "VoiceChat started" };
|
|
670499
|
+
}
|
|
670500
|
+
async function stopVoiceChat() {
|
|
670501
|
+
if (!_voiceChatSession) return { ok: true, message: "No active session" };
|
|
670502
|
+
try {
|
|
670503
|
+
if (_voiceChatSession.stop) {
|
|
670504
|
+
await _voiceChatSession.stop();
|
|
670505
|
+
}
|
|
670506
|
+
} catch {
|
|
670507
|
+
}
|
|
670508
|
+
_voiceChatSession = null;
|
|
670509
|
+
setState(_listenEngine?.isActive ? "listening" : "idle");
|
|
670510
|
+
return { ok: true, message: "VoiceChat stopped" };
|
|
670511
|
+
}
|
|
670512
|
+
function isVoiceChatActive() {
|
|
670513
|
+
return _voiceChatSession?.isActive ?? false;
|
|
670514
|
+
}
|
|
670515
|
+
function setState(s2) {
|
|
670516
|
+
if (_state3 === s2) return;
|
|
670517
|
+
_state3 = s2;
|
|
670518
|
+
getVoiceBus().emit("state", s2);
|
|
670519
|
+
}
|
|
670520
|
+
function setSpeaking(speaking) {
|
|
670521
|
+
_ttsSpeaking = speaking;
|
|
670522
|
+
if (speaking) {
|
|
670523
|
+
setState("speaking");
|
|
670524
|
+
getVoiceBus().emit("tts_start");
|
|
670525
|
+
} else {
|
|
670526
|
+
setState(_listenEngine?.isActive ? "listening" : "idle");
|
|
670527
|
+
getVoiceBus().emit("tts_end");
|
|
670868
670528
|
}
|
|
670869
|
-
return "loopback";
|
|
670870
670529
|
}
|
|
670871
|
-
function
|
|
670872
|
-
|
|
670873
|
-
if (
|
|
670874
|
-
|
|
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
|
+
});
|
|
670875
670539
|
}
|
|
670876
|
-
function
|
|
670877
|
-
|
|
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
|
+
}
|
|
670878
670550
|
}
|
|
670879
|
-
|
|
670880
|
-
|
|
670881
|
-
|
|
670882
|
-
|
|
670883
|
-
|
|
670884
|
-
|
|
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, "");
|
|
670885
670580
|
}
|
|
670886
|
-
function
|
|
670887
|
-
|
|
670888
|
-
const
|
|
670889
|
-
|
|
670890
|
-
|
|
670891
|
-
const
|
|
670892
|
-
|
|
670893
|
-
|
|
670894
|
-
|
|
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();
|
|
670895
670650
|
}
|
|
670896
|
-
if (/^169\.254\./.test(clean5)) return true;
|
|
670897
|
-
if (/^f[cd][0-9a-f]{2}:/i.test(clean5)) return true;
|
|
670898
|
-
if (/^fe[89ab][0-9a-f]:/i.test(clean5)) return true;
|
|
670899
|
-
return false;
|
|
670900
670651
|
}
|
|
670901
|
-
function
|
|
670902
|
-
|
|
670903
|
-
if (
|
|
670904
|
-
|
|
670905
|
-
|
|
670652
|
+
function buildNonInteractiveSummary(cmdName, _args, config) {
|
|
670653
|
+
const cfg = config ?? loadConfig();
|
|
670654
|
+
if (cmdName === "setup" || cmdName === "wizard") {
|
|
670655
|
+
return [
|
|
670656
|
+
"omnius setup",
|
|
670657
|
+
"",
|
|
670658
|
+
"The setup wizard is an interactive terminal flow. In the GUI command bridge it is summarized instead of opening prompts, installing software, starting Ollama, or pulling models.",
|
|
670659
|
+
"",
|
|
670660
|
+
`Current backend: ${cfg.backendType ?? "ollama"}`,
|
|
670661
|
+
`Current endpoint: ${cfg.backendUrl ?? "http://127.0.0.1:11434"}`,
|
|
670662
|
+
`Current model: ${cfg.model ?? "qwen3.5:latest"}`,
|
|
670663
|
+
"",
|
|
670664
|
+
"Available non-interactive setup actions:",
|
|
670665
|
+
" /endpoint <url> Set or inspect the inference endpoint.",
|
|
670666
|
+
" /model <name> Set the active model directly.",
|
|
670667
|
+
" /models Show model-selection guidance.",
|
|
670668
|
+
" /config Inspect persisted configuration.",
|
|
670669
|
+
" /doctor Run diagnostics from the terminal if deeper repair is needed.",
|
|
670670
|
+
"",
|
|
670671
|
+
"Open a terminal and run `omnius`, then use /setup for the full guided wizard."
|
|
670672
|
+
].join("\n");
|
|
670673
|
+
}
|
|
670674
|
+
if (cmdName === "models") {
|
|
670675
|
+
return [
|
|
670676
|
+
"omnius models",
|
|
670677
|
+
"",
|
|
670678
|
+
"The model picker is interactive in the TUI. The GUI bridge does not probe remote model endpoints here, so it cannot hang on a stale backend.",
|
|
670679
|
+
"",
|
|
670680
|
+
`Active model: ${cfg.model ?? "qwen3.5:latest"}`,
|
|
670681
|
+
`Endpoint: ${cfg.backendUrl ?? "http://127.0.0.1:11434"}`,
|
|
670682
|
+
`Backend: ${cfg.backendType ?? "ollama"}`,
|
|
670683
|
+
"",
|
|
670684
|
+
"Use /model <name> to set a model directly, /endpoint to switch providers, or open the TUI for the searchable model picker."
|
|
670685
|
+
].join("\n");
|
|
670686
|
+
}
|
|
670687
|
+
return null;
|
|
670688
|
+
}
|
|
670689
|
+
function acquireLock2() {
|
|
670690
|
+
let release;
|
|
670691
|
+
const next = new Promise((res) => {
|
|
670692
|
+
release = res;
|
|
670693
|
+
});
|
|
670694
|
+
const prev = _passthroughLock;
|
|
670695
|
+
_passthroughLock = next;
|
|
670696
|
+
return prev.then(() => release);
|
|
670697
|
+
}
|
|
670698
|
+
function buildSyntheticContext(config, repoRoot) {
|
|
670699
|
+
const cfg = config ?? loadConfig();
|
|
670700
|
+
const root = repoRoot ?? process.cwd();
|
|
670701
|
+
let colorsEnabled = loadProjectSettings(root).colors ?? loadGlobalSettings().colors ?? true;
|
|
670702
|
+
let selfModifyEnabled = loadProjectSettings(root).selfModify ?? loadGlobalSettings().selfModify ?? false;
|
|
670703
|
+
return {
|
|
670704
|
+
config: cfg,
|
|
670705
|
+
repoRoot: root,
|
|
670706
|
+
rl: makeRejectingReadline(),
|
|
670707
|
+
setModel: async (model) => {
|
|
670708
|
+
try {
|
|
670709
|
+
const engine = getDaemonListenEngine();
|
|
670710
|
+
await engine.setModel(model);
|
|
670711
|
+
renderInfo(`Model switched to: ${model}`);
|
|
670712
|
+
return `model set to ${model}`;
|
|
670713
|
+
} catch (err) {
|
|
670714
|
+
renderError(`Failed to set model: ${err instanceof Error ? err.message : String(err)}`);
|
|
670715
|
+
return "";
|
|
670716
|
+
}
|
|
670717
|
+
},
|
|
670718
|
+
setVerbose: (_verbose) => {
|
|
670719
|
+
},
|
|
670720
|
+
setEndpoint: async (_url, _t, _k) => {
|
|
670721
|
+
renderError(`/endpoint is TUI-only — use the GUI endpoint picker instead.`);
|
|
670722
|
+
return "";
|
|
670723
|
+
},
|
|
670724
|
+
deactivateStatusBar: () => {
|
|
670725
|
+
},
|
|
670726
|
+
disableMouse: () => {
|
|
670727
|
+
},
|
|
670728
|
+
enableMouse: () => {
|
|
670729
|
+
},
|
|
670730
|
+
isMouseEnabled: () => false,
|
|
670731
|
+
lockFooter: () => {
|
|
670732
|
+
},
|
|
670733
|
+
unlockFooter: () => {
|
|
670734
|
+
},
|
|
670735
|
+
stopBanner: () => {
|
|
670736
|
+
},
|
|
670737
|
+
killEphemeral: () => {
|
|
670738
|
+
},
|
|
670739
|
+
setKeyPool: (_keys) => {
|
|
670740
|
+
},
|
|
670741
|
+
clearScreen: () => {
|
|
670742
|
+
},
|
|
670743
|
+
newSession: () => {
|
|
670744
|
+
},
|
|
670745
|
+
refreshBanner: () => {
|
|
670746
|
+
},
|
|
670747
|
+
exit: () => {
|
|
670748
|
+
renderError("/quit and /exit are TUI-only — close the browser tab to end the GUI session.");
|
|
670749
|
+
},
|
|
670750
|
+
voiceToggle: async () => {
|
|
670751
|
+
renderError(`voice ${TUI_ONLY_HINT} — use the GUI voice button instead.`);
|
|
670752
|
+
return "voice not available in GUI";
|
|
670753
|
+
},
|
|
670754
|
+
voiceSetModel: async (_id2) => {
|
|
670755
|
+
renderError(`voice model ${TUI_ONLY_HINT}`);
|
|
670756
|
+
return "";
|
|
670757
|
+
},
|
|
670758
|
+
getColors: () => colorsEnabled,
|
|
670759
|
+
setColors: (enabled2) => {
|
|
670760
|
+
colorsEnabled = enabled2;
|
|
670761
|
+
},
|
|
670762
|
+
getSelfModify: () => selfModifyEnabled,
|
|
670763
|
+
setSelfModify: (enabled2) => {
|
|
670764
|
+
selfModifyEnabled = enabled2;
|
|
670765
|
+
},
|
|
670766
|
+
saveSettings: (settings) => {
|
|
670767
|
+
saveProjectSettings(root, settings);
|
|
670768
|
+
saveGlobalSettings(settings);
|
|
670769
|
+
},
|
|
670770
|
+
saveLocalSettings: (settings) => {
|
|
670771
|
+
saveProjectSettings(root, settings);
|
|
670772
|
+
}
|
|
670773
|
+
};
|
|
670906
670774
|
}
|
|
670907
|
-
function
|
|
670908
|
-
|
|
670909
|
-
|
|
670910
|
-
|
|
670911
|
-
|
|
670912
|
-
|
|
670913
|
-
|
|
670914
|
-
|
|
670915
|
-
|
|
670775
|
+
function makeRejectingReadline() {
|
|
670776
|
+
const reject = () => {
|
|
670777
|
+
throw new Error(
|
|
670778
|
+
"interactive prompts are not supported via the GUI command bridge — run this command from the TUI (open a terminal and type `omnius`)."
|
|
670779
|
+
);
|
|
670780
|
+
};
|
|
670781
|
+
const noop2 = () => {
|
|
670782
|
+
};
|
|
670783
|
+
return {
|
|
670784
|
+
question: (_q, _cb) => reject(),
|
|
670785
|
+
close: noop2,
|
|
670786
|
+
write: noop2,
|
|
670787
|
+
on: () => noop2,
|
|
670788
|
+
once: () => noop2,
|
|
670789
|
+
off: () => noop2,
|
|
670790
|
+
removeListener: () => noop2,
|
|
670791
|
+
pause: noop2,
|
|
670792
|
+
resume: noop2,
|
|
670793
|
+
setPrompt: noop2,
|
|
670794
|
+
prompt: noop2,
|
|
670795
|
+
line: "",
|
|
670796
|
+
cursor: 0,
|
|
670797
|
+
terminal: false,
|
|
670798
|
+
input: { isTTY: false, on: noop2, once: noop2, removeListener: noop2, pause: noop2, resume: noop2 },
|
|
670799
|
+
output: { write: noop2, columns: 80, rows: 24, isTTY: false }
|
|
670800
|
+
};
|
|
670916
670801
|
}
|
|
670917
|
-
var
|
|
670918
|
-
|
|
670802
|
+
var _passthroughLock, TUI_ONLY_HINT;
|
|
670803
|
+
var init_command_passthrough = __esm({
|
|
670804
|
+
"packages/cli/src/api/command-passthrough.ts"() {
|
|
670919
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)";
|
|
670920
670813
|
}
|
|
670921
670814
|
});
|
|
670922
670815
|
|
|
670923
|
-
// packages/cli/src/api/
|
|
670924
|
-
|
|
670925
|
-
|
|
670926
|
-
|
|
670927
|
-
|
|
670928
|
-
|
|
670929
|
-
|
|
670930
|
-
|
|
670931
|
-
|
|
670932
|
-
|
|
670933
|
-
|
|
670934
|
-
|
|
670935
|
-
}
|
|
670936
|
-
|
|
670937
|
-
|
|
670938
|
-
|
|
670939
|
-
function rootSentinelPath(root) {
|
|
670940
|
-
return join149(projectDir(root), ".root");
|
|
670941
|
-
}
|
|
670942
|
-
function ensureDir(root) {
|
|
670943
|
-
const dir = projectDir(root);
|
|
670944
|
-
mkdirSync85(dir, { recursive: true });
|
|
670945
|
-
const sentinel = rootSentinelPath(root);
|
|
670946
|
-
try {
|
|
670947
|
-
if (!existsSync136(sentinel)) {
|
|
670948
|
-
writeFileSync75(sentinel, `${resolve60(root)}
|
|
670949
|
-
`, "utf8");
|
|
670950
|
-
}
|
|
670951
|
-
} catch {
|
|
670952
|
-
}
|
|
670953
|
-
}
|
|
670954
|
-
function readProjectPreferences(root) {
|
|
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() {
|
|
670955
670832
|
try {
|
|
670956
|
-
|
|
670957
|
-
|
|
670958
|
-
const raw = readFileSync113(file, "utf8");
|
|
670833
|
+
if (!existsSync135(PROJECTS_FILE)) return { projects: [], schemaVersion: 1 };
|
|
670834
|
+
const raw = readFileSync112(PROJECTS_FILE, "utf8");
|
|
670959
670835
|
const parsed = JSON.parse(raw);
|
|
670960
|
-
if (!parsed || parsed.
|
|
670961
|
-
return {
|
|
670836
|
+
if (!parsed || !Array.isArray(parsed.projects)) return { projects: [], schemaVersion: 1 };
|
|
670837
|
+
return { projects: parsed.projects, schemaVersion: 1 };
|
|
670962
670838
|
} catch {
|
|
670963
|
-
return {
|
|
670839
|
+
return { projects: [], schemaVersion: 1 };
|
|
670964
670840
|
}
|
|
670965
670841
|
}
|
|
670966
|
-
function
|
|
670967
|
-
|
|
670968
|
-
const
|
|
670969
|
-
|
|
670970
|
-
|
|
670971
|
-
|
|
670972
|
-
|
|
670973
|
-
|
|
670974
|
-
|
|
670975
|
-
const
|
|
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
|
-
}
|
|
670842
|
+
function writeAll(file) {
|
|
670843
|
+
mkdirSync84(OMNIUS_DIR3, { recursive: true });
|
|
670844
|
+
const tmp = `${PROJECTS_FILE}.${randomUUID18().slice(0, 8)}.tmp`;
|
|
670845
|
+
writeFileSync74(tmp, JSON.stringify(file, null, 2), "utf8");
|
|
670846
|
+
renameSync11(tmp, PROJECTS_FILE);
|
|
670847
|
+
}
|
|
670848
|
+
function listProjects() {
|
|
670849
|
+
const { projects } = readAll2();
|
|
670850
|
+
const alive = [];
|
|
670851
|
+
for (const p2 of projects) {
|
|
670985
670852
|
try {
|
|
670986
|
-
|
|
670853
|
+
if (statSync48(p2.root).isDirectory()) alive.push(p2);
|
|
670987
670854
|
} catch {
|
|
670988
670855
|
}
|
|
670989
|
-
throw err;
|
|
670990
|
-
}
|
|
670991
|
-
return merged;
|
|
670992
|
-
}
|
|
670993
|
-
function deleteProjectPreferences(root) {
|
|
670994
|
-
try {
|
|
670995
|
-
const file = prefsPath(root);
|
|
670996
|
-
if (!existsSync136(file)) return false;
|
|
670997
|
-
unlinkSync31(file);
|
|
670998
|
-
return true;
|
|
670999
|
-
} catch {
|
|
671000
|
-
return false;
|
|
671001
670856
|
}
|
|
670857
|
+
alive.sort((a2, b) => b.lastSeen - a2.lastSeen);
|
|
670858
|
+
return alive;
|
|
671002
670859
|
}
|
|
671003
|
-
|
|
671004
|
-
|
|
671005
|
-
|
|
671006
|
-
|
|
671007
|
-
|
|
671008
|
-
|
|
671009
|
-
|
|
671010
|
-
|
|
671011
|
-
|
|
671012
|
-
|
|
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")
|
|
671013
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);
|
|
671014
670884
|
}
|
|
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
|
-
}
|
|
671031
|
-
function wordCount(s2) {
|
|
671032
|
-
const words = s2.trim().match(/[\p{L}\p{N}][\p{L}\p{N}'’_-]*/gu);
|
|
671033
|
-
return words ? words.length : 0;
|
|
671034
|
-
}
|
|
671035
|
-
function repeatingCharPenalty(s2) {
|
|
671036
|
-
let maxRun = 1, cur = 1;
|
|
671037
|
-
for (let i2 = 1; i2 < s2.length; i2++) {
|
|
671038
|
-
if (s2[i2] === s2[i2 - 1]) cur++;
|
|
671039
|
-
else {
|
|
671040
|
-
if (cur > maxRun) maxRun = cur;
|
|
671041
|
-
cur = 1;
|
|
671042
|
-
}
|
|
671043
|
-
}
|
|
671044
|
-
if (cur > maxRun) maxRun = cur;
|
|
671045
|
-
return Math.min(1, Math.max(0, (maxRun - 3) / 10));
|
|
670885
|
+
writeAll(file);
|
|
670886
|
+
return entry;
|
|
671046
670887
|
}
|
|
671047
|
-
function
|
|
671048
|
-
const
|
|
671049
|
-
|
|
671050
|
-
|
|
671051
|
-
|
|
671052
|
-
|
|
671053
|
-
|
|
671054
|
-
|
|
671055
|
-
if (wc >= 6 && alpha >= 0.6) score = 0.85;
|
|
671056
|
-
else if (wc >= 3 && alpha >= 0.5) score = 0.7;
|
|
671057
|
-
else if (wc >= 2 && alpha >= 0.4) score = 0.5;
|
|
671058
|
-
else if (wc >= 1 && alpha >= 0.3 && len >= 4) score = 0.35;
|
|
671059
|
-
else score = 0.15;
|
|
671060
|
-
score -= repeatingCharPenalty(t2) * 0.4;
|
|
671061
|
-
if (typeof confidence2 === "number" && !Number.isNaN(confidence2)) {
|
|
671062
|
-
score = 0.7 * score + 0.3 * clamp0114(confidence2);
|
|
671063
|
-
}
|
|
671064
|
-
return clamp0114(score);
|
|
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;
|
|
671065
670896
|
}
|
|
671066
|
-
function
|
|
671067
|
-
|
|
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;
|
|
671068
670906
|
}
|
|
671069
|
-
function
|
|
671070
|
-
|
|
671071
|
-
for (const line of lines) {
|
|
671072
|
-
const t2 = line.trim();
|
|
671073
|
-
if (!t2.startsWith("{") || !t2.endsWith("}")) continue;
|
|
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
|
+
});
|
|
670947
|
+
|
|
670948
|
+
// packages/cli/src/tui/mouse-filter.ts
|
|
670949
|
+
var mouse_filter_exports = {};
|
|
670950
|
+
__export(mouse_filter_exports, {
|
|
670951
|
+
MouseFilterStream: () => MouseFilterStream
|
|
670952
|
+
});
|
|
670953
|
+
import { Transform } from "node:stream";
|
|
670954
|
+
var MouseFilterStream;
|
|
670955
|
+
var init_mouse_filter = __esm({
|
|
670956
|
+
"packages/cli/src/tui/mouse-filter.ts"() {
|
|
670957
|
+
"use strict";
|
|
670958
|
+
MouseFilterStream = class extends Transform {
|
|
670959
|
+
buffer = "";
|
|
670960
|
+
onScroll = null;
|
|
670961
|
+
onActivity = null;
|
|
670962
|
+
onPointer = null;
|
|
670963
|
+
onKeyboard = null;
|
|
670964
|
+
flushTimer = null;
|
|
670965
|
+
expectPrefixlessMouseUntil = 0;
|
|
670966
|
+
constructor(scrollHandler, activityHandler, pointerHandler, keyboardHandler) {
|
|
670967
|
+
super();
|
|
670968
|
+
this.onScroll = scrollHandler;
|
|
670969
|
+
this.onActivity = activityHandler ?? null;
|
|
670970
|
+
this.onPointer = pointerHandler ?? null;
|
|
670971
|
+
this.onKeyboard = keyboardHandler ?? null;
|
|
670972
|
+
}
|
|
670973
|
+
_transform(chunk, _encoding, callback) {
|
|
670974
|
+
this.buffer += chunk.toString();
|
|
670975
|
+
this.processBuffer(callback);
|
|
670976
|
+
}
|
|
670977
|
+
processBuffer(callback) {
|
|
670978
|
+
let output = "";
|
|
670979
|
+
let i2 = 0;
|
|
670980
|
+
while (i2 < this.buffer.length) {
|
|
670981
|
+
const remaining = this.buffer.slice(i2);
|
|
670982
|
+
const prefixlessMouse = this.matchPrefixlessSgrMouse(remaining);
|
|
670983
|
+
if (prefixlessMouse) {
|
|
670984
|
+
this.handleSgrMouse(prefixlessMouse);
|
|
670985
|
+
i2 += prefixlessMouse.raw.length;
|
|
670986
|
+
continue;
|
|
670987
|
+
}
|
|
670988
|
+
if (this.looksLikePartialPrefixlessSgrMouse(remaining)) {
|
|
670989
|
+
break;
|
|
670990
|
+
}
|
|
670991
|
+
if (this.buffer[i2] === "\x1B") {
|
|
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;
|
|
671009
|
+
}
|
|
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
|
+
}
|
|
671021
|
+
}
|
|
671022
|
+
output += this.buffer[i2];
|
|
671023
|
+
i2++;
|
|
671024
|
+
}
|
|
671025
|
+
this.buffer = this.buffer.slice(i2);
|
|
671026
|
+
if (output.length > 0) {
|
|
671027
|
+
if (this.onKeyboard) this.onKeyboard();
|
|
671028
|
+
this.push(output);
|
|
671029
|
+
}
|
|
671030
|
+
if (this.buffer.length > 0) {
|
|
671031
|
+
if (this.flushTimer) clearTimeout(this.flushTimer);
|
|
671032
|
+
this.flushTimer = setTimeout(() => {
|
|
671033
|
+
if (this.buffer.length > 0) {
|
|
671034
|
+
if (this.buffer.startsWith("\x1B[<") || this.buffer.startsWith("\x1B[M")) {
|
|
671035
|
+
this.expectPrefixlessMouseUntil = Date.now() + 1e3;
|
|
671036
|
+
this.buffer = "";
|
|
671037
|
+
} else if (this.buffer === "\x1B[") {
|
|
671038
|
+
this.expectPrefixlessMouseUntil = Date.now() + 1e3;
|
|
671039
|
+
this.buffer = "";
|
|
671040
|
+
} else if (this.looksLikePartialPrefixlessSgrMouse(this.buffer)) {
|
|
671041
|
+
this.buffer = "";
|
|
671042
|
+
} else if (this.buffer === "\x1B") {
|
|
671043
|
+
this.push(this.buffer);
|
|
671044
|
+
this.buffer = "";
|
|
671045
|
+
} else {
|
|
671046
|
+
this.push(this.buffer);
|
|
671047
|
+
this.buffer = "";
|
|
671048
|
+
}
|
|
671049
|
+
}
|
|
671050
|
+
}, 50);
|
|
671051
|
+
}
|
|
671052
|
+
callback();
|
|
671053
|
+
}
|
|
671054
|
+
_flush(callback) {
|
|
671055
|
+
if (this.flushTimer) {
|
|
671056
|
+
clearTimeout(this.flushTimer);
|
|
671057
|
+
this.flushTimer = null;
|
|
671058
|
+
}
|
|
671059
|
+
if (this.buffer.length > 0) {
|
|
671060
|
+
if (this.buffer.startsWith("\x1B[<") || this.buffer.startsWith("\x1B[M") || this.buffer === "\x1B[" || this.looksLikePartialPrefixlessSgrMouse(this.buffer)) {
|
|
671061
|
+
this.buffer = "";
|
|
671062
|
+
callback();
|
|
671063
|
+
return;
|
|
671064
|
+
}
|
|
671065
|
+
this.push(this.buffer);
|
|
671066
|
+
this.buffer = "";
|
|
671067
|
+
}
|
|
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;
|
|
671091
|
+
}
|
|
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
|
+
}
|
|
671118
|
+
}
|
|
671119
|
+
if (this.onActivity) this.onActivity();
|
|
671120
|
+
}
|
|
671121
|
+
};
|
|
671122
|
+
}
|
|
671123
|
+
});
|
|
671125
671124
|
|
|
671126
|
-
|
|
671127
|
-
|
|
671128
|
-
|
|
671129
|
-
|
|
671130
|
-
|
|
671131
|
-
|
|
671132
|
-
|
|
671133
|
-
|
|
671134
|
-
|
|
671135
|
-
|
|
671136
|
-
|
|
671137
|
-
|
|
671138
|
-
|
|
671139
|
-
|
|
671140
|
-
|
|
671141
|
-
|
|
671142
|
-
|
|
671143
|
-
|
|
671144
|
-
|
|
671145
|
-
|
|
671146
|
-
|
|
671147
|
-
|
|
671148
|
-
|
|
671149
|
-
|
|
671150
|
-
|
|
671151
|
-
|
|
671152
|
-
|
|
671153
|
-
|
|
671154
|
-
|
|
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) {
|
|
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) {
|
|
671172
671154
|
super();
|
|
671173
|
-
this.
|
|
671174
|
-
this.
|
|
671175
|
-
this.
|
|
671176
|
-
this.
|
|
671177
|
-
|
|
671178
|
-
|
|
671179
|
-
|
|
671180
|
-
this.verbose = Boolean(opts.verbose);
|
|
671181
|
-
this.debugSnr = Boolean(opts.debugSnr);
|
|
671182
|
-
this.heuristicsEnabled = opts.heuristicsEnabled !== false;
|
|
671183
|
-
if (typeof opts.vadSilenceMs === "number" && opts.vadSilenceMs > 0) {
|
|
671184
|
-
this._vadSilenceMs = Math.floor(opts.vadSilenceMs);
|
|
671185
|
-
}
|
|
671186
|
-
this.onStatus = opts.onStatus ?? (() => {
|
|
671187
|
-
});
|
|
671188
|
-
this.onUserSpeech = opts.onUserSpeech ?? (() => {
|
|
671189
|
-
});
|
|
671190
|
-
this.onPartialTranscript = opts.onPartialTranscript ?? (() => {
|
|
671191
|
-
});
|
|
671192
|
-
this.onAgentSpeech = opts.onAgentSpeech ?? (() => {
|
|
671155
|
+
this._input = input;
|
|
671156
|
+
this._history = [...options2?.history ?? []];
|
|
671157
|
+
this._historySize = options2?.historySize ?? 500;
|
|
671158
|
+
this._completer = options2?.completer ?? null;
|
|
671159
|
+
input.on("data", (chunk) => {
|
|
671160
|
+
if (this._paused || this._closed) return;
|
|
671161
|
+
this.feed(chunk.toString("utf8"));
|
|
671193
671162
|
});
|
|
671194
|
-
|
|
671163
|
+
input.on("end", () => {
|
|
671164
|
+
if (!this._closed) this.close();
|
|
671195
671165
|
});
|
|
671196
671166
|
}
|
|
671197
|
-
|
|
671198
|
-
|
|
671167
|
+
/** Process raw input data — parse escape sequences and printable chars */
|
|
671168
|
+
feed(data) {
|
|
671169
|
+
const beforeLine = this.line;
|
|
671170
|
+
const beforeCursor = this.cursor;
|
|
671171
|
+
this._buffer += data;
|
|
671172
|
+
this._processBuffer();
|
|
671173
|
+
this._emitChangeIfNeeded(beforeLine, beforeCursor);
|
|
671199
671174
|
}
|
|
671200
|
-
|
|
671201
|
-
|
|
671175
|
+
/** Pause input processing (for overlay transitions) */
|
|
671176
|
+
pause() {
|
|
671177
|
+
this._paused = true;
|
|
671202
671178
|
}
|
|
671203
|
-
|
|
671204
|
-
|
|
671205
|
-
|
|
671206
|
-
setState(next) {
|
|
671207
|
-
if (this._state === next) return;
|
|
671208
|
-
const prev = this._state;
|
|
671209
|
-
this._state = next;
|
|
671210
|
-
this.onStateChange(next);
|
|
671211
|
-
this.emit("stateChange", { from: prev, to: next });
|
|
671179
|
+
/** Resume input processing */
|
|
671180
|
+
resume() {
|
|
671181
|
+
this._paused = false;
|
|
671212
671182
|
}
|
|
671213
|
-
|
|
671214
|
-
|
|
671215
|
-
|
|
671216
|
-
|
|
671217
|
-
if (this.
|
|
671218
|
-
|
|
671219
|
-
this.
|
|
671220
|
-
await this.voice.toggle();
|
|
671221
|
-
}
|
|
671222
|
-
this.active = true;
|
|
671223
|
-
this.context = [{ role: "system", content: SYSTEM_PROMPT2 }];
|
|
671224
|
-
if (this.toolRelay) {
|
|
671225
|
-
this.toolCatalogNote = `Available tools (emit one-line JSON: {"tool":string,"args":object}):
|
|
671226
|
-
- voice_env{} → environment facts (cwd, os, cpu, mem)
|
|
671227
|
-
- voice_status{} → main-agent status
|
|
671228
|
-
- voice_list_files{dir?: string='.'} → list directory (bounded)
|
|
671229
|
-
- voice_read_file{path: string, max?: number=2048} → read file snippet
|
|
671230
|
-
- voice_to_main{message: string, start?: boolean=true} → relay/start task`;
|
|
671231
|
-
this.context.push({ role: "system", content: this.toolCatalogNote });
|
|
671232
|
-
}
|
|
671233
|
-
this.turnCount = 0;
|
|
671234
|
-
if (this.verbose) this.onStatus("VoiceChat active — LISTENING");
|
|
671235
|
-
this._onTranscript = (...args) => {
|
|
671236
|
-
let text2;
|
|
671237
|
-
let isFinal;
|
|
671238
|
-
let snr;
|
|
671239
|
-
let confidence2;
|
|
671240
|
-
if (typeof args[0] === "object" && args[0] !== null) {
|
|
671241
|
-
const evt = args[0];
|
|
671242
|
-
text2 = evt.text ?? "";
|
|
671243
|
-
isFinal = evt.isFinal ?? false;
|
|
671244
|
-
snr = evt.snr;
|
|
671245
|
-
confidence2 = evt.confidence;
|
|
671246
|
-
} else {
|
|
671247
|
-
text2 = String(args[0] ?? "");
|
|
671248
|
-
isFinal = Boolean(args[1]);
|
|
671249
|
-
}
|
|
671250
|
-
if (!text2.trim()) return;
|
|
671251
|
-
this.handleTranscript(text2.trim(), isFinal, snr, confidence2);
|
|
671252
|
-
};
|
|
671253
|
-
this._onError = (err) => {
|
|
671254
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
671255
|
-
this.onStatus(`ASR error (voicechat continues without mic): ${msg.slice(0, 80)}`);
|
|
671256
|
-
if (this.active && !this._retryMicTimer) {
|
|
671257
|
-
this._retryMicTimer = setTimeout(async () => {
|
|
671258
|
-
this._retryMicTimer = null;
|
|
671259
|
-
if (!this.active) return;
|
|
671260
|
-
try {
|
|
671261
|
-
await this.listen.stop().catch(() => {
|
|
671262
|
-
});
|
|
671263
|
-
await this.listen.start();
|
|
671264
|
-
if (this.verbose) this.onStatus("Mic auto-recovered — LISTENING");
|
|
671265
|
-
} catch {
|
|
671266
|
-
}
|
|
671267
|
-
}, 1e3);
|
|
671268
|
-
}
|
|
671269
|
-
};
|
|
671270
|
-
this.listen.on("transcript", this._onTranscript);
|
|
671271
|
-
this.listen.on("error", this._onError);
|
|
671272
|
-
try {
|
|
671273
|
-
await this.listen.start();
|
|
671274
|
-
this.setState("LISTENING");
|
|
671275
|
-
if (this.verbose) this.onStatus("Mic active — LISTENING for speech...");
|
|
671276
|
-
} catch (err) {
|
|
671277
|
-
this.onStatus(`Mic failed: ${err instanceof Error ? err.message : String(err)}. VoiceChat active without mic.`);
|
|
671278
|
-
this.setState("LISTENING");
|
|
671183
|
+
/** Close the input handler */
|
|
671184
|
+
close() {
|
|
671185
|
+
if (this._closed) return;
|
|
671186
|
+
this._closed = true;
|
|
671187
|
+
if (this._flushTimer) {
|
|
671188
|
+
clearTimeout(this._flushTimer);
|
|
671189
|
+
this._flushTimer = null;
|
|
671279
671190
|
}
|
|
671191
|
+
this.emit("close");
|
|
671280
671192
|
}
|
|
671281
|
-
|
|
671282
|
-
|
|
671283
|
-
|
|
671284
|
-
|
|
671285
|
-
|
|
671286
|
-
|
|
671287
|
-
|
|
671288
|
-
|
|
671289
|
-
|
|
671290
|
-
|
|
671291
|
-
|
|
671292
|
-
|
|
671293
|
-
|
|
671294
|
-
|
|
671295
|
-
|
|
671296
|
-
|
|
671297
|
-
|
|
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;
|
|
671298
671217
|
}
|
|
671299
|
-
if (this.
|
|
671300
|
-
this.
|
|
671301
|
-
this.
|
|
671218
|
+
if (this._historyIndex < this._history.length - 1) {
|
|
671219
|
+
this._historyIndex++;
|
|
671220
|
+
this.line = this._history[this._historyIndex];
|
|
671221
|
+
this.cursor = this.line.length;
|
|
671302
671222
|
}
|
|
671303
|
-
|
|
671304
|
-
|
|
671305
|
-
|
|
671223
|
+
}
|
|
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];
|
|
671306
671232
|
}
|
|
671307
|
-
|
|
671308
|
-
|
|
671309
|
-
|
|
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
|
+
}
|
|
671310
671251
|
}
|
|
671311
|
-
|
|
671312
|
-
|
|
671313
|
-
this.
|
|
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;
|
|
671314
671259
|
}
|
|
671315
|
-
|
|
671316
|
-
|
|
671317
|
-
|
|
671318
|
-
|
|
671319
|
-
|
|
671320
|
-
if (this.
|
|
671321
|
-
|
|
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
|
+
}
|
|
671322
671276
|
}
|
|
671323
|
-
if (
|
|
671324
|
-
|
|
671325
|
-
|
|
671326
|
-
|
|
671327
|
-
|
|
671328
|
-
|
|
671329
|
-
|
|
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;
|
|
671330
671306
|
}
|
|
671331
|
-
|
|
671332
|
-
|
|
671333
|
-
|
|
671334
|
-
|
|
671335
|
-
|
|
671336
|
-
|
|
671337
|
-
|
|
671338
|
-
|
|
671339
|
-
this.silenceTimer = setTimeout(() => {
|
|
671340
|
-
if (this._state === "CAPTURING") {
|
|
671341
|
-
this.finalizeSegment();
|
|
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;
|
|
671342
671315
|
}
|
|
671343
|
-
}
|
|
671344
|
-
|
|
671345
|
-
|
|
671346
|
-
|
|
671347
|
-
// ---------------------------------------------------------------------------
|
|
671348
|
-
finalizeSegment() {
|
|
671349
|
-
const text2 = this.captureBuffer.trim();
|
|
671350
|
-
if (this.silenceTimer) {
|
|
671351
|
-
clearTimeout(this.silenceTimer);
|
|
671352
|
-
this.silenceTimer = null;
|
|
671353
|
-
}
|
|
671354
|
-
if (this.maxSegmentTimer) {
|
|
671355
|
-
clearTimeout(this.maxSegmentTimer);
|
|
671356
|
-
this.maxSegmentTimer = null;
|
|
671357
|
-
}
|
|
671358
|
-
this.captureBuffer = "";
|
|
671359
|
-
if (!text2) {
|
|
671360
|
-
this.setState("LISTENING");
|
|
671361
|
-
return;
|
|
671362
|
-
}
|
|
671363
|
-
const score = this.lastSignalScore ?? computeSignalFromText(text2);
|
|
671364
|
-
if (score < MIN_SIGNAL_SCORE || NOISE_ONLY_RE.test(text2)) {
|
|
671365
|
-
if (this.debugSnr) this.onStatus(`Ignoring low-signal utterance (SNR:${score.toFixed(2)}): ${truncateForLog(text2, 48)}`);
|
|
671366
|
-
this.emit("snrFiltered", { score, text: text2 });
|
|
671367
|
-
this.setState("LISTENING");
|
|
671368
|
-
this.captureBuffer = "";
|
|
671369
|
-
this.lastSignalScore = null;
|
|
671370
|
-
return;
|
|
671316
|
+
};
|
|
671317
|
+
if (this.line.length === 0) {
|
|
671318
|
+
pushWrappedSegment("", 0);
|
|
671319
|
+
return { charPositions, rawLines };
|
|
671371
671320
|
}
|
|
671372
|
-
|
|
671373
|
-
this.
|
|
671374
|
-
|
|
671375
|
-
|
|
671376
|
-
|
|
671377
|
-
|
|
671378
|
-
|
|
671379
|
-
|
|
671380
|
-
|
|
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;
|
|
671381
671331
|
}
|
|
671382
671332
|
}
|
|
671383
|
-
|
|
671384
|
-
this.context.splice(1, 1);
|
|
671385
|
-
}
|
|
671386
|
-
this.think();
|
|
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;
|
|
671358
|
+
}
|
|
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;
|
|
671403
671394
|
}
|
|
671404
|
-
|
|
671395
|
+
break;
|
|
671405
671396
|
}
|
|
671406
|
-
|
|
671407
|
-
|
|
671408
|
-
|
|
671409
|
-
|
|
671410
|
-
|
|
671411
|
-
|
|
671412
|
-
|
|
671413
|
-
|
|
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;
|
|
671438
|
-
}
|
|
671439
|
-
} catch {
|
|
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;
|
|
671440
671405
|
}
|
|
671406
|
+
i2++;
|
|
671407
|
+
continue;
|
|
671441
671408
|
}
|
|
671442
|
-
|
|
671443
|
-
|
|
671444
|
-
|
|
671445
|
-
|
|
671446
|
-
|
|
671447
|
-
|
|
671448
|
-
|
|
671449
|
-
|
|
671450
|
-
|
|
671451
|
-
|
|
671452
|
-
} catch (e2) {
|
|
671453
|
-
toolOutput = `Tool ${name10} failed: ${e2 instanceof Error ? e2.message : String(e2)}`;
|
|
671409
|
+
if (code8 < 32 || code8 === 127) {
|
|
671410
|
+
if (code8 === 13) {
|
|
671411
|
+
if (this._preSubmit?.()) {
|
|
671412
|
+
i2++;
|
|
671413
|
+
continue;
|
|
671414
|
+
}
|
|
671415
|
+
this._submit();
|
|
671416
|
+
i2++;
|
|
671417
|
+
if (this._buffer[i2] === "\n") i2++;
|
|
671418
|
+
continue;
|
|
671454
671419
|
}
|
|
671455
|
-
if (
|
|
671456
|
-
|
|
671457
|
-
|
|
671420
|
+
if (code8 === 10) {
|
|
671421
|
+
this._insertText("\n");
|
|
671422
|
+
i2++;
|
|
671423
|
+
continue;
|
|
671458
671424
|
}
|
|
671459
|
-
this.
|
|
671460
|
-
|
|
671425
|
+
this._handleControl(code8);
|
|
671426
|
+
i2++;
|
|
671427
|
+
continue;
|
|
671461
671428
|
}
|
|
671462
|
-
|
|
671463
|
-
|
|
671464
|
-
|
|
671465
|
-
|
|
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;
|
|
671466
671464
|
}
|
|
671467
|
-
|
|
671468
|
-
|
|
671469
|
-
|
|
671470
|
-
|
|
671471
|
-
|
|
671472
|
-
|
|
671473
|
-
|
|
671474
|
-
|
|
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;
|
|
671475
671507
|
}
|
|
671476
|
-
this.
|
|
671477
|
-
|
|
671478
|
-
|
|
671479
|
-
if (
|
|
671480
|
-
this.
|
|
671508
|
+
this.emit("up");
|
|
671509
|
+
return;
|
|
671510
|
+
case "B":
|
|
671511
|
+
if (params === "1;2") {
|
|
671512
|
+
this.emit("shiftdown");
|
|
671513
|
+
return;
|
|
671481
671514
|
}
|
|
671482
|
-
|
|
671483
|
-
|
|
671484
|
-
|
|
671485
|
-
|
|
671515
|
+
this.emit("down");
|
|
671516
|
+
return;
|
|
671517
|
+
case "C":
|
|
671518
|
+
if (params === "1;5") {
|
|
671519
|
+
this.emit("ctrl-right");
|
|
671520
|
+
return;
|
|
671521
|
+
}
|
|
671522
|
+
if (this.cursor < this.line.length) this.cursor++;
|
|
671523
|
+
return;
|
|
671524
|
+
case "D":
|
|
671525
|
+
if (params === "1;5") {
|
|
671526
|
+
this.emit("ctrl-left");
|
|
671527
|
+
return;
|
|
671528
|
+
}
|
|
671529
|
+
if (this.cursor > 0) this.cursor--;
|
|
671530
|
+
return;
|
|
671531
|
+
case "H":
|
|
671532
|
+
this.cursor = 0;
|
|
671533
|
+
return;
|
|
671534
|
+
case "F":
|
|
671535
|
+
this.cursor = this.line.length;
|
|
671536
|
+
return;
|
|
671537
|
+
case "~":
|
|
671538
|
+
if (this._isShiftEnterCSI(params)) {
|
|
671539
|
+
this._insertText("\n");
|
|
671540
|
+
return;
|
|
671541
|
+
}
|
|
671542
|
+
if (params === "3") {
|
|
671543
|
+
if (this.cursor < this.line.length) {
|
|
671544
|
+
this.line = this.line.slice(0, this.cursor) + this.line.slice(this.cursor + 1);
|
|
671486
671545
|
}
|
|
671487
|
-
|
|
671488
|
-
const estimatedMs = Math.max(1500, response.length / 5 * (6e4 / 150));
|
|
671489
|
-
await new Promise((r2) => setTimeout(r2, estimatedMs));
|
|
671546
|
+
return;
|
|
671490
671547
|
}
|
|
671548
|
+
if (params === "5") {
|
|
671549
|
+
this.emit("pageup");
|
|
671550
|
+
return;
|
|
671551
|
+
}
|
|
671552
|
+
if (params === "6") {
|
|
671553
|
+
this.emit("pagedown");
|
|
671554
|
+
return;
|
|
671555
|
+
}
|
|
671556
|
+
return;
|
|
671557
|
+
case "u": {
|
|
671558
|
+
const parts = params.split(";");
|
|
671559
|
+
const codepoint = parseInt(parts[0] ?? "0");
|
|
671560
|
+
const modifiers = parseInt(parts[1] ?? "1");
|
|
671561
|
+
const hasCtrl = modifiers - 1 & 4;
|
|
671562
|
+
const hasShift = modifiers - 1 & 1;
|
|
671563
|
+
if (hasShift && (codepoint === 10 || codepoint === 13)) {
|
|
671564
|
+
this._insertText("\n");
|
|
671565
|
+
return;
|
|
671566
|
+
}
|
|
671567
|
+
if (hasCtrl && hasShift) {
|
|
671568
|
+
if (codepoint === 67) {
|
|
671569
|
+
this.emit("ctrl-shift-c");
|
|
671570
|
+
return;
|
|
671571
|
+
}
|
|
671572
|
+
if (codepoint === 66) {
|
|
671573
|
+
this.emit("ctrl-shift-b");
|
|
671574
|
+
return;
|
|
671575
|
+
}
|
|
671576
|
+
if (codepoint === 86) {
|
|
671577
|
+
this.emit("ctrl-shift-v");
|
|
671578
|
+
return;
|
|
671579
|
+
}
|
|
671580
|
+
}
|
|
671581
|
+
if (hasCtrl && !hasShift && (codepoint === 73 || codepoint === 105)) {
|
|
671582
|
+
this.emit("ctrl-i");
|
|
671583
|
+
return;
|
|
671584
|
+
}
|
|
671585
|
+
return;
|
|
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
|
-
|
|
671786
|
-
|
|
671787
|
-
|
|
671788
|
-
try {
|
|
671789
|
-
const result = await voice.synthesizeToPCM(text2);
|
|
671790
|
-
if (!result || !result.pcm || result.pcm.length === 0) return;
|
|
671791
|
-
getVoiceBus().emit("agent_text", { text: text2 });
|
|
671792
|
-
getVoiceBus().emit("tts_pcm", result.pcm, result.sampleRate);
|
|
671793
|
-
} catch (err) {
|
|
671794
|
-
const m2 = err instanceof Error ? err.message : String(err);
|
|
671795
|
-
getVoiceBus().emit("error", `tts: ${m2}`);
|
|
671796
|
-
} finally {
|
|
671797
|
-
setSpeaking(false);
|
|
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";
|
|
671798
671811
|
}
|
|
671799
671812
|
}
|
|
671800
|
-
|
|
671801
|
-
|
|
671802
|
-
|
|
671803
|
-
async function startVoiceChat(opts) {
|
|
671804
|
-
if (_voiceChatSession?.isActive) {
|
|
671805
|
-
return { ok: true, message: "VoiceChat already running" };
|
|
671813
|
+
var init_access_policy = __esm({
|
|
671814
|
+
"packages/cli/src/api/access-policy.ts"() {
|
|
671815
|
+
"use strict";
|
|
671806
671816
|
}
|
|
671807
|
-
|
|
671808
|
-
|
|
671809
|
-
|
|
671810
|
-
|
|
671811
|
-
|
|
671812
|
-
|
|
671813
|
-
|
|
671814
|
-
|
|
671815
|
-
|
|
671816
|
-
|
|
671817
|
-
|
|
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" };
|
|
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);
|
|
671827
671828
|
}
|
|
671828
|
-
|
|
671829
|
-
|
|
671829
|
+
function projectDir(root) {
|
|
671830
|
+
return join149(PROJECTS_DIR, projectKey(root));
|
|
671831
|
+
}
|
|
671832
|
+
function prefsPath(root) {
|
|
671833
|
+
return join149(projectDir(root), "preferences.json");
|
|
671834
|
+
}
|
|
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);
|
|
@@ -692364,6 +692421,197 @@ data: ${JSON.stringify(data)}
|
|
|
692364
692421
|
jsonResponse(res, ok3 ? 200 : 500, ok3 ? { unit, action, status: "ok" } : { error: "action failed", unit, action });
|
|
692365
692422
|
return;
|
|
692366
692423
|
}
|
|
692424
|
+
if (pathname === "/v1/theme" && method === "GET") {
|
|
692425
|
+
const config = loadConfig();
|
|
692426
|
+
jsonResponse(res, 200, { theme: config.theme ?? "system", fontScale: config.fontScale ?? 1 });
|
|
692427
|
+
return;
|
|
692428
|
+
}
|
|
692429
|
+
if (pathname === "/v1/theme" && method === "POST") {
|
|
692430
|
+
const body = await parseJsonBody(req3);
|
|
692431
|
+
const config = loadConfig();
|
|
692432
|
+
if (body?.theme) config.theme = body.theme;
|
|
692433
|
+
if (typeof body?.fontScale === "number") config.fontScale = body.fontScale;
|
|
692434
|
+
try {
|
|
692435
|
+
require4("./config").saveConfig?.(config);
|
|
692436
|
+
} catch {
|
|
692437
|
+
}
|
|
692438
|
+
jsonResponse(res, 200, { ok: true, theme: config.theme, fontScale: config.fontScale });
|
|
692439
|
+
return;
|
|
692440
|
+
}
|
|
692441
|
+
if (pathname === "/v1/tor/status" && method === "GET") {
|
|
692442
|
+
const config = loadConfig();
|
|
692443
|
+
jsonResponse(res, 200, { enabled: config.torEnabled ?? false });
|
|
692444
|
+
return;
|
|
692445
|
+
}
|
|
692446
|
+
if (pathname === "/v1/tor/enable" && method === "POST") {
|
|
692447
|
+
const body = await parseJsonBody(req3);
|
|
692448
|
+
const config = loadConfig();
|
|
692449
|
+
config.torEnabled = Boolean(body?.enabled);
|
|
692450
|
+
try {
|
|
692451
|
+
require4("./config").saveConfig?.(config);
|
|
692452
|
+
} catch {
|
|
692453
|
+
}
|
|
692454
|
+
jsonResponse(res, 200, { ok: true, enabled: config.torEnabled });
|
|
692455
|
+
return;
|
|
692456
|
+
}
|
|
692457
|
+
if (pathname === "/v1/update" && method === "POST") {
|
|
692458
|
+
const config = loadConfig();
|
|
692459
|
+
config.updating = true;
|
|
692460
|
+
try {
|
|
692461
|
+
require4("./config").saveConfig?.(config);
|
|
692462
|
+
} catch {
|
|
692463
|
+
}
|
|
692464
|
+
setImmediate(() => {
|
|
692465
|
+
try {
|
|
692466
|
+
const { execSync: execSync62 } = require4("node:child_process");
|
|
692467
|
+
execSync62("npm update -g omnius 2>/dev/null || true", { stdio: "pipe" });
|
|
692468
|
+
} catch {
|
|
692469
|
+
}
|
|
692470
|
+
});
|
|
692471
|
+
jsonResponse(res, 200, { ok: true, message: "Update initiated" });
|
|
692472
|
+
return;
|
|
692473
|
+
}
|
|
692474
|
+
if (pathname === "/v1/share/generate" && method === "POST") {
|
|
692475
|
+
const body = await parseJsonBody(req3);
|
|
692476
|
+
const config = loadConfig();
|
|
692477
|
+
const shareDir = config.shareDir || `${require4("node:os").homedir()}/.omnius/share`;
|
|
692478
|
+
const fs11 = require4("node:fs");
|
|
692479
|
+
const path12 = require4("node:path");
|
|
692480
|
+
try {
|
|
692481
|
+
fs11.mkdirSync(shareDir, { recursive: true });
|
|
692482
|
+
const filename = `${Date.now()}-${(body?.title || "share").replace(/[^a-z0-9]/gi, "_")}.md`;
|
|
692483
|
+
const filepath = path12.join(shareDir, filename);
|
|
692484
|
+
fs11.writeFileSync(filepath, body?.content || "");
|
|
692485
|
+
jsonResponse(res, 200, { ok: true, path: filepath, filename });
|
|
692486
|
+
} catch (err) {
|
|
692487
|
+
jsonResponse(res, 500, { error: "share_failed", message: err instanceof Error ? err.message : String(err) });
|
|
692488
|
+
}
|
|
692489
|
+
return;
|
|
692490
|
+
}
|
|
692491
|
+
if (pathname === "/v1/config/endpoint/history" && method === "GET") {
|
|
692492
|
+
const config = loadConfig();
|
|
692493
|
+
const history = config.endpointHistory || [];
|
|
692494
|
+
jsonResponse(res, 200, { history });
|
|
692495
|
+
return;
|
|
692496
|
+
}
|
|
692497
|
+
if (pathname === "/v1/voice/speak" && method === "POST") {
|
|
692498
|
+
const body = await parseJsonBody(req3);
|
|
692499
|
+
const text2 = body?.text || "";
|
|
692500
|
+
if (!text2) {
|
|
692501
|
+
jsonResponse(res, 400, { error: "missing_text" });
|
|
692502
|
+
return;
|
|
692503
|
+
}
|
|
692504
|
+
try {
|
|
692505
|
+
const { execSync: execSync62 } = require4("node:child_process");
|
|
692506
|
+
let audioPath = null;
|
|
692507
|
+
const ttsCmds = [
|
|
692508
|
+
`espeak "${text2}" -w /tmp/tts_${Date.now()}.wav 2>/dev/null`,
|
|
692509
|
+
`pico2wave -w /tmp/tts_${Date.now()}.wav "text:${text2}" 2>/dev/null`,
|
|
692510
|
+
`say "${text2}" 2>/dev/null`
|
|
692511
|
+
];
|
|
692512
|
+
for (const cmd of ttsCmds) {
|
|
692513
|
+
try {
|
|
692514
|
+
execSync62(cmd, { stdio: "pipe" });
|
|
692515
|
+
audioPath = "/tmp/tts_" + Date.now() + ".wav";
|
|
692516
|
+
break;
|
|
692517
|
+
} catch {
|
|
692518
|
+
continue;
|
|
692519
|
+
}
|
|
692520
|
+
}
|
|
692521
|
+
if (audioPath) {
|
|
692522
|
+
const fs11 = require4("node:fs");
|
|
692523
|
+
const data = fs11.readFileSync(audioPath).toString("base64");
|
|
692524
|
+
fs11.unlinkSync(audioPath);
|
|
692525
|
+
jsonResponse(res, 200, { ok: true, audio: data, format: "wav" });
|
|
692526
|
+
} else {
|
|
692527
|
+
jsonResponse(res, 500, { error: "tts_unavailable", message: "No TTS backend found" });
|
|
692528
|
+
}
|
|
692529
|
+
} catch (err) {
|
|
692530
|
+
jsonResponse(res, 500, { error: "tts_failed", message: err instanceof Error ? err.message : String(err) });
|
|
692531
|
+
}
|
|
692532
|
+
return;
|
|
692533
|
+
}
|
|
692534
|
+
if (pathname === "/v1/voice/supertonic-settings" && method === "GET") {
|
|
692535
|
+
const config = loadConfig();
|
|
692536
|
+
jsonResponse(res, 200, { settings: config.supertonicSettings || {} });
|
|
692537
|
+
return;
|
|
692538
|
+
}
|
|
692539
|
+
if (pathname === "/v1/voice/supertonic-settings" && method === "POST") {
|
|
692540
|
+
const body = await parseJsonBody(req3);
|
|
692541
|
+
const config = loadConfig();
|
|
692542
|
+
config.supertonicSettings = { ...config.supertonicSettings, ...body };
|
|
692543
|
+
try {
|
|
692544
|
+
require4("./config").saveConfig?.(config);
|
|
692545
|
+
} catch {
|
|
692546
|
+
}
|
|
692547
|
+
jsonResponse(res, 200, { ok: true, settings: config.supertonicSettings });
|
|
692548
|
+
return;
|
|
692549
|
+
}
|
|
692550
|
+
if (pathname === "/v1/voice/clone-refs" && method === "GET") {
|
|
692551
|
+
const config = loadConfig();
|
|
692552
|
+
const refs = config.voiceCloneRefs || [];
|
|
692553
|
+
jsonResponse(res, 200, { refs });
|
|
692554
|
+
return;
|
|
692555
|
+
}
|
|
692556
|
+
if (pathname === "/v1/voice/clone-refs" && method === "POST") {
|
|
692557
|
+
const body = await parseJsonBody(req3);
|
|
692558
|
+
const config = loadConfig();
|
|
692559
|
+
const refs = config.voiceCloneRefs || [];
|
|
692560
|
+
const newRef = { filename: body?.filename || body?.path || "", path: body?.path || body?.filename || "", active: false };
|
|
692561
|
+
refs.push(newRef);
|
|
692562
|
+
config.voiceCloneRefs = refs;
|
|
692563
|
+
try {
|
|
692564
|
+
require4("./config").saveConfig?.(config);
|
|
692565
|
+
} catch {
|
|
692566
|
+
}
|
|
692567
|
+
jsonResponse(res, 200, { ok: true, ref: newRef });
|
|
692568
|
+
return;
|
|
692569
|
+
}
|
|
692570
|
+
if (pathname?.startsWith("/v1/voice/clone-refs/") && pathname.endsWith("/activate") && method === "POST") {
|
|
692571
|
+
const parts = pathname.split("/");
|
|
692572
|
+
const filename = parts[4] || "";
|
|
692573
|
+
const config = loadConfig();
|
|
692574
|
+
const refs = config.voiceCloneRefs || [];
|
|
692575
|
+
const ref = refs.find((r2) => r2.filename === filename);
|
|
692576
|
+
if (ref) {
|
|
692577
|
+
ref.active = true;
|
|
692578
|
+
try {
|
|
692579
|
+
require4("./config").saveConfig?.(config);
|
|
692580
|
+
} catch {
|
|
692581
|
+
}
|
|
692582
|
+
}
|
|
692583
|
+
jsonResponse(res, 200, { ok: true, ref });
|
|
692584
|
+
return;
|
|
692585
|
+
}
|
|
692586
|
+
if (pathname?.startsWith("/v1/voice/clone-refs/") && pathname.endsWith("/rename") && method === "POST") {
|
|
692587
|
+
const parts = pathname.split("/");
|
|
692588
|
+
const filename = parts[4] || "";
|
|
692589
|
+
const body = await parseJsonBody(req3);
|
|
692590
|
+
const config = loadConfig();
|
|
692591
|
+
const refs = config.voiceCloneRefs || [];
|
|
692592
|
+
const ref = refs.find((r2) => r2.filename === filename);
|
|
692593
|
+
if (ref && body?.newFilename) {
|
|
692594
|
+
ref.filename = body.newFilename;
|
|
692595
|
+
try {
|
|
692596
|
+
require4("./config").saveConfig?.(config);
|
|
692597
|
+
} catch {
|
|
692598
|
+
}
|
|
692599
|
+
}
|
|
692600
|
+
jsonResponse(res, 200, { ok: true, ref });
|
|
692601
|
+
return;
|
|
692602
|
+
}
|
|
692603
|
+
if (pathname?.startsWith("/v1/voice/clone-refs/") && method === "DELETE") {
|
|
692604
|
+
const parts = pathname.split("/");
|
|
692605
|
+
const filename = parts[4] || "";
|
|
692606
|
+
const config = loadConfig();
|
|
692607
|
+
config.voiceCloneRefs = (config.voiceCloneRefs || []).filter((r2) => r2.filename !== filename);
|
|
692608
|
+
try {
|
|
692609
|
+
require4("./config").saveConfig?.(config);
|
|
692610
|
+
} catch {
|
|
692611
|
+
}
|
|
692612
|
+
jsonResponse(res, 200, { ok: true, deleted: filename });
|
|
692613
|
+
return;
|
|
692614
|
+
}
|
|
692367
692615
|
if ((pathname === "/v1/chat" || pathname === "/api/chat") && method === "POST") {
|
|
692368
692616
|
if (!checkAuth(req3, res, "run")) {
|
|
692369
692617
|
status = 401;
|