jarvis-hq 0.0.5 → 0.0.6
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/client-html.js +191 -6
- package/dist/dictation.js +8 -6
- package/dist/server.js +15 -4
- package/package.json +1 -1
package/dist/client-html.js
CHANGED
|
@@ -367,6 +367,20 @@ exports.html = `<!DOCTYPE html>
|
|
|
367
367
|
color: var(--muted); white-space: pre-wrap; overflow-wrap: anywhere; max-height: 150px; overflow: auto;
|
|
368
368
|
}
|
|
369
369
|
.perm .acts { display: flex; gap: 8px; }
|
|
370
|
+
.ask-questions { display: flex; flex-direction: column; gap: 16px; margin-bottom: 14px; }
|
|
371
|
+
.ask-q { display: flex; flex-direction: column; gap: 8px; }
|
|
372
|
+
.ask-q-text { font-size: 13.5px; font-weight: 500; }
|
|
373
|
+
.ask-opts { display: flex; flex-direction: column; gap: 6px; }
|
|
374
|
+
.ask-opt-btn {
|
|
375
|
+
text-align: left; padding: 7px 12px; border-radius: 8px; font-size: 13px;
|
|
376
|
+
background: var(--surface-2); border: 1px solid var(--border); color: var(--text);
|
|
377
|
+
cursor: pointer; box-shadow: none;
|
|
378
|
+
}
|
|
379
|
+
.ask-opt-btn:hover { border-color: var(--accent); }
|
|
380
|
+
.ask-opt-btn.selected { background: color-mix(in srgb, var(--accent) 12%, var(--surface)); border-color: var(--accent); color: var(--text); }
|
|
381
|
+
.ask-opt-desc { font-size: 11.5px; color: var(--muted); margin-top: 1px; padding-left: 2px; }
|
|
382
|
+
.ask-other { width: 100%; padding: 7px 12px; border-radius: 8px; font-size: 13px; border: 1px solid var(--border); background: var(--surface-2); color: var(--text); outline: none; }
|
|
383
|
+
.ask-other:focus { border-color: var(--accent); }
|
|
370
384
|
|
|
371
385
|
/* --- Composer --- */
|
|
372
386
|
.composer { border-top: 1px solid var(--border); background: var(--bg); }
|
|
@@ -487,6 +501,11 @@ exports.html = `<!DOCTYPE html>
|
|
|
487
501
|
box-shadow: var(--shadow-md); animation: rise .18s ease;
|
|
488
502
|
}
|
|
489
503
|
@keyframes rise { from { opacity: 0; transform: translate(-50%, 8px); } }
|
|
504
|
+
.toast-action {
|
|
505
|
+
margin-left: 10px; padding: 2px 10px; border-radius: 999px;
|
|
506
|
+
background: var(--bg); color: var(--text); border: none;
|
|
507
|
+
font-size: 13px; font-weight: 600; cursor: pointer;
|
|
508
|
+
}
|
|
490
509
|
|
|
491
510
|
@media (max-width: 640px) {
|
|
492
511
|
.wrap { padding: 0 16px; }
|
|
@@ -1731,6 +1750,7 @@ exports.html = `<!DOCTYPE html>
|
|
|
1731
1750
|
}
|
|
1732
1751
|
|
|
1733
1752
|
function renderPermission(data) {
|
|
1753
|
+
if (data.toolName === "AskUserQuestion") { renderAskUser(data); return; }
|
|
1734
1754
|
var card = el("div", "perm");
|
|
1735
1755
|
card.appendChild(el("div", "head", "Allow " + data.toolName + "?"));
|
|
1736
1756
|
card.appendChild(el("pre", null, JSON.stringify(data.input, null, 2)));
|
|
@@ -1756,6 +1776,100 @@ exports.html = `<!DOCTYPE html>
|
|
|
1756
1776
|
scrollDown();
|
|
1757
1777
|
}
|
|
1758
1778
|
|
|
1779
|
+
function renderAskUser(data) {
|
|
1780
|
+
var input = data.input || {};
|
|
1781
|
+
var questions = Array.isArray(input.questions) ? input.questions : [];
|
|
1782
|
+
var answers = {}; // question text -> selected label(s)
|
|
1783
|
+
|
|
1784
|
+
var card = el("div", "perm");
|
|
1785
|
+
card.appendChild(el("div", "head", "Questions"));
|
|
1786
|
+
|
|
1787
|
+
var qsDiv = el("div", "ask-questions");
|
|
1788
|
+
|
|
1789
|
+
questions.forEach(function (q, qi) {
|
|
1790
|
+
answers[q.question] = q.multiSelect ? [] : null;
|
|
1791
|
+
|
|
1792
|
+
var qDiv = el("div", "ask-q");
|
|
1793
|
+
qDiv.appendChild(el("div", "ask-q-text", q.question));
|
|
1794
|
+
|
|
1795
|
+
var optsDiv = el("div", "ask-opts");
|
|
1796
|
+
|
|
1797
|
+
(q.options || []).forEach(function (opt) {
|
|
1798
|
+
var row = el("div");
|
|
1799
|
+
var btn = el("button", "ask-opt-btn", opt.label);
|
|
1800
|
+
btn.onclick = function () {
|
|
1801
|
+
if (q.multiSelect) {
|
|
1802
|
+
var arr = answers[q.question];
|
|
1803
|
+
var idx = arr.indexOf(opt.label);
|
|
1804
|
+
if (idx >= 0) { arr.splice(idx, 1); btn.classList.remove("selected"); }
|
|
1805
|
+
else { arr.push(opt.label); btn.classList.add("selected"); }
|
|
1806
|
+
} else {
|
|
1807
|
+
answers[q.question] = opt.label;
|
|
1808
|
+
optsDiv.querySelectorAll(".ask-opt-btn").forEach(function (b) { b.classList.remove("selected"); });
|
|
1809
|
+
btn.classList.add("selected");
|
|
1810
|
+
}
|
|
1811
|
+
};
|
|
1812
|
+
row.appendChild(btn);
|
|
1813
|
+
if (opt.description) row.appendChild(el("div", "ask-opt-desc", opt.description));
|
|
1814
|
+
optsDiv.appendChild(row);
|
|
1815
|
+
});
|
|
1816
|
+
|
|
1817
|
+
var otherInput = el("input");
|
|
1818
|
+
otherInput.type = "text";
|
|
1819
|
+
otherInput.placeholder = "Other\u2026";
|
|
1820
|
+
otherInput.className = "ask-other";
|
|
1821
|
+
otherInput.oninput = function () {
|
|
1822
|
+
var val = otherInput.value.trim();
|
|
1823
|
+
if (q.multiSelect) {
|
|
1824
|
+
// handled on submit
|
|
1825
|
+
} else {
|
|
1826
|
+
if (val) {
|
|
1827
|
+
answers[q.question] = val;
|
|
1828
|
+
optsDiv.querySelectorAll(".ask-opt-btn").forEach(function (b) { b.classList.remove("selected"); });
|
|
1829
|
+
} else if (!answers[q.question] || typeof answers[q.question] === "string") {
|
|
1830
|
+
answers[q.question] = null;
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
};
|
|
1834
|
+
optsDiv.appendChild(otherInput);
|
|
1835
|
+
qDiv.appendChild(optsDiv);
|
|
1836
|
+
qsDiv.appendChild(qDiv);
|
|
1837
|
+
});
|
|
1838
|
+
|
|
1839
|
+
card.appendChild(qsDiv);
|
|
1840
|
+
|
|
1841
|
+
var acts = el("div", "acts");
|
|
1842
|
+
var submit = el("button", "btn primary", "Submit");
|
|
1843
|
+
submit.onclick = function () {
|
|
1844
|
+
// Collect "other" text inputs for multi-select questions
|
|
1845
|
+
var qDivs = qsDiv.querySelectorAll(".ask-q");
|
|
1846
|
+
questions.forEach(function (q, qi) {
|
|
1847
|
+
if (q.multiSelect) {
|
|
1848
|
+
var inp = qDivs[qi].querySelector(".ask-other");
|
|
1849
|
+
var val = inp ? inp.value.trim() : "";
|
|
1850
|
+
if (val && answers[q.question].indexOf(val) === -1) answers[q.question].push(val);
|
|
1851
|
+
}
|
|
1852
|
+
});
|
|
1853
|
+
submit.disabled = true;
|
|
1854
|
+
api("/sessions/" + encodeURIComponent(sessionId) + "/permission", {
|
|
1855
|
+
method: "POST",
|
|
1856
|
+
body: { toolUseID: data.toolUseID, answers: answers }
|
|
1857
|
+
}).then(function () {
|
|
1858
|
+
var summary = questions.map(function (q) {
|
|
1859
|
+
var a = answers[q.question];
|
|
1860
|
+
return Array.isArray(a) ? a.join(", ") : (a || "\u2014");
|
|
1861
|
+
}).join(" \u00b7 ");
|
|
1862
|
+
card.replaceWith(el("div", "note", summary));
|
|
1863
|
+
pendingPerms = Math.max(0, pendingPerms - 1);
|
|
1864
|
+
if (!pendingPerms && state === "running") setActivity("Thinking\u2026");
|
|
1865
|
+
}).catch(showError);
|
|
1866
|
+
};
|
|
1867
|
+
acts.appendChild(submit);
|
|
1868
|
+
card.appendChild(acts);
|
|
1869
|
+
$("messages-inner").appendChild(card);
|
|
1870
|
+
scrollDown();
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1759
1873
|
function finishTurn() {
|
|
1760
1874
|
closeStream();
|
|
1761
1875
|
liveBubble = null;
|
|
@@ -1924,6 +2038,70 @@ exports.html = `<!DOCTYPE html>
|
|
|
1924
2038
|
});
|
|
1925
2039
|
}
|
|
1926
2040
|
|
|
2041
|
+
// IndexedDB helpers — save the raw audio before the API call so a failed
|
|
2042
|
+
// transcription can be retried without re-recording.
|
|
2043
|
+
var _dictDb = null;
|
|
2044
|
+
function openDictationDb() {
|
|
2045
|
+
if (_dictDb) return Promise.resolve(_dictDb);
|
|
2046
|
+
return new Promise(function (resolve, reject) {
|
|
2047
|
+
var req = indexedDB.open("dictation", 1);
|
|
2048
|
+
req.onupgradeneeded = function () { req.result.createObjectStore("pending"); };
|
|
2049
|
+
req.onsuccess = function () { _dictDb = req.result; resolve(_dictDb); };
|
|
2050
|
+
req.onerror = function () { reject(req.error); };
|
|
2051
|
+
});
|
|
2052
|
+
}
|
|
2053
|
+
function savePendingAudio(audio, mimeType) {
|
|
2054
|
+
return openDictationDb().then(function (db) {
|
|
2055
|
+
return new Promise(function (resolve, reject) {
|
|
2056
|
+
var tx = db.transaction("pending", "readwrite");
|
|
2057
|
+
tx.objectStore("pending").put({ audio: audio, mimeType: mimeType }, "clip");
|
|
2058
|
+
tx.oncomplete = resolve;
|
|
2059
|
+
tx.onerror = function () { reject(tx.error); };
|
|
2060
|
+
});
|
|
2061
|
+
}).catch(function () {}); // best-effort
|
|
2062
|
+
}
|
|
2063
|
+
function deletePendingAudio() {
|
|
2064
|
+
return openDictationDb().then(function (db) {
|
|
2065
|
+
return new Promise(function (resolve) {
|
|
2066
|
+
var tx = db.transaction("pending", "readwrite");
|
|
2067
|
+
tx.objectStore("pending").delete("clip");
|
|
2068
|
+
tx.oncomplete = resolve;
|
|
2069
|
+
tx.onerror = resolve;
|
|
2070
|
+
});
|
|
2071
|
+
}).catch(function () {});
|
|
2072
|
+
}
|
|
2073
|
+
function loadPendingAudio() {
|
|
2074
|
+
return openDictationDb().then(function (db) {
|
|
2075
|
+
return new Promise(function (resolve) {
|
|
2076
|
+
var req = db.transaction("pending", "readonly").objectStore("pending").get("clip");
|
|
2077
|
+
req.onsuccess = function () { resolve(req.result || null); };
|
|
2078
|
+
req.onerror = function () { resolve(null); };
|
|
2079
|
+
});
|
|
2080
|
+
}).catch(function () { return null; });
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
function sendDictation(audio, mimeType, btn, textarea) {
|
|
2084
|
+
return api("/dictate", { method: "POST", body: { audio: audio, mimeType: mimeType } })
|
|
2085
|
+
.then(function (d) {
|
|
2086
|
+
deletePendingAudio();
|
|
2087
|
+
insertDictation(textarea, d.text);
|
|
2088
|
+
}).catch(function (err) {
|
|
2089
|
+
toast(err.message || "Dictation failed", {
|
|
2090
|
+
label: "Retry",
|
|
2091
|
+
fn: function () {
|
|
2092
|
+
loadPendingAudio().then(function (saved) {
|
|
2093
|
+
if (!saved) { toast("No saved clip to retry"); return; }
|
|
2094
|
+
btn.classList.add("busy");
|
|
2095
|
+
sendDictation(saved.audio, saved.mimeType, btn, textarea)
|
|
2096
|
+
.then(function () { btn.classList.remove("busy"); });
|
|
2097
|
+
});
|
|
2098
|
+
},
|
|
2099
|
+
});
|
|
2100
|
+
}).then(function () {
|
|
2101
|
+
btn.classList.remove("busy");
|
|
2102
|
+
});
|
|
2103
|
+
}
|
|
2104
|
+
|
|
1927
2105
|
function stopRecording() {
|
|
1928
2106
|
var rec = recording;
|
|
1929
2107
|
if (!rec) return;
|
|
@@ -1941,6 +2119,7 @@ exports.html = `<!DOCTYPE html>
|
|
|
1941
2119
|
if (!window.isSecureContext) {
|
|
1942
2120
|
toast("Dictation needs https or localhost"); return;
|
|
1943
2121
|
}
|
|
2122
|
+
deletePendingAudio();
|
|
1944
2123
|
navigator.mediaDevices.getUserMedia({ audio: true }).then(function (stream) {
|
|
1945
2124
|
var mime = recordingMime();
|
|
1946
2125
|
var recorder = mime ? new MediaRecorder(stream, { mimeType: mime }) : new MediaRecorder(stream);
|
|
@@ -1951,12 +2130,11 @@ exports.html = `<!DOCTYPE html>
|
|
|
1951
2130
|
var blob = new Blob(chunks, { type: type });
|
|
1952
2131
|
if (blob.size < 1000) { btn.classList.remove("busy"); return; } // tapped twice by accident
|
|
1953
2132
|
blobToBase64(blob).then(function (audio) {
|
|
1954
|
-
return
|
|
1955
|
-
|
|
1956
|
-
|
|
2133
|
+
return savePendingAudio(audio, type).then(function () {
|
|
2134
|
+
return sendDictation(audio, type, btn, textarea);
|
|
2135
|
+
});
|
|
1957
2136
|
}).catch(function (err) {
|
|
1958
2137
|
toast(err.message || "Dictation failed");
|
|
1959
|
-
}).then(function () {
|
|
1960
2138
|
btn.classList.remove("busy");
|
|
1961
2139
|
});
|
|
1962
2140
|
};
|
|
@@ -1977,12 +2155,19 @@ exports.html = `<!DOCTYPE html>
|
|
|
1977
2155
|
};
|
|
1978
2156
|
}
|
|
1979
2157
|
|
|
1980
|
-
function toast(text) {
|
|
2158
|
+
function toast(text, action) {
|
|
1981
2159
|
var old = document.querySelector(".toast");
|
|
1982
2160
|
if (old) old.remove();
|
|
1983
2161
|
var t = el("div", "toast", text);
|
|
2162
|
+
if (action) {
|
|
2163
|
+
var btn = document.createElement("button");
|
|
2164
|
+
btn.className = "toast-action";
|
|
2165
|
+
btn.textContent = action.label;
|
|
2166
|
+
btn.onclick = function () { t.remove(); action.fn(); };
|
|
2167
|
+
t.appendChild(btn);
|
|
2168
|
+
}
|
|
1984
2169
|
document.body.appendChild(t);
|
|
1985
|
-
setTimeout(function () { t.remove(); }, 2400);
|
|
2170
|
+
setTimeout(function () { if (t.parentNode) t.remove(); }, action ? 7000 : 2400);
|
|
1986
2171
|
}
|
|
1987
2172
|
|
|
1988
2173
|
function copyText(text) {
|
package/dist/dictation.js
CHANGED
|
@@ -21,15 +21,17 @@ const OPENAI_URL = "https://api.openai.com/v1/chat/completions";
|
|
|
21
21
|
const CLEANUP_MODEL = "gpt-4o-mini";
|
|
22
22
|
// Roughly two minutes of opus; anything longer is a mistake, not a message.
|
|
23
23
|
const MAX_AUDIO_BYTES = 8 * 1024 * 1024;
|
|
24
|
-
const CLEANUP_PROMPT = `You are
|
|
25
|
-
The user will provide a
|
|
24
|
+
const CLEANUP_PROMPT = `You are a voice transcription cleanup assistant.
|
|
25
|
+
The user will provide a raw voice transcript. Your job is light editing only — preserve their exact words and phrasing as much as possible.
|
|
26
26
|
|
|
27
27
|
Your task:
|
|
28
|
-
1.
|
|
29
|
-
2.
|
|
30
|
-
3.
|
|
28
|
+
1. Remove filler words ("uh", "um", "er", "like") and false starts.
|
|
29
|
+
2. If the user spells out a word (e.g., "t-h-r-e-s-h-o-l-d"), replace the previous misspelled or unclear attempt with the correctly spelled word and drop the spelling itself.
|
|
30
|
+
3. Resolve simple self-corrections (e.g., "delete the file, actually remove the folder" → "remove the folder").
|
|
31
|
+
4. Apply light markdown formatting where it clearly fits: wrap code, file names, variable names, and commands in backticks; use a bullet list if the user enumerates multiple items.
|
|
32
|
+
5. Output ONLY the cleaned transcript. No explanations, no quotes, no commentary.
|
|
31
33
|
|
|
32
|
-
|
|
34
|
+
Do not paraphrase, summarize, reorder, or interpret — just clean up the words they said.`;
|
|
33
35
|
function dictationConfigured() {
|
|
34
36
|
return {
|
|
35
37
|
deepgram: Boolean(process.env.DEEPGRAM_API_KEY),
|
package/dist/server.js
CHANGED
|
@@ -221,7 +221,7 @@ async function handleRequest(req, res, availableAgents, workspaceCwd) {
|
|
|
221
221
|
return;
|
|
222
222
|
}
|
|
223
223
|
const body = await (0, server_common_1.readBody)(req);
|
|
224
|
-
const { toolUseID, approved, updatedInput } = body;
|
|
224
|
+
const { toolUseID, approved, updatedInput, answers } = body;
|
|
225
225
|
if (!toolUseID) {
|
|
226
226
|
(0, server_common_1.jsonError)(res, 400, "toolUseID is required");
|
|
227
227
|
return;
|
|
@@ -232,9 +232,20 @@ async function handleRequest(req, res, availableAgents, workspaceCwd) {
|
|
|
232
232
|
if (pending) {
|
|
233
233
|
store.pendingPermissions.delete(toolUseID);
|
|
234
234
|
(0, server_common_1.notifyPermissionsChanged)();
|
|
235
|
-
pending.
|
|
236
|
-
|
|
237
|
-
|
|
235
|
+
if (pending.toolName === "AskUserQuestion" && answers) {
|
|
236
|
+
const questions = pending.input?.questions ?? [];
|
|
237
|
+
const lines = questions.map((q) => {
|
|
238
|
+
const a = answers[q.question];
|
|
239
|
+
const answer = Array.isArray(a) ? (a.length ? a.join(", ") : "(none selected)") : (a || "(no answer)");
|
|
240
|
+
return `- ${q.question}: ${answer}`;
|
|
241
|
+
});
|
|
242
|
+
pending.resolve({ behavior: "deny", message: "User's answers:\n" + lines.join("\n") });
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
pending.resolve(approved
|
|
246
|
+
? { behavior: "allow", updatedInput: updatedInput ?? pending.input }
|
|
247
|
+
: { behavior: "deny", message: "User denied" });
|
|
248
|
+
}
|
|
238
249
|
}
|
|
239
250
|
}
|
|
240
251
|
else if (store.agent === "opencode" && store.sdkSessionId) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jarvis-hq",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"description": "Jarvis — a persistent AI engineering partner. One entry point from any device; project-aware handoff and delegation to Claude Code running on your machine.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|