openzoo 0.43.8 → 0.44.0
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 +12 -4
- package/lib/namespace.js +81 -18
- package/package.json +1 -1
package/lib/grokui.mjs
CHANGED
|
@@ -529,7 +529,7 @@ const APP_HTML = `<!doctype html>
|
|
|
529
529
|
.tname { font-size: 14px; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
530
530
|
.tprev { font-size: 12px; color: #8e8e93; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
531
531
|
.tdot { width: 8px; height: 8px; border-radius: 50%; background: #0a84ff; flex: 0 0 8px; }
|
|
532
|
-
#main { flex: 1; display: flex; flex-direction: column; height: 100vh; }
|
|
532
|
+
#main { flex: 1; min-width: 0; display: flex; flex-direction: column; height: 100vh; }
|
|
533
533
|
#chatHeader { padding: 14px 20px; border-bottom: 1px solid #1c1c1e; display: flex; align-items: center; gap: 10px;
|
|
534
534
|
font-weight: 600; }
|
|
535
535
|
#chatHeader .tavatar { width: 26px; height: 26px; border-radius: 7px; font-size: 11px; flex: 0 0 26px; }
|
|
@@ -549,16 +549,23 @@ const APP_HTML = `<!doctype html>
|
|
|
549
549
|
#hud .hember { color: #f28c4d; }
|
|
550
550
|
#hud .hfoot { border-top: 1px solid #333340; margin-top: 10px; padding-top: 8px; color: #999aa8; font-size: 10px; }
|
|
551
551
|
#sidebar, #main { -webkit-app-region: no-drag; }
|
|
552
|
-
#log { flex: 1;
|
|
552
|
+
#log { flex: 1; min-width: 0; overflow-y: auto; overflow-x: hidden; padding: 20px 24px 12px;
|
|
553
|
+
display: flex; flex-direction: column; gap: 6px;
|
|
553
554
|
-webkit-user-select: text; user-select: text; }
|
|
554
555
|
.hdr { align-self: flex-start; display: flex; align-items: center; gap: 6px; margin: 12px 0 4px;
|
|
555
556
|
color: #8e8e93; font-size: 13px; }
|
|
556
557
|
.hdr .avatar { width: 18px; height: 18px; border-radius: 5px; display: flex; align-items: center;
|
|
557
558
|
justify-content: center; color: #fff; font-size: 9px; font-weight: 700; }
|
|
558
|
-
.
|
|
559
|
+
/* min-width:0 is load-bearing. A flex item defaults to min-width:auto, so it
|
|
560
|
+
refuses to shrink below its content's intrinsic width — one long
|
|
561
|
+
unbreakable line (a curl command, a JSON blob) in a <pre> then stretches
|
|
562
|
+
the row past max-width, widens the whole column, and pushes the header's
|
|
563
|
+
cost button off-screen. */
|
|
564
|
+
.row { display: flex; max-width: 78%; min-width: 0; margin: 2px 0; }
|
|
559
565
|
.row.user { align-self: flex-end; }
|
|
560
566
|
.row.bot { align-self: flex-start; }
|
|
561
567
|
.bubble { padding: 11px 16px; border-radius: 20px; white-space: pre-wrap; word-break: break-word;
|
|
568
|
+
min-width: 0; max-width: 100%; overflow-x: hidden;
|
|
562
569
|
-webkit-user-select: text; user-select: text; cursor: text; }
|
|
563
570
|
.bubble a { color: #6ab0ff; text-decoration: underline; cursor: pointer; }
|
|
564
571
|
/* rendered markdown. The bubble is pre-wrap for plain text, but block
|
|
@@ -582,7 +589,8 @@ const APP_HTML = `<!doctype html>
|
|
|
582
589
|
.md-table th { background: #1c1c1e; font-weight: 600; }
|
|
583
590
|
.bubble-images { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 8px; }
|
|
584
591
|
.bubble-images img { max-width: 160px; max-height: 160px; border-radius: 12px; display: block; }
|
|
585
|
-
.runcard { background: #1c1c1e; border: 1px solid #333; border-radius: 14px; padding: 12px 14px;
|
|
592
|
+
.runcard { background: #1c1c1e; border: 1px solid #333; border-radius: 14px; padding: 12px 14px;
|
|
593
|
+
max-width: 100%; min-width: 0; overflow: hidden; }
|
|
586
594
|
.runcmd { font-family: Menlo, monospace; font-size: 12.5px; color: #ececec; white-space: pre-wrap;
|
|
587
595
|
word-break: break-word; margin-bottom: 8px; }
|
|
588
596
|
.runactions { display: flex; gap: 8px; }
|
package/lib/namespace.js
CHANGED
|
@@ -15,26 +15,30 @@ import { loadOrCreateWallet } from './wallet.js';
|
|
|
15
15
|
* restarts and reveal nothing. It is hashed again server-side, so the value on
|
|
16
16
|
* the wire is not the address either.
|
|
17
17
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* stale cached build), so a corpus bound by a
|
|
24
|
-
* invisible to a chat sent through an older proxy —
|
|
25
|
-
*
|
|
26
|
-
* whole body at full price. MEASURED twice in one session
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
18
|
+
* ALWAYS ON. There is no flag.
|
|
19
|
+
*
|
|
20
|
+
* It used to be opt-in and off, because a header the client merely ASSERTED
|
|
21
|
+
* was the wrong mechanism: the surfaces disagree in practice (the MCP server,
|
|
22
|
+
* the CLI and the proxy are separate processes upgraded at different times,
|
|
23
|
+
* and npx happily serves a stale cached build), so a corpus bound by a
|
|
24
|
+
* namespaced `zoo_bind` went invisible to a chat sent through an older proxy —
|
|
25
|
+
* the context vanished, spill binds 400'd, and the gateway fell open and
|
|
26
|
+
* forwarded the whole body at full price. MEASURED twice in one session.
|
|
27
|
+
*
|
|
28
|
+
* That objection was about an UNVERIFIED claim and no longer holds: the value
|
|
29
|
+
* is signed (see signNamespace) and the gateway REQUIRES the signature,
|
|
30
|
+
* deriving the tenant from the proven signer. A flag was worse than useless
|
|
31
|
+
* here — off meant every caller shared one tenant, where a context is
|
|
32
|
+
* protected only by its id being unguessable and anyone may write into the
|
|
33
|
+
* same space, and a flag that is sometimes off is exactly the version skew
|
|
34
|
+
* that made contexts vanish. One behaviour, every surface, always.
|
|
35
|
+
*
|
|
36
|
+
* BREAKING: corpora bound before this lived in the shared tenant and are not
|
|
37
|
+
* reachable from a signed request. Re-bind them.
|
|
33
38
|
*/
|
|
34
39
|
let cached = null;
|
|
35
40
|
|
|
36
41
|
export function namespaceHeaderValue() {
|
|
37
|
-
if (process.env.OPENZOO_NAMESPACE !== '1') return '';
|
|
38
42
|
if (cached) return cached;
|
|
39
43
|
try {
|
|
40
44
|
const w = loadOrCreateWallet();
|
|
@@ -47,8 +51,67 @@ export function namespaceHeaderValue() {
|
|
|
47
51
|
return cached;
|
|
48
52
|
}
|
|
49
53
|
|
|
50
|
-
/**
|
|
54
|
+
/**
|
|
55
|
+
* PROVE the namespace claim instead of merely asserting it.
|
|
56
|
+
*
|
|
57
|
+
* The header above is derived from the PUBLIC key, so on its own it is not
|
|
58
|
+
* authentication: anyone who knows this wallet's address can recompute the
|
|
59
|
+
* same value and land in the same sidecar tenant — and since the sidecar gates
|
|
60
|
+
* context access purely on tenant match, that is the whole door. The gateway
|
|
61
|
+
* (nsauth.ts) verifies a signature over `openzoo-namespace:<ns>:<ts>` and
|
|
62
|
+
* derives the tenant from sha256(chain:signer:namespace), which binds the
|
|
63
|
+
* PROVEN signer in, so another wallet signing an identical label lands
|
|
64
|
+
* somewhere else.
|
|
65
|
+
*
|
|
66
|
+
* Signed with the same Solana key x402 already pays from — no new secret, and
|
|
67
|
+
* nothing leaves the machine but a public key and a signature. The timestamp
|
|
68
|
+
* is what bounds replay (the gateway enforces its own window), so it is minted
|
|
69
|
+
* per call and these headers are deliberately NOT cached.
|
|
70
|
+
*/
|
|
71
|
+
const PKCS8_ED25519_PREFIX = Buffer.from('302e020100300506032b657004220420', 'hex');
|
|
72
|
+
|
|
73
|
+
function signNamespace(namespace) {
|
|
74
|
+
const w = loadOrCreateWallet();
|
|
75
|
+
const timestamp = String(Date.now());
|
|
76
|
+
// Node has no raw-ed25519 signer, but it will build one from a PKCS8 DER
|
|
77
|
+
// wrapper around the 32-byte seed (a Solana secretKey is seed||pubkey).
|
|
78
|
+
const der = Buffer.concat([PKCS8_ED25519_PREFIX, Buffer.from(w.keypair.secretKey.slice(0, 32))]);
|
|
79
|
+
const key = crypto.createPrivateKey({ key: der, format: 'der', type: 'pkcs8' });
|
|
80
|
+
const sig = crypto.sign(null, Buffer.from(`openzoo-namespace:${namespace}:${timestamp}`), key);
|
|
81
|
+
return {
|
|
82
|
+
'x-openzoo-namespace-sig': base58encode(sig),
|
|
83
|
+
'x-openzoo-namespace-signer': w.keypair.publicKey.toBase58(),
|
|
84
|
+
'x-openzoo-namespace-ts': timestamp,
|
|
85
|
+
'x-openzoo-namespace-chain': 'solana',
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Merge the namespace header — and its proof — into any headers object. */
|
|
51
90
|
export function withNamespace(headers = {}) {
|
|
52
91
|
const ns = namespaceHeaderValue();
|
|
53
|
-
|
|
92
|
+
if (!ns) return headers;
|
|
93
|
+
// No unsigned fallback. The gateway REQUIRES the signature, so a bare
|
|
94
|
+
// namespace buys nothing — it would just be silently demoted to the shared
|
|
95
|
+
// tenant, which looks like "my corpus vanished" instead of a clear failure.
|
|
96
|
+
return { ...headers, 'x-openzoo-namespace': ns, ...signNamespace(ns) };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Bitcoin-alphabet base58, matching what the gateway's bs58 decode expects.
|
|
100
|
+
// Local rather than a dependency for the same reason lib/x402.js keeps its own.
|
|
101
|
+
const B58 = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
|
|
102
|
+
function base58encode(bytes) {
|
|
103
|
+
const digits = [0];
|
|
104
|
+
for (const byte of bytes) {
|
|
105
|
+
let carry = byte;
|
|
106
|
+
for (let j = 0; j < digits.length; j++) {
|
|
107
|
+
carry += digits[j] << 8;
|
|
108
|
+
digits[j] = carry % 58;
|
|
109
|
+
carry = (carry / 58) | 0;
|
|
110
|
+
}
|
|
111
|
+
while (carry > 0) { digits.push(carry % 58); carry = (carry / 58) | 0; }
|
|
112
|
+
}
|
|
113
|
+
let out = '';
|
|
114
|
+
for (const b of bytes) { if (b === 0) out += '1'; else break; } // leading zeros
|
|
115
|
+
for (let i = digits.length - 1; i >= 0; i--) out += B58[digits[i]];
|
|
116
|
+
return out;
|
|
54
117
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openzoo",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.44.0",
|
|
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",
|