openzoo 0.50.44 → 0.50.46

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.
@@ -39,7 +39,7 @@ import { randomUUID } from 'node:crypto';
39
39
  import { encodeAvailableModels, encodeForMethod, encodeEnsureSandBox, encodeGetGrokBotSendStatus, decodeProtoFields, unwrapConnect } from './cursorapi.js';
40
40
  import {
41
41
  accountPodPath, accountAgentsPath, rosterForAccount, rosterForEvent,
42
- readHouseRoster, houseAgentsPath, shapeAgent, agentBrief,
42
+ readHouseRoster, houseAgentsPath, shapeAgent, agentBrief, briefFromName,
43
43
  } from './grokbotAccount.js';
44
44
  import { formatSpendFooter, mergeTurnProof } from './spendProof.js';
45
45
  import { prefixVisitorRichText } from './grokbotweb.js';
@@ -361,8 +361,18 @@ function useHouseRoster() {
361
361
  return Boolean(process.env.OZ_HIJACK_POD) && !sniffOn();
362
362
  }
363
363
  function loadAgents() {
364
- const house = readHouseRoster(HOME, activeAccountId);
365
- return house.length ? house : [];
364
+ const house = readHouseRoster(HOME, activeAccountId) || [];
365
+ const shaped = house.map(shapeAgent);
366
+ const dirty = shaped.some((a, i) => a.brief && a.brief !== String(house[i]?.brief || house[i]?.description || ''));
367
+ if (dirty && shaped.length) {
368
+ writeJsonFile(houseAgentsPath(HOME), shaped);
369
+ if (activeAccountId) {
370
+ const p = accountAgentsPath(HOME, activeAccountId);
371
+ if (p) writeJsonFile(p, shaped);
372
+ }
373
+ }
374
+ for (const a of shaped) seedBriefOnCanvas(a);
375
+ return shaped;
366
376
  }
367
377
  function saveAgents(a) {
368
378
  if (!Array.isArray(a)) return;
@@ -1251,8 +1261,66 @@ function mintLocalAgent(parsed = {}) {
1251
1261
  }
1252
1262
  saveAgents(list.map(shapeAgent));
1253
1263
  agentTranscript(id);
1264
+ seedBriefOnCanvas(agent);
1254
1265
  return agent;
1255
1266
  }
1267
+
1268
+ function entryKickText(e) {
1269
+ if (!e || typeof e !== 'object') return '';
1270
+ if (e.kind === 'send-message') return String(e.message?.content || '');
1271
+ return String(e.content || '');
1272
+ }
1273
+
1274
+ const briefKicked = new Set();
1275
+ function needsJobKick(agent) {
1276
+ if (!agent?.id || !String(agent.brief || '').trim()) return false;
1277
+ if (briefKicked.has(agent.id)) return false;
1278
+ const entries = agentTranscript(agent.id).entries || [];
1279
+ if (!entries.length) return true;
1280
+ if (entries.length > 4) return false;
1281
+ const blob = entries.map(entryKickText).join('\n');
1282
+ if (/\[brief\]/i.test(blob) && entries.length <= 3) {
1283
+ if (/Working from this brief/i.test(blob)) return true;
1284
+ if (entries.length === 1) return true;
1285
+ }
1286
+ return false;
1287
+ }
1288
+
1289
+ function kickBriefedAgent(agent, log = () => {}) {
1290
+ if (!needsJobKick(agent)) return false;
1291
+ briefKicked.add(agent.id);
1292
+ const nonce = `oz-kick-${agent.id}`;
1293
+ log(`cursor-backend: brief-kick start ${agent.id} ${JSON.stringify(agent.name)}`);
1294
+ setImmediate(async () => {
1295
+ try {
1296
+ const z = await zooComplete(
1297
+ 'Your brief is in this thread. Start that job now. Use tools. Do real work. Do not wait for another human message. Do not only say understood.',
1298
+ log,
1299
+ agent.id,
1300
+ {},
1301
+ );
1302
+ const line = fanoutLine(agent.id, 'assistant', z.text, { clientNonce: nonce, requestId: nonce });
1303
+ ssePush('transcript', { ...gatewayEntry(line), agentId: agent.id });
1304
+ bumpAgent(agent.id, { preview: z.text, notify: true });
1305
+ log(`cursor-backend: brief-kick done ${agent.id} seq=${line.seq}`);
1306
+ } catch (e) {
1307
+ briefKicked.delete(agent.id);
1308
+ log(`cursor-backend: brief-kick failed ${agent.id}: ${e.message}`);
1309
+ }
1310
+ });
1311
+ return true;
1312
+ }
1313
+
1314
+ function seedBriefOnCanvas(agent, log) {
1315
+ if (!agent?.id || !String(agent.brief || '').trim()) return false;
1316
+ const t = agentTranscript(agent.id);
1317
+ if (!(t.entries || []).length) {
1318
+ const nonce = `oz-brief-${agent.id}`;
1319
+ const userLine = fanoutLine(agent.id, 'user', `[brief]\n${agent.brief}`, { clientNonce: nonce, requestId: nonce });
1320
+ ssePush('transcript', { ...gatewayEntry(userLine), agentId: agent.id });
1321
+ }
1322
+ return kickBriefedAgent(agent, log || ((m) => console.error(m)));
1323
+ }
1256
1324
  function groupMemberIds(agentId) {
1257
1325
  const a = (cachedAgentList() || []).find((x) => x.id === agentId);
1258
1326
  if (!a?.isGroup) return [];
@@ -2078,12 +2146,12 @@ async function runLocalTool(name, args, log, ctx = {}) {
2078
2146
  return JSON.stringify(got);
2079
2147
  }
2080
2148
  if (name === 'create_agent') {
2081
- const agent = mintLocalAgent({
2082
- name: String(args.name || args.title || 'New Bot').slice(0, 80),
2083
- brief: String(args.brief || args.instructions || ''),
2084
- });
2149
+ const name = String(args.name || args.title || 'New Bot').slice(0, 80);
2150
+ const brief = String(args.brief || args.instructions || '').trim() || briefFromName(name);
2151
+ const agent = mintLocalAgent({ name, brief });
2085
2152
  pushCreatedAgent(agent, { select: args.select !== false });
2086
- log(`cursor-backend: create_agent tool id=${agent.id} name=${JSON.stringify(agent.name)}`);
2153
+ seedBriefOnCanvas(agent);
2154
+ log(`cursor-backend: create_agent tool id=${agent.id} name=${JSON.stringify(agent.name)} brief=${brief ? brief.length : 0}c`);
2087
2155
  return JSON.stringify({ ok: true, id: agent.id, name: agent.name, brief: agent.brief || '' });
2088
2156
  }
2089
2157
  if (name === 'set_brief') {
@@ -111,6 +111,15 @@ function activityTs(agent, activity) {
111
111
  * nulls, then clears the whole persisted tray. That is how a group vanished
112
112
  * after the first send: bumpAgent wrote a partial row, restore returned null.
113
113
  */
114
+ /** Standing job from a sidebar name like "6 · Content Studio" or "Bot 1 — Marketing (X)". */
115
+ export function briefFromName(name) {
116
+ const n = String(name || '').trim();
117
+ if (!n || /^(new bot|chat|group)$/i.test(n)) return '';
118
+ const role = n.replace(/^\d+\s*[·.•.\-—–]+\s*/, '').trim() || n;
119
+ if (role.length < 2) return '';
120
+ return `You are ${n}. Your job is ${role}. Do that job. Do not ask the human to re-brief you. Coordinate with list_agents and message_agent.`;
121
+ }
122
+
114
123
  /** Standing job text. shapeAgent used to drop this, so every restart was amnesia. */
115
124
  export function agentBrief(raw = {}) {
116
125
  const a = raw && typeof raw === 'object' ? raw : {};
@@ -119,7 +128,8 @@ export function agentBrief(raw = {}) {
119
128
  if (typeof v === 'string' && v.trim()) return v.trim().slice(0, 8000);
120
129
  }
121
130
  const d = String(a.description || '').trim();
122
- return d.slice(0, 8000);
131
+ if (d) return d.slice(0, 8000);
132
+ return briefFromName(a.name || a.title).slice(0, 8000);
123
133
  }
124
134
 
125
135
  export function shapeAgent(raw = {}) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openzoo",
3
- "version": "0.50.44",
3
+ "version": "0.50.46",
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",