@agfpd/iapeer-memory 0.2.0 → 0.2.2
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/package.json +2 -2
- package/src/binary.ts +17 -10
- package/src/cli.ts +26 -8
- package/src/commands/hook.ts +7 -8
- package/src/commands/init.ts +32 -29
- package/src/commands/install-binary.ts +3 -2
- package/src/commands/memoryd.ts +3 -2
- package/src/commands/status.ts +15 -8
- package/src/commands/uninstall.ts +19 -26
- package/src/commands/update.ts +9 -8
- package/src/commands/verify.ts +15 -8
- package/src/egress.ts +181 -0
- package/src/fleet.ts +33 -35
- package/src/provision.ts +3 -2
- package/src/roles.ts +2 -1
- package/src/signing.ts +19 -23
- package/src/slot.ts +30 -30
- package/src/surfaces/claude.ts +9 -6
- package/src/surfaces/codex.ts +3 -2
- package/src/sync-versions.ts +3 -2
- package/src/templates/index.ts +2 -1
- package/src/templates/roles-en.ts +6 -0
- package/src/templates/roles-ru.ts +6 -0
- package/src/watcher.ts +72 -68
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agfpd/iapeer-memory",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "iapeer-memory — peer memory for the iapeer ecosystem: vault, memoryd (index/search/MCP-http), layer-5 context fragments, role doctrines. The package IS the system; the claude/codex plugins are thin session sockets (docs/10-distribution.md, ADR-009).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"access": "public"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@agfpd/iapeer-memory-core": "0.2.
|
|
30
|
+
"@agfpd/iapeer-memory-core": "0.2.2"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@types/bun": "^1.2.0",
|
package/src/binary.ts
CHANGED
|
@@ -23,7 +23,9 @@
|
|
|
23
23
|
|
|
24
24
|
import fs from "node:fs";
|
|
25
25
|
import path from "node:path";
|
|
26
|
+
import type { Egress } from "./egress.js";
|
|
26
27
|
import { signInstalledBinary, type SigningOutcome } from "./signing.js";
|
|
28
|
+
import { guardedUnlinkSync } from "@agfpd/iapeer-memory-core";
|
|
27
29
|
|
|
28
30
|
export function isCompiledRuntime(): boolean {
|
|
29
31
|
return import.meta.url.includes("/$bunfs/");
|
|
@@ -34,7 +36,10 @@ export type InstallBinaryOutcome =
|
|
|
34
36
|
| { action: "skipped-compiled"; outPath: string }
|
|
35
37
|
| { action: "failed"; outPath: string; detail: string };
|
|
36
38
|
|
|
37
|
-
export function installBinary(
|
|
39
|
+
export function installBinary(
|
|
40
|
+
egress: Egress,
|
|
41
|
+
opts: { outPath: string },
|
|
42
|
+
): InstallBinaryOutcome {
|
|
38
43
|
const { outPath } = opts;
|
|
39
44
|
if (isCompiledRuntime()) {
|
|
40
45
|
return { action: "skipped-compiled", outPath };
|
|
@@ -47,20 +52,22 @@ export function installBinary(opts: { outPath: string }): InstallBinaryOutcome {
|
|
|
47
52
|
`.${path.basename(outPath)}.build.tmp`,
|
|
48
53
|
);
|
|
49
54
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
+
// Self-runtime spawn (egress allowance 2): compiling OUR cli with OUR bun
|
|
56
|
+
// to a path-conventioned target — works in sandboxed e2e by design.
|
|
57
|
+
const proc = egress.spawnSync([
|
|
58
|
+
process.execPath, "build", "--compile", cliPath, "--outfile", tmp,
|
|
59
|
+
]);
|
|
60
|
+
if (proc.spawnError || proc.exitCode !== 0 || !fs.existsSync(tmp)) {
|
|
55
61
|
try {
|
|
56
|
-
if (fs.existsSync(tmp))
|
|
62
|
+
if (fs.existsSync(tmp)) guardedUnlinkSync(tmp);
|
|
57
63
|
} catch {
|
|
58
64
|
// best effort
|
|
59
65
|
}
|
|
60
66
|
return {
|
|
61
67
|
action: "failed",
|
|
62
68
|
outPath,
|
|
63
|
-
detail:
|
|
69
|
+
detail:
|
|
70
|
+
proc.spawnError || proc.stderr.trim() || `bun build exited ${proc.exitCode}`,
|
|
64
71
|
};
|
|
65
72
|
}
|
|
66
73
|
|
|
@@ -68,12 +75,12 @@ export function installBinary(opts: { outPath: string }): InstallBinaryOutcome {
|
|
|
68
75
|
fs.renameSync(tmp, outPath); // atomic swap — safe over a running binary on macOS
|
|
69
76
|
// Stable-identity re-sign on EVERY compile path (TCC grants survive
|
|
70
77
|
// updates — contract with iapeer, see signing.ts). Soft-fail by design.
|
|
71
|
-
const signing = signInstalledBinary(outPath);
|
|
78
|
+
const signing = signInstalledBinary(egress, outPath);
|
|
72
79
|
return { action: "compiled", outPath, bytes: fs.statSync(outPath).size, signing };
|
|
73
80
|
}
|
|
74
81
|
|
|
75
82
|
export function removeBinary(outPath: string): "removed" | "absent" {
|
|
76
83
|
if (!fs.existsSync(outPath)) return "absent";
|
|
77
|
-
|
|
84
|
+
guardedUnlinkSync(outPath);
|
|
78
85
|
return "removed";
|
|
79
86
|
}
|
package/src/cli.ts
CHANGED
|
@@ -13,7 +13,9 @@
|
|
|
13
13
|
* verify found problems, 2 usage error or not-yet-implemented stage.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
+
import { isUnderProdAnchor, sandboxEnvArmed } from "@agfpd/iapeer-memory-core";
|
|
16
17
|
import { loadConfigFile } from "./config-env.js";
|
|
18
|
+
import { liveEgress } from "./egress.js";
|
|
17
19
|
import { memoryPaths } from "./paths.js";
|
|
18
20
|
import { packageVersion } from "./version.js";
|
|
19
21
|
import { cmdFmUpdate } from "./commands/fm-update.js";
|
|
@@ -78,10 +80,26 @@ export async function main(argv: string[]): Promise<number> {
|
|
|
78
80
|
|
|
79
81
|
// The config file is the env context of every command (except pure
|
|
80
82
|
// help/version, where a broken file must not block the output).
|
|
83
|
+
// Deny-by-default §7.2 (accepted): under a test sandbox the LIVE host's
|
|
84
|
+
// config.env is never read — it would pull the live vault path, the
|
|
85
|
+
// embedding/reranker endpoints and the production port into a sandboxed
|
|
86
|
+
// process. A test must pass ITS OWN IAPEER_MEMORY_CONFIG_FILE.
|
|
81
87
|
if (cmd && !["help", "--help", "-h", "version", "--version", "-V"].includes(cmd)) {
|
|
82
|
-
|
|
88
|
+
const configFile = memoryPaths().configFile;
|
|
89
|
+
if (sandboxEnvArmed() && isUnderProdAnchor(configFile)) {
|
|
90
|
+
console.error(
|
|
91
|
+
`iapeer-memory: live config.env skipped under the test sandbox (${configFile}) — pass IAPEER_MEMORY_CONFIG_FILE`,
|
|
92
|
+
);
|
|
93
|
+
} else {
|
|
94
|
+
loadConfigFile(configFile);
|
|
95
|
+
}
|
|
83
96
|
}
|
|
84
97
|
|
|
98
|
+
// The ONE egress construction point (deny-by-default §4 П2): under a
|
|
99
|
+
// test-sandbox env this is a refusing handle — commands degrade to their
|
|
100
|
+
// SKIP semantics; nothing below re-checks the env.
|
|
101
|
+
const egress = liveEgress();
|
|
102
|
+
|
|
85
103
|
switch (cmd) {
|
|
86
104
|
case undefined:
|
|
87
105
|
case "help":
|
|
@@ -95,17 +113,17 @@ export async function main(argv: string[]): Promise<number> {
|
|
|
95
113
|
console.log(packageVersion());
|
|
96
114
|
return 0;
|
|
97
115
|
case "init":
|
|
98
|
-
return cmdInit(rest);
|
|
116
|
+
return cmdInit(rest, egress);
|
|
99
117
|
case "uninstall":
|
|
100
|
-
return cmdUninstall(rest);
|
|
118
|
+
return cmdUninstall(rest, egress);
|
|
101
119
|
case "status":
|
|
102
|
-
return cmdStatus(rest);
|
|
120
|
+
return cmdStatus(rest, egress);
|
|
103
121
|
case "verify":
|
|
104
|
-
return cmdVerify(rest);
|
|
122
|
+
return cmdVerify(rest, egress);
|
|
105
123
|
case "update":
|
|
106
|
-
return cmdUpdate(rest);
|
|
124
|
+
return cmdUpdate(rest, egress);
|
|
107
125
|
case "install-binary":
|
|
108
|
-
return cmdInstallBinary(rest);
|
|
126
|
+
return cmdInstallBinary(rest, egress);
|
|
109
127
|
case "provision-peer":
|
|
110
128
|
return cmdProvisionPeer(rest);
|
|
111
129
|
case "unprovision-peer":
|
|
@@ -119,7 +137,7 @@ export async function main(argv: string[]): Promise<number> {
|
|
|
119
137
|
case "memoryd":
|
|
120
138
|
return cmdMemoryd(rest);
|
|
121
139
|
case "hook":
|
|
122
|
-
return cmdHook(rest);
|
|
140
|
+
return cmdHook(rest, egress);
|
|
123
141
|
default:
|
|
124
142
|
console.error(`iapeer-memory: unknown command: ${cmd}\n`);
|
|
125
143
|
console.error(USAGE);
|
package/src/commands/hook.ts
CHANGED
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
|
|
33
33
|
import fs from "node:fs";
|
|
34
34
|
import path from "node:path";
|
|
35
|
+
import type { Egress } from "../egress.js";
|
|
35
36
|
import {
|
|
36
37
|
DEFAULT_CURATOR_SET,
|
|
37
38
|
fmUpdate,
|
|
@@ -41,6 +42,7 @@ import {
|
|
|
41
42
|
} from "@agfpd/iapeer-memory-core";
|
|
42
43
|
import { memoryPaths, type MemoryPaths } from "../paths.js";
|
|
43
44
|
import { DEFAULT_HEARTBEAT_STALE_MS } from "./verify.js";
|
|
45
|
+
import { guardedWriteFileSync } from "@agfpd/iapeer-memory-core";
|
|
44
46
|
|
|
45
47
|
/** Tools whose writes stamp frontmatter. P5 adds "apply_patch" (codex). */
|
|
46
48
|
export const POST_WRITE_TOOLS: ReadonlySet<string> = new Set([
|
|
@@ -193,7 +195,7 @@ export function runSessionStart(opts: {
|
|
|
193
195
|
if (!recentKick) {
|
|
194
196
|
try {
|
|
195
197
|
fs.mkdirSync(paths.stateDir, { recursive: true });
|
|
196
|
-
|
|
198
|
+
guardedWriteFileSync(stamp, `${new Date(nowMs).toISOString()}\n`);
|
|
197
199
|
opts.kick?.();
|
|
198
200
|
kicked = true;
|
|
199
201
|
} catch {
|
|
@@ -227,7 +229,7 @@ function logHookError(err: unknown): void {
|
|
|
227
229
|
}
|
|
228
230
|
}
|
|
229
231
|
|
|
230
|
-
export async function cmdHook(argv: string[]): Promise<number> {
|
|
232
|
+
export async function cmdHook(argv: string[], egress: Egress): Promise<number> {
|
|
231
233
|
const [event] = argv;
|
|
232
234
|
try {
|
|
233
235
|
switch (event) {
|
|
@@ -239,13 +241,10 @@ export async function cmdHook(argv: string[]): Promise<number> {
|
|
|
239
241
|
case "session-start": {
|
|
240
242
|
const result = runSessionStart({
|
|
241
243
|
kick: () => {
|
|
244
|
+
// Self-runtime detached spawn (egress allowance 2): the child
|
|
245
|
+
// re-enters main() with its own egress — sandbox env inherits.
|
|
242
246
|
const cli = new URL("../cli.ts", import.meta.url).pathname;
|
|
243
|
-
|
|
244
|
-
stdout: "ignore",
|
|
245
|
-
stderr: "ignore",
|
|
246
|
-
stdin: "ignore",
|
|
247
|
-
});
|
|
248
|
-
proc.unref();
|
|
247
|
+
egress.spawnDetached([process.execPath, cli, "verify", "--repair"]);
|
|
249
248
|
},
|
|
250
249
|
});
|
|
251
250
|
if (result.output) console.log(result.output);
|
package/src/commands/init.ts
CHANGED
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
type LocaleId,
|
|
33
33
|
} from "@agfpd/iapeer-memory-core";
|
|
34
34
|
import { installBinary } from "../binary.js";
|
|
35
|
+
import { IAPEER_BIN, type Egress } from "../egress.js";
|
|
35
36
|
import { memoryPaths } from "../paths.js";
|
|
36
37
|
import { provisionVault, writeDefaultConfig } from "../provision.js";
|
|
37
38
|
import { writeRolesManifest, type RoleEntry } from "../roles.js";
|
|
@@ -73,7 +74,9 @@ type InitFlags = {
|
|
|
73
74
|
* the fleet may still carry the predecessor's guide; ours lands by a separate
|
|
74
75
|
* decision after the plugin swap). */
|
|
75
76
|
skipGuide: boolean;
|
|
76
|
-
|
|
77
|
+
/** Explicitly named core binary (--iapeer-bin) — undefined means the PATH
|
|
78
|
+
* default; the distinction feeds the egress explicit-bin allowance. */
|
|
79
|
+
iapeerBin?: string;
|
|
77
80
|
};
|
|
78
81
|
|
|
79
82
|
function parseFlags(argv: string[]): InitFlags | null {
|
|
@@ -83,7 +86,6 @@ function parseFlags(argv: string[]): InitFlags | null {
|
|
|
83
86
|
skipEcosystem: false,
|
|
84
87
|
skipBinary: false,
|
|
85
88
|
skipGuide: false,
|
|
86
|
-
iapeerBin: "iapeer",
|
|
87
89
|
};
|
|
88
90
|
for (let i = 0; i < argv.length; i++) {
|
|
89
91
|
const a = argv[i];
|
|
@@ -99,7 +101,7 @@ function parseFlags(argv: string[]): InitFlags | null {
|
|
|
99
101
|
case "--skip-ecosystem": f.skipEcosystem = true; break;
|
|
100
102
|
case "--skip-binary": f.skipBinary = true; break;
|
|
101
103
|
case "--skip-guide": f.skipGuide = true; break;
|
|
102
|
-
case "--iapeer-bin": f.iapeerBin = take()
|
|
104
|
+
case "--iapeer-bin": f.iapeerBin = take(); break;
|
|
103
105
|
default:
|
|
104
106
|
console.error(`iapeer-memory init: unknown flag: ${a}`);
|
|
105
107
|
return null;
|
|
@@ -114,22 +116,23 @@ type PeerInfo = { personality: string; intelligence?: string; cwd?: string };
|
|
|
114
116
|
|
|
115
117
|
type RunResult = { exitCode: number; stdout: string; stderr: string };
|
|
116
118
|
|
|
117
|
-
/**
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}
|
|
119
|
+
/** Egress spawn that never throws — a missing binary (and a refusing test
|
|
120
|
+
* egress) is a result, not a crash: the ecosystem half degrades to the
|
|
121
|
+
* same skip path as «iapeer not on this host». */
|
|
122
|
+
function run(
|
|
123
|
+
egress: Egress,
|
|
124
|
+
cmd: string[],
|
|
125
|
+
opts?: { explicitBin?: boolean },
|
|
126
|
+
): RunResult {
|
|
127
|
+
const proc = egress.spawnSync(cmd, { explicitBin: opts?.explicitBin });
|
|
128
|
+
if (proc.spawnError) return { exitCode: 127, stdout: "", stderr: proc.spawnError };
|
|
129
|
+
return { exitCode: proc.exitCode, stdout: proc.stdout, stderr: proc.stderr };
|
|
129
130
|
}
|
|
130
131
|
|
|
131
|
-
function listPeers(
|
|
132
|
-
const proc = run([iapeerBin, "list", "--json"]
|
|
132
|
+
function listPeers(egress: Egress, iapeerBin?: string): PeerInfo[] | null {
|
|
133
|
+
const proc = run(egress, [iapeerBin ?? IAPEER_BIN, "list", "--json"], {
|
|
134
|
+
explicitBin: iapeerBin !== undefined,
|
|
135
|
+
});
|
|
133
136
|
if (proc.exitCode !== 0) return null;
|
|
134
137
|
try {
|
|
135
138
|
return JSON.parse(proc.stdout) as PeerInfo[];
|
|
@@ -155,7 +158,7 @@ function ask(question: string, fallback: string): string {
|
|
|
155
158
|
|
|
156
159
|
// ── the command ──────────────────────────────────────────────────────────────
|
|
157
160
|
|
|
158
|
-
export async function cmdInit(argv: string[]): Promise<number> {
|
|
161
|
+
export async function cmdInit(argv: string[], egress: Egress): Promise<number> {
|
|
159
162
|
const flags = parseFlags(argv);
|
|
160
163
|
if (!flags) return 2;
|
|
161
164
|
|
|
@@ -169,7 +172,7 @@ export async function cmdInit(argv: string[]): Promise<number> {
|
|
|
169
172
|
let embeddingEndpoint = flags.embeddingEndpoint ?? "";
|
|
170
173
|
const rerankerEndpoint = flags.rerankerEndpoint ?? "";
|
|
171
174
|
|
|
172
|
-
const peers = flags.skipEcosystem ? null : listPeers(flags.iapeerBin);
|
|
175
|
+
const peers = flags.skipEcosystem ? null : listPeers(egress, flags.iapeerBin);
|
|
173
176
|
const humanDefault = flags.human ?? naturalPeerDefault(peers) ?? "";
|
|
174
177
|
|
|
175
178
|
if (!vault) {
|
|
@@ -215,7 +218,7 @@ export async function cmdInit(argv: string[]): Promise<number> {
|
|
|
215
218
|
if (flags.skipDeps) {
|
|
216
219
|
step("deps", "skipped (--skip-deps)");
|
|
217
220
|
} else {
|
|
218
|
-
const ver = run([flags.iapeerBin, "version"]);
|
|
221
|
+
const ver = run(egress, [flags.iapeerBin ?? IAPEER_BIN, "version"], { explicitBin: flags.iapeerBin !== undefined });
|
|
219
222
|
if (ver.exitCode !== 0) {
|
|
220
223
|
step("deps", "iapeer foundation not found on PATH — install it first (npx @agfpd/iapeer)", false);
|
|
221
224
|
} else {
|
|
@@ -259,7 +262,7 @@ export async function cmdInit(argv: string[]): Promise<number> {
|
|
|
259
262
|
if (flags.skipBinary) {
|
|
260
263
|
step("binary", "skipped (--skip-binary)");
|
|
261
264
|
} else {
|
|
262
|
-
const bin = installBinary({ outPath: paths.binaryPath });
|
|
265
|
+
const bin = installBinary(egress, { outPath: paths.binaryPath });
|
|
263
266
|
step(
|
|
264
267
|
"binary",
|
|
265
268
|
bin.action === "compiled"
|
|
@@ -290,7 +293,7 @@ export async function cmdInit(argv: string[]): Promise<number> {
|
|
|
290
293
|
const personality = rolePersonality(role);
|
|
291
294
|
const exists = (peers ?? []).some((p) => p.personality === personality);
|
|
292
295
|
if (!exists) {
|
|
293
|
-
const created = run([flags.iapeerBin, "create", personality]);
|
|
296
|
+
const created = run(egress, [flags.iapeerBin ?? IAPEER_BIN, "create", personality], { explicitBin: flags.iapeerBin !== undefined });
|
|
294
297
|
if (created.exitCode !== 0) {
|
|
295
298
|
rolesOk = false;
|
|
296
299
|
console.log(` roles create ${personality} failed: ${created.stderr.trim()}`);
|
|
@@ -303,7 +306,7 @@ export async function cmdInit(argv: string[]): Promise<number> {
|
|
|
303
306
|
// `iapeer list --json` — iapeer 0.2.14), otherwise the core's
|
|
304
307
|
// DOCUMENTED create default (no --path — требование Артура;
|
|
305
308
|
// IAPEER_ROOT-aware).
|
|
306
|
-
const freshPeers = createdAny ? listPeers(flags.iapeerBin) : peers;
|
|
309
|
+
const freshPeers = createdAny ? listPeers(egress, flags.iapeerBin) : peers;
|
|
307
310
|
for (const role of ROLE_NAMES) {
|
|
308
311
|
const personality = rolePersonality(role);
|
|
309
312
|
const registryCwd = (freshPeers ?? []).find((p) => p.personality === personality)?.cwd;
|
|
@@ -354,7 +357,7 @@ export async function cmdInit(argv: string[]): Promise<number> {
|
|
|
354
357
|
if (flags.skipEcosystem) {
|
|
355
358
|
step("fleet", "skipped (--skip-ecosystem)");
|
|
356
359
|
} else {
|
|
357
|
-
const fleet = writeFleetMap({
|
|
360
|
+
const fleet = writeFleetMap(egress, {
|
|
358
361
|
fleetMapPath: paths.fleetMapPath,
|
|
359
362
|
iapeerBin: flags.iapeerBin,
|
|
360
363
|
});
|
|
@@ -376,7 +379,7 @@ export async function cmdInit(argv: string[]): Promise<number> {
|
|
|
376
379
|
step("timers", "skipped (--skip-ecosystem)");
|
|
377
380
|
} else {
|
|
378
381
|
writeLauncherScript({ launcherPath: paths.launcherPath, binaryPath: paths.binaryPath });
|
|
379
|
-
const sent = registerWatcher({
|
|
382
|
+
const sent = registerWatcher(egress, {
|
|
380
383
|
launcherPath: paths.launcherPath,
|
|
381
384
|
iapeerBin: flags.iapeerBin,
|
|
382
385
|
});
|
|
@@ -395,11 +398,11 @@ export async function cmdInit(argv: string[]): Promise<number> {
|
|
|
395
398
|
vaultPath: vault,
|
|
396
399
|
inboxFolders: [getTaxonomy(locale).folders.inbox, getTaxonomy(locale).folders.inboxHuman],
|
|
397
400
|
});
|
|
398
|
-
const sweep = registerTimer({
|
|
401
|
+
const sweep = registerTimer(egress, {
|
|
399
402
|
message: sweepTimerMessage({ checkScriptPath: paths.checkScriptPath }),
|
|
400
403
|
iapeerBin: flags.iapeerBin,
|
|
401
404
|
});
|
|
402
|
-
const dream = registerTimer({
|
|
405
|
+
const dream = registerTimer(egress, {
|
|
403
406
|
message: dreamTimerMessage(),
|
|
404
407
|
iapeerBin: flags.iapeerBin,
|
|
405
408
|
});
|
|
@@ -480,7 +483,7 @@ export async function cmdInit(argv: string[]): Promise<number> {
|
|
|
480
483
|
false,
|
|
481
484
|
);
|
|
482
485
|
} else {
|
|
483
|
-
const off = applyMemoryPlugin({ mode: "off", iapeerBin: flags.iapeerBin });
|
|
486
|
+
const off = applyMemoryPlugin(egress, { mode: "off", iapeerBin: flags.iapeerBin });
|
|
484
487
|
step(
|
|
485
488
|
"plugin-off",
|
|
486
489
|
off.suppressed
|
|
@@ -517,7 +520,7 @@ export async function cmdInit(argv: string[]): Promise<number> {
|
|
|
517
520
|
if (flags.skipEcosystem) {
|
|
518
521
|
step("sweep", "skipped (--skip-ecosystem)");
|
|
519
522
|
} else {
|
|
520
|
-
const sweep = run([flags.iapeerBin, "native-memory", "off", "--all"]);
|
|
523
|
+
const sweep = run(egress, [flags.iapeerBin ?? IAPEER_BIN, "native-memory", "off", "--all"], { explicitBin: flags.iapeerBin !== undefined });
|
|
521
524
|
// One line per peer-runtime — summarise ALL peers, not the last line
|
|
522
525
|
// (e2e §A finding: ".pop()" named one peer while three were swept).
|
|
523
526
|
const sweepLines = sweep.stdout.trim().split("\n").filter(Boolean);
|
|
@@ -6,9 +6,10 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { installBinary } from "../binary.js";
|
|
9
|
+
import type { Egress } from "../egress.js";
|
|
9
10
|
import { memoryPaths } from "../paths.js";
|
|
10
11
|
|
|
11
|
-
export function cmdInstallBinary(argv: string[]): number {
|
|
12
|
+
export function cmdInstallBinary(argv: string[], egress: Egress): number {
|
|
12
13
|
let outPath = memoryPaths().binaryPath;
|
|
13
14
|
for (let i = 0; i < argv.length; i++) {
|
|
14
15
|
if (argv[i] === "--out") {
|
|
@@ -24,7 +25,7 @@ export function cmdInstallBinary(argv: string[]): number {
|
|
|
24
25
|
}
|
|
25
26
|
}
|
|
26
27
|
|
|
27
|
-
const outcome = installBinary({ outPath });
|
|
28
|
+
const outcome = installBinary(egress, { outPath });
|
|
28
29
|
switch (outcome.action) {
|
|
29
30
|
case "compiled":
|
|
30
31
|
console.log(
|
package/src/commands/memoryd.ts
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
import fs from "node:fs";
|
|
20
20
|
import { configFromEnv, startMemoryd } from "@agfpd/iapeer-memory-core";
|
|
21
21
|
import { authorIndexPath, memoryPaths } from "../paths.js";
|
|
22
|
+
import { guardedWriteFileSync, guardedUnlinkSync } from "@agfpd/iapeer-memory-core";
|
|
22
23
|
|
|
23
24
|
export async function cmdMemoryd(argv: string[]): Promise<number> {
|
|
24
25
|
let mcpPort: number | undefined;
|
|
@@ -56,7 +57,7 @@ export async function cmdMemoryd(argv: string[]): Promise<number> {
|
|
|
56
57
|
}
|
|
57
58
|
// pid file — uninstall's stop handle (best-effort; stale files are
|
|
58
59
|
// harmless: the reader checks liveness before signalling).
|
|
59
|
-
|
|
60
|
+
guardedWriteFileSync(paths.pidPath, `${process.pid}\n`);
|
|
60
61
|
|
|
61
62
|
const freshEditWindowRaw = process.env.IAPEER_MEMORY_FRESH_EDIT_WINDOW_S;
|
|
62
63
|
const freshEditWindowS =
|
|
@@ -101,7 +102,7 @@ export async function cmdMemoryd(argv: string[]): Promise<number> {
|
|
|
101
102
|
.close()
|
|
102
103
|
.then(() => {
|
|
103
104
|
try {
|
|
104
|
-
|
|
105
|
+
guardedUnlinkSync(paths.pidPath);
|
|
105
106
|
} catch {
|
|
106
107
|
// best effort
|
|
107
108
|
}
|
package/src/commands/status.ts
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
isLocaleId,
|
|
15
15
|
prepareSqliteRuntime,
|
|
16
16
|
} from "@agfpd/iapeer-memory-core";
|
|
17
|
+
import type { Egress } from "../egress.js";
|
|
17
18
|
import { memoryPaths } from "../paths.js";
|
|
18
19
|
import { readSlot } from "../slot.js";
|
|
19
20
|
import { packageVersion } from "../version.js";
|
|
@@ -50,10 +51,13 @@ export function searchPipelineLine(env: Record<string, string | undefined>): str
|
|
|
50
51
|
|
|
51
52
|
/** Live pipeline from the running memoryd — the same per-component statuses
|
|
52
53
|
* every vault_search returns. Null when memoryd is unreachable. */
|
|
53
|
-
export async function probeSearchPipeline(
|
|
54
|
+
export async function probeSearchPipeline(
|
|
55
|
+
egress: Egress,
|
|
56
|
+
port: number,
|
|
57
|
+
): Promise<string | null> {
|
|
54
58
|
ensureLoopbackNotProxied(); // fleet-class: proxy-env lies about live loopback ports
|
|
55
59
|
try {
|
|
56
|
-
const res = await fetch(`http://127.0.0.1:${port}/mcp`, {
|
|
60
|
+
const res = await egress.fetch(`http://127.0.0.1:${port}/mcp`, {
|
|
57
61
|
method: "POST",
|
|
58
62
|
headers: {
|
|
59
63
|
"content-type": "application/json",
|
|
@@ -85,10 +89,13 @@ export async function probeSearchPipeline(port: number): Promise<string | null>
|
|
|
85
89
|
}
|
|
86
90
|
}
|
|
87
91
|
|
|
88
|
-
async function probeMcp(
|
|
92
|
+
async function probeMcp(
|
|
93
|
+
egress: Egress,
|
|
94
|
+
port: number,
|
|
95
|
+
): Promise<{ line: string; alive: boolean }> {
|
|
89
96
|
ensureLoopbackNotProxied(); // fleet-class: proxy-env lies about live loopback ports
|
|
90
97
|
try {
|
|
91
|
-
const res = await fetch(`http://127.0.0.1:${port}/mcp`, {
|
|
98
|
+
const res = await egress.fetch(`http://127.0.0.1:${port}/mcp`, {
|
|
92
99
|
method: "POST",
|
|
93
100
|
headers: { "content-type": "application/json" },
|
|
94
101
|
body: "{}",
|
|
@@ -102,7 +109,7 @@ async function probeMcp(port: number): Promise<{ line: string; alive: boolean }>
|
|
|
102
109
|
}
|
|
103
110
|
}
|
|
104
111
|
|
|
105
|
-
export async function cmdStatus(argv: string[]): Promise<number> {
|
|
112
|
+
export async function cmdStatus(argv: string[], egress: Egress): Promise<number> {
|
|
106
113
|
if (argv.length) {
|
|
107
114
|
console.error(`iapeer-memory status: unknown flag: ${argv[0]}`);
|
|
108
115
|
return 2;
|
|
@@ -111,7 +118,7 @@ export async function cmdStatus(argv: string[]): Promise<number> {
|
|
|
111
118
|
const version = packageVersion();
|
|
112
119
|
console.log(`iapeer-memory v${version}`);
|
|
113
120
|
|
|
114
|
-
const results = runVerify({ repair: false });
|
|
121
|
+
const results = runVerify(egress, { repair: false });
|
|
115
122
|
const width = Math.max(...results.map((r) => r.name.length), 12);
|
|
116
123
|
for (const r of results) {
|
|
117
124
|
const mark =
|
|
@@ -128,11 +135,11 @@ export async function cmdStatus(argv: string[]): Promise<number> {
|
|
|
128
135
|
);
|
|
129
136
|
|
|
130
137
|
const port = Number(process.env.IAPEER_MEMORY_MCP_PORT || "") || 8766;
|
|
131
|
-
const mcp = await probeMcp(port);
|
|
138
|
+
const mcp = await probeMcp(egress, port);
|
|
132
139
|
console.log(` ${"mcp-endpoint".padEnd(width)} ${mcp.line}`);
|
|
133
140
|
// The live pipeline is only probed when the endpoint is alive — a dead
|
|
134
141
|
// port already told us everything (and the static view says the rest).
|
|
135
|
-
const livePipeline = mcp.alive ? await probeSearchPipeline(port) : null;
|
|
142
|
+
const livePipeline = mcp.alive ? await probeSearchPipeline(egress, port) : null;
|
|
136
143
|
console.log(
|
|
137
144
|
` ${"search".padEnd(width)} ` +
|
|
138
145
|
(livePipeline ?? `${searchPipelineLine(process.env)} (memoryd down — static view)`),
|
|
@@ -17,12 +17,14 @@
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
import fs from "node:fs";
|
|
20
|
+
import type { Egress } from "../egress.js";
|
|
20
21
|
import { memoryPaths } from "../paths.js";
|
|
21
22
|
import { removeBinary } from "../binary.js";
|
|
22
23
|
import { readFleetMap } from "../fleet.js";
|
|
23
24
|
import { applyMemoryPlugin, readSlot, removeSlot, SLOT_PROVIDER } from "../slot.js";
|
|
24
25
|
import { withProvisionLock } from "../surfaces/lock.js";
|
|
25
26
|
import { sweepUnprovision } from "../surfaces/sweep.js";
|
|
27
|
+
import { guardedUnlinkSync } from "@agfpd/iapeer-memory-core";
|
|
26
28
|
import {
|
|
27
29
|
DREAM_TRIGGER_ID,
|
|
28
30
|
SWEEP_TRIGGER_ID,
|
|
@@ -38,18 +40,12 @@ import {
|
|
|
38
40
|
* the command closes the "signal a stranger" class. Probe failure → false
|
|
39
41
|
* (never signal on uncertainty).
|
|
40
42
|
*/
|
|
41
|
-
export function pidLooksLikeOurs(pid: number): boolean {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (proc.exitCode !== 0) return false;
|
|
48
|
-
const command = proc.stdout.toString().trim();
|
|
49
|
-
return command.includes("memoryd");
|
|
50
|
-
} catch {
|
|
51
|
-
return false;
|
|
52
|
-
}
|
|
43
|
+
export function pidLooksLikeOurs(egress: Egress, pid: number): boolean {
|
|
44
|
+
// `ps` probe — egress allowance 3 (read-only lookup FEEDING the verified
|
|
45
|
+
// kill; refusing it would break the guard itself). Never throws.
|
|
46
|
+
const proc = egress.spawnSync(["ps", "-o", "command=", "-p", String(pid)]);
|
|
47
|
+
if (proc.spawnError || proc.exitCode !== 0) return false;
|
|
48
|
+
return proc.stdout.trim().includes("memoryd");
|
|
53
49
|
}
|
|
54
50
|
|
|
55
51
|
/**
|
|
@@ -60,30 +56,27 @@ export function pidLooksLikeOurs(pid: number): boolean {
|
|
|
60
56
|
* by uninstall (stop) and update (managed restart: SIGTERM → the notifier
|
|
61
57
|
* watcher relaunches via the launcher with the fresh binary, ADR-010).
|
|
62
58
|
*/
|
|
63
|
-
export function stopMemorydByPidFile(pidPath: string): string {
|
|
59
|
+
export function stopMemorydByPidFile(egress: Egress, pidPath: string): string {
|
|
64
60
|
let line = "not running (no pid file)";
|
|
65
61
|
try {
|
|
66
62
|
const pid = Number(fs.readFileSync(pidPath, "utf-8").trim());
|
|
67
63
|
if (Number.isInteger(pid) && pid > 1) {
|
|
68
|
-
if (!pidLooksLikeOurs(pid)) {
|
|
64
|
+
if (!pidLooksLikeOurs(egress, pid)) {
|
|
69
65
|
line = `pid file points at a non-memoryd process (${pid}) — NOT signalling; stale file removed`;
|
|
70
66
|
} else {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
} catch {
|
|
75
|
-
line = `stale pid file (process ${pid} gone) — removed`;
|
|
76
|
-
}
|
|
67
|
+
line = egress.kill(pid, "SIGTERM").delivered
|
|
68
|
+
? `SIGTERM sent to pid ${pid} (command verified)`
|
|
69
|
+
: `stale pid file (process ${pid} gone) — removed`;
|
|
77
70
|
}
|
|
78
71
|
}
|
|
79
|
-
|
|
72
|
+
guardedUnlinkSync(pidPath);
|
|
80
73
|
} catch {
|
|
81
74
|
// no pid file — nothing to stop
|
|
82
75
|
}
|
|
83
76
|
return line;
|
|
84
77
|
}
|
|
85
78
|
|
|
86
|
-
export function cmdUninstall(argv: string[]): number {
|
|
79
|
+
export function cmdUninstall(argv: string[], egress: Egress): number {
|
|
87
80
|
let keepBinary = false;
|
|
88
81
|
let iapeerBin = "iapeer";
|
|
89
82
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -141,7 +134,7 @@ export function cmdUninstall(argv: string[]): number {
|
|
|
141
134
|
// session plugin off via the core verb WHILE the declaration is alive
|
|
142
135
|
// (it derives the identity from it; agreed order, auto-removal).
|
|
143
136
|
if (declared.plugin) {
|
|
144
|
-
const off = applyMemoryPlugin({ mode: "off", iapeerBin });
|
|
137
|
+
const off = applyMemoryPlugin(egress, { mode: "off", iapeerBin });
|
|
145
138
|
console.log(
|
|
146
139
|
`plugin : ${
|
|
147
140
|
off.suppressed
|
|
@@ -164,7 +157,7 @@ export function cmdUninstall(argv: string[]): number {
|
|
|
164
157
|
|
|
165
158
|
// notifier wiring: best-effort unregister of all three triggers (not-found
|
|
166
159
|
// is soft on the notifier side; teaching replies go to the index session).
|
|
167
|
-
const unreg = unregisterWatcher({ iapeerBin });
|
|
160
|
+
const unreg = unregisterWatcher(egress, { iapeerBin });
|
|
168
161
|
console.log(
|
|
169
162
|
`watcher : ${
|
|
170
163
|
unreg.ok
|
|
@@ -173,7 +166,7 @@ export function cmdUninstall(argv: string[]): number {
|
|
|
173
166
|
}`,
|
|
174
167
|
);
|
|
175
168
|
for (const id of [SWEEP_TRIGGER_ID, DREAM_TRIGGER_ID]) {
|
|
176
|
-
const t = unregisterTimer({ id, iapeerBin });
|
|
169
|
+
const t = unregisterTimer(egress, { id, iapeerBin });
|
|
177
170
|
console.log(
|
|
178
171
|
`timer : ${
|
|
179
172
|
t.ok
|
|
@@ -183,7 +176,7 @@ export function cmdUninstall(argv: string[]): number {
|
|
|
183
176
|
);
|
|
184
177
|
}
|
|
185
178
|
|
|
186
|
-
console.log(`memoryd : ${stopMemorydByPidFile(paths.pidPath)}`);
|
|
179
|
+
console.log(`memoryd : ${stopMemorydByPidFile(egress, paths.pidPath)}`);
|
|
187
180
|
|
|
188
181
|
if (keepBinary) {
|
|
189
182
|
console.log(`binary : kept (${paths.binaryPath})`);
|