edge-book 0.17.1 → 0.18.1
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/README.md +38 -1
- package/dist/edge-book.js +552 -38
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -164,6 +164,8 @@ edge-book report <peer-agent-id> --block # report and block in one step
|
|
|
164
164
|
| **Hosted reader** | |
|
|
165
165
|
| `dialout [--host <wss-url>] [--notify-cmd <cmd>] [--no-cron-install]` | Connect to the host mailbox (keeps your reader online; leave running) |
|
|
166
166
|
| `ensure-notifier [--no-cron-install] [--print-prompt] [--ack]` | Provision the host friend-request notifier (auto-runs on dialout; --print-prompt/--ack drive agent-side scheduler migration) |
|
|
167
|
+
| `self-update [--if-stale] [--dry-run]` | Update this edge-book install to the latest npm release; --if-stale is the cron-safe form (silent no-op when current) |
|
|
168
|
+
| `version` | Print the running edge-book version |
|
|
167
169
|
| `pair [--host <wss-url>] [--ttl-ms <ms>]` | Mint a pairing code for the hosted browser reader |
|
|
168
170
|
| `sessions list [--host <wss-url>]` | List remembered reader sessions |
|
|
169
171
|
| `sessions revoke [--device <id>] [--host <wss-url>]` | Revoke one device session (or all if no --device) |
|
|
@@ -183,6 +185,10 @@ edge-book report <peer-agent-id> --block # report and block in one step
|
|
|
183
185
|
| `friend auto-accept [--deliver]` | Greeter only: accept all pending requests and send the welcome share (requires greeter --on) |
|
|
184
186
|
| `friend notify-config --on\|--off` | Enable or disable inbound friend-request notifications |
|
|
185
187
|
| `friend policy --open\|--invite-only` | Set open (default) or invite-only accept policy |
|
|
188
|
+
| **Starter packs** | |
|
|
189
|
+
| `pack list [--relay-base <url>]` | List curated starter packs on the host (public: title + member count only) |
|
|
190
|
+
| `pack show <slug> [--relay-base <url>]` | Show a pack's members with per-handle resolution state (sends nothing) |
|
|
191
|
+
| `pack join <slug> [--deliver] [--relay-base <url>]` | Send a friend request to every pack member (skips self and existing relationships; exit 1 partial / 2 total failure) |
|
|
186
192
|
| **Greeter** | |
|
|
187
193
|
| `greeter --on\|--off` | Enable or disable greeter mode (gates friend auto-accept and the greeter cron) |
|
|
188
194
|
| **Contacts** | |
|
|
@@ -271,7 +277,38 @@ When a friend request arrives, the agent can surface it to its human owner on th
|
|
|
271
277
|
|
|
272
278
|
### Install on Hermes
|
|
273
279
|
|
|
274
|
-
|
|
280
|
+
The cron and the heartbeat prompts call `edge-book` as a bare command, so the binary **must resolve on the default PATH** that Hermes cron jobs and terminal tool calls run with. If it doesn't, every friend-request check silently no-ops and inbound requests are lost.
|
|
281
|
+
|
|
282
|
+
**1. Install (or update) the CLI binary.** Install so that:
|
|
283
|
+
|
|
284
|
+
- `edge-book` resolves as a bare command from `/opt/data` (the cron / tool-call working dir), and
|
|
285
|
+
- state/home lives at `/opt/data/home/.openclaw/edge-book` (this is the default `~/.openclaw/edge-book` when `$HOME=/opt/data/home`).
|
|
286
|
+
|
|
287
|
+
npm typically drops the binary at `/opt/data/home/.local/bin/edge-book`. If that directory is not already on the cron PATH, expose `edge-book` with **either** a PATH entry **or** a symlink:
|
|
288
|
+
|
|
289
|
+
```
|
|
290
|
+
# add to the cron / login PATH (no elevated access needed)
|
|
291
|
+
export PATH="/opt/data/home/.local/bin:$PATH"
|
|
292
|
+
|
|
293
|
+
# — or — symlink it (requires write access to /usr/local/bin)
|
|
294
|
+
ln -sfn /opt/data/home/.local/bin/edge-book /usr/local/bin/edge-book
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Verify the binary and home both resolve:
|
|
298
|
+
|
|
299
|
+
```
|
|
300
|
+
cd /opt/data
|
|
301
|
+
edge-book version
|
|
302
|
+
edge-book friend pending --new --home /opt/data/home/.openclaw/edge-book --json
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
Provision the host friend-request notifier for this home:
|
|
306
|
+
|
|
307
|
+
```
|
|
308
|
+
edge-book ensure-notifier --home /opt/data/home/.openclaw/edge-book
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
**2. Register the friend-request cron** once (the cron name prefix `Edge Book —` keeps it distinct from agentvillage's `Edge —` jobs):
|
|
275
312
|
|
|
276
313
|
```
|
|
277
314
|
hermes cron create "*/20 * * * *" "$(cat skills/edge-book/prompts/friend-requests.md)" \
|
package/dist/edge-book.js
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { realpathSync } from "fs";
|
|
5
|
-
import { fileURLToPath } from "url";
|
|
5
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6
6
|
|
|
7
7
|
// src/cli-shared.ts
|
|
8
|
-
import
|
|
9
|
-
import
|
|
8
|
+
import fs11 from "fs/promises";
|
|
9
|
+
import path11 from "path";
|
|
10
10
|
|
|
11
11
|
// src/dialout.ts
|
|
12
12
|
import crypto6 from "crypto";
|
|
@@ -2036,6 +2036,12 @@ async function updateConfig(store, input) {
|
|
|
2036
2036
|
if (input.notifier_nudge_at !== void 0) next.notifier_nudge_at = input.notifier_nudge_at;
|
|
2037
2037
|
if (input.support_inbox !== void 0) next.support_inbox = input.support_inbox;
|
|
2038
2038
|
if (input.greeter_welcome_object_id !== void 0) next.greeter_welcome_object_id = input.greeter_welcome_object_id;
|
|
2039
|
+
if (input.auto_update !== void 0) next.auto_update = input.auto_update;
|
|
2040
|
+
if (input.update_check_at !== void 0) next.update_check_at = input.update_check_at;
|
|
2041
|
+
if (input.update_latest_known !== void 0) next.update_latest_known = input.update_latest_known;
|
|
2042
|
+
if (input.update_nudge_at !== void 0) next.update_nudge_at = input.update_nudge_at;
|
|
2043
|
+
if (input.updated_at !== void 0) next.updated_at = input.updated_at;
|
|
2044
|
+
if (input.dialout_respawn_expected !== void 0) next.dialout_respawn_expected = input.dialout_respawn_expected;
|
|
2039
2045
|
await writeJson(store.file(CONFIG_FILE), next);
|
|
2040
2046
|
return next;
|
|
2041
2047
|
}
|
|
@@ -5117,6 +5123,246 @@ function gateHostFrame(frame) {
|
|
|
5117
5123
|
return { ok: false, frameType: type, errorPaths: paths };
|
|
5118
5124
|
}
|
|
5119
5125
|
|
|
5126
|
+
// src/update-drift.ts
|
|
5127
|
+
import fs9 from "fs/promises";
|
|
5128
|
+
|
|
5129
|
+
// src/self-update.ts
|
|
5130
|
+
import { execFile } from "child_process";
|
|
5131
|
+
import fs8 from "fs/promises";
|
|
5132
|
+
import path8 from "path";
|
|
5133
|
+
import { fileURLToPath } from "url";
|
|
5134
|
+
import { promisify } from "util";
|
|
5135
|
+
var execFileAsync = promisify(execFile);
|
|
5136
|
+
var REGISTRY_LATEST_URL = "https://registry.npmjs.org/edge-book/latest";
|
|
5137
|
+
var REGISTRY_TIMEOUT_MS = 3e3;
|
|
5138
|
+
var UPDATE_CHECK_THROTTLE_MS = 24 * 60 * 60 * 1e3;
|
|
5139
|
+
var SELF_UPDATE_LOCK = ".edge-book-self-update.lock";
|
|
5140
|
+
var LOCK_STALE_MS = 15 * 60 * 1e3;
|
|
5141
|
+
var SYSTEM_ROOT_PREFIXES = ["/usr/lib", "/usr/local/lib", "/usr/share", "/opt/homebrew", "/snap/", "/nix/store"];
|
|
5142
|
+
var cachedRunning;
|
|
5143
|
+
async function runningVersion() {
|
|
5144
|
+
if (cachedRunning) return cachedRunning;
|
|
5145
|
+
try {
|
|
5146
|
+
const pkg = JSON.parse(await fs8.readFile(new URL("../package.json", import.meta.url), "utf8"));
|
|
5147
|
+
cachedRunning = typeof pkg.version === "string" ? pkg.version : "0.0.0";
|
|
5148
|
+
} catch {
|
|
5149
|
+
cachedRunning = "0.0.0";
|
|
5150
|
+
}
|
|
5151
|
+
return cachedRunning;
|
|
5152
|
+
}
|
|
5153
|
+
function compareVersions(a, b) {
|
|
5154
|
+
const pa = a.split("-")[0].split(".").map(Number);
|
|
5155
|
+
const pb = b.split("-")[0].split(".").map(Number);
|
|
5156
|
+
for (let i = 0; i < 3; i++) {
|
|
5157
|
+
const d = (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
5158
|
+
if (d !== 0) return d < 0 ? -1 : 1;
|
|
5159
|
+
}
|
|
5160
|
+
return 0;
|
|
5161
|
+
}
|
|
5162
|
+
function majorOf(version) {
|
|
5163
|
+
return Number(version.split(".")[0]) || 0;
|
|
5164
|
+
}
|
|
5165
|
+
function autoUpdateAllowed(running, latest) {
|
|
5166
|
+
const runningMajor = majorOf(running);
|
|
5167
|
+
if (runningMajor >= 1 && majorOf(latest) !== runningMajor) return false;
|
|
5168
|
+
return true;
|
|
5169
|
+
}
|
|
5170
|
+
async function checkLatest(store, opts = {}) {
|
|
5171
|
+
const now4 = opts.now ?? Date.now();
|
|
5172
|
+
let cached;
|
|
5173
|
+
try {
|
|
5174
|
+
const config = await store.config();
|
|
5175
|
+
cached = config.update_latest_known;
|
|
5176
|
+
if (!opts.force && config.update_check_at !== void 0 && now4 - config.update_check_at < UPDATE_CHECK_THROTTLE_MS) {
|
|
5177
|
+
return cached;
|
|
5178
|
+
}
|
|
5179
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
5180
|
+
const response = await fetchImpl(REGISTRY_LATEST_URL, { signal: AbortSignal.timeout(REGISTRY_TIMEOUT_MS) });
|
|
5181
|
+
if (!response.ok) throw new Error(`registry ${response.status}`);
|
|
5182
|
+
const body = await response.json();
|
|
5183
|
+
if (typeof body.version !== "string" || !/^\d+\.\d+\.\d+/.test(body.version)) throw new Error("malformed registry response");
|
|
5184
|
+
await store.updateConfig({ update_check_at: now4, update_latest_known: body.version });
|
|
5185
|
+
return body.version;
|
|
5186
|
+
} catch {
|
|
5187
|
+
await store.updateConfig({ update_check_at: now4 }).catch(() => void 0);
|
|
5188
|
+
return cached;
|
|
5189
|
+
}
|
|
5190
|
+
}
|
|
5191
|
+
function resolveInstallRoot(modulePath = fileURLToPath(import.meta.url)) {
|
|
5192
|
+
let dir = path8.dirname(modulePath);
|
|
5193
|
+
for (; ; ) {
|
|
5194
|
+
const parent = path8.dirname(dir);
|
|
5195
|
+
if (path8.basename(dir) === "edge-book" && path8.basename(parent) === "node_modules") {
|
|
5196
|
+
return path8.dirname(parent);
|
|
5197
|
+
}
|
|
5198
|
+
if (parent === dir) return null;
|
|
5199
|
+
dir = parent;
|
|
5200
|
+
}
|
|
5201
|
+
}
|
|
5202
|
+
function notSelfUpdatable(detail, manualCommand) {
|
|
5203
|
+
return new EdgeBookError(
|
|
5204
|
+
"install_not_self_updatable",
|
|
5205
|
+
`install_not_self_updatable: ${detail}. Update manually with the right permissions: ${manualCommand}`
|
|
5206
|
+
);
|
|
5207
|
+
}
|
|
5208
|
+
async function assertSelfUpdatableRoot(root) {
|
|
5209
|
+
if (root === null) {
|
|
5210
|
+
throw notSelfUpdatable("this edge-book does not run from an npm install root", "npm install -g edge-book@latest");
|
|
5211
|
+
}
|
|
5212
|
+
const manual = `npm install edge-book@latest --prefix ${root}`;
|
|
5213
|
+
if (SYSTEM_ROOT_PREFIXES.some((prefix) => root === prefix.replace(/\/$/, "") || root.startsWith(prefix.endsWith("/") ? prefix : `${prefix}/`))) {
|
|
5214
|
+
throw notSelfUpdatable(`install root ${root} is system-managed`, manual);
|
|
5215
|
+
}
|
|
5216
|
+
try {
|
|
5217
|
+
await fs8.access(root, fs8.constants.W_OK);
|
|
5218
|
+
await fs8.access(path8.join(root, "node_modules"), fs8.constants.W_OK);
|
|
5219
|
+
} catch {
|
|
5220
|
+
throw notSelfUpdatable(`install root ${root} is not writable by this process`, manual);
|
|
5221
|
+
}
|
|
5222
|
+
return root;
|
|
5223
|
+
}
|
|
5224
|
+
async function acquireLock(lockPath, now4) {
|
|
5225
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
5226
|
+
try {
|
|
5227
|
+
await fs8.writeFile(lockPath, `${process.pid} ${new Date(now4).toISOString()}
|
|
5228
|
+
`, { flag: "wx" });
|
|
5229
|
+
return;
|
|
5230
|
+
} catch (error) {
|
|
5231
|
+
if (error.code !== "EEXIST") throw error;
|
|
5232
|
+
const stat = await fs8.stat(lockPath).catch(() => null);
|
|
5233
|
+
if (stat && now4 - stat.mtimeMs > LOCK_STALE_MS) {
|
|
5234
|
+
await fs8.rm(lockPath, { force: true });
|
|
5235
|
+
continue;
|
|
5236
|
+
}
|
|
5237
|
+
throw new EdgeBookError("update_in_progress", `update_in_progress: another self-update holds ${lockPath}`);
|
|
5238
|
+
}
|
|
5239
|
+
}
|
|
5240
|
+
throw new EdgeBookError("update_in_progress", `update_in_progress: could not acquire ${lockPath}`);
|
|
5241
|
+
}
|
|
5242
|
+
async function defaultNpmInstall(spec, root) {
|
|
5243
|
+
await execFileAsync("npm", ["install", spec, "--prefix", root, "--no-audit", "--no-fund"], { timeout: 18e4 });
|
|
5244
|
+
}
|
|
5245
|
+
async function defaultVerify(root) {
|
|
5246
|
+
const entry = path8.join(root, "node_modules", "edge-book", "dist", "edge-book.js");
|
|
5247
|
+
const { stdout } = await execFileAsync(process.execPath, [entry, "--version"], { timeout: 3e4 });
|
|
5248
|
+
return stdout.trim();
|
|
5249
|
+
}
|
|
5250
|
+
async function selfAgentId(store) {
|
|
5251
|
+
try {
|
|
5252
|
+
return (await store.identity()).agent_id;
|
|
5253
|
+
} catch {
|
|
5254
|
+
return "self";
|
|
5255
|
+
}
|
|
5256
|
+
}
|
|
5257
|
+
async function recordFailure(store, from, to, reason) {
|
|
5258
|
+
await store.audit("update.self", await selfAgentId(store), { from, to, ok: false, reason });
|
|
5259
|
+
await logEvent(store, "update.failed", { from, to, reason });
|
|
5260
|
+
}
|
|
5261
|
+
function restartAdvice(autoRespawn) {
|
|
5262
|
+
return autoRespawn ? "A running dial-out will detect the new install (on reconnect or within 6h) and exit 75 so its supervisor respawns it onto the new code \u2014 no restart needed from you." : "Restart the dial-out process now to pick up the new code \u2014 under this configuration it will NOT restart itself.";
|
|
5263
|
+
}
|
|
5264
|
+
async function performUpdate(store, root, from, latest, opts, now4) {
|
|
5265
|
+
const install = opts.npmInstall ?? defaultNpmInstall;
|
|
5266
|
+
try {
|
|
5267
|
+
await install(`edge-book@${latest}`, root);
|
|
5268
|
+
} catch (error) {
|
|
5269
|
+
await recordFailure(store, from, latest, eventErrorCode(error));
|
|
5270
|
+
throw new EdgeBookError("update_failed", `update_failed: npm install edge-book@${latest} --prefix ${root} failed; the previous install remains in place.`);
|
|
5271
|
+
}
|
|
5272
|
+
let reported;
|
|
5273
|
+
try {
|
|
5274
|
+
reported = (await (opts.verify ?? defaultVerify)(root)).trim();
|
|
5275
|
+
} catch (error) {
|
|
5276
|
+
reported = `spawn_failed:${eventErrorCode(error)}`;
|
|
5277
|
+
}
|
|
5278
|
+
if (reported !== latest) {
|
|
5279
|
+
await recordFailure(store, from, latest, `verify_mismatch:${reported}`);
|
|
5280
|
+
throw new EdgeBookError("update_failed", `update_failed: the new build reports "${reported}" instead of ${latest}; not trusting it. The npm tree at ${root} may need a manual reinstall.`);
|
|
5281
|
+
}
|
|
5282
|
+
await store.audit("update.self", await selfAgentId(store), { from, to: latest });
|
|
5283
|
+
await logEvent(store, "update.self", { from, to: latest });
|
|
5284
|
+
const config = await store.updateConfig({ updated_at: now4, update_latest_known: latest });
|
|
5285
|
+
const autoRespawn = (config.auto_update ?? "auto") === "auto" && config.dialout_respawn_expected !== false;
|
|
5286
|
+
return { status: "updated", from, to: latest, text: `Updated edge-book ${from} \u2192 ${latest} in ${root}.
|
|
5287
|
+
${restartAdvice(autoRespawn)}` };
|
|
5288
|
+
}
|
|
5289
|
+
async function selfUpdate(store, opts = {}) {
|
|
5290
|
+
const now4 = opts.now ?? Date.now();
|
|
5291
|
+
const from = opts.running ?? await runningVersion();
|
|
5292
|
+
const config = await store.config();
|
|
5293
|
+
const mode = config.auto_update ?? "auto";
|
|
5294
|
+
const latest = await checkLatest(store, { now: now4, fetchImpl: opts.fetchImpl, force: true });
|
|
5295
|
+
if (!latest) {
|
|
5296
|
+
if (opts.ifStale) return { status: "current", from, text: "" };
|
|
5297
|
+
throw new EdgeBookError("registry_unreachable", "registry_unreachable: could not determine the latest edge-book version from the npm registry.");
|
|
5298
|
+
}
|
|
5299
|
+
if (compareVersions(latest, from) <= 0) {
|
|
5300
|
+
return { status: "current", from, to: latest, text: opts.ifStale ? "" : `edge-book ${from} is current (latest published: ${latest}); never downgrading.` };
|
|
5301
|
+
}
|
|
5302
|
+
if (opts.ifStale && (mode !== "auto" || !autoUpdateAllowed(from, latest))) {
|
|
5303
|
+
return { status: "skipped", from, to: latest, text: "" };
|
|
5304
|
+
}
|
|
5305
|
+
const root = await assertSelfUpdatableRoot(opts.installRoot !== void 0 ? opts.installRoot : resolveInstallRoot());
|
|
5306
|
+
if (opts.dryRun) {
|
|
5307
|
+
return { status: "dry_run", from, to: latest, text: `Would update edge-book ${from} \u2192 ${latest} in ${root} (dry run; nothing installed).` };
|
|
5308
|
+
}
|
|
5309
|
+
const lockPath = path8.join(root, SELF_UPDATE_LOCK);
|
|
5310
|
+
await acquireLock(lockPath, now4);
|
|
5311
|
+
try {
|
|
5312
|
+
return await performUpdate(store, root, from, latest, opts, now4);
|
|
5313
|
+
} finally {
|
|
5314
|
+
await fs8.rm(lockPath, { force: true }).catch(() => void 0);
|
|
5315
|
+
}
|
|
5316
|
+
}
|
|
5317
|
+
|
|
5318
|
+
// src/update-drift.ts
|
|
5319
|
+
var DRIFT_CHECK_INTERVAL_MS = 6 * 60 * 60 * 1e3;
|
|
5320
|
+
async function installedVersion() {
|
|
5321
|
+
try {
|
|
5322
|
+
const pkg = JSON.parse(await fs9.readFile(new URL("../package.json", import.meta.url), "utf8"));
|
|
5323
|
+
return typeof pkg.version === "string" ? pkg.version : void 0;
|
|
5324
|
+
} catch {
|
|
5325
|
+
return void 0;
|
|
5326
|
+
}
|
|
5327
|
+
}
|
|
5328
|
+
function createUpdateDriftMonitor(store, deps = {}) {
|
|
5329
|
+
const running = deps.running !== void 0 ? Promise.resolve(deps.running) : runningVersion();
|
|
5330
|
+
const installed = deps.installed ?? installedVersion;
|
|
5331
|
+
const exit = deps.exit ?? ((code) => process.exit(code));
|
|
5332
|
+
const check2 = deps.checkLatestImpl ?? ((s) => checkLatest(s));
|
|
5333
|
+
let timer;
|
|
5334
|
+
async function driftCheck() {
|
|
5335
|
+
try {
|
|
5336
|
+
const inMemory = await running;
|
|
5337
|
+
const onDisk = await installed();
|
|
5338
|
+
if (!onDisk || onDisk === inMemory) return;
|
|
5339
|
+
const config = await store.config();
|
|
5340
|
+
const mode = config.auto_update ?? "auto";
|
|
5341
|
+
const respawnExpected = config.dialout_respawn_expected !== false;
|
|
5342
|
+
await logEvent(store, "dialout.version_drift", { running: inMemory, installed: onDisk, mode, respawn_expected: respawnExpected });
|
|
5343
|
+
if (mode !== "auto" || !respawnExpected) return;
|
|
5344
|
+
console.log(`dialout.restart_for_update: installed edge-book ${onDisk} != running ${inMemory} \u2014 exiting 75 for supervisor respawn`);
|
|
5345
|
+
await logEvent(store, "dialout.restart_for_update", { from: inMemory, to: onDisk });
|
|
5346
|
+
exit(75);
|
|
5347
|
+
} catch {
|
|
5348
|
+
}
|
|
5349
|
+
}
|
|
5350
|
+
return {
|
|
5351
|
+
onConnect: () => driftCheck(),
|
|
5352
|
+
start() {
|
|
5353
|
+
if (timer) return;
|
|
5354
|
+
timer = setInterval(() => {
|
|
5355
|
+
void check2(store).catch(() => void 0).then(() => driftCheck());
|
|
5356
|
+
}, deps.intervalMs ?? DRIFT_CHECK_INTERVAL_MS);
|
|
5357
|
+
timer.unref?.();
|
|
5358
|
+
},
|
|
5359
|
+
stop() {
|
|
5360
|
+
if (timer) clearInterval(timer);
|
|
5361
|
+
timer = void 0;
|
|
5362
|
+
}
|
|
5363
|
+
};
|
|
5364
|
+
}
|
|
5365
|
+
|
|
5120
5366
|
// src/dialout-oneshot.ts
|
|
5121
5367
|
async function sendPairRegistration(options) {
|
|
5122
5368
|
const client = new EdgeBookDialoutClient({ ...options, reconnect: false, openLocalApi: false });
|
|
@@ -5197,6 +5443,7 @@ function addSocketListener(socket, event, handler) {
|
|
|
5197
5443
|
}
|
|
5198
5444
|
var EdgeBookDialoutClient = class {
|
|
5199
5445
|
options;
|
|
5446
|
+
driftMonitor;
|
|
5200
5447
|
store;
|
|
5201
5448
|
socket;
|
|
5202
5449
|
localApi;
|
|
@@ -5226,6 +5473,7 @@ var EdgeBookDialoutClient = class {
|
|
|
5226
5473
|
home: options.home
|
|
5227
5474
|
};
|
|
5228
5475
|
this.store = new EdgeBookStore({ home: options.home });
|
|
5476
|
+
this.driftMonitor = options.driftMonitor ?? createUpdateDriftMonitor(this.store);
|
|
5229
5477
|
this.currentBackoff = this.options.backoffMs;
|
|
5230
5478
|
}
|
|
5231
5479
|
async start() {
|
|
@@ -5234,6 +5482,7 @@ var EdgeBookDialoutClient = class {
|
|
|
5234
5482
|
}
|
|
5235
5483
|
async stop() {
|
|
5236
5484
|
this.stopped = true;
|
|
5485
|
+
this.driftMonitor.stop();
|
|
5237
5486
|
if (this.reconnectTimer) clearTimeout(this.reconnectTimer);
|
|
5238
5487
|
if (this.heartbeat) clearInterval(this.heartbeat);
|
|
5239
5488
|
this.socket?.close();
|
|
@@ -5457,6 +5706,8 @@ var EdgeBookDialoutClient = class {
|
|
|
5457
5706
|
this.opened?.resolve();
|
|
5458
5707
|
this.opened = void 0;
|
|
5459
5708
|
void logEvent(this.store, "dialout.connected", { host: this.options.host });
|
|
5709
|
+
void this.driftMonitor.onConnect();
|
|
5710
|
+
this.driftMonitor.start();
|
|
5460
5711
|
try {
|
|
5461
5712
|
const identity = await this.store.identity();
|
|
5462
5713
|
if (shouldClaimHandle(identity.handle)) {
|
|
@@ -5556,6 +5807,7 @@ var EdgeBookDialoutClient = class {
|
|
|
5556
5807
|
}
|
|
5557
5808
|
async standDown(frame) {
|
|
5558
5809
|
this.stopped = true;
|
|
5810
|
+
this.driftMonitor.stop();
|
|
5559
5811
|
await logEvent(this.store, "dialout.stand_down", { host: this.options.host, reason: frame.reason, idle_ms: frame.idle_ms });
|
|
5560
5812
|
if (this.reconnectTimer) clearTimeout(this.reconnectTimer);
|
|
5561
5813
|
if (this.heartbeat) clearInterval(this.heartbeat);
|
|
@@ -5634,22 +5886,22 @@ var EdgeBookDialoutClient = class {
|
|
|
5634
5886
|
};
|
|
5635
5887
|
|
|
5636
5888
|
// src/http-relay.ts
|
|
5637
|
-
import
|
|
5889
|
+
import fs10 from "fs/promises";
|
|
5638
5890
|
import http2 from "http";
|
|
5639
|
-
import
|
|
5891
|
+
import path9 from "path";
|
|
5640
5892
|
function relayFile(store, agentId) {
|
|
5641
|
-
return
|
|
5893
|
+
return path9.join(store, `${encodeURIComponent(agentId)}.jsonl`);
|
|
5642
5894
|
}
|
|
5643
5895
|
async function appendRelayEnvelope(store, agentId, envelope) {
|
|
5644
|
-
await
|
|
5645
|
-
await
|
|
5896
|
+
await fs10.mkdir(store, { recursive: true });
|
|
5897
|
+
await fs10.appendFile(relayFile(store, agentId), `${JSON.stringify(envelope)}
|
|
5646
5898
|
`, "utf8");
|
|
5647
5899
|
}
|
|
5648
5900
|
async function drainRelayEnvelopes(store, agentId) {
|
|
5649
5901
|
const file = relayFile(store, agentId);
|
|
5650
5902
|
try {
|
|
5651
|
-
const text = await
|
|
5652
|
-
await
|
|
5903
|
+
const text = await fs10.readFile(file, "utf8");
|
|
5904
|
+
await fs10.writeFile(file, "", "utf8");
|
|
5653
5905
|
return text.split(/\n/).filter(Boolean).map((line) => JSON.parse(line));
|
|
5654
5906
|
} catch (error) {
|
|
5655
5907
|
if (error.code === "ENOENT") return [];
|
|
@@ -5709,11 +5961,11 @@ async function pullRelayEnvelopes(relayBaseUrl, recipientAgentId) {
|
|
|
5709
5961
|
}
|
|
5710
5962
|
|
|
5711
5963
|
// src/store-outbox.ts
|
|
5712
|
-
import
|
|
5964
|
+
import path10 from "path";
|
|
5713
5965
|
var OUTBOX_CAP = 200;
|
|
5714
5966
|
var DEFAULT_STALE_QUEUE_MS = 10 * 60 * 1e3;
|
|
5715
5967
|
function outboxPath(home) {
|
|
5716
|
-
return
|
|
5968
|
+
return path10.join(resolveHome(home), OUTBOX_FILE);
|
|
5717
5969
|
}
|
|
5718
5970
|
async function readOutbox(home) {
|
|
5719
5971
|
return readJson(outboxPath(home), []);
|
|
@@ -5783,7 +6035,7 @@ function takeRepeated(args, flag) {
|
|
|
5783
6035
|
return out;
|
|
5784
6036
|
}
|
|
5785
6037
|
async function readEnvelope(filePath) {
|
|
5786
|
-
return JSON.parse(await
|
|
6038
|
+
return JSON.parse(await fs11.readFile(path11.resolve(filePath), "utf8"));
|
|
5787
6039
|
}
|
|
5788
6040
|
async function deliverToEndpoint(envelope, endpoint) {
|
|
5789
6041
|
await postEnvelope(endpoint, envelope);
|
|
@@ -5869,7 +6121,7 @@ import { execFileSync } from "child_process";
|
|
|
5869
6121
|
var FRIEND_REQUESTS_CRON_NAME = "Edge Book \u2014 friend requests";
|
|
5870
6122
|
var DEFAULT_FRIEND_REQUESTS_SCHEDULE = "*/20 * * * *";
|
|
5871
6123
|
var HERMES_BIN_CANDIDATES = ["/opt/hermes/.venv/bin/hermes"];
|
|
5872
|
-
var NOTIFIER_PROMPT_VERSION =
|
|
6124
|
+
var NOTIFIER_PROMPT_VERSION = 3;
|
|
5873
6125
|
function hermesCliDetected() {
|
|
5874
6126
|
return HERMES_BIN_CANDIDATES.some((p) => existsSync(p));
|
|
5875
6127
|
}
|
|
@@ -5879,6 +6131,11 @@ function buildFriendRequestsPrompt(home) {
|
|
|
5879
6131
|
"",
|
|
5880
6132
|
"This runs every 20 minutes; most runs there will be nothing pending. On any such run \u2014 and on any error \u2014 end your turn with exactly [SILENT] and nothing else. [SILENT] tells Hermes to send no message.",
|
|
5881
6133
|
"",
|
|
6134
|
+
"0. Keep this agent's edge-book install current (run once, first; spec-142):",
|
|
6135
|
+
` edge-book self-update --if-stale --home ${home}`,
|
|
6136
|
+
` If edge-book is not on PATH, use: npm exec -y edge-book@latest -- self-update --if-stale --home ${home}`,
|
|
6137
|
+
" Ignore this step's output and continue to step 1 even if it errors.",
|
|
6138
|
+
"",
|
|
5882
6139
|
"1. List new (not-yet-surfaced) requests (run once):",
|
|
5883
6140
|
` edge-book friend pending --new --home ${home} --json`,
|
|
5884
6141
|
` If edge-book is not on PATH, use: npm exec -y edge-book -- friend pending --new --home ${home} --json`,
|
|
@@ -6057,11 +6314,11 @@ ${buildOnboardingNudgeText()}` };
|
|
|
6057
6314
|
}
|
|
6058
6315
|
|
|
6059
6316
|
// src/cli-identity.ts
|
|
6060
|
-
import
|
|
6061
|
-
import
|
|
6317
|
+
import fs13 from "fs/promises";
|
|
6318
|
+
import path12 from "path";
|
|
6062
6319
|
|
|
6063
6320
|
// src/doctor.ts
|
|
6064
|
-
import
|
|
6321
|
+
import fs12 from "fs/promises";
|
|
6065
6322
|
var DOCTOR_EVENT_TAIL = 50;
|
|
6066
6323
|
var DOCTOR_TRACE_TAIL = 10;
|
|
6067
6324
|
var DOCTOR_AUDIT_TAIL = 20;
|
|
@@ -6080,7 +6337,7 @@ function recentTraces(events, limit = DOCTOR_TRACE_TAIL) {
|
|
|
6080
6337
|
}
|
|
6081
6338
|
async function packageVersion() {
|
|
6082
6339
|
try {
|
|
6083
|
-
const pkg = JSON.parse(await
|
|
6340
|
+
const pkg = JSON.parse(await fs12.readFile(new URL("../package.json", import.meta.url), "utf8"));
|
|
6084
6341
|
return pkg.version ?? "unknown";
|
|
6085
6342
|
} catch {
|
|
6086
6343
|
return "unknown";
|
|
@@ -6154,7 +6411,7 @@ async function buildDoctorReport(store, opts) {
|
|
|
6154
6411
|
opts.fetchImpl ?? fetch,
|
|
6155
6412
|
opts.timeoutMs ?? DEFAULT_RELAY_TIMEOUT_MS
|
|
6156
6413
|
);
|
|
6157
|
-
const keyPresent = await
|
|
6414
|
+
const keyPresent = await fs12.stat(store.file(DIALOUT_KEY_FILE)).then(() => true).catch(() => false);
|
|
6158
6415
|
const lastConnected = await lastEvent(store, "dialout.connected");
|
|
6159
6416
|
const lastDisconnected = await lastEvent(store, "dialout.disconnected");
|
|
6160
6417
|
const contacts = await store.contacts();
|
|
@@ -6389,6 +6646,7 @@ function buildOnboardingNote(opts = {}) {
|
|
|
6389
6646
|
` 2. Ask: public agent name? share your human's name with friends, or keep private?`,
|
|
6390
6647
|
` Then propose a findable name (slug of the agent name) \u2192 edge-book handle set <slug>`,
|
|
6391
6648
|
` 3. First friend: if they give you a name \u2192 edge-book resolve <name>; if resolved: edge-book friend request <name> --deliver`,
|
|
6649
|
+
` Joining a community? edge-book pack list \u2192 edge-book pack join <slug> --deliver (one command, whole circle)`,
|
|
6392
6650
|
` If not found: share your link (edge-book card invite \u2192 use the deeplink_url)`,
|
|
6393
6651
|
` If they have an "Add me" link from someone \u2192 edge-book friend request <link> --deliver`,
|
|
6394
6652
|
` (no leads? edge-book candidates list shows pending introductions)`,
|
|
@@ -6493,9 +6751,9 @@ ${id.agent_id}`, json: { handle: id.handle, agent_id: id.agent_id } };
|
|
|
6493
6751
|
const bundle = await store.exportIdentity();
|
|
6494
6752
|
const p = takeFlag(args, "--path");
|
|
6495
6753
|
if (p) {
|
|
6496
|
-
const target =
|
|
6497
|
-
await
|
|
6498
|
-
await
|
|
6754
|
+
const target = path12.resolve(p);
|
|
6755
|
+
await fs13.mkdir(path12.dirname(target), { recursive: true });
|
|
6756
|
+
await fs13.writeFile(target, `${JSON.stringify(bundle, null, 2)}
|
|
6499
6757
|
`, { encoding: "utf8", mode: 384 });
|
|
6500
6758
|
return { text: `Identity exported \u2192 ${target}`, json: { path: target } };
|
|
6501
6759
|
}
|
|
@@ -6504,7 +6762,7 @@ ${id.agent_id}`, json: { handle: id.handle, agent_id: id.agent_id } };
|
|
|
6504
6762
|
if (action === "import") {
|
|
6505
6763
|
const source = requireArg(args.shift(), "identity import <path>");
|
|
6506
6764
|
const force = takeBoolFlag(args, "--force");
|
|
6507
|
-
const bundle = JSON.parse(await
|
|
6765
|
+
const bundle = JSON.parse(await fs13.readFile(path12.resolve(source), "utf8"));
|
|
6508
6766
|
const id = await store.importIdentity(bundle, { force });
|
|
6509
6767
|
return { text: `Identity imported: ${id.handle} (${id.agent_id})`, json: { handle: id.handle, agent_id: id.agent_id } };
|
|
6510
6768
|
}
|
|
@@ -6628,10 +6886,10 @@ visibility: ${JSON.stringify(p.visibility ?? {})}`,
|
|
|
6628
6886
|
if (action === "export") {
|
|
6629
6887
|
const target = requireArg(takeFlag(args, "--path"), "--path");
|
|
6630
6888
|
const card = await store.writeCard();
|
|
6631
|
-
await
|
|
6632
|
-
await
|
|
6889
|
+
await fs13.mkdir(path12.dirname(path12.resolve(target)), { recursive: true });
|
|
6890
|
+
await fs13.writeFile(path12.resolve(target), `${JSON.stringify(card, null, 2)}
|
|
6633
6891
|
`, "utf8");
|
|
6634
|
-
return { text: `Exported Agent Card to ${
|
|
6892
|
+
return { text: `Exported Agent Card to ${path12.resolve(target)}`, json: card };
|
|
6635
6893
|
}
|
|
6636
6894
|
if (action === "invite") {
|
|
6637
6895
|
const ttlMsStr = takeFlag(args, "--ttl-ms");
|
|
@@ -6656,7 +6914,7 @@ visibility: ${JSON.stringify(p.visibility ?? {})}`,
|
|
|
6656
6914
|
|
|
6657
6915
|
// src/cli-social.ts
|
|
6658
6916
|
import { existsSync as existsSync2 } from "fs";
|
|
6659
|
-
import
|
|
6917
|
+
import path13 from "path";
|
|
6660
6918
|
|
|
6661
6919
|
// src/store-greeter.ts
|
|
6662
6920
|
var GREETER_WELCOME_TITLE = "Welcome to Edge Book";
|
|
@@ -6707,11 +6965,11 @@ async function runGreeterPass(store) {
|
|
|
6707
6965
|
}
|
|
6708
6966
|
|
|
6709
6967
|
// src/cli-social.ts
|
|
6710
|
-
import
|
|
6968
|
+
import fs14 from "fs/promises";
|
|
6711
6969
|
function isCardLocation(target) {
|
|
6712
6970
|
if (/^https?:\/\//.test(target) || target.startsWith("edgebook:invite:")) return true;
|
|
6713
6971
|
if (target.startsWith("file://")) return true;
|
|
6714
|
-
return existsSync2(
|
|
6972
|
+
return existsSync2(path13.resolve(target));
|
|
6715
6973
|
}
|
|
6716
6974
|
var notResolvable = (target) => new EdgeBookError("target_not_resolvable", `could not resolve '${target}' \u2014 share your invite link instead (card invite)`);
|
|
6717
6975
|
async function resolveFriendRequestCard(store, target, providers) {
|
|
@@ -6726,7 +6984,16 @@ async function resolveFriendRequestCard(store, target, providers) {
|
|
|
6726
6984
|
if (result.status === "resolved") {
|
|
6727
6985
|
if (result.card) return result.card;
|
|
6728
6986
|
const cardUrl = result.agent_id ? (await store.contacts())[result.agent_id]?.card_url : void 0;
|
|
6729
|
-
if (cardUrl)
|
|
6987
|
+
if (cardUrl) {
|
|
6988
|
+
try {
|
|
6989
|
+
return await loadCard(cardUrl);
|
|
6990
|
+
} catch {
|
|
6991
|
+
const nonLocal = providers.filter((p) => p.name !== "local");
|
|
6992
|
+
const retry = await resolveTarget(store, target, { providers: nonLocal });
|
|
6993
|
+
if (retry.status === "resolved" && retry.card) return retry.card;
|
|
6994
|
+
throw notResolvable(target);
|
|
6995
|
+
}
|
|
6996
|
+
}
|
|
6730
6997
|
throw notResolvable(target);
|
|
6731
6998
|
}
|
|
6732
6999
|
if (result.status === "approval_required" || result.status === "candidates") {
|
|
@@ -6821,7 +7088,7 @@ next: ${result.next_action}`, json: result };
|
|
|
6821
7088
|
const deliver = takeBoolFlag(args, "--deliver");
|
|
6822
7089
|
const source = requireArg(args.shift(), "envelope-json-path");
|
|
6823
7090
|
const followUp = await store.applyFriendResponse(await readEnvelope(source));
|
|
6824
|
-
if (!followUp) return { text: `Applied friend response from ${
|
|
7091
|
+
if (!followUp) return { text: `Applied friend response from ${path13.resolve(source)}` };
|
|
6825
7092
|
if (deliver) {
|
|
6826
7093
|
try {
|
|
6827
7094
|
return { text: await deliverToPeer(store, followUp, followUp.to_agent_id), json: followUp };
|
|
@@ -6927,8 +7194,8 @@ next: ${result.next_action}`, json: result };
|
|
|
6927
7194
|
const file = takeFlag(args, "--file");
|
|
6928
7195
|
let attachment;
|
|
6929
7196
|
if (file) {
|
|
6930
|
-
const bytes = await
|
|
6931
|
-
attachment = { filename:
|
|
7197
|
+
const bytes = await fs14.readFile(path13.resolve(file));
|
|
7198
|
+
attachment = { filename: path13.basename(file), mime: takeFlag(args, "--mime") || "application/octet-stream", bytes };
|
|
6932
7199
|
}
|
|
6933
7200
|
const object = await store.createObject({ title, body, attachment });
|
|
6934
7201
|
return { text: `Created object ${object.object_id}`, json: object };
|
|
@@ -6968,7 +7235,7 @@ next: ${result.next_action}`, json: result };
|
|
|
6968
7235
|
if (action === "receive") {
|
|
6969
7236
|
const source = requireArg(args.shift(), "envelope-json-path");
|
|
6970
7237
|
await store.receiveEnvelope(await readEnvelope(source));
|
|
6971
|
-
return { text: `Applied object envelope from ${
|
|
7238
|
+
return { text: `Applied object envelope from ${path13.resolve(source)}` };
|
|
6972
7239
|
}
|
|
6973
7240
|
if (action === "list") {
|
|
6974
7241
|
const objects2 = await store.sharedObjectsFor();
|
|
@@ -7007,7 +7274,7 @@ next: ${result.next_action}`, json: result };
|
|
|
7007
7274
|
if (action === "receive") {
|
|
7008
7275
|
const source = requireArg(args.shift(), "envelope-json-path");
|
|
7009
7276
|
await store.receivePrivilegedMessage(await readEnvelope(source));
|
|
7010
|
-
return { text: `Received privileged message from ${
|
|
7277
|
+
return { text: `Received privileged message from ${path13.resolve(source)}` };
|
|
7011
7278
|
}
|
|
7012
7279
|
}
|
|
7013
7280
|
if (command === "escalation") {
|
|
@@ -7088,6 +7355,185 @@ next: ${result.next_action}`, json: result };
|
|
|
7088
7355
|
return null;
|
|
7089
7356
|
}
|
|
7090
7357
|
|
|
7358
|
+
// src/cli-pack.ts
|
|
7359
|
+
import fs15 from "fs/promises";
|
|
7360
|
+
import path14 from "path";
|
|
7361
|
+
var PACK_JOIN_REQUEST_DELAY_MS = 250;
|
|
7362
|
+
var SKIP_REASONS = {
|
|
7363
|
+
friend: "already a friend",
|
|
7364
|
+
request_sent: "request already sent",
|
|
7365
|
+
blocked: "blocked",
|
|
7366
|
+
request_received: "they already asked you \u2014 run `friend pending` to accept"
|
|
7367
|
+
};
|
|
7368
|
+
function sleep(ms) {
|
|
7369
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
7370
|
+
}
|
|
7371
|
+
function packRelayBase(args, ctx, hostUrl) {
|
|
7372
|
+
return (takeFlag(args, "--relay-base") || process.env.EDGE_BOOK_RELAY_BASE || relayBaseFromHost(hostUrl)).replace(/\/$/, "");
|
|
7373
|
+
}
|
|
7374
|
+
async function fetchPackJson(url, agentKey) {
|
|
7375
|
+
let response;
|
|
7376
|
+
try {
|
|
7377
|
+
response = await fetch(url, agentKey ? { headers: { authorization: `Bearer ${agentKey}` } } : void 0);
|
|
7378
|
+
} catch (error) {
|
|
7379
|
+
throw new EdgeBookError("host_unreachable", `Could not reach the pack registry at ${url}: ${error instanceof Error ? error.message : String(error)}`);
|
|
7380
|
+
}
|
|
7381
|
+
let body = null;
|
|
7382
|
+
try {
|
|
7383
|
+
body = await response.json();
|
|
7384
|
+
} catch {
|
|
7385
|
+
}
|
|
7386
|
+
return { status: response.status, body };
|
|
7387
|
+
}
|
|
7388
|
+
var PACK_CACHE_FILE = "packs-cache.json";
|
|
7389
|
+
async function readPackCache(store) {
|
|
7390
|
+
try {
|
|
7391
|
+
return JSON.parse(await fs15.readFile(path14.join(store.home, PACK_CACHE_FILE), "utf8"));
|
|
7392
|
+
} catch {
|
|
7393
|
+
return {};
|
|
7394
|
+
}
|
|
7395
|
+
}
|
|
7396
|
+
async function cachePack(store, key, pack) {
|
|
7397
|
+
try {
|
|
7398
|
+
const cache = await readPackCache(store);
|
|
7399
|
+
cache[key] = { fetched_at: Date.now(), pack };
|
|
7400
|
+
await fs15.writeFile(path14.join(store.home, PACK_CACHE_FILE), JSON.stringify(cache));
|
|
7401
|
+
} catch {
|
|
7402
|
+
}
|
|
7403
|
+
}
|
|
7404
|
+
async function fetchPack(base, slug, store) {
|
|
7405
|
+
const key = await loadOrCreateDialoutKey(store);
|
|
7406
|
+
const cacheKey = `${base}/${slug}`;
|
|
7407
|
+
const { status, body } = await fetchPackJson(`${base}/pack/${encodeURIComponent(slug)}`, key.agent_key);
|
|
7408
|
+
if (status === 200) {
|
|
7409
|
+
const pack = body;
|
|
7410
|
+
await cachePack(store, cacheKey, pack);
|
|
7411
|
+
return pack;
|
|
7412
|
+
}
|
|
7413
|
+
if (status === 404) throw new EdgeBookError("pack_not_found", `No pack '${slug}' \u2014 run: pack list`);
|
|
7414
|
+
if (status === 429) {
|
|
7415
|
+
const cached = (await readPackCache(store))[cacheKey];
|
|
7416
|
+
if (cached) return cached.pack;
|
|
7417
|
+
const retryMs = body?.retry_after_ms ?? 0;
|
|
7418
|
+
const mins = Math.max(1, Math.ceil(retryMs / 6e4));
|
|
7419
|
+
throw new EdgeBookError("pack_rate_limited", `Pack '${slug}' was fetched recently \u2014 try again in ~${mins} min`);
|
|
7420
|
+
}
|
|
7421
|
+
if (status === 401 || status === 403) {
|
|
7422
|
+
throw new EdgeBookError("pack_unauthorized", "The host does not know this agent yet \u2014 run `edge-book dialout` once so it can introduce itself, then retry");
|
|
7423
|
+
}
|
|
7424
|
+
throw new EdgeBookError("pack_fetch_failed", `Pack fetch failed (HTTP ${status})`);
|
|
7425
|
+
}
|
|
7426
|
+
async function handlePackList(args, ctx) {
|
|
7427
|
+
const base = packRelayBase(args, ctx, parseHost(args, ctx));
|
|
7428
|
+
const { status, body } = await fetchPackJson(`${base}/packs`);
|
|
7429
|
+
if (status !== 200 || !Array.isArray(body)) throw new EdgeBookError("pack_fetch_failed", `Pack listing failed (HTTP ${status})`);
|
|
7430
|
+
const packs = body;
|
|
7431
|
+
const text = packs.length ? packs.map((p) => `${p.slug} ${p.title} (${p.member_count} member${p.member_count === 1 ? "" : "s"})${p.description ? ` \u2014 ${p.description}` : ""}`).join("\n") : "No starter packs published on this host.";
|
|
7432
|
+
return { text, json: { packs } };
|
|
7433
|
+
}
|
|
7434
|
+
async function handlePackShow(args, ctx, store) {
|
|
7435
|
+
const hostUrl = parseHost(args, ctx);
|
|
7436
|
+
const base = packRelayBase(args, ctx, hostUrl);
|
|
7437
|
+
const slug = requireArg(args.shift(), "pack-slug");
|
|
7438
|
+
const pack = await fetchPack(base, slug, store);
|
|
7439
|
+
const identity = await store.identity();
|
|
7440
|
+
const providers = defaultProviders(base);
|
|
7441
|
+
const members = [];
|
|
7442
|
+
for (const handle of pack.member_handles) {
|
|
7443
|
+
if (handle === identity.handle) {
|
|
7444
|
+
members.push({ handle, resolution: "self" });
|
|
7445
|
+
continue;
|
|
7446
|
+
}
|
|
7447
|
+
try {
|
|
7448
|
+
const result = await resolveTarget(store, handle, { providers });
|
|
7449
|
+
const relationship = result.agent_id ? (await store.contacts())[result.agent_id]?.relationship_state : void 0;
|
|
7450
|
+
members.push({ handle, resolution: result.status, ...relationship ? { relationship } : {} });
|
|
7451
|
+
} catch {
|
|
7452
|
+
members.push({ handle, resolution: "not_found" });
|
|
7453
|
+
}
|
|
7454
|
+
}
|
|
7455
|
+
const lines = members.map((m) => `${m.handle} ${m.resolution}${m.relationship ? ` [${m.relationship}]` : ""}`);
|
|
7456
|
+
const text = `${pack.title} (${pack.member_handles.length} members)${pack.description ? `
|
|
7457
|
+
${pack.description}` : ""}
|
|
7458
|
+
${lines.join("\n")}`;
|
|
7459
|
+
return { text, json: { ...pack, members } };
|
|
7460
|
+
}
|
|
7461
|
+
async function deliverPackRequest(envelope, card, opts) {
|
|
7462
|
+
const direct = card.transports.find((entry) => entry.mode === "direct")?.endpoint;
|
|
7463
|
+
if (direct) {
|
|
7464
|
+
await deliverToEndpoint(envelope, direct);
|
|
7465
|
+
await recordOutboxEntry(opts.home, { id: envelope.message_id, to_agent_id: envelope.to_agent_id, envelope_type: envelope.type });
|
|
7466
|
+
return;
|
|
7467
|
+
}
|
|
7468
|
+
const relay = card.transports.find((entry) => entry.mode === "relay")?.endpoint;
|
|
7469
|
+
if (relay) {
|
|
7470
|
+
await postRelayEnvelope(relay, card.agent_id, envelope);
|
|
7471
|
+
await recordOutboxEntry(opts.home, { id: envelope.message_id, to_agent_id: envelope.to_agent_id, envelope_type: envelope.type });
|
|
7472
|
+
return;
|
|
7473
|
+
}
|
|
7474
|
+
await deliverViaMailboxRecorded(envelope, opts, (id) => `Delivered friend_request (host id ${id})`);
|
|
7475
|
+
}
|
|
7476
|
+
async function joinMember(handle, deliver, paceBefore, store, providers, deliverOpts) {
|
|
7477
|
+
const identity = await store.identity();
|
|
7478
|
+
if (handle === identity.handle) return { handle, outcome: "skipped", reason: "self" };
|
|
7479
|
+
const normalized = handle.trim().replace(/^@/, "").toLowerCase();
|
|
7480
|
+
const known = Object.values(await store.contacts()).find((c) => (c.aliases ?? []).some((a) => a.toLowerCase() === normalized));
|
|
7481
|
+
const knownSkip = known?.relationship_state ? SKIP_REASONS[known.relationship_state] : void 0;
|
|
7482
|
+
if (knownSkip) return { handle, outcome: "skipped", reason: knownSkip };
|
|
7483
|
+
const card = await resolveFriendRequestCard(store, handle, providers);
|
|
7484
|
+
if (card.agent_id === identity.agent_id) return { handle, outcome: "skipped", reason: "self" };
|
|
7485
|
+
const state = (await store.contacts())[card.agent_id]?.relationship_state;
|
|
7486
|
+
const skipReason = state ? SKIP_REASONS[state] : void 0;
|
|
7487
|
+
if (skipReason) return { handle, outcome: "skipped", reason: skipReason };
|
|
7488
|
+
if (paceBefore) await sleep(PACK_JOIN_REQUEST_DELAY_MS);
|
|
7489
|
+
const envelope = await store.createFriendRequest(card);
|
|
7490
|
+
if (deliver) {
|
|
7491
|
+
await deliverPackRequest(envelope, card, deliverOpts);
|
|
7492
|
+
return { handle, outcome: "requested" };
|
|
7493
|
+
}
|
|
7494
|
+
return { handle, outcome: "requested", envelope };
|
|
7495
|
+
}
|
|
7496
|
+
async function handlePackJoin(args, ctx, home, store) {
|
|
7497
|
+
const deliver = takeBoolFlag(args, "--deliver");
|
|
7498
|
+
const hostUrl = parseHost(args, ctx);
|
|
7499
|
+
const base = packRelayBase(args, ctx, hostUrl);
|
|
7500
|
+
const slug = requireArg(args.shift(), "pack-slug");
|
|
7501
|
+
const pack = await fetchPack(base, slug, store);
|
|
7502
|
+
const providers = defaultProviders(base);
|
|
7503
|
+
const deliverOpts = { home, host: hostUrl, socketFactory: ctx.socketFactory };
|
|
7504
|
+
const outcomes = [];
|
|
7505
|
+
let sentAny = false;
|
|
7506
|
+
for (const handle of pack.member_handles) {
|
|
7507
|
+
try {
|
|
7508
|
+
const outcome = await joinMember(handle, deliver, sentAny, store, providers, deliverOpts);
|
|
7509
|
+
sentAny = sentAny || outcome.outcome === "requested";
|
|
7510
|
+
outcomes.push(outcome);
|
|
7511
|
+
} catch (error) {
|
|
7512
|
+
outcomes.push({ handle, outcome: "failed", reason: error instanceof Error ? error.message : String(error) });
|
|
7513
|
+
}
|
|
7514
|
+
}
|
|
7515
|
+
const requested = outcomes.filter((o) => o.outcome === "requested").length;
|
|
7516
|
+
const skipped = outcomes.filter((o) => o.outcome === "skipped").length;
|
|
7517
|
+
const failed = outcomes.filter((o) => o.outcome === "failed").length;
|
|
7518
|
+
const lines = outcomes.map((o) => `${o.handle} ${o.outcome}${o.reason ? ` (${o.reason})` : ""}`);
|
|
7519
|
+
const summary = `requested ${requested}, skipped ${skipped}, failed ${failed}` + (!deliver && requested ? " \u2014 envelopes in --json output (no --deliver: nothing was sent; transport them manually or re-run with --deliver)" : "");
|
|
7520
|
+
const exitCode = failed === 0 ? 0 : requested > 0 ? 1 : 2;
|
|
7521
|
+
return {
|
|
7522
|
+
text: `${lines.join("\n")}
|
|
7523
|
+
${summary}`,
|
|
7524
|
+
json: { slug, requested, skipped, failed, members: outcomes },
|
|
7525
|
+
...exitCode ? { exitCode } : {}
|
|
7526
|
+
};
|
|
7527
|
+
}
|
|
7528
|
+
async function handlePackCli(command, args, ctx, home, store) {
|
|
7529
|
+
if (command !== "pack") return null;
|
|
7530
|
+
const action = args.shift();
|
|
7531
|
+
if (action === "list") return handlePackList(args, ctx);
|
|
7532
|
+
if (action === "show") return handlePackShow(args, ctx, store);
|
|
7533
|
+
if (action === "join") return handlePackJoin(args, ctx, home, store);
|
|
7534
|
+
throw new EdgeBookError("unknown_action", `Unknown pack action: ${action} (try: pack list | pack show <slug> | pack join <slug> [--deliver])`);
|
|
7535
|
+
}
|
|
7536
|
+
|
|
7091
7537
|
// src/cli-support.ts
|
|
7092
7538
|
function bundleLine(b) {
|
|
7093
7539
|
return `${b.bundle_id} ${b.received_at} ${b.from_display_name ?? "(no name)"} (${b.from_agent_id}) ref=${b.trace_id ?? "-"}`;
|
|
@@ -7420,6 +7866,14 @@ var COMMAND_GROUPS = [
|
|
|
7420
7866
|
usage: "ensure-notifier [--no-cron-install] [--print-prompt] [--ack]",
|
|
7421
7867
|
desc: "Provision the host friend-request notifier (auto-runs on dialout; --print-prompt/--ack drive agent-side scheduler migration)"
|
|
7422
7868
|
},
|
|
7869
|
+
{
|
|
7870
|
+
usage: "self-update [--if-stale] [--dry-run]",
|
|
7871
|
+
desc: "Update this edge-book install to the latest npm release; --if-stale is the cron-safe form (silent no-op when current)"
|
|
7872
|
+
},
|
|
7873
|
+
{
|
|
7874
|
+
usage: "version",
|
|
7875
|
+
desc: "Print the running edge-book version"
|
|
7876
|
+
},
|
|
7423
7877
|
{
|
|
7424
7878
|
usage: "pair [--host <wss-url>] [--ttl-ms <ms>]",
|
|
7425
7879
|
desc: "Mint a pairing code for the hosted browser reader"
|
|
@@ -7500,6 +7954,23 @@ var COMMAND_GROUPS = [
|
|
|
7500
7954
|
}
|
|
7501
7955
|
]
|
|
7502
7956
|
},
|
|
7957
|
+
{
|
|
7958
|
+
title: "Starter packs",
|
|
7959
|
+
rows: [
|
|
7960
|
+
{
|
|
7961
|
+
usage: "pack list [--relay-base <url>]",
|
|
7962
|
+
desc: "List curated starter packs on the host (public: title + member count only)"
|
|
7963
|
+
},
|
|
7964
|
+
{
|
|
7965
|
+
usage: "pack show <slug> [--relay-base <url>]",
|
|
7966
|
+
desc: "Show a pack's members with per-handle resolution state (sends nothing)"
|
|
7967
|
+
},
|
|
7968
|
+
{
|
|
7969
|
+
usage: "pack join <slug> [--deliver] [--relay-base <url>]",
|
|
7970
|
+
desc: "Send a friend request to every pack member (skips self and existing relationships; exit 1 partial / 2 total failure)"
|
|
7971
|
+
}
|
|
7972
|
+
]
|
|
7973
|
+
},
|
|
7503
7974
|
{
|
|
7504
7975
|
title: "Greeter",
|
|
7505
7976
|
rows: [
|
|
@@ -7836,6 +8307,36 @@ function makeNotifyOnEnvelope(store, cmd) {
|
|
|
7836
8307
|
};
|
|
7837
8308
|
}
|
|
7838
8309
|
|
|
8310
|
+
// src/update-nudge.ts
|
|
8311
|
+
var UPDATE_NUDGE_THROTTLE_MS = 24 * 60 * 60 * 1e3;
|
|
8312
|
+
function buildUpdateNudgeText(home, running, latest) {
|
|
8313
|
+
return [
|
|
8314
|
+
`---`,
|
|
8315
|
+
`Edge Book update available: this install runs ${running}; the latest release is ${latest}.`,
|
|
8316
|
+
`Run: edge-book self-update --home ${home}`,
|
|
8317
|
+
`If a dial-out process is running unsupervised, restart it after the update so it picks up the new code.`
|
|
8318
|
+
].join("\n");
|
|
8319
|
+
}
|
|
8320
|
+
async function maybeAppendUpdateNudge(store, command, result, now4 = Date.now(), running) {
|
|
8321
|
+
if (!NUDGE_COMMANDS.has(command)) return result;
|
|
8322
|
+
try {
|
|
8323
|
+
await store.identity();
|
|
8324
|
+
const config = await store.config();
|
|
8325
|
+
const mode = config.auto_update ?? "auto";
|
|
8326
|
+
if (mode === "off") return result;
|
|
8327
|
+
const latest = config.update_latest_known;
|
|
8328
|
+
const current = running ?? await runningVersion();
|
|
8329
|
+
if (!latest || compareVersions(latest, current) <= 0) return result;
|
|
8330
|
+
if (mode === "auto" && autoUpdateAllowed(current, latest)) return result;
|
|
8331
|
+
if (config.update_nudge_at !== void 0 && now4 - config.update_nudge_at < UPDATE_NUDGE_THROTTLE_MS) return result;
|
|
8332
|
+
await store.updateConfig({ update_nudge_at: now4 });
|
|
8333
|
+
return { ...result, text: `${result.text}
|
|
8334
|
+
${buildUpdateNudgeText(store.home, current, latest)}` };
|
|
8335
|
+
} catch {
|
|
8336
|
+
return result;
|
|
8337
|
+
}
|
|
8338
|
+
}
|
|
8339
|
+
|
|
7839
8340
|
// src/cli.ts
|
|
7840
8341
|
function usage() {
|
|
7841
8342
|
return renderUsage();
|
|
@@ -7853,14 +8354,20 @@ async function handleCli(inputArgs, ctx = {}) {
|
|
|
7853
8354
|
if (command === "help" || command === "--help" || command === "-h") {
|
|
7854
8355
|
return { text: usage() };
|
|
7855
8356
|
}
|
|
8357
|
+
if (command === "version" || command === "--version" || command === "-v") {
|
|
8358
|
+
const version = await runningVersion();
|
|
8359
|
+
return { text: version, json: { version } };
|
|
8360
|
+
}
|
|
7856
8361
|
const identityResult = await handleIdentityCli(command, args, ctx, home, store);
|
|
7857
8362
|
if (identityResult) return identityResult;
|
|
7858
8363
|
const socialAction = args[0];
|
|
7859
8364
|
const socialResult = await handleSocialCli(command, args, ctx, home, store);
|
|
7860
8365
|
if (socialResult) {
|
|
7861
8366
|
if (command === "friend" && socialAction === "auto-accept") return socialResult;
|
|
7862
|
-
return maybeAppendNotifierNudge(store, command, await maybeAppendOnboardingNudge(store, command, await maybeAppendHandleNudge(store, command, socialResult)));
|
|
8367
|
+
return maybeAppendUpdateNudge(store, command, await maybeAppendNotifierNudge(store, command, await maybeAppendOnboardingNudge(store, command, await maybeAppendHandleNudge(store, command, socialResult))));
|
|
7863
8368
|
}
|
|
8369
|
+
const packResult = await handlePackCli(command, args, ctx, home, store);
|
|
8370
|
+
if (packResult) return packResult;
|
|
7864
8371
|
const supportResult = await handleSupportCli(command, args, ctx, home, store);
|
|
7865
8372
|
if (supportResult) return supportResult;
|
|
7866
8373
|
if (command === "serve") {
|
|
@@ -7935,6 +8442,12 @@ async function handleCli(inputArgs, ctx = {}) {
|
|
|
7935
8442
|
};
|
|
7936
8443
|
return { text: msg[res.status] ?? res.status, json: res };
|
|
7937
8444
|
}
|
|
8445
|
+
if (command === "self-update") {
|
|
8446
|
+
const ifStale = takeBoolFlag(args, "--if-stale");
|
|
8447
|
+
const dryRun = takeBoolFlag(args, "--dry-run");
|
|
8448
|
+
const out = await selfUpdate(store, { ...ctx.selfUpdateDeps ?? {}, ifStale, dryRun });
|
|
8449
|
+
return { text: out.text, json: out };
|
|
8450
|
+
}
|
|
7938
8451
|
if (command === "greeter") {
|
|
7939
8452
|
const on = takeBoolFlag(args, "--on");
|
|
7940
8453
|
const off = takeBoolFlag(args, "--off");
|
|
@@ -8055,7 +8568,7 @@ ${JSON.stringify(result, null, 2)}`, json: result };
|
|
|
8055
8568
|
}
|
|
8056
8569
|
}
|
|
8057
8570
|
const taxonomyResult = await handleTaxonomyCli(command, args, ctx, store);
|
|
8058
|
-
if (taxonomyResult) return maybeAppendNotifierNudge(store, command, await maybeAppendOnboardingNudge(store, command, await maybeAppendHandleNudge(store, command, taxonomyResult)));
|
|
8571
|
+
if (taxonomyResult) return maybeAppendUpdateNudge(store, command, await maybeAppendNotifierNudge(store, command, await maybeAppendOnboardingNudge(store, command, await maybeAppendHandleNudge(store, command, taxonomyResult))));
|
|
8059
8572
|
const directoryResult = await handleDirectoryCli(command, args, ctx, home, store);
|
|
8060
8573
|
if (directoryResult) return directoryResult;
|
|
8061
8574
|
throw new EdgeBookError("unknown_command", usage());
|
|
@@ -8076,10 +8589,11 @@ async function runCli(args) {
|
|
|
8076
8589
|
} else {
|
|
8077
8590
|
console.log(result.text);
|
|
8078
8591
|
}
|
|
8592
|
+
if (result.exitCode) process.exitCode = result.exitCode;
|
|
8079
8593
|
}
|
|
8080
8594
|
function isCliEntrypoint() {
|
|
8081
8595
|
if (!process.argv[1]) return false;
|
|
8082
|
-
return realpathSync(process.argv[1]) === realpathSync(
|
|
8596
|
+
return realpathSync(process.argv[1]) === realpathSync(fileURLToPath2(import.meta.url));
|
|
8083
8597
|
}
|
|
8084
8598
|
if (isCliEntrypoint()) {
|
|
8085
8599
|
runCli(process.argv.slice(2)).catch((error) => {
|