openzoo 0.48.10 → 0.48.11
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/grokui.mjs +45 -4
- package/package.json +1 -1
package/lib/grokui.mjs
CHANGED
|
@@ -29,7 +29,17 @@ const STORE_FILE = path.join(STORE_DIR, 'grokui-threads.json');
|
|
|
29
29
|
// chat. safeResolveIn rejects any path that would escape that thread's root
|
|
30
30
|
// (../, absolute paths, symlink tricks via normalize) — access is real, but
|
|
31
31
|
// always contained to whatever root was explicitly chosen for that thread.
|
|
32
|
-
|
|
32
|
+
// Where a thread's WRITE/READ/RUN/LS/GLOB/GREP are scoped by default.
|
|
33
|
+
//
|
|
34
|
+
// Overridable because a BOX puts uploaded files somewhere else: box-server
|
|
35
|
+
// unpacks them into /workspace, while this defaulted to ~/.openzoo/grokui-
|
|
36
|
+
// workspace. So a user uploaded a 670-part archive, asked a bot to find it,
|
|
37
|
+
// and got "GLOB **/prooffront: no matches" — the bot was searching an empty
|
|
38
|
+
// directory and looked broken while the files sat one path away. The site had
|
|
39
|
+
// resorted to printing "grokui: /dir /workspace" as a hint for the user to
|
|
40
|
+
// fix it by hand every time.
|
|
41
|
+
const WORKSPACE_DIR = process.env.OZ_WORKSPACE_DIR
|
|
42
|
+
|| path.join(homedir(), '.openzoo', 'grokui-workspace');
|
|
33
43
|
mkdirSync(WORKSPACE_DIR, { recursive: true });
|
|
34
44
|
function expandHome(p) { return p.startsWith('~') ? path.join(homedir(), p.slice(1)) : p; }
|
|
35
45
|
function dirFor(threadId) { return threads.get(threadId)?.dir || WORKSPACE_DIR; }
|
|
@@ -1227,10 +1237,18 @@ async function runTurn(threadId, userText, onEvent, images) {
|
|
|
1227
1237
|
}
|
|
1228
1238
|
if (t.runMode === 'auto') extras.push({ role: 'system', content: AUTO_DIRECTIVE });
|
|
1229
1239
|
const callMsgs = extras.length ? [...t.messages, ...extras] : t.messages;
|
|
1240
|
+
const ask = async () => (onEvent
|
|
1241
|
+
? (await brainStream(callMsgs, (delta) => onEvent({ type: 'delta', name: t.name, color: t.color, delta }), t.contextId, t.model)).trim()
|
|
1242
|
+
: (await brain(callMsgs, t.contextId, t.model)).trim());
|
|
1230
1243
|
try {
|
|
1231
|
-
reply =
|
|
1232
|
-
|
|
1233
|
-
|
|
1244
|
+
reply = await ask();
|
|
1245
|
+
// An EMPTY completion is transient far more often than it is meaningful —
|
|
1246
|
+
// it showed up repeatedly as a dead "(no response)" bubble that cost the
|
|
1247
|
+
// user a turn and told them nothing. Retry once before giving up, and if
|
|
1248
|
+
// it is still empty, say what actually happened instead of "(no
|
|
1249
|
+
// response)", which reads like the harness broke.
|
|
1250
|
+
if (!reply) reply = await ask();
|
|
1251
|
+
if (!reply) reply = '(the model returned an empty completion — usually a transient provider hiccup or a stop-sequence firing early. Say "continue" to retry.)';
|
|
1234
1252
|
} catch (e) {
|
|
1235
1253
|
reply = `error: ${e.message}`;
|
|
1236
1254
|
}
|
|
@@ -1283,6 +1301,29 @@ async function runTurn(threadId, userText, onEvent, images) {
|
|
|
1283
1301
|
t.status = 'idle';
|
|
1284
1302
|
t.lastActivityAt = Date.now();
|
|
1285
1303
|
saveThreads();
|
|
1304
|
+
|
|
1305
|
+
// AUTO CONTINUES AFTER *ANY* DIRECTIVE, not just RUN.
|
|
1306
|
+
//
|
|
1307
|
+
// Only the RUN branch above fed its output back and looped, so a turn that
|
|
1308
|
+
// used SPAWN / WRITE / EDIT / MCP / GLOB executed one directive, posted its
|
|
1309
|
+
// ack, and stopped dead — the user had to type "continue" to get each
|
|
1310
|
+
// subsequent step, on every single turn, which is not what auto means. The
|
|
1311
|
+
// model never saw its own directive's result either, so it could not react
|
|
1312
|
+
// to "no matches" or "already exists".
|
|
1313
|
+
//
|
|
1314
|
+
// Same budget as RUN (shared t.autoSteps, reset when the user speaks), so
|
|
1315
|
+
// this cannot spend more than a chained RUN loop already could.
|
|
1316
|
+
if (t.runMode === 'auto' && ack !== null && ack !== undefined) {
|
|
1317
|
+
t.autoSteps = (t.autoSteps || 0) + 1;
|
|
1318
|
+
if (t.autoSteps < AUTO_MAX_STEPS) {
|
|
1319
|
+
runTurn(threadId, `(directive result)\n${ack}`, onEvent).catch(() => {});
|
|
1320
|
+
return;
|
|
1321
|
+
}
|
|
1322
|
+
const note = `(auto stopped after ${AUTO_MAX_STEPS} steps — say "continue" to keep going)`;
|
|
1323
|
+
t.history.push({ who: 'bot', text: note });
|
|
1324
|
+
onEvent?.({ type: 'final', name: t.name, color: t.color, text: note });
|
|
1325
|
+
saveThreads();
|
|
1326
|
+
}
|
|
1286
1327
|
bindThread(t).catch(() => {});
|
|
1287
1328
|
}
|
|
1288
1329
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openzoo",
|
|
3
|
-
"version": "0.48.
|
|
3
|
+
"version": "0.48.11",
|
|
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",
|