openzoo 0.50.39 → 0.50.40
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/lib/cursorbackend.js +232 -10
- package/lib/xb +0 -0
- package/package.json +1 -1
package/lib/cursorbackend.js
CHANGED
|
@@ -1129,12 +1129,99 @@ function appendLine(agentId, role, text, extra = {}) {
|
|
|
1129
1129
|
timestampMs: ts,
|
|
1130
1130
|
requestId,
|
|
1131
1131
|
...(extra.author && typeof extra.author === 'object' ? { author: extra.author } : {}),
|
|
1132
|
+
...(extra.ephemeral ? { ephemeral: true } : {}),
|
|
1132
1133
|
};
|
|
1133
1134
|
}
|
|
1134
1135
|
t.entries.push(e);
|
|
1135
1136
|
saveTranscripts();
|
|
1136
1137
|
return e;
|
|
1137
1138
|
}
|
|
1139
|
+
|
|
1140
|
+
/** One zooComplete per agent. A new sendPrompt (or Stop) aborts the previous
|
|
1141
|
+
* loop so "try again" does not stack 32-step exec storms with an empty canvas. */
|
|
1142
|
+
export function createZooTurnQueue() {
|
|
1143
|
+
const inflight = new Map();
|
|
1144
|
+
return {
|
|
1145
|
+
begin(agentId, nonce) {
|
|
1146
|
+
const id = String(agentId || '');
|
|
1147
|
+
const prev = inflight.get(id);
|
|
1148
|
+
if (prev && prev.nonce !== nonce) {
|
|
1149
|
+
try { prev.abort.abort(new Error('superseded')); } catch { /* */ }
|
|
1150
|
+
}
|
|
1151
|
+
const abort = new AbortController();
|
|
1152
|
+
inflight.set(id, { nonce, abort });
|
|
1153
|
+
return abort;
|
|
1154
|
+
},
|
|
1155
|
+
isCurrent(agentId, nonce) {
|
|
1156
|
+
return inflight.get(String(agentId || ''))?.nonce === nonce;
|
|
1157
|
+
},
|
|
1158
|
+
end(agentId, nonce) {
|
|
1159
|
+
const id = String(agentId || '');
|
|
1160
|
+
const cur = inflight.get(id);
|
|
1161
|
+
if (cur && cur.nonce === nonce) inflight.delete(id);
|
|
1162
|
+
},
|
|
1163
|
+
abort(agentId) {
|
|
1164
|
+
const id = String(agentId || '');
|
|
1165
|
+
const prev = inflight.get(id);
|
|
1166
|
+
if (!prev) return 0;
|
|
1167
|
+
try { prev.abort.abort(new Error('interrupted')); } catch { /* */ }
|
|
1168
|
+
inflight.delete(id);
|
|
1169
|
+
return 1;
|
|
1170
|
+
},
|
|
1171
|
+
};
|
|
1172
|
+
}
|
|
1173
|
+
const zooTurns = createZooTurnQueue();
|
|
1174
|
+
|
|
1175
|
+
export function formatZooProgress({ step, maxSteps, names, command } = {}) {
|
|
1176
|
+
const tools = Array.isArray(names) ? names.filter(Boolean).join(', ') : String(names || 'tools');
|
|
1177
|
+
const cmd = command ? ` ${JSON.stringify(String(command).slice(0, 80))}` : '';
|
|
1178
|
+
return `Working on your Mac (step ${Number(step) + 1}/${maxSteps || '?'}): ${tools}${cmd}`;
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
/** One canvas line per tool. Asar ingest is append-only — mutating a working
|
|
1182
|
+
* bubble is still silence. Keep it short; the model history skips ephemeral. */
|
|
1183
|
+
export function formatZooToolLine({ name, args = {}, result } = {}) {
|
|
1184
|
+
const detail = args.command || args.path || args.name || args.window || '';
|
|
1185
|
+
const head = detail
|
|
1186
|
+
? `${name} ${JSON.stringify(String(detail).slice(0, 90))}`
|
|
1187
|
+
: String(name || 'tool');
|
|
1188
|
+
let tail = String(result || '').replace(/\s+/g, ' ').trim();
|
|
1189
|
+
if (tail.startsWith('{')) {
|
|
1190
|
+
try {
|
|
1191
|
+
const o = JSON.parse(String(result));
|
|
1192
|
+
if (o && typeof o === 'object') {
|
|
1193
|
+
tail = [o.path, o.id, o.name, o.bytes != null ? `${o.bytes}b` : '', o.ok === true ? 'ok' : '']
|
|
1194
|
+
.filter(Boolean).join(' ') || tail;
|
|
1195
|
+
}
|
|
1196
|
+
} catch { /* raw */ }
|
|
1197
|
+
}
|
|
1198
|
+
tail = tail.slice(0, 160);
|
|
1199
|
+
return `→ ${head}${tail ? `\n${tail}` : ''}`;
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
export function combinedAbortSignal(a, b) {
|
|
1203
|
+
if (!a) return b;
|
|
1204
|
+
if (!b) return a;
|
|
1205
|
+
if (typeof AbortSignal.any === 'function') return AbortSignal.any([a, b]);
|
|
1206
|
+
const c = new AbortController();
|
|
1207
|
+
const on = () => { try { c.abort(); } catch { /* */ } };
|
|
1208
|
+
if (a.aborted || b.aborted) { on(); return c.signal; }
|
|
1209
|
+
a.addEventListener('abort', on, { once: true });
|
|
1210
|
+
b.addEventListener('abort', on, { once: true });
|
|
1211
|
+
return c.signal;
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
export function isSupersededError(e) {
|
|
1215
|
+
const m = String(e?.message || e || '');
|
|
1216
|
+
return /superseded|interrupted/i.test(m);
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
function paintChatUpdate(agentId, note, extra = {}) {
|
|
1220
|
+
const line = fanoutLine(agentId, 'assistant', note, { ...extra, ephemeral: true });
|
|
1221
|
+
ssePush('transcript', { ...gatewayEntry(line), agentId });
|
|
1222
|
+
bumpAgent(agentId, { preview: note, notify: false });
|
|
1223
|
+
return line;
|
|
1224
|
+
}
|
|
1138
1225
|
function fanoutLine(primaryId, role, text, extra = {}) {
|
|
1139
1226
|
// Only the addressed agent. Writing to every tailedAgents id mixed canvases
|
|
1140
1227
|
// so a new/empty chat showed someone else's thread.
|
|
@@ -1254,7 +1341,7 @@ function mergeAgentLists(remote) {
|
|
|
1254
1341
|
return sortAgentsByActivity(out);
|
|
1255
1342
|
}
|
|
1256
1343
|
function gatewayEntry(e) {
|
|
1257
|
-
const { seq, pulledRemote, promptRaw, ...rest } = e;
|
|
1344
|
+
const { seq, pulledRemote, promptRaw, ephemeral, ...rest } = e;
|
|
1258
1345
|
return rest;
|
|
1259
1346
|
}
|
|
1260
1347
|
|
|
@@ -1286,6 +1373,7 @@ function historyMessages(agentId, currentPrompt) {
|
|
|
1286
1373
|
if (!e || typeof e !== 'object') continue;
|
|
1287
1374
|
let role = null;
|
|
1288
1375
|
let text = '';
|
|
1376
|
+
if (e.ephemeral) continue;
|
|
1289
1377
|
if (e.kind === 'send-message' || e.role === 'assistant' || e.kind === 'assistant') {
|
|
1290
1378
|
text = entryPlainText(e.message ?? e.content ?? e.text);
|
|
1291
1379
|
role = 'assistant';
|
|
@@ -1622,9 +1710,10 @@ async function execLocal(command, cwd, log) {
|
|
|
1622
1710
|
return `${out}${err}`.trim() || `(exit ${got.exitCode ?? 0})`;
|
|
1623
1711
|
}
|
|
1624
1712
|
log(`cursor-backend: exec cwd=${dir} ${JSON.stringify(String(command).slice(0, 80))}`);
|
|
1713
|
+
const execMs = Math.min(180000, Math.max(15000, Number(process.env.OPENZOO_EXEC_TIMEOUT_MS || 90000)));
|
|
1625
1714
|
const { stdout, stderr } = await execFileAsync('/bin/zsh', ['-lc', command], {
|
|
1626
1715
|
cwd: dir,
|
|
1627
|
-
timeout:
|
|
1716
|
+
timeout: execMs,
|
|
1628
1717
|
maxBuffer: 2 * 1024 * 1024,
|
|
1629
1718
|
env: process.env,
|
|
1630
1719
|
});
|
|
@@ -1672,8 +1761,62 @@ const LOCAL_TOOLS = [
|
|
|
1672
1761
|
parameters: { type: 'object', properties: { path: { type: 'string' } }, required: ['path'] },
|
|
1673
1762
|
},
|
|
1674
1763
|
},
|
|
1764
|
+
{
|
|
1765
|
+
type: 'function',
|
|
1766
|
+
function: {
|
|
1767
|
+
name: 'screenshot',
|
|
1768
|
+
description: 'Capture the Mac screen (or the front window) and attach the image so you can SEE the UI. Use this before filling forms or clicking. Do not guess at a dashboard you have not screenshotted this turn.',
|
|
1769
|
+
parameters: {
|
|
1770
|
+
type: 'object',
|
|
1771
|
+
properties: {
|
|
1772
|
+
window: { type: 'string', description: 'Optional window title substring; default is the full display.' },
|
|
1773
|
+
},
|
|
1774
|
+
},
|
|
1775
|
+
},
|
|
1776
|
+
},
|
|
1777
|
+
{
|
|
1778
|
+
type: 'function',
|
|
1779
|
+
function: {
|
|
1780
|
+
name: 'create_agent',
|
|
1781
|
+
description: 'Mint another Grok Bot in the sidebar. Use this when asked to spawn bots — talking about spawning is not enough.',
|
|
1782
|
+
parameters: {
|
|
1783
|
+
type: 'object',
|
|
1784
|
+
properties: {
|
|
1785
|
+
name: { type: 'string', description: 'Sidebar label for the new bot.' },
|
|
1786
|
+
select: { type: 'boolean', description: 'If true (default), switch the UI to the new bot.' },
|
|
1787
|
+
},
|
|
1788
|
+
required: ['name'],
|
|
1789
|
+
},
|
|
1790
|
+
},
|
|
1791
|
+
},
|
|
1675
1792
|
];
|
|
1676
1793
|
|
|
1794
|
+
export const LOCAL_TOOL_NAMES = LOCAL_TOOLS.map((t) => t.function.name);
|
|
1795
|
+
|
|
1796
|
+
async function captureScreenshot(log) {
|
|
1797
|
+
const dir = path.join(os.tmpdir(), 'openzoo-screens');
|
|
1798
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
1799
|
+
const stamp = Date.now();
|
|
1800
|
+
const raw = path.join(dir, `oz-${stamp}.png`);
|
|
1801
|
+
const jpg = path.join(dir, `oz-${stamp}.jpg`);
|
|
1802
|
+
await execFileAsync('screencapture', ['-x', '-C', raw], { timeout: 15000 });
|
|
1803
|
+
if (!fs.existsSync(raw) || fs.statSync(raw).size < 80) {
|
|
1804
|
+
throw new Error('screencapture wrote nothing — Screen Recording permission may be off for Grok Bot / Terminal');
|
|
1805
|
+
}
|
|
1806
|
+
try {
|
|
1807
|
+
await execFileAsync('sips', [
|
|
1808
|
+
'-Z', '1400', '-s', 'format', 'jpeg', '-s', 'formatOptions', '70',
|
|
1809
|
+
raw, '--out', jpg,
|
|
1810
|
+
], { timeout: 15000 });
|
|
1811
|
+
if (fs.existsSync(jpg) && fs.statSync(jpg).size > 80) {
|
|
1812
|
+
return { path: jpg, mime: 'image/jpeg', buf: fs.readFileSync(jpg) };
|
|
1813
|
+
}
|
|
1814
|
+
} catch (e) {
|
|
1815
|
+
log(`cursor-backend: sips screenshot resize failed: ${e.message}`);
|
|
1816
|
+
}
|
|
1817
|
+
return { path: raw, mime: 'image/png', buf: fs.readFileSync(raw) };
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1677
1820
|
async function runLocalTool(name, args, log) {
|
|
1678
1821
|
try {
|
|
1679
1822
|
if (name === 'read_file') {
|
|
@@ -1697,6 +1840,24 @@ async function runLocalTool(name, args, log) {
|
|
|
1697
1840
|
if (name === 'exec') {
|
|
1698
1841
|
return await execLocal(String(args.command || ''), args.cwd, log);
|
|
1699
1842
|
}
|
|
1843
|
+
if (name === 'screenshot') {
|
|
1844
|
+
const shot = await captureScreenshot(log);
|
|
1845
|
+
return JSON.stringify({
|
|
1846
|
+
ok: true,
|
|
1847
|
+
path: shot.path,
|
|
1848
|
+
mime: shot.mime,
|
|
1849
|
+
bytes: shot.buf.length,
|
|
1850
|
+
dataUrl: `data:${shot.mime};base64,${shot.buf.toString('base64')}`,
|
|
1851
|
+
});
|
|
1852
|
+
}
|
|
1853
|
+
if (name === 'create_agent') {
|
|
1854
|
+
const agent = mintLocalAgent({
|
|
1855
|
+
name: String(args.name || args.title || 'New Bot').slice(0, 80),
|
|
1856
|
+
});
|
|
1857
|
+
pushCreatedAgent(agent, { select: args.select !== false });
|
|
1858
|
+
log(`cursor-backend: create_agent tool id=${agent.id} name=${JSON.stringify(agent.name)}`);
|
|
1859
|
+
return JSON.stringify({ ok: true, id: agent.id, name: agent.name });
|
|
1860
|
+
}
|
|
1700
1861
|
return `unknown tool ${name}`;
|
|
1701
1862
|
} catch (e) {
|
|
1702
1863
|
return `ERROR ${e.message}`;
|
|
@@ -1718,7 +1879,7 @@ function zooTextFromMessage(msg, data) {
|
|
|
1718
1879
|
return '';
|
|
1719
1880
|
}
|
|
1720
1881
|
|
|
1721
|
-
async function zooComplete(prompt, log, agentId, parsed = {}) {
|
|
1882
|
+
async function zooComplete(prompt, log, agentId, parsed = {}, opts = {}) {
|
|
1722
1883
|
const model = currentModel(agentId);
|
|
1723
1884
|
const helper = localExecSse.size > 0;
|
|
1724
1885
|
const visitor = visitorFromSend(parsed);
|
|
@@ -1726,8 +1887,12 @@ async function zooComplete(prompt, log, agentId, parsed = {}) {
|
|
|
1726
1887
|
const spoken = visitor && !/^You are /.test(String(prompt || ''))
|
|
1727
1888
|
? labeledVisitorPrompt(visitor, prompt)
|
|
1728
1889
|
: prompt;
|
|
1890
|
+
const throwIfAborted = () => {
|
|
1891
|
+
if (opts.signal?.aborted) throw new Error('superseded');
|
|
1892
|
+
};
|
|
1729
1893
|
await ensureTranscriptHydrated(agentId, log);
|
|
1730
1894
|
log(`cursor-backend: zoo POST :8402 model=${model} helper=${helper ? localExecSse.size : 0} hist=${historyMessages(agentId, spoken).length}${chatOnly ? ` visitor=${visitor.shortname} chat-only` : ''} ${JSON.stringify((spoken || '').slice(0, 60))}`);
|
|
1895
|
+
if (typeof opts.onProgress === 'function') opts.onProgress('Working on your Mac…');
|
|
1731
1896
|
|
|
1732
1897
|
const images = [];
|
|
1733
1898
|
const textFiles = [];
|
|
@@ -1783,10 +1948,12 @@ async function zooComplete(prompt, log, agentId, parsed = {}) {
|
|
|
1783
1948
|
: [
|
|
1784
1949
|
`You are ${model} served through openzoo inside Grok Bot.`,
|
|
1785
1950
|
`You HAVE local tools on the user's computer via ${via}.`,
|
|
1786
|
-
'Tools: read_file, write_file, exec, list_dir. USE THEM. Write files to disk instead of pasting giant HTML into chat.',
|
|
1951
|
+
'Tools: read_file, write_file, exec, list_dir, screenshot, create_agent. USE THEM. Write files to disk instead of pasting giant HTML into chat.',
|
|
1952
|
+
'create_agent mints a new Grok Bot in the sidebar. When asked to spawn bots, call it — do not only describe spawning.',
|
|
1953
|
+
'screenshot captures the display and attaches the image. For any on-screen form, dashboard, or click target, screenshot first this turn. Do not guess at UI you have not seen.',
|
|
1787
1954
|
'Pasted images arrive as attachments — you can see them when present. Do not claim you cannot see images if they are in this turn.',
|
|
1788
1955
|
'Never claim you lack filesystem access or local-exec. If a tool errors, report the error.',
|
|
1789
|
-
'After using tools you MUST still write a normal chat reply: what you did, file paths written, and what to open. Empty content is a bug.',
|
|
1956
|
+
'After using tools you MUST still write a normal chat reply: what you did, file paths written, and what to open. Empty content is a bug. A new user message cancels this turn — leave a visible reply before that happens.',
|
|
1790
1957
|
'Do not stop mid-task. Do not write "Stopped on research", "nothing to open yet", "no app files written this turn", or "say go again". Keep using tools until the files the user asked for exist on disk THIS turn. Summarize only after those writes succeed.',
|
|
1791
1958
|
'A spend footer is appended after your reply by the host — ignore it.',
|
|
1792
1959
|
'Prior turns of THIS Grok Bot chat are in the messages below. Do not claim the thread starts blank or that earlier questions did not arrive.',
|
|
@@ -1823,12 +1990,13 @@ async function zooComplete(prompt, log, agentId, parsed = {}) {
|
|
|
1823
1990
|
const usedTools = [];
|
|
1824
1991
|
const KEEP_GOING = 'Do not stop. Do not wait for another message. Write the files to disk now with write_file / exec. Keep going until the requested paths exist. A summary is only allowed after the files are written.';
|
|
1825
1992
|
const zooPost = async (payload) => {
|
|
1993
|
+
throwIfAborted();
|
|
1826
1994
|
const ms = Number(process.env.OPENZOO_ASK_TIMEOUT_MS || 10 * 60_000);
|
|
1827
1995
|
const post = () => fetch('http://127.0.0.1:8402/v1/chat/completions', {
|
|
1828
1996
|
method: 'POST',
|
|
1829
1997
|
headers: { 'content-type': 'application/json', authorization: 'Bearer sk-openzoo' },
|
|
1830
1998
|
body: JSON.stringify(payload),
|
|
1831
|
-
signal: AbortSignal.timeout(ms),
|
|
1999
|
+
signal: combinedAbortSignal(opts.signal, AbortSignal.timeout(ms)),
|
|
1832
2000
|
});
|
|
1833
2001
|
let r;
|
|
1834
2002
|
let lastErr;
|
|
@@ -1872,7 +2040,9 @@ async function zooComplete(prompt, log, agentId, parsed = {}) {
|
|
|
1872
2040
|
return { r, data };
|
|
1873
2041
|
};
|
|
1874
2042
|
let keepGoingNudge = 0;
|
|
2043
|
+
const pendingVision = [];
|
|
1875
2044
|
for (let step = 0; step < maxSteps; step++) {
|
|
2045
|
+
throwIfAborted();
|
|
1876
2046
|
const payload = { model, messages, max_tokens: maxTok };
|
|
1877
2047
|
if (!chatOnly) {
|
|
1878
2048
|
payload.tools = LOCAL_TOOLS;
|
|
@@ -1896,25 +2066,49 @@ async function zooComplete(prompt, log, agentId, parsed = {}) {
|
|
|
1896
2066
|
}
|
|
1897
2067
|
continue;
|
|
1898
2068
|
}
|
|
1899
|
-
|
|
2069
|
+
const names = calls.map((c) => c.function?.name || c.name);
|
|
2070
|
+
log(`cursor-backend: zoo tools step=${step} n=${calls.length} ${names.join(',')}`);
|
|
1900
2071
|
messages.push(msg);
|
|
1901
2072
|
for (const c of calls) {
|
|
2073
|
+
throwIfAborted();
|
|
1902
2074
|
let args = {};
|
|
1903
2075
|
try { args = JSON.parse(c.function?.arguments || c.arguments || '{}'); } catch { args = {}; }
|
|
1904
2076
|
const name = c.function?.name || c.name || '';
|
|
1905
|
-
|
|
2077
|
+
let result = await runLocalTool(name, args, log);
|
|
2078
|
+
if (name === 'screenshot') {
|
|
2079
|
+
try {
|
|
2080
|
+
const shot = JSON.parse(result);
|
|
2081
|
+
if (shot?.dataUrl) {
|
|
2082
|
+
pendingVision.push({ path: shot.path, dataUrl: shot.dataUrl });
|
|
2083
|
+
result = `screenshot ${shot.path} ${shot.bytes}b — image attached, look at it before acting`;
|
|
2084
|
+
}
|
|
2085
|
+
} catch { /* keep raw */ }
|
|
2086
|
+
}
|
|
1906
2087
|
usedTools.push({
|
|
1907
2088
|
name,
|
|
1908
2089
|
path: args.path,
|
|
1909
2090
|
command: args.command ? String(args.command).slice(0, 120) : undefined,
|
|
1910
2091
|
note: String(result).slice(0, 200),
|
|
1911
2092
|
});
|
|
2093
|
+
if (typeof opts.onProgress === 'function') {
|
|
2094
|
+
opts.onProgress(formatZooToolLine({ name, args, result }));
|
|
2095
|
+
}
|
|
1912
2096
|
messages.push({
|
|
1913
2097
|
role: 'tool',
|
|
1914
2098
|
tool_call_id: c.id,
|
|
1915
2099
|
content: String(result).slice(0, 120000),
|
|
1916
2100
|
});
|
|
1917
2101
|
}
|
|
2102
|
+
if (pendingVision.length) {
|
|
2103
|
+
messages.push({
|
|
2104
|
+
role: 'user',
|
|
2105
|
+
content: [
|
|
2106
|
+
...pendingVision.map((img) => ({ type: 'image_url', image_url: { url: img.dataUrl } })),
|
|
2107
|
+
{ type: 'text', text: pendingVision.map((img) => `screenshot: ${img.path}`).join('\n') },
|
|
2108
|
+
],
|
|
2109
|
+
});
|
|
2110
|
+
pendingVision.length = 0;
|
|
2111
|
+
}
|
|
1918
2112
|
continue;
|
|
1919
2113
|
}
|
|
1920
2114
|
text = zooTextFromMessage(msg, data);
|
|
@@ -2081,7 +2275,7 @@ async function handleHijackedPodHttp(req, res, full, body, log) {
|
|
|
2081
2275
|
'getBotTemplateExportPolicy', 'getTeachRecordingStatus', 'isGlobalSearchEnabled',
|
|
2082
2276
|
'isEgressTunnelAvailable', 'listBoxMcpServers',
|
|
2083
2277
|
'setWindowFocused', 'getAgentAutomations',
|
|
2084
|
-
'
|
|
2278
|
+
'requestDiskSaverAudit',
|
|
2085
2279
|
'setAgentUnread', 'setAgentHiddenFromSidebar', 'setAgentNotificationsEnabled',
|
|
2086
2280
|
'setAgentNotifyOnUpdates', 'setAgentAvatarBytes', 'getAgentAvatar',
|
|
2087
2281
|
]);
|
|
@@ -2100,6 +2294,15 @@ async function handleHijackedPodHttp(req, res, full, body, log) {
|
|
|
2100
2294
|
log(`cursor-backend: createAgent local id=${agent.id} name=${JSON.stringify(agent.name)}`);
|
|
2101
2295
|
return true;
|
|
2102
2296
|
}
|
|
2297
|
+
if (name === 'interruptAgentRun') {
|
|
2298
|
+
let parsed = {};
|
|
2299
|
+
try { parsed = JSON.parse(String(body || '{}')); } catch { parsed = {}; }
|
|
2300
|
+
const id = String(parsed.id || parsed.agentId || focusedAgentId || '');
|
|
2301
|
+
const n = id ? zooTurns.abort(id) : 0;
|
|
2302
|
+
jsonSend(res, { ok: true, interrupted: n, id });
|
|
2303
|
+
log(`cursor-backend: interruptAgentRun id=${id || '?'} n=${n}`);
|
|
2304
|
+
return true;
|
|
2305
|
+
}
|
|
2103
2306
|
if (name === 'kickstartAgent') {
|
|
2104
2307
|
let parsed = {};
|
|
2105
2308
|
try { parsed = JSON.parse(String(body || '{}')); } catch { parsed = {}; }
|
|
@@ -2262,6 +2465,7 @@ async function handleHijackedPodHttp(req, res, full, body, log) {
|
|
|
2262
2465
|
log(`cursor-backend: >> sendPrompt agent=${agentId} keys=${Object.keys(parsed).join(',')} attachments=${attN}${visitor ? ` visitor=${visitor.shortname}` : ''} prompt=${JSON.stringify((prompt || '').slice(0, 80))}`);
|
|
2263
2466
|
lastSendEchoId = String(nonce);
|
|
2264
2467
|
jsonSend(res, { accepted: true });
|
|
2468
|
+
const turn = zooTurns.begin(agentId, nonce);
|
|
2265
2469
|
const userLine = fanoutLine(agentId, 'user', uiText, {
|
|
2266
2470
|
clientNonce: nonce,
|
|
2267
2471
|
requestId: nonce,
|
|
@@ -2275,6 +2479,7 @@ async function handleHijackedPodHttp(req, res, full, body, log) {
|
|
|
2275
2479
|
setImmediate(async () => {
|
|
2276
2480
|
let text = '';
|
|
2277
2481
|
let grouped = false;
|
|
2482
|
+
const stillCurrent = () => zooTurns.isCurrent(agentId, nonce) && !turn.signal.aborted;
|
|
2278
2483
|
try {
|
|
2279
2484
|
if (modelCmd) {
|
|
2280
2485
|
const want = modelCmd[1];
|
|
@@ -2298,15 +2503,31 @@ async function handleHijackedPodHttp(req, res, full, body, log) {
|
|
|
2298
2503
|
grouped = true;
|
|
2299
2504
|
await runGroupQueue({ agentId, humanPrompt: prompt, parsed, nonce, log });
|
|
2300
2505
|
} else {
|
|
2301
|
-
const z = await zooComplete(prompt, log, agentId, parsed
|
|
2506
|
+
const z = await zooComplete(prompt, log, agentId, parsed, {
|
|
2507
|
+
signal: turn.signal,
|
|
2508
|
+
onProgress: (note) => {
|
|
2509
|
+
if (!stillCurrent()) return;
|
|
2510
|
+
paintChatUpdate(agentId, note, { clientNonce: nonce, requestId: nonce });
|
|
2511
|
+
},
|
|
2512
|
+
});
|
|
2302
2513
|
text = z.text;
|
|
2303
2514
|
}
|
|
2304
2515
|
}
|
|
2305
2516
|
} catch (e) {
|
|
2517
|
+
if (!stillCurrent() || isSupersededError(e)) {
|
|
2518
|
+
log(`cursor-backend: sendPrompt superseded agent=${agentId} nonce=${nonce} ${e.message}`);
|
|
2519
|
+
zooTurns.end(agentId, nonce);
|
|
2520
|
+
return;
|
|
2521
|
+
}
|
|
2306
2522
|
grouped = false;
|
|
2307
2523
|
text = `openzoo error: ${e.message}`;
|
|
2308
2524
|
log(`cursor-backend: sendPrompt zoo failed: ${e.message}`);
|
|
2309
2525
|
}
|
|
2526
|
+
if (!stillCurrent()) {
|
|
2527
|
+
zooTurns.end(agentId, nonce);
|
|
2528
|
+
log(`cursor-backend: sendPrompt dropped stale agent=${agentId} nonce=${nonce}`);
|
|
2529
|
+
return;
|
|
2530
|
+
}
|
|
2310
2531
|
if (!grouped) {
|
|
2311
2532
|
const line = fanoutLine(agentId, 'assistant', text, { clientNonce: nonce, requestId: nonce });
|
|
2312
2533
|
ssePush('transcript', { ...gatewayEntry(line), agentId });
|
|
@@ -2315,6 +2536,7 @@ async function handleHijackedPodHttp(req, res, full, body, log) {
|
|
|
2315
2536
|
} else {
|
|
2316
2537
|
log(`cursor-backend: sendPrompt group done agent=${agentId}`);
|
|
2317
2538
|
}
|
|
2539
|
+
zooTurns.end(agentId, nonce);
|
|
2318
2540
|
});
|
|
2319
2541
|
return true;
|
|
2320
2542
|
}
|
package/lib/xb
ADDED
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openzoo",
|
|
3
|
-
"version": "0.50.
|
|
3
|
+
"version": "0.50.40",
|
|
4
4
|
"description": "Local x402-paying proxy + MCP server for openzoo.fun — point any OpenAI-compatible harness (Cursor, Claude Code, aider, SDKs) at localhost and it pays per call from a local burner wallet. Solana and Base rails live; Robinhood experimental.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|