+1
-1
@@ -41,7 +41,7 @@ Workspace skills live in `skills/<name>/SKILL.md`. When a request matches a skil
|
||||
- `read_file` / `list_dir` / `grep` / `write_file` / `search_replace` — workspace files.
|
||||
- `run_terminal_cmd` — local shell. Public HTTP via curl or wget is blocked; use web tools.
|
||||
- `web_search` / `google_search` / `fetch_page` / `web_fetch` / `wiki_search` / `hn_search` / `code_search` — public reads, no extra keys.
|
||||
- Desktop and computer-use tools are registered by Jarvis. Observe and act only with an active grant.
|
||||
- Desktop and computer-use tools are registered by Jarvis. After Grant desktop, call `cu_observe`, then `cu_click` / `cu_type`. Do not paste tool JSON into chat.
|
||||
- `ask_user_question` — wait for a user choice.
|
||||
|
||||
Keep going until the user’s request is fully complete. Never stop after announcing the next step. When the work is done, speak a short summary. A greeting does not need a long summary.
|
||||
|
||||
Vendored
+2
-1
@@ -38,6 +38,7 @@ const pendingCustom = new Map();
|
||||
const pendingAsks = new Map();
|
||||
const pendingPlans = new Map();
|
||||
const MAX_TURNS = 24;
|
||||
const CU_FREE_ROUNDS = new Set(['todo_write', 'update_goal', 'cu_observe', 'cu_find', 'cu_tree', 'cu_status', 'cu_zoom']);
|
||||
const SUBAGENT_TURNS = 8;
|
||||
const CUSTOM_TOOL_TIMEOUT_MS = 60000;
|
||||
const ASK_TIMEOUT_MS = 10 * 60 * 1000;
|
||||
@@ -877,7 +878,7 @@ async function runTurn(ctx) {
|
||||
}
|
||||
if (stopEarly) break;
|
||||
}
|
||||
if (prepared.some((item) => item.name !== 'todo_write' && item.name !== 'update_goal')) {
|
||||
if (prepared.some((item) => !CU_FREE_ROUNDS.has(item.name))) {
|
||||
toolBudget.markToolRound(budget);
|
||||
}
|
||||
if (stopEarly) {
|
||||
|
||||
Vendored
+1
@@ -303,6 +303,7 @@ const COMPACT_TOOL_ALLOW = [
|
||||
'cu_status',
|
||||
'cu_observe',
|
||||
'cu_find',
|
||||
'cu_tree',
|
||||
'cu_click',
|
||||
'cu_type',
|
||||
'cu_key',
|
||||
|
||||
Vendored
+49
-3
@@ -10,13 +10,59 @@ function clip(s) {
|
||||
return t.slice(0, MAX_DELTA);
|
||||
}
|
||||
|
||||
function eventDelta(ev) {
|
||||
if (ev == null) return '';
|
||||
if (typeof ev === 'string' || typeof ev === 'number' || typeof ev === 'boolean') return clip(ev);
|
||||
return clip(ev.text || ev.delta || ev.content || ev.chunk || '');
|
||||
}
|
||||
|
||||
const THINK_OPEN = '<think>';
|
||||
const THINK_CLOSE = '</think>';
|
||||
|
||||
function trailingPartial(buf, marker) {
|
||||
const max = Math.min(buf.length, marker.length - 1);
|
||||
for (let len = max; len > 0; len--) {
|
||||
if (marker.startsWith(buf.slice(buf.length - len))) return len;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function expandThinkEvents(n, state) {
|
||||
const st = state && typeof state === 'object' ? state : { inThink: false, carry: '' };
|
||||
if (!n) return { events: [], state: st };
|
||||
if (n.type === 'thinkingDelta') {
|
||||
const delta = n.delta || '';
|
||||
return { events: delta ? [n] : [], state: st };
|
||||
}
|
||||
if (n.type !== 'contentDelta') return { events: [n], state: st };
|
||||
let rest = String(st.carry || '') + String(n.delta || '');
|
||||
const events = [];
|
||||
let inThink = !!st.inThink;
|
||||
while (rest) {
|
||||
const marker = inThink ? THINK_CLOSE : THINK_OPEN;
|
||||
const idx = rest.indexOf(marker);
|
||||
if (idx < 0) {
|
||||
const partial = trailingPartial(rest, marker);
|
||||
const emit = partial ? rest.slice(0, rest.length - partial) : rest;
|
||||
const carry = partial ? rest.slice(rest.length - partial) : '';
|
||||
if (emit) events.push({ type: inThink ? 'thinkingDelta' : 'contentDelta', delta: emit });
|
||||
return { events, state: { inThink, carry } };
|
||||
}
|
||||
const before = rest.slice(0, idx);
|
||||
if (before) events.push({ type: inThink ? 'thinkingDelta' : 'contentDelta', delta: before });
|
||||
rest = rest.slice(idx + marker.length);
|
||||
inThink = !inThink;
|
||||
}
|
||||
return { events, state: { inThink, carry: '' } };
|
||||
}
|
||||
|
||||
function normalizeCompletionEvent(ev) {
|
||||
if (!ev || !ev.type) return null;
|
||||
if (ev.type === 'contentDelta') {
|
||||
return { type: 'contentDelta', delta: clip(ev.delta || ev.text || ev.content || '') };
|
||||
return { type: 'contentDelta', delta: eventDelta(ev) };
|
||||
}
|
||||
if (ev.type === 'thinkingDelta') {
|
||||
return { type: 'thinkingDelta', delta: clip(ev.delta || ev.text || ev.content || '') };
|
||||
return { type: 'thinkingDelta', delta: eventDelta(ev) };
|
||||
}
|
||||
if (ev.type === 'toolCall') {
|
||||
const call = ev.call || ev.toolCall || ev;
|
||||
@@ -35,4 +81,4 @@ function normalizeCompletionEvent(ev) {
|
||||
return { type: ev.type, delta: ev.delta != null ? clip(ev.delta) : undefined, call: ev.call };
|
||||
}
|
||||
|
||||
module.exports = { normalizeCompletionEvent, MAX_DELTA };
|
||||
module.exports = { normalizeCompletionEvent, expandThinkEvents, eventDelta, MAX_DELTA };
|
||||
|
||||
Vendored
+45
-26
@@ -396,6 +396,7 @@ async function complete(opts, onEvent) {
|
||||
|
||||
let text = '';
|
||||
let thinking = '';
|
||||
let thinkState = { inThink: false, carry: '' };
|
||||
const toolCalls = [];
|
||||
const abortRun = () => {
|
||||
try {
|
||||
@@ -420,39 +421,57 @@ async function complete(opts, onEvent) {
|
||||
const n = events.normalizeCompletionEvent(ev);
|
||||
if (!n) continue;
|
||||
watch.bump();
|
||||
if (n.type === 'contentDelta') {
|
||||
text += n.delta;
|
||||
if (onEvent) onEvent(n);
|
||||
if (repeatLoop.isRepeating(text)) {
|
||||
abortRun();
|
||||
settleTimeout();
|
||||
return;
|
||||
const split = events.expandThinkEvents(n, thinkState);
|
||||
thinkState = split.state;
|
||||
for (const p of split.events) {
|
||||
if (p.type === 'contentDelta') {
|
||||
text += p.delta;
|
||||
if (onEvent) onEvent(p);
|
||||
if (repeatLoop.isRepeating(text)) {
|
||||
abortRun();
|
||||
settleTimeout();
|
||||
return;
|
||||
}
|
||||
} else if (p.type === 'thinkingDelta') {
|
||||
thinking += p.delta;
|
||||
if (onEvent) onEvent(p);
|
||||
if (repeatLoop.isRepeating(thinking)) {
|
||||
abortRun();
|
||||
settleTimeout();
|
||||
return;
|
||||
}
|
||||
} else if (p.type === 'toolCall') {
|
||||
toolCalls.push(p.call);
|
||||
if (onEvent) onEvent(p);
|
||||
} else if (onEvent) {
|
||||
onEvent(p);
|
||||
}
|
||||
} else if (n.type === 'thinkingDelta') {
|
||||
thinking += n.delta;
|
||||
if (onEvent) onEvent(n);
|
||||
if (repeatLoop.isRepeating(thinking)) {
|
||||
abortRun();
|
||||
settleTimeout();
|
||||
return;
|
||||
}
|
||||
} else if (n.type === 'toolCall') {
|
||||
toolCalls.push(n.call);
|
||||
if (onEvent) onEvent(n);
|
||||
} else if (onEvent) {
|
||||
onEvent(n);
|
||||
}
|
||||
}
|
||||
} else if (run.tokenStream) {
|
||||
for await (const token of run.tokenStream) {
|
||||
if (watch.timedOut()) return;
|
||||
watch.bump();
|
||||
text += token;
|
||||
if (onEvent) onEvent({ type: 'contentDelta', delta: token });
|
||||
if (repeatLoop.isRepeating(text)) {
|
||||
abortRun();
|
||||
settleTimeout();
|
||||
return;
|
||||
const split = events.expandThinkEvents({ type: 'contentDelta', delta: token }, thinkState);
|
||||
thinkState = split.state;
|
||||
for (const p of split.events) {
|
||||
if (p.type === 'thinkingDelta') {
|
||||
thinking += p.delta;
|
||||
if (onEvent) onEvent(p);
|
||||
if (repeatLoop.isRepeating(thinking)) {
|
||||
abortRun();
|
||||
settleTimeout();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
text += p.delta;
|
||||
if (onEvent) onEvent({ type: 'contentDelta', delta: p.delta });
|
||||
if (repeatLoop.isRepeating(text)) {
|
||||
abortRun();
|
||||
settleTimeout();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (run.toolCallStream) {
|
||||
|
||||
+11
@@ -29,6 +29,17 @@ const ALIASES = {
|
||||
write: 'write_file',
|
||||
ls: 'list_dir',
|
||||
status: 'jarvis_status',
|
||||
click: 'cu_click',
|
||||
type: 'cu_type',
|
||||
type_text: 'cu_type',
|
||||
observe: 'cu_observe',
|
||||
look: 'cu_observe',
|
||||
screenshot: 'cu_observe',
|
||||
find: 'cu_find',
|
||||
hover: 'cu_hover',
|
||||
scroll: 'cu_scroll',
|
||||
key: 'cu_key',
|
||||
press: 'cu_key',
|
||||
};
|
||||
|
||||
function knownNames(tools) {
|
||||
|
||||
Reference in New Issue
Block a user