openzoo 0.50.47 → 0.50.49
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 +176 -3
- package/lib/grokbotAccount.js +66 -0
- package/lib/ozSpendChip.js +19 -17
- package/package.json +1 -1
package/lib/cursorbackend.js
CHANGED
|
@@ -40,6 +40,8 @@ import { encodeAvailableModels, encodeForMethod, encodeEnsureSandBox, encodeGetG
|
|
|
40
40
|
import {
|
|
41
41
|
accountPodPath, accountAgentsPath, rosterForAccount, rosterForEvent,
|
|
42
42
|
readHouseRoster, houseAgentsPath, shapeAgent, agentBrief, briefFromName,
|
|
43
|
+
readWakeups, writeWakeups, shapeWakeup, parseWakeupEvery, wantsWakeupCron,
|
|
44
|
+
DEFAULT_WAKEUP_PROMPT,
|
|
43
45
|
} from './grokbotAccount.js';
|
|
44
46
|
import { formatSpendFooter, mergeTurnProof } from './spendProof.js';
|
|
45
47
|
import { prefixVisitorRichText } from './grokbotweb.js';
|
|
@@ -1181,10 +1183,130 @@ export function createZooTurnQueue() {
|
|
|
1181
1183
|
inflight.delete(id);
|
|
1182
1184
|
return 1;
|
|
1183
1185
|
},
|
|
1186
|
+
busy(agentId) {
|
|
1187
|
+
return inflight.has(String(agentId || ''));
|
|
1188
|
+
},
|
|
1184
1189
|
};
|
|
1185
1190
|
}
|
|
1186
1191
|
const zooTurns = createZooTurnQueue();
|
|
1187
1192
|
|
|
1193
|
+
const wakeupTimers = new Map();
|
|
1194
|
+
let wakeupLog = () => {};
|
|
1195
|
+
let wakeupsRestored = false;
|
|
1196
|
+
|
|
1197
|
+
function persistWakeups(map) {
|
|
1198
|
+
try { writeWakeups(HOME, map); } catch (e) {
|
|
1199
|
+
wakeupLog(`cursor-backend: wakeups save failed: ${e.message}`);
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
export function listAgentWakeups() {
|
|
1204
|
+
return readWakeups(HOME);
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
export function cancelAgentWakeup(agentId) {
|
|
1208
|
+
const id = String(agentId || '');
|
|
1209
|
+
const t = wakeupTimers.get(id);
|
|
1210
|
+
if (t) {
|
|
1211
|
+
clearTimeout(t);
|
|
1212
|
+
wakeupTimers.delete(id);
|
|
1213
|
+
}
|
|
1214
|
+
const map = readWakeups(HOME);
|
|
1215
|
+
if (!map[id]) return { ok: true, cancelled: false };
|
|
1216
|
+
delete map[id];
|
|
1217
|
+
persistWakeups(map);
|
|
1218
|
+
return { ok: true, cancelled: true, id };
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
function armWakeup(rec) {
|
|
1222
|
+
const id = rec.agentId;
|
|
1223
|
+
const prev = wakeupTimers.get(id);
|
|
1224
|
+
if (prev) clearTimeout(prev);
|
|
1225
|
+
const delay = Math.max(1000, Number(rec.nextAt) - Date.now());
|
|
1226
|
+
const t = setTimeout(() => {
|
|
1227
|
+
fireAgentWakeup(id).catch((e) => wakeupLog(`cursor-backend: wakeup fire ${id}: ${e.message}`));
|
|
1228
|
+
}, delay);
|
|
1229
|
+
wakeupTimers.set(id, t);
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
export function scheduleAgentWakeup(agentId, opts = {}) {
|
|
1233
|
+
const id = String(agentId || '').trim();
|
|
1234
|
+
if (!id) return { ok: false, error: 'no agent' };
|
|
1235
|
+
const now = Date.now();
|
|
1236
|
+
const every = opts.every ?? opts.everySec;
|
|
1237
|
+
const rec = shapeWakeup(id, {
|
|
1238
|
+
every,
|
|
1239
|
+
prompt: opts.prompt,
|
|
1240
|
+
lastAt: 0,
|
|
1241
|
+
nextAt: now + parseWakeupEvery(every) * 1000,
|
|
1242
|
+
}, now);
|
|
1243
|
+
const map = readWakeups(HOME);
|
|
1244
|
+
map[id] = rec;
|
|
1245
|
+
persistWakeups(map);
|
|
1246
|
+
armWakeup(rec);
|
|
1247
|
+
wakeupLog(`cursor-backend: wakeup every ${rec.everySec}s agent=${id}`);
|
|
1248
|
+
return { ok: true, ...rec };
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
async function fireAgentWakeup(agentId) {
|
|
1252
|
+
const id = String(agentId || '');
|
|
1253
|
+
const map = readWakeups(HOME);
|
|
1254
|
+
const rec = map[id];
|
|
1255
|
+
if (!rec) {
|
|
1256
|
+
wakeupTimers.delete(id);
|
|
1257
|
+
return;
|
|
1258
|
+
}
|
|
1259
|
+
rec.lastAt = Date.now();
|
|
1260
|
+
rec.nextAt = rec.lastAt + rec.everySec * 1000;
|
|
1261
|
+
persistWakeups(map);
|
|
1262
|
+
armWakeup(rec);
|
|
1263
|
+
if (zooTurns.busy(id)) {
|
|
1264
|
+
wakeupLog(`cursor-backend: wakeup skip busy agent=${id}`);
|
|
1265
|
+
return;
|
|
1266
|
+
}
|
|
1267
|
+
const nonce = `oz-wakeup-${id}-${rec.lastAt}`;
|
|
1268
|
+
const prompt = rec.prompt || DEFAULT_WAKEUP_PROMPT;
|
|
1269
|
+
const turn = zooTurns.begin(id, nonce);
|
|
1270
|
+
const userLine = fanoutLine(id, 'user', `[wakeup]\n${prompt}`, { clientNonce: nonce, requestId: nonce });
|
|
1271
|
+
ssePush('transcript', { ...gatewayEntry(userLine), agentId: id });
|
|
1272
|
+
bumpAgent(id, { preview: '[wakeup]', notify: false });
|
|
1273
|
+
wakeupLog(`cursor-backend: wakeup start agent=${id}`);
|
|
1274
|
+
try {
|
|
1275
|
+
const z = await zooComplete(prompt, wakeupLog, id, {}, {
|
|
1276
|
+
signal: turn.signal,
|
|
1277
|
+
onProgress: (note) => {
|
|
1278
|
+
if (!zooTurns.isCurrent(id, nonce)) return;
|
|
1279
|
+
paintChatUpdate(id, note, { clientNonce: nonce, requestId: nonce });
|
|
1280
|
+
},
|
|
1281
|
+
});
|
|
1282
|
+
if (!zooTurns.isCurrent(id, nonce)) return;
|
|
1283
|
+
const line = fanoutLine(id, 'assistant', z.text, { clientNonce: nonce, requestId: nonce });
|
|
1284
|
+
ssePush('transcript', { ...gatewayEntry(line), agentId: id });
|
|
1285
|
+
bumpAgent(id, { preview: z.text, notify: true });
|
|
1286
|
+
wakeupLog(`cursor-backend: wakeup done agent=${id} seq=${line.seq}`);
|
|
1287
|
+
} catch (e) {
|
|
1288
|
+
if (!isSupersededError(e)) wakeupLog(`cursor-backend: wakeup failed agent=${id}: ${e.message}`);
|
|
1289
|
+
} finally {
|
|
1290
|
+
zooTurns.end(id, nonce);
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
export function restoreAgentWakeups(log = () => {}) {
|
|
1295
|
+
wakeupLog = log;
|
|
1296
|
+
if (wakeupsRestored) return 0;
|
|
1297
|
+
wakeupsRestored = true;
|
|
1298
|
+
const map = readWakeups(HOME);
|
|
1299
|
+
const ids = Object.keys(map);
|
|
1300
|
+
const now = Date.now();
|
|
1301
|
+
ids.forEach((id, i) => {
|
|
1302
|
+
map[id].nextAt = now + 5000 + i * 2000;
|
|
1303
|
+
});
|
|
1304
|
+
if (ids.length) persistWakeups(map);
|
|
1305
|
+
for (const id of ids) armWakeup(map[id]);
|
|
1306
|
+
if (ids.length) log(`cursor-backend: wakeups restored ${ids.length}`);
|
|
1307
|
+
return ids.length;
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1188
1310
|
export function formatZooProgress({ step, maxSteps, names, command } = {}) {
|
|
1189
1311
|
const tools = Array.isArray(names) ? names.filter(Boolean).join(', ') : String(names || 'tools');
|
|
1190
1312
|
const cmd = command ? ` ${JSON.stringify(String(command).slice(0, 80))}` : '';
|
|
@@ -1195,7 +1317,7 @@ export function formatZooProgress({ step, maxSteps, names, command } = {}) {
|
|
|
1195
1317
|
* bubble is still silence. Keep it short; the model history skips ephemeral. */
|
|
1196
1318
|
export function formatZooToolLine({ name, args = {}, result } = {}) {
|
|
1197
1319
|
const detail = args.command || args.path || args.name || args.window
|
|
1198
|
-
|| args.query || args.url || args.key || args.app
|
|
1320
|
+
|| args.query || args.url || args.key || args.app || args.every
|
|
1199
1321
|
|| (args.x != null && args.y != null ? `${args.x},${args.y}` : '')
|
|
1200
1322
|
|| (args.text != null ? String(args.text).slice(0, 80) : '');
|
|
1201
1323
|
const head = detail
|
|
@@ -2003,6 +2125,34 @@ const LOCAL_TOOLS = [
|
|
|
2003
2125
|
},
|
|
2004
2126
|
},
|
|
2005
2127
|
},
|
|
2128
|
+
{
|
|
2129
|
+
type: 'function',
|
|
2130
|
+
function: {
|
|
2131
|
+
name: 'schedule_wakeup',
|
|
2132
|
+
description: 'Host timer that keeps this bot working with no human message. "never stop" / cron. Default every 5m. Min 60s. Does not spawn bots.',
|
|
2133
|
+
parameters: {
|
|
2134
|
+
type: 'object',
|
|
2135
|
+
properties: {
|
|
2136
|
+
every: { type: 'string', description: '5m, 1h, 90s. Default 5m. Floor 60s.' },
|
|
2137
|
+
prompt: { type: 'string', description: 'What to do on each tick.' },
|
|
2138
|
+
agent: { type: 'string', description: 'Name or id. Default: this bot.' },
|
|
2139
|
+
},
|
|
2140
|
+
},
|
|
2141
|
+
},
|
|
2142
|
+
},
|
|
2143
|
+
{
|
|
2144
|
+
type: 'function',
|
|
2145
|
+
function: {
|
|
2146
|
+
name: 'cancel_wakeup',
|
|
2147
|
+
description: 'Stop the host wakeup timer for this bot (or agent).',
|
|
2148
|
+
parameters: {
|
|
2149
|
+
type: 'object',
|
|
2150
|
+
properties: {
|
|
2151
|
+
agent: { type: 'string' },
|
|
2152
|
+
},
|
|
2153
|
+
},
|
|
2154
|
+
},
|
|
2155
|
+
},
|
|
2006
2156
|
];
|
|
2007
2157
|
|
|
2008
2158
|
export const LOCAL_TOOL_NAMES = LOCAL_TOOLS.map((t) => t.function.name);
|
|
@@ -2151,8 +2301,12 @@ async function runLocalTool(name, args, log, ctx = {}) {
|
|
|
2151
2301
|
const agent = mintLocalAgent({ name, brief });
|
|
2152
2302
|
pushCreatedAgent(agent, { select: args.select !== false });
|
|
2153
2303
|
seedBriefOnCanvas(agent);
|
|
2304
|
+
const n = (cachedAgentList() || []).length;
|
|
2154
2305
|
log(`cursor-backend: create_agent tool id=${agent.id} name=${JSON.stringify(agent.name)} brief=${brief ? brief.length : 0}c`);
|
|
2155
|
-
|
|
2306
|
+
const warning = n >= 8
|
|
2307
|
+
? `Mac already has ${n} bots. Prefer schedule_wakeup over spawning. Only mint a NEW named role.`
|
|
2308
|
+
: undefined;
|
|
2309
|
+
return JSON.stringify({ ok: true, id: agent.id, name: agent.name, brief: agent.brief || '', warning });
|
|
2156
2310
|
}
|
|
2157
2311
|
if (name === 'set_brief') {
|
|
2158
2312
|
const who = findAgent(args.agent || args.id || args.name) || findAgent(ctx.agentId);
|
|
@@ -2162,10 +2316,12 @@ async function runLocalTool(name, args, log, ctx = {}) {
|
|
|
2162
2316
|
}
|
|
2163
2317
|
if (name === 'list_agents') {
|
|
2164
2318
|
const list = cachedAgentList() || [];
|
|
2319
|
+
const wakes = readWakeups(HOME);
|
|
2165
2320
|
return JSON.stringify(list.map((a) => ({
|
|
2166
2321
|
id: a.id,
|
|
2167
2322
|
name: a.name,
|
|
2168
2323
|
brief: String(a.brief || '').slice(0, 120),
|
|
2324
|
+
wakeup: wakes[a.id] ? { everySec: wakes[a.id].everySec, nextAt: wakes[a.id].nextAt } : null,
|
|
2169
2325
|
})));
|
|
2170
2326
|
}
|
|
2171
2327
|
if (name === 'message_agent') {
|
|
@@ -2177,6 +2333,17 @@ async function runLocalTool(name, args, log, ctx = {}) {
|
|
|
2177
2333
|
log,
|
|
2178
2334
|
});
|
|
2179
2335
|
}
|
|
2336
|
+
if (name === 'schedule_wakeup') {
|
|
2337
|
+
const who = findAgent(args.agent || args.id || args.name) || findAgent(ctx.agentId);
|
|
2338
|
+
if (!who) return 'ERROR no such agent';
|
|
2339
|
+
const got = scheduleAgentWakeup(who.id, { every: args.every, prompt: args.prompt });
|
|
2340
|
+
return JSON.stringify(got);
|
|
2341
|
+
}
|
|
2342
|
+
if (name === 'cancel_wakeup') {
|
|
2343
|
+
const who = findAgent(args.agent || args.id || args.name) || findAgent(ctx.agentId);
|
|
2344
|
+
if (!who) return 'ERROR no such agent';
|
|
2345
|
+
return JSON.stringify(cancelAgentWakeup(who.id));
|
|
2346
|
+
}
|
|
2180
2347
|
return `unknown tool ${name}`;
|
|
2181
2348
|
} catch (e) {
|
|
2182
2349
|
return `ERROR ${e.message}`;
|
|
@@ -2273,8 +2440,9 @@ async function zooComplete(prompt, log, agentId, parsed = {}, opts = {}) {
|
|
|
2273
2440
|
return brief ? `${who} Standing brief (persisted): ${brief}` : who;
|
|
2274
2441
|
})(),
|
|
2275
2442
|
`You HAVE local tools on the user's computer via ${via}.`,
|
|
2276
|
-
'Tools: read_file, write_file, exec, list_dir, screenshot, click, type_text, key, ui_tree, focus_app, open_url, create_agent, set_brief, list_agents, message_agent.',
|
|
2443
|
+
'Tools: read_file, write_file, exec, list_dir, screenshot, click, type_text, key, ui_tree, focus_app, open_url, create_agent, set_brief, list_agents, message_agent, schedule_wakeup, cancel_wakeup.',
|
|
2277
2444
|
'create_agent mints a sidebar bot. ALWAYS pass brief so they keep the job across restart. set_brief updates it. list_agents + message_agent talk to other bots (one hop). Do not tell the human to copy-paste between canvases.',
|
|
2445
|
+
'NEVER STOP / cron / keep working between human messages: call schedule_wakeup every="5m". That is a host timer. There is no crontab. Do not spawn more bots for persistence. Do not re-read SITREP-NOW.md or STANDING-ORDERS.md every turn — do the next file or click. exec sysctl/uptime is not the job. If the tray already has named workers, answer "no" to spawning more.',
|
|
2278
2446
|
'You CAN click the Mac and fill/submit browser forms. That is required when the user asks. Do not write a markdown briefing instead of clicking. Do not tell the user to click.',
|
|
2279
2447
|
'Form loop: focus_app Brave Browser → screenshot → click the field (query or x,y) → type_text → screenshot to confirm → click Submit / key enter. screenshot.screen is click coordinate space. Prefer click query="Submit".',
|
|
2280
2448
|
'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.',
|
|
@@ -2805,6 +2973,10 @@ async function handleHijackedPodHttp(req, res, full, body, log) {
|
|
|
2805
2973
|
log(`cursor-backend: >> sendPrompt agent=${agentId} keys=${Object.keys(parsed).join(',')} attachments=${attN}${visitor ? ` visitor=${visitor.shortname}` : ''} prompt=${JSON.stringify((prompt || '').slice(0, 80))}`);
|
|
2806
2974
|
lastSendEchoId = String(nonce);
|
|
2807
2975
|
jsonSend(res, { accepted: true });
|
|
2976
|
+
if (!visitor && wantsWakeupCron(prompt)) {
|
|
2977
|
+
const cron = scheduleAgentWakeup(agentId, { every: '5m' });
|
|
2978
|
+
log(`cursor-backend: never-stop cron agent=${agentId} every=${cron.everySec}s`);
|
|
2979
|
+
}
|
|
2808
2980
|
const turn = zooTurns.begin(agentId, nonce);
|
|
2809
2981
|
const userLine = fanoutLine(agentId, 'user', uiText, {
|
|
2810
2982
|
clientNonce: nonce,
|
|
@@ -3013,6 +3185,7 @@ function respond(req, res, method, models) {
|
|
|
3013
3185
|
* unprivileged). Returns { server, port }.
|
|
3014
3186
|
*/
|
|
3015
3187
|
export function startCursorBackend({ port = 8443, models, log = () => {} } = {}) {
|
|
3188
|
+
restoreAgentWakeups(log);
|
|
3016
3189
|
const { cert, key } = ensureCert(log);
|
|
3017
3190
|
const certPem = fs.readFileSync(cert);
|
|
3018
3191
|
const keyPem = fs.readFileSync(key);
|
package/lib/grokbotAccount.js
CHANGED
|
@@ -184,6 +184,72 @@ export function rosterForEvent(list, activity) {
|
|
|
184
184
|
});
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
+
export const WAKEUP_MIN_SEC = 60;
|
|
188
|
+
export const WAKEUP_MAX_SEC = 6 * 3600;
|
|
189
|
+
export const WAKEUP_DEFAULT_SEC = 5 * 60;
|
|
190
|
+
export const DEFAULT_WAKEUP_PROMPT = 'Wakeup. Continue your brief. Do the next real action (write a file, click, or message a worker). Do not only re-read sitrep. Do not spawn more bots. Do not exec sysctl/uptime.';
|
|
191
|
+
|
|
192
|
+
export function wakeupsPath(home) {
|
|
193
|
+
return path.join(home, '.openzoo', 'grokbot-wakeups.json');
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** "5m" / "1h" / "90" / "30s". Floor 60s so a never-stop cron cannot storm. */
|
|
197
|
+
export function parseWakeupEvery(raw) {
|
|
198
|
+
const s = String(raw ?? '').trim().toLowerCase();
|
|
199
|
+
if (!s) return WAKEUP_DEFAULT_SEC;
|
|
200
|
+
const m = s.match(/^(\d+(?:\.\d+)?)\s*(s|sec|secs|seconds?|m|min|mins|minutes?|h|hr|hrs|hours?)?$/);
|
|
201
|
+
if (!m) return WAKEUP_DEFAULT_SEC;
|
|
202
|
+
const n = Number(m[1]);
|
|
203
|
+
if (!Number.isFinite(n) || n <= 0) return WAKEUP_DEFAULT_SEC;
|
|
204
|
+
const u = m[2] || 's';
|
|
205
|
+
let sec = n;
|
|
206
|
+
if (/^m/.test(u)) sec = n * 60;
|
|
207
|
+
else if (/^h/.test(u)) sec = n * 3600;
|
|
208
|
+
return Math.min(WAKEUP_MAX_SEC, Math.max(WAKEUP_MIN_SEC, Math.round(sec)));
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function wantsWakeupCron(prompt) {
|
|
212
|
+
const s = String(prompt || '');
|
|
213
|
+
if (/\bcron\b.{0,48}\bwakeups?\b/i.test(s)) return true;
|
|
214
|
+
if (/\bwakeups?\b.{0,48}\bcron\b/i.test(s)) return true;
|
|
215
|
+
if (/\bnever stop\b/i.test(s)) return true;
|
|
216
|
+
if (/\bschedule\b.{0,24}\bwakeups?\b/i.test(s)) return true;
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export function shapeWakeup(agentId, rec = {}, now = Date.now()) {
|
|
221
|
+
const everySec = parseWakeupEvery(rec.everySec ?? rec.every ?? rec.interval);
|
|
222
|
+
const prompt = String(rec.prompt || DEFAULT_WAKEUP_PROMPT).trim().slice(0, 2000)
|
|
223
|
+
|| DEFAULT_WAKEUP_PROMPT;
|
|
224
|
+
const lastAt = Number(rec.lastAt) || 0;
|
|
225
|
+
let nextAt = Number(rec.nextAt) || 0;
|
|
226
|
+
if (!Number.isFinite(nextAt) || nextAt <= 0) nextAt = now + everySec * 1000;
|
|
227
|
+
return {
|
|
228
|
+
agentId: String(agentId || rec.agentId || ''),
|
|
229
|
+
everySec,
|
|
230
|
+
prompt,
|
|
231
|
+
lastAt,
|
|
232
|
+
nextAt,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export function readWakeups(home) {
|
|
237
|
+
const raw = readJsonFile(wakeupsPath(home));
|
|
238
|
+
const src = raw && typeof raw === 'object' && !Array.isArray(raw) ? raw : {};
|
|
239
|
+
const out = {};
|
|
240
|
+
for (const [id, rec] of Object.entries(src)) {
|
|
241
|
+
const w = shapeWakeup(id, rec);
|
|
242
|
+
if (w.agentId) out[w.agentId] = w;
|
|
243
|
+
}
|
|
244
|
+
return out;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function writeWakeups(home, map) {
|
|
248
|
+
const dir = path.dirname(wakeupsPath(home));
|
|
249
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
250
|
+
fs.writeFileSync(wakeupsPath(home), JSON.stringify(map || {}, null, 2));
|
|
251
|
+
}
|
|
252
|
+
|
|
187
253
|
export function callerKeyFromAuth(authorization) {
|
|
188
254
|
const a = String(authorization || '').trim();
|
|
189
255
|
if (!a) return '';
|
package/lib/ozSpendChip.js
CHANGED
|
@@ -181,20 +181,22 @@ function ozVisibleSpendText(root) {
|
|
|
181
181
|
return s;
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
-
|
|
185
|
-
|
|
184
|
+
/** Per-node. Global keepLen ate list items when vis had no newlines (head ''). */
|
|
185
|
+
export function stripSpendFromText(s) {
|
|
186
|
+
const t = String(s || '');
|
|
187
|
+
const cut = t.search(/::oz-spend::/i);
|
|
188
|
+
if (cut >= 0) return cut > 0 ? t.slice(0, cut) : '';
|
|
189
|
+
if (/^\s*(this call \$|spent \$[0-9.]+(?:\s|·)|tx https?:|memo |proves )/i.test(t)) return '';
|
|
190
|
+
return t;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function ozBlankSpendIn(root) {
|
|
186
194
|
const w = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null);
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
for (let i = 0; i < tns.length; i += 1) {
|
|
190
|
-
const tn = tns[i];
|
|
195
|
+
while (w.nextNode()) {
|
|
196
|
+
const tn = w.currentNode;
|
|
191
197
|
if (tn.parentElement && tn.parentElement.closest && tn.parentElement.closest('.oz-spend')) continue;
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
off += s.length;
|
|
195
|
-
if (off <= keepLen) continue;
|
|
196
|
-
if (start >= keepLen) tn.nodeValue = '';
|
|
197
|
-
else tn.nodeValue = s.slice(0, keepLen - start);
|
|
198
|
+
const next = stripSpendFromText(tn.nodeValue || '');
|
|
199
|
+
if (next !== tn.nodeValue) tn.nodeValue = next;
|
|
198
200
|
}
|
|
199
201
|
}
|
|
200
202
|
|
|
@@ -283,11 +285,10 @@ function ozCollapseSpend() {
|
|
|
283
285
|
if (prev && prev !== host) {
|
|
284
286
|
ozAttachSpendChip(prev, split);
|
|
285
287
|
host.setAttribute('data-oz-spend-hide', '1');
|
|
286
|
-
ozBlankAfter(host, 0);
|
|
287
288
|
continue;
|
|
288
289
|
}
|
|
289
290
|
}
|
|
290
|
-
|
|
291
|
+
ozBlankSpendIn(host);
|
|
291
292
|
ozAttachSpendChip(host, split);
|
|
292
293
|
}
|
|
293
294
|
ozHideSpendLeftovers();
|
|
@@ -310,8 +311,8 @@ export function spendChipSource() {
|
|
|
310
311
|
return [
|
|
311
312
|
'(function ozSpendChip(){',
|
|
312
313
|
"'use strict';",
|
|
313
|
-
'if (window.__OZ_SPEND_CHIP__ ===
|
|
314
|
-
'window.__OZ_SPEND_CHIP__ =
|
|
314
|
+
'if (window.__OZ_SPEND_CHIP__ === 4) return;',
|
|
315
|
+
'window.__OZ_SPEND_CHIP__ = 4;',
|
|
315
316
|
chipUsd.toString(),
|
|
316
317
|
labelFromSpendBody.toString(),
|
|
317
318
|
spendLinesOnly.toString(),
|
|
@@ -320,7 +321,8 @@ export function spendChipSource() {
|
|
|
320
321
|
ozEnsureSpendCss.toString(),
|
|
321
322
|
ozSpendHost.toString(),
|
|
322
323
|
ozVisibleSpendText.toString(),
|
|
323
|
-
|
|
324
|
+
stripSpendFromText.toString(),
|
|
325
|
+
ozBlankSpendIn.toString(),
|
|
324
326
|
ozHideSpendLeftovers.toString(),
|
|
325
327
|
ozPreviousMessageCard.toString(),
|
|
326
328
|
ozAttachSpendChip.toString(),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openzoo",
|
|
3
|
-
"version": "0.50.
|
|
3
|
+
"version": "0.50.49",
|
|
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",
|