bloby-bot 0.28.0 → 0.29.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/LICENSE +75 -0
- package/README.md +12 -0
- package/package.json +3 -2
- package/scripts/tempo-balance.ts +71 -0
- package/scripts/test-mpp-buy.ts +153 -0
- package/worker/prompts/bloby-system-prompt.txt +40 -2
package/LICENSE
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
License text copyright (c) 2020 MariaDB Corporation Ab, All Rights Reserved.
|
|
2
|
+
"Business Source License" is a trademark of MariaDB Corporation Ab.
|
|
3
|
+
|
|
4
|
+
Parameters
|
|
5
|
+
|
|
6
|
+
Licensor: BERTA ONE LLC
|
|
7
|
+
Licensed Work: bloby-bot. The Licensed Work is (c) 2026 BERTA ONE LLC.
|
|
8
|
+
Additional Use Grant: You may make production use of the Licensed Work,
|
|
9
|
+
including for commercial purposes, provided that Your
|
|
10
|
+
use does not consist of offering to third parties a
|
|
11
|
+
hosted, managed, or multi-tenant service that (a)
|
|
12
|
+
provides Bloby agent functionality as a service, and
|
|
13
|
+
(b) operates a relay, marketplace, or equivalent
|
|
14
|
+
backend that substitutes for the services offered at
|
|
15
|
+
bloby.bot.
|
|
16
|
+
|
|
17
|
+
For clarity: deploying Bloby on behalf of a client,
|
|
18
|
+
reselling installation or configuration services,
|
|
19
|
+
running Bloby for Your own internal or single-tenant
|
|
20
|
+
use, and replacing the bloby.bot relay with Your own
|
|
21
|
+
private infrastructure (e.g., Tailscale, custom
|
|
22
|
+
tunnel) for non-competing use are all permitted.
|
|
23
|
+
|
|
24
|
+
For interpretive guidance, please contact
|
|
25
|
+
legal@bloby.bot.
|
|
26
|
+
Change Date: 2028-04-30
|
|
27
|
+
Change License: Apache License, Version 2.0
|
|
28
|
+
|
|
29
|
+
For information about alternative licensing arrangements for the Licensed
|
|
30
|
+
Work, please contact legal@bloby.bot.
|
|
31
|
+
|
|
32
|
+
Notice
|
|
33
|
+
|
|
34
|
+
Business Source License 1.1
|
|
35
|
+
|
|
36
|
+
Terms
|
|
37
|
+
|
|
38
|
+
The Licensor hereby grants you the right to copy, modify, create derivative
|
|
39
|
+
works, redistribute, and make non-production use of the Licensed Work. The
|
|
40
|
+
Licensor may make an Additional Use Grant, above, permitting limited production use.
|
|
41
|
+
|
|
42
|
+
Effective on the Change Date, or the fourth anniversary of the first publicly
|
|
43
|
+
available distribution of a specific version of the Licensed Work under this
|
|
44
|
+
License, whichever comes first, the Licensor hereby grants you rights under
|
|
45
|
+
the terms of the Change License, and the rights granted in the paragraph
|
|
46
|
+
above terminate.
|
|
47
|
+
|
|
48
|
+
If your use of the Licensed Work does not comply with the requirements
|
|
49
|
+
currently in effect as described in this License, you must purchase a
|
|
50
|
+
commercial license from the Licensor, its affiliated entities, or authorized
|
|
51
|
+
resellers, or you must refrain from using the Licensed Work.
|
|
52
|
+
|
|
53
|
+
All copies of the original and modified Licensed Work, and derivative works
|
|
54
|
+
of the Licensed Work, are subject to this License. This License applies
|
|
55
|
+
separately for each version of the Licensed Work and the Change Date may vary
|
|
56
|
+
for each version of the Licensed Work released by Licensor.
|
|
57
|
+
|
|
58
|
+
You must conspicuously display this License on each original or modified copy
|
|
59
|
+
of the Licensed Work. If you receive the Licensed Work in original or
|
|
60
|
+
modified form from a third party, the terms and conditions set forth in this
|
|
61
|
+
License apply to your use of that work.
|
|
62
|
+
|
|
63
|
+
Any use of the Licensed Work in violation of this License will automatically
|
|
64
|
+
terminate your rights under this License for the current and all other
|
|
65
|
+
versions of the Licensed Work.
|
|
66
|
+
|
|
67
|
+
This License does not grant you any right in any trademark or logo of
|
|
68
|
+
Licensor or its affiliates (provided that you may use a trademark or logo of
|
|
69
|
+
Licensor as expressly required by this License).
|
|
70
|
+
|
|
71
|
+
TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
|
|
72
|
+
AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
|
|
73
|
+
EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
|
|
74
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
|
|
75
|
+
TITLE.
|
package/README.md
CHANGED
|
@@ -591,3 +591,15 @@ Quick Tunnel is the default for simplicity -- zero configuration, no Cloudflare
|
|
|
591
591
|
|
|
592
592
|
**Why memory files instead of a database for agent memory?**
|
|
593
593
|
Files are the natural interface for the Claude Agent SDK -- it can read and write them with its built-in tools. No custom tool needed, no API integration. The agent manages its own memory with the same tools it uses to edit code.
|
|
594
|
+
|
|
595
|
+
---
|
|
596
|
+
|
|
597
|
+
## License
|
|
598
|
+
|
|
599
|
+
Bloby is licensed under the **Business Source License 1.1** (BSL 1.1).
|
|
600
|
+
|
|
601
|
+
- **Permitted:** self-hosted personal/internal use, modifications, redistribution, deploying Bloby for clients (consulting/integration), and replacing the `bloby.bot` relay with your own private infrastructure.
|
|
602
|
+
- **Restricted:** offering Bloby as a hosted/managed/multi-tenant service that operates a relay or marketplace competing with `bloby.bot`.
|
|
603
|
+
- **Change Date:** 2028-04-30 — on this date the license auto-converts to **Apache License 2.0**.
|
|
604
|
+
|
|
605
|
+
See [LICENSE](./LICENSE) for the full terms. For commercial licensing inquiries, contact `legal@bloby.bot`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bloby-bot",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.29.0",
|
|
4
4
|
"releaseNotes": [
|
|
5
5
|
"1. # voice note (PTT bubble)",
|
|
6
6
|
"2. # audio file + caption",
|
|
@@ -9,11 +9,12 @@
|
|
|
9
9
|
],
|
|
10
10
|
"description": "Self-hosted, self-evolving AI agent with its own dashboard.",
|
|
11
11
|
"type": "module",
|
|
12
|
-
"license": "
|
|
12
|
+
"license": "BUSL-1.1",
|
|
13
13
|
"bin": {
|
|
14
14
|
"bloby": "./bin/cli.js"
|
|
15
15
|
},
|
|
16
16
|
"files": [
|
|
17
|
+
"LICENSE",
|
|
17
18
|
"bin/",
|
|
18
19
|
"cli/",
|
|
19
20
|
"dist-bloby/",
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// Read on-chain USDC balances for one or more Tempo addresses.
|
|
2
|
+
//
|
|
3
|
+
// Fill in the CONFIG block below, then run from repo root:
|
|
4
|
+
// cp scripts/tempo-balance.ts /tmp/tempo-transfer/ && \
|
|
5
|
+
// (cd /tmp/tempo-transfer && ./node_modules/.bin/tsx tempo-balance.ts)
|
|
6
|
+
//
|
|
7
|
+
// (The script must be executed from inside the isolated /tmp/tempo-transfer/
|
|
8
|
+
// install — the repo's own node_modules has a @noble/hashes version conflict
|
|
9
|
+
// that breaks viem's ESM import resolution. To rebuild the isolated dir if
|
|
10
|
+
// missing: mkdir -p /tmp/tempo-transfer && cd /tmp/tempo-transfer \
|
|
11
|
+
// && npm init -y && npm install viem@2.47.6 tsx)
|
|
12
|
+
//
|
|
13
|
+
// Each entry prints: USDC balance, nonce (0 = wallet has never sent a tx),
|
|
14
|
+
// and whether the address is an EOA or a contract.
|
|
15
|
+
|
|
16
|
+
// ─── CONFIG ──────────────────────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
const ACCOUNTS = [
|
|
19
|
+
{ label: 'Bot wallet', wallet: '0xc65d8a0dB80f637337B87e42b8BEb5D86D46f942' },
|
|
20
|
+
{ label: 'Treasury', wallet: '0x084A80e395FaE8Aaf58F591f47F63DA1EeF06bbC' },
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
import { createPublicClient, http, formatUnits } from 'viem';
|
|
26
|
+
import { tempo } from 'viem/chains';
|
|
27
|
+
|
|
28
|
+
const USDC = '0x20c000000000000000000000b9537d11c60e8b50' as const;
|
|
29
|
+
const DECIMALS = 6;
|
|
30
|
+
const RPC = 'https://rpc.tempo.xyz';
|
|
31
|
+
|
|
32
|
+
const erc20Abi = [{
|
|
33
|
+
name: 'balanceOf',
|
|
34
|
+
type: 'function',
|
|
35
|
+
stateMutability: 'view',
|
|
36
|
+
inputs: [{ name: 'account', type: 'address' }],
|
|
37
|
+
outputs: [{ name: '', type: 'uint256' }],
|
|
38
|
+
}] as const;
|
|
39
|
+
|
|
40
|
+
async function main() {
|
|
41
|
+
const client = createPublicClient({ chain: tempo, transport: http(RPC) });
|
|
42
|
+
const block = await client.getBlockNumber();
|
|
43
|
+
|
|
44
|
+
console.log(`Block: ${block}`);
|
|
45
|
+
console.log('');
|
|
46
|
+
|
|
47
|
+
for (const { label, wallet } of ACCOUNTS) {
|
|
48
|
+
const address = wallet as `0x${string}`;
|
|
49
|
+
|
|
50
|
+
const [balance, nonce, code] = await Promise.all([
|
|
51
|
+
client.readContract({
|
|
52
|
+
address: USDC, abi: erc20Abi, functionName: 'balanceOf', args: [address],
|
|
53
|
+
}),
|
|
54
|
+
client.getTransactionCount({ address }),
|
|
55
|
+
client.getCode({ address }),
|
|
56
|
+
]);
|
|
57
|
+
|
|
58
|
+
const isContract = code && code !== '0x';
|
|
59
|
+
|
|
60
|
+
console.log(`${label} ${address}`);
|
|
61
|
+
console.log(` USDC: ${formatUnits(balance, DECIMALS)}`);
|
|
62
|
+
console.log(` nonce: ${nonce}${nonce === 0 ? ' (never sent a tx)' : ''}`);
|
|
63
|
+
console.log(` type: ${isContract ? 'contract' : 'EOA'}`);
|
|
64
|
+
console.log('');
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
main().catch((err) => {
|
|
69
|
+
console.error(err);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
});
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// End-to-end test for the marketplace single-product MPP buy flow.
|
|
2
|
+
//
|
|
3
|
+
// Fill in the CONFIG block, then run:
|
|
4
|
+
// cp scripts/test-mpp-buy.ts /tmp/tempo-transfer/ && \
|
|
5
|
+
// (cd /tmp/tempo-transfer && ./node_modules/.bin/tsx test-mpp-buy.ts)
|
|
6
|
+
//
|
|
7
|
+
// (The script must run inside the isolated /tmp/tempo-transfer/ install — the
|
|
8
|
+
// repo's @noble/hashes version conflicts with viem's ESM imports here. To
|
|
9
|
+
// rebuild that dir: mkdir -p /tmp/tempo-transfer && cd /tmp/tempo-transfer \
|
|
10
|
+
// && npm init -y && npm install viem@2.47.6 mppx tsx)
|
|
11
|
+
//
|
|
12
|
+
// What it does:
|
|
13
|
+
// 1. Reads the bot wallet's USDC balance + treasury + (optionally) seller.
|
|
14
|
+
// 2. POSTs to /api/marketplace/buy/:productId via the mppx client.
|
|
15
|
+
// 3. If the product has a seller with a wallet, the on-chain charge splits
|
|
16
|
+
// 80/20 — verify both wallets move in the same block.
|
|
17
|
+
// 4. Follows the first download URL to confirm the JWT-gated download works.
|
|
18
|
+
|
|
19
|
+
// ─── CONFIG ──────────────────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
const RELAY = 'https://api.bloby.bot';
|
|
22
|
+
const PRODUCT_ID = '<FILL_ME — e.g. sebastians-skill>';
|
|
23
|
+
|
|
24
|
+
const BOT = {
|
|
25
|
+
relayToken: '<FILL_ME — 64-hex-char relay token>',
|
|
26
|
+
privateKey: '<FILL_ME — bot wallet private key, with or without 0x prefix>',
|
|
27
|
+
wallet: '<FILL_ME — bot wallet address>',
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// Optional: seller wallet to monitor for the 80% split landing.
|
|
31
|
+
// Leave null if the product has no seller (then 100% goes to treasury).
|
|
32
|
+
const SELLER_WALLET: string | null = null;
|
|
33
|
+
|
|
34
|
+
const TREASURY = '0x084A80e395FaE8Aaf58F591f47F63DA1EeF06bbC';
|
|
35
|
+
|
|
36
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
import { Mppx, tempo } from 'mppx/client';
|
|
39
|
+
import { privateKeyToAccount } from 'viem/accounts';
|
|
40
|
+
import { createPublicClient, http, formatUnits } from 'viem';
|
|
41
|
+
import { tempo as tempoChain } from 'viem/chains';
|
|
42
|
+
|
|
43
|
+
const USDC = '0x20c000000000000000000000b9537d11c60e8b50' as const;
|
|
44
|
+
const RPC = 'https://rpc.tempo.xyz';
|
|
45
|
+
|
|
46
|
+
const erc20Abi = [{
|
|
47
|
+
name: 'balanceOf',
|
|
48
|
+
type: 'function',
|
|
49
|
+
stateMutability: 'view',
|
|
50
|
+
inputs: [{ name: 'account', type: 'address' }],
|
|
51
|
+
outputs: [{ name: '', type: 'uint256' }],
|
|
52
|
+
}] as const;
|
|
53
|
+
|
|
54
|
+
function normalizePk(pk: string): `0x${string}` {
|
|
55
|
+
const t = pk.trim();
|
|
56
|
+
return (t.startsWith('0x') ? t : `0x${t}`) as `0x${string}`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function balanceOf(client: any, address: `0x${string}`) {
|
|
60
|
+
return client.readContract({
|
|
61
|
+
address: USDC, abi: erc20Abi, functionName: 'balanceOf', args: [address],
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function main() {
|
|
66
|
+
for (const [k, v] of [['PRODUCT_ID', PRODUCT_ID], ['BOT.relayToken', BOT.relayToken], ['BOT.privateKey', BOT.privateKey], ['BOT.wallet', BOT.wallet]]) {
|
|
67
|
+
if (typeof v !== 'string' || v.startsWith('<')) {
|
|
68
|
+
console.error(`Fill in ${k} before running.`);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const account = privateKeyToAccount(normalizePk(BOT.privateKey));
|
|
74
|
+
if (account.address.toLowerCase() !== BOT.wallet.toLowerCase()) {
|
|
75
|
+
console.error(`privateKey derives ${account.address}, but BOT.wallet is ${BOT.wallet}`);
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const publicClient = createPublicClient({ chain: tempoChain, transport: http(RPC) });
|
|
80
|
+
|
|
81
|
+
const [botBefore, treasuryBefore, sellerBefore] = await Promise.all([
|
|
82
|
+
balanceOf(publicClient, account.address),
|
|
83
|
+
balanceOf(publicClient, TREASURY as `0x${string}`),
|
|
84
|
+
SELLER_WALLET ? balanceOf(publicClient, SELLER_WALLET as `0x${string}`) : Promise.resolve(0n),
|
|
85
|
+
]);
|
|
86
|
+
|
|
87
|
+
console.log(`Wallet: ${account.address}`);
|
|
88
|
+
console.log(`Balance before: ${formatUnits(botBefore, 6)} USDC`);
|
|
89
|
+
console.log(`Treasury before: ${formatUnits(treasuryBefore, 6)} USDC`);
|
|
90
|
+
if (SELLER_WALLET) console.log(`Seller before: ${formatUnits(sellerBefore, 6)} USDC`);
|
|
91
|
+
console.log('');
|
|
92
|
+
|
|
93
|
+
const mppx = Mppx.create({
|
|
94
|
+
polyfill: false,
|
|
95
|
+
methods: [tempo({ account })],
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const url = `${RELAY}/api/marketplace/buy/${PRODUCT_ID}`;
|
|
99
|
+
console.log(`POST ${url}`);
|
|
100
|
+
|
|
101
|
+
const res = await mppx.fetch(url, {
|
|
102
|
+
method: 'POST',
|
|
103
|
+
headers: {
|
|
104
|
+
'X-Bloby-Token': BOT.relayToken,
|
|
105
|
+
'Content-Type': 'application/json',
|
|
106
|
+
},
|
|
107
|
+
body: JSON.stringify({}),
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const body = await res.text();
|
|
111
|
+
const receipt = res.headers.get('Payment-Receipt');
|
|
112
|
+
console.log(`Status: ${res.status}`);
|
|
113
|
+
console.log(`Body: ${body}`);
|
|
114
|
+
console.log(`Payment-Receipt: ${receipt ?? '(none)'}`);
|
|
115
|
+
console.log('');
|
|
116
|
+
|
|
117
|
+
if (!res.ok) {
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const parsed = JSON.parse(body);
|
|
122
|
+
if (parsed.skills?.length) {
|
|
123
|
+
console.log(`Bought ${parsed.skills.length} skill(s) via "${parsed.paidVia}":`);
|
|
124
|
+
for (const skill of parsed.skills) {
|
|
125
|
+
console.log(` - ${skill.name}@${skill.version} → ${skill.url}`);
|
|
126
|
+
}
|
|
127
|
+
console.log('');
|
|
128
|
+
|
|
129
|
+
// Verify the first download URL actually returns a tarball
|
|
130
|
+
const dlUrl = parsed.skills[0].url;
|
|
131
|
+
console.log(`Probing download: HEAD ${dlUrl}`);
|
|
132
|
+
const dl = await fetch(dlUrl, { method: 'GET' });
|
|
133
|
+
const contentLength = dl.headers.get('Content-Length');
|
|
134
|
+
console.log(` status: ${dl.status} content-type: ${dl.headers.get('Content-Type')} size: ${contentLength ?? '?'} bytes`);
|
|
135
|
+
// drain body to release connection
|
|
136
|
+
if (dl.body) await dl.arrayBuffer();
|
|
137
|
+
console.log('');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const [botAfter, treasuryAfter, sellerAfter] = await Promise.all([
|
|
141
|
+
balanceOf(publicClient, account.address),
|
|
142
|
+
balanceOf(publicClient, TREASURY as `0x${string}`),
|
|
143
|
+
SELLER_WALLET ? balanceOf(publicClient, SELLER_WALLET as `0x${string}`) : Promise.resolve(0n),
|
|
144
|
+
]);
|
|
145
|
+
console.log(`Balance after: ${formatUnits(botAfter, 6)} USDC (Δ ${formatUnits(botAfter - botBefore, 6)})`);
|
|
146
|
+
console.log(`Treasury after: ${formatUnits(treasuryAfter, 6)} USDC (Δ +${formatUnits(treasuryAfter - treasuryBefore, 6)})`);
|
|
147
|
+
if (SELLER_WALLET) console.log(`Seller after: ${formatUnits(sellerAfter, 6)} USDC (Δ +${formatUnits(sellerAfter - sellerBefore, 6)})`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
main().catch((err) => {
|
|
151
|
+
console.error(err);
|
|
152
|
+
process.exit(1);
|
|
153
|
+
});
|
|
@@ -325,13 +325,16 @@ curl -s -X POST https://api.bloby.bot/api/services/test-mpp/use \
|
|
|
325
325
|
# → "PINEAPPLE-MPP-OK · paid via {free|balance|mpp} · 2026-…"
|
|
326
326
|
```
|
|
327
327
|
|
|
328
|
-
For services that may fall back to MPP, use the `mppx` CLI to auto-handle the 402 → sign → retry loop
|
|
328
|
+
For services that may fall back to MPP, use the `mppx` CLI to auto-handle the 402 → sign → retry loop. **The mppx CLI uses its own OS-keychain by default and does NOT know about your wallet** — pass your private key inline via `MPPX_PRIVATE_KEY` (which mppx checks before the keychain):
|
|
329
329
|
|
|
330
330
|
```bash
|
|
331
|
-
|
|
331
|
+
MPPX_PRIVATE_KEY=$(jq -r .wallet.privateKey ~/.bloby/config.json) \
|
|
332
|
+
npx -y mppx http://api.bloby.bot/api/services/test-mpp/use \
|
|
332
333
|
-X POST -H "X-Bloby-Token: $RELAY_TOKEN"
|
|
333
334
|
```
|
|
334
335
|
|
|
336
|
+
If you see `ACCOUNT_NOT_FOUND - No account found.`, you forgot to set `MPPX_PRIVATE_KEY` — the CLI looked in its empty keychain. Re-run with the env var.
|
|
337
|
+
|
|
335
338
|
**Wallet on disk:** `~/.bloby/config.json` field `wallet` — `address` is public, `privateKey` is secret. Never print the private key in chat. The address is fine to share.
|
|
336
339
|
|
|
337
340
|
**Running low?** Tell your human you need funds. Don't mention USDC, Tempo, or crypto unless they ask — to them, it's just adding dollars to your wallet via the "Add Funds" button in the chat header.
|
|
@@ -369,6 +372,21 @@ Always prefer editing existing files over creating new ones. This prevents file
|
|
|
369
372
|
## Careful Execution
|
|
370
373
|
Consider the reversibility and blast radius of actions. Prefer `trash` over `rm` — recoverable beats gone forever. If something fails, pivot — don't retry the same thing blindly. Read error messages carefully and address root causes.
|
|
371
374
|
|
|
375
|
+
## Stop looping (HARD RULE)
|
|
376
|
+
|
|
377
|
+
If your second attempt at fixing a problem produces the same error as the first, **STOP**. Do not try a third variation of the same family of fix.
|
|
378
|
+
|
|
379
|
+
- If the error is identical, your diagnosis is wrong. Re-read the error from scratch, ignoring your previous theory.
|
|
380
|
+
- Run a different *kind* of check (read the file, check the filesystem, inspect logs) — not another *kind* of fix.
|
|
381
|
+
- If you genuinely can't identify the root cause after re-diagnosing, escalate: tell your human plainly what's broken, what you tried, and ask them to restart bloby (`bloby restart` from their terminal). Asking for help is faster than burning their time on a doomed loop.
|
|
382
|
+
|
|
383
|
+
Examples of patterns that count as "the same family of fix":
|
|
384
|
+
- Clearing Vite cache → installing dummy dep → touching files → adding new dep — these are all "force Vite to re-bundle" and count as ONE attempt, not four.
|
|
385
|
+
- Restarting the same process twice in a row.
|
|
386
|
+
- Running `npm install` repeatedly when the underlying error is unchanged.
|
|
387
|
+
|
|
388
|
+
A user staring at a black dashboard while you try the seventh variation of the same fix is worse off than a user who got told "I think node_modules is in a bad state, can you restart bloby?" after the second failure.
|
|
389
|
+
|
|
372
390
|
## Parallel Operations
|
|
373
391
|
Run independent tool calls in parallel. Don't serialize what can run concurrently. When you need to read multiple files, read them all at once.
|
|
374
392
|
|
|
@@ -503,6 +521,26 @@ Before installing, check if a suitable package is already in `node_modules/`. Pr
|
|
|
503
521
|
|
|
504
522
|
**Never** run `npm install` from the parent directory or modify the parent's `package.json`. Your dependencies are sandboxed to workspace — this boundary is enforced at the runtime level.
|
|
505
523
|
|
|
524
|
+
### Hard rule: imports MUST have matching installed packages
|
|
525
|
+
|
|
526
|
+
Every `import` statement you add to a file must correspond to a real package in `node_modules/`. Workflow:
|
|
527
|
+
|
|
528
|
+
1. **Install first, import second.** Run `npm install <pkg>` BEFORE you save a file that imports it. Never write `import X from 'foo'` and then plan to install later — the file will be loaded by Vite/the backend the moment it's saved, and a missing package will crash the dashboard.
|
|
529
|
+
2. **Confirm it landed.** After `npm install`, verify with `ls workspace/node_modules/<pkg>/package.json` (or `cat` the workspace `package.json` to confirm it was added to `dependencies`). If the directory isn't there, the install didn't actually take.
|
|
530
|
+
3. **Same rule for delegated work.** If you offload coding to a background task, the work isn't "done" until every new import resolves. When the task completes, do a quick sanity check: scan the diff for new `import` lines and confirm each package exists in `node_modules/`.
|
|
531
|
+
4. **Never use `--no-save`.** Always let `npm install` write to `package.json`. Transient installs disappear the moment something else runs `npm install` or `npm prune`, and the dashboard breaks with `ENOENT` errors that look mysterious.
|
|
532
|
+
|
|
533
|
+
### Diagnosing Vite / dev-server errors
|
|
534
|
+
|
|
535
|
+
When the dashboard shows a black screen or Vite logs an error, READ the error before acting. Don't reflexively clear caches.
|
|
536
|
+
|
|
537
|
+
- **`Error: ENOENT: no such file or directory, open '.../node_modules/<pkg>/...'`** → The package is **physically missing on disk**. The fix is `npm install <pkg>` — full stop. Clearing `.vite/deps/`, touching files, adding dummy deps, or restarting will NOT recreate the missing file.
|
|
538
|
+
- **`Failed to resolve import "<pkg>"`** with no path in the error → Same diagnosis: package isn't installed. Run `npm install <pkg>`.
|
|
539
|
+
- **Pre-bundling / optimizer errors that don't reference a missing path, OR errors that persist after dependency changes** → Vite's dep cache is stale. Clear it: `rm -rf workspace/node_modules/.vite` and reload. (This is the ONLY case where clearing the cache helps.)
|
|
540
|
+
- **Backend crash loop** → Read `.backend.log`. Don't guess.
|
|
541
|
+
|
|
542
|
+
If you've tried a fix and the same error recurs, do NOT try a variation of the same fix. Re-diagnose from the error message, or stop and ask your human to restart bloby. See "Stop looping" below.
|
|
543
|
+
|
|
506
544
|
## Backend Lifecycle (Critical)
|
|
507
545
|
|
|
508
546
|
The supervisor manages the backend process. You don't need to manage it yourself.
|