@agfpd/iapeer-memory 0.1.13 → 0.2.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/package.json +2 -2
- package/src/binary.ts +17 -10
- package/src/cli.ts +38 -8
- package/src/commands/hook.ts +7 -8
- package/src/commands/init.ts +136 -66
- package/src/commands/install-binary.ts +3 -2
- package/src/commands/memoryd.ts +3 -2
- package/src/commands/provision-peer.ts +172 -0
- package/src/commands/status.ts +15 -8
- package/src/commands/uninstall.ts +78 -48
- package/src/commands/update.ts +125 -53
- package/src/commands/verify.ts +125 -13
- package/src/egress.ts +181 -0
- package/src/fleet.ts +94 -31
- package/src/paths.ts +5 -0
- package/src/provision.ts +3 -2
- package/src/roles.ts +2 -1
- package/src/signing.ts +19 -23
- package/src/slot.ts +97 -52
- package/src/surfaces/claude.ts +497 -0
- package/src/surfaces/codex.ts +156 -0
- package/src/surfaces/lock.ts +72 -0
- package/src/surfaces/sweep.ts +170 -0
- package/src/sync-versions.ts +3 -2
- package/src/templates/index.ts +2 -1
- package/src/templates/skills.ts +196 -0
- package/src/watcher.ts +72 -68
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `iapeer-memory provision-peer|unprovision-peer --cwd <abs> --runtime <r>
|
|
3
|
+
* --personality <p> [--occasion <o>]` — the provider half of the iapeer
|
|
4
|
+
* v1.2 slot contract
|
|
5
|
+
* (ADR-009 v1.2: direct per-peer surfaces; the core shells into THIS command
|
|
6
|
+
* at peer birth/sweeps and never learns the surface forms).
|
|
7
|
+
*
|
|
8
|
+
* Contract obligations (§7, agreed with the iapeer core 10.06):
|
|
9
|
+
* - argv form, absolute paths, no shell — the core spawns us directly;
|
|
10
|
+
* - IDEMPOTENT: re-running repairs, never corrupts (every surface is a
|
|
11
|
+
* read-merge-write of our own keys only, atomic);
|
|
12
|
+
* - tolerant of PARALLEL calls — the host-wide provision lock serialises
|
|
13
|
+
* bodies (lock.ts);
|
|
14
|
+
* - {occasion} dictionary: birth | sweep-on | off-peer | off-all | remove.
|
|
15
|
+
* We have NO host-global surfaces (требование Артура №3 «глобально не
|
|
16
|
+
* класть» — even the codex MCP form is project-local), so the ref-count
|
|
17
|
+
* distinction off-peer vs off-all is moot here: every occasion of the
|
|
18
|
+
* un-verb means «strip this peer's surfaces». Validated, logged, not
|
|
19
|
+
* branched on.
|
|
20
|
+
*
|
|
21
|
+
* Runtime forms: claude — hooks + mcp + skills (surfaces/claude.ts);
|
|
22
|
+
* codex — per-peer MCP via `<cwd>/.codex/config.toml` (surfaces/codex.ts;
|
|
23
|
+
* hooks/skills are the P5 experiment). Exit: 0 ok, 1 a surface failed,
|
|
24
|
+
* 2 usage.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import fs from "node:fs";
|
|
28
|
+
import { memoryPaths } from "../paths.js";
|
|
29
|
+
import {
|
|
30
|
+
provisionClaudePeer,
|
|
31
|
+
unprovisionClaudePeer,
|
|
32
|
+
type SurfaceOutcome,
|
|
33
|
+
} from "../surfaces/claude.js";
|
|
34
|
+
import { provisionCodexPeer, unprovisionCodexPeer } from "../surfaces/codex.js";
|
|
35
|
+
import { withProvisionLock } from "../surfaces/lock.js";
|
|
36
|
+
|
|
37
|
+
/** The memoryd MCP port FACT of this host (config.env is already loaded into
|
|
38
|
+
* the process env by the CLI boot) — baked literally into both MCP surface
|
|
39
|
+
* forms (no env substitution in either, D2 decision). Shared by the verbs
|
|
40
|
+
* here and the fleet sweeps in init/update/verify. Same resolution as
|
|
41
|
+
* status.ts. */
|
|
42
|
+
export function mcpPort(): number {
|
|
43
|
+
return Number(process.env.IAPEER_MEMORY_MCP_PORT || "") || 8766;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const OCCASIONS = ["birth", "sweep-on", "off-peer", "off-all", "remove"] as const;
|
|
47
|
+
export type Occasion = (typeof OCCASIONS)[number];
|
|
48
|
+
|
|
49
|
+
const RUNTIMES = ["claude", "codex"] as const;
|
|
50
|
+
|
|
51
|
+
type Flags = {
|
|
52
|
+
cwd: string;
|
|
53
|
+
runtime: (typeof RUNTIMES)[number];
|
|
54
|
+
occasion: Occasion;
|
|
55
|
+
personality: string;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
function parseFlags(
|
|
59
|
+
verb: "provision-peer" | "unprovision-peer",
|
|
60
|
+
argv: string[],
|
|
61
|
+
defaultOccasion: Occasion,
|
|
62
|
+
): Flags | null {
|
|
63
|
+
let cwd = "";
|
|
64
|
+
let runtime = "";
|
|
65
|
+
let occasion: string = defaultOccasion;
|
|
66
|
+
let personality = "";
|
|
67
|
+
for (let i = 0; i < argv.length; i++) {
|
|
68
|
+
const a = argv[i];
|
|
69
|
+
switch (a) {
|
|
70
|
+
case "--cwd": cwd = argv[++i] ?? ""; break;
|
|
71
|
+
case "--runtime": runtime = argv[++i] ?? ""; break;
|
|
72
|
+
case "--occasion": occasion = argv[++i] ?? ""; break;
|
|
73
|
+
case "--personality": personality = argv[++i] ?? ""; break;
|
|
74
|
+
default:
|
|
75
|
+
console.error(`iapeer-memory ${verb}: unknown flag: ${a}`);
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (!cwd || !cwd.startsWith("/")) {
|
|
80
|
+
console.error(`iapeer-memory ${verb}: --cwd must be an absolute path (got "${cwd}")`);
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
if (!(RUNTIMES as readonly string[]).includes(runtime)) {
|
|
84
|
+
console.error(`iapeer-memory ${verb}: --runtime must be one of ${RUNTIMES.join("|")} (got "${runtime}")`);
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
if (!(OCCASIONS as readonly string[]).includes(occasion)) {
|
|
88
|
+
console.error(`iapeer-memory ${verb}: --occasion must be one of ${OCCASIONS.join("|")} (got "${occasion}")`);
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
// claude provision bakes the LITERAL identity header (battle form of the
|
|
92
|
+
// core's own .mcp.json) — without the personality there is nothing honest
|
|
93
|
+
// to bake. The core's executor supports the {personality} placeholder
|
|
94
|
+
// (agreed 11.06); the package sweep reads it from fleet.json. The un-verb
|
|
95
|
+
// needs no identity: removal matches our key/marks, not the header value.
|
|
96
|
+
if (verb === "provision-peer" && runtime === "claude" && !personality) {
|
|
97
|
+
console.error(
|
|
98
|
+
"iapeer-memory provision-peer: --personality is required for --runtime claude " +
|
|
99
|
+
"(the literal MCP identity header is baked at provision time)",
|
|
100
|
+
);
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
cwd,
|
|
105
|
+
runtime: runtime as Flags["runtime"],
|
|
106
|
+
occasion: occasion as Occasion,
|
|
107
|
+
personality,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function report(verb: string, flags: Flags, outcomes: SurfaceOutcome[]): number {
|
|
112
|
+
let failed = false;
|
|
113
|
+
for (const o of outcomes) {
|
|
114
|
+
if (o.action === "failed") failed = true;
|
|
115
|
+
console.log(
|
|
116
|
+
`${o.action === "failed" ? "FAIL" : "ok "} ${o.surface.padEnd(7)} ${o.action}${o.detail ? ` — ${o.detail}` : ""} (${o.path})`,
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
console.log(
|
|
120
|
+
`${verb}: ${flags.runtime} peer at ${flags.cwd} (occasion: ${flags.occasion})` +
|
|
121
|
+
(failed ? " — FAILED; re-run is the repair path (idempotent)" : "") +
|
|
122
|
+
"\npickup: surfaces apply on the peer's NEXT session start (live sessions do not re-read them)",
|
|
123
|
+
);
|
|
124
|
+
return failed ? 1 : 0;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function cmdProvisionPeer(argv: string[]): number {
|
|
128
|
+
const flags = parseFlags("provision-peer", argv, "sweep-on");
|
|
129
|
+
if (!flags) return 2;
|
|
130
|
+
if (!fs.existsSync(flags.cwd)) {
|
|
131
|
+
console.error(`iapeer-memory provision-peer: cwd does not exist: ${flags.cwd}`);
|
|
132
|
+
return 1;
|
|
133
|
+
}
|
|
134
|
+
const paths = memoryPaths();
|
|
135
|
+
const locked = withProvisionLock({
|
|
136
|
+
stateDir: paths.stateDir,
|
|
137
|
+
fn: () =>
|
|
138
|
+
flags.runtime === "codex"
|
|
139
|
+
? provisionCodexPeer({ cwd: flags.cwd, port: mcpPort() })
|
|
140
|
+
: provisionClaudePeer({
|
|
141
|
+
cwd: flags.cwd,
|
|
142
|
+
hooksDir: paths.hooksDir,
|
|
143
|
+
port: mcpPort(),
|
|
144
|
+
personality: flags.personality,
|
|
145
|
+
}),
|
|
146
|
+
});
|
|
147
|
+
if (!locked.acquired) {
|
|
148
|
+
console.error(`iapeer-memory provision-peer: ${locked.detail}`);
|
|
149
|
+
return 1;
|
|
150
|
+
}
|
|
151
|
+
return report("provision-peer", flags, locked.result);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function cmdUnprovisionPeer(argv: string[]): number {
|
|
155
|
+
const flags = parseFlags("unprovision-peer", argv, "off-peer");
|
|
156
|
+
if (!flags) return 2;
|
|
157
|
+
// a vanished cwd is a VALID un-provision target (occasion=remove races the
|
|
158
|
+
// peer directory removal) — every surface simply reports `absent`
|
|
159
|
+
const paths = memoryPaths();
|
|
160
|
+
const locked = withProvisionLock({
|
|
161
|
+
stateDir: paths.stateDir,
|
|
162
|
+
fn: () =>
|
|
163
|
+
flags.runtime === "codex"
|
|
164
|
+
? unprovisionCodexPeer({ cwd: flags.cwd })
|
|
165
|
+
: unprovisionClaudePeer({ cwd: flags.cwd }),
|
|
166
|
+
});
|
|
167
|
+
if (!locked.acquired) {
|
|
168
|
+
console.error(`iapeer-memory unprovision-peer: ${locked.detail}`);
|
|
169
|
+
return 1;
|
|
170
|
+
}
|
|
171
|
+
return report("unprovision-peer", flags, locked.result);
|
|
172
|
+
}
|
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)`),
|
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
* host. SYMMETRY OBLIGATION of the memory-slot contract: the provider that
|
|
4
4
|
* writes the slot declaration removes it.
|
|
5
5
|
*
|
|
6
|
-
* What it removes:
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
6
|
+
* What it removes: direct session surfaces across the fleet (ADR-009 v1.2 —
|
|
7
|
+
* own entries/keys/dirs only, swept BEFORE the declaration falls), the slot
|
|
8
|
+
* declaration (own only — a foreign slot is refused), notifier triggers,
|
|
9
|
+
* memoryd (verified-pid stop), the compiled binary. What it deliberately
|
|
10
|
+
* KEEPS: the vault (user data), the package config (operator-owned),
|
|
11
|
+
* state/cache (cheap to rebuild, may hold migrate backups!).
|
|
11
12
|
*
|
|
12
13
|
* Native auto-memory of the fleet is NOT restored (contract decision,
|
|
13
14
|
* c968219): silent re-enabling would quietly resurrect split memory across
|
|
@@ -16,9 +17,14 @@
|
|
|
16
17
|
*/
|
|
17
18
|
|
|
18
19
|
import fs from "node:fs";
|
|
20
|
+
import type { Egress } from "../egress.js";
|
|
19
21
|
import { memoryPaths } from "../paths.js";
|
|
20
22
|
import { removeBinary } from "../binary.js";
|
|
23
|
+
import { readFleetMap } from "../fleet.js";
|
|
21
24
|
import { applyMemoryPlugin, readSlot, removeSlot, SLOT_PROVIDER } from "../slot.js";
|
|
25
|
+
import { withProvisionLock } from "../surfaces/lock.js";
|
|
26
|
+
import { sweepUnprovision } from "../surfaces/sweep.js";
|
|
27
|
+
import { guardedUnlinkSync } from "@agfpd/iapeer-memory-core";
|
|
22
28
|
import {
|
|
23
29
|
DREAM_TRIGGER_ID,
|
|
24
30
|
SWEEP_TRIGGER_ID,
|
|
@@ -34,18 +40,12 @@ import {
|
|
|
34
40
|
* the command closes the "signal a stranger" class. Probe failure → false
|
|
35
41
|
* (never signal on uncertainty).
|
|
36
42
|
*/
|
|
37
|
-
export function pidLooksLikeOurs(pid: number): boolean {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if (proc.exitCode !== 0) return false;
|
|
44
|
-
const command = proc.stdout.toString().trim();
|
|
45
|
-
return command.includes("memoryd");
|
|
46
|
-
} catch {
|
|
47
|
-
return false;
|
|
48
|
-
}
|
|
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");
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
/**
|
|
@@ -56,30 +56,27 @@ export function pidLooksLikeOurs(pid: number): boolean {
|
|
|
56
56
|
* by uninstall (stop) and update (managed restart: SIGTERM → the notifier
|
|
57
57
|
* watcher relaunches via the launcher with the fresh binary, ADR-010).
|
|
58
58
|
*/
|
|
59
|
-
export function stopMemorydByPidFile(pidPath: string): string {
|
|
59
|
+
export function stopMemorydByPidFile(egress: Egress, pidPath: string): string {
|
|
60
60
|
let line = "not running (no pid file)";
|
|
61
61
|
try {
|
|
62
62
|
const pid = Number(fs.readFileSync(pidPath, "utf-8").trim());
|
|
63
63
|
if (Number.isInteger(pid) && pid > 1) {
|
|
64
|
-
if (!pidLooksLikeOurs(pid)) {
|
|
64
|
+
if (!pidLooksLikeOurs(egress, pid)) {
|
|
65
65
|
line = `pid file points at a non-memoryd process (${pid}) — NOT signalling; stale file removed`;
|
|
66
66
|
} else {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
} catch {
|
|
71
|
-
line = `stale pid file (process ${pid} gone) — removed`;
|
|
72
|
-
}
|
|
67
|
+
line = egress.kill(pid, "SIGTERM").delivered
|
|
68
|
+
? `SIGTERM sent to pid ${pid} (command verified)`
|
|
69
|
+
: `stale pid file (process ${pid} gone) — removed`;
|
|
73
70
|
}
|
|
74
71
|
}
|
|
75
|
-
|
|
72
|
+
guardedUnlinkSync(pidPath);
|
|
76
73
|
} catch {
|
|
77
74
|
// no pid file — nothing to stop
|
|
78
75
|
}
|
|
79
76
|
return line;
|
|
80
77
|
}
|
|
81
78
|
|
|
82
|
-
export function cmdUninstall(argv: string[]): number {
|
|
79
|
+
export function cmdUninstall(argv: string[], egress: Egress): number {
|
|
83
80
|
let keepBinary = false;
|
|
84
81
|
let iapeerBin = "iapeer";
|
|
85
82
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -95,26 +92,59 @@ export function cmdUninstall(argv: string[]): number {
|
|
|
95
92
|
const paths = memoryPaths();
|
|
96
93
|
let failed = false;
|
|
97
94
|
|
|
98
|
-
//
|
|
99
|
-
//
|
|
100
|
-
//
|
|
101
|
-
// order with the core, auto-removal: a dead provider's plugin must not
|
|
102
|
-
// keep injecting). Guard: only when the slot is OURS — running `off`
|
|
103
|
-
// against a foreign declaration would strip the FOREIGN provider's plugin.
|
|
104
|
-
// codex nuance: the codex plugin is host-global, so `off` there is always
|
|
105
|
-
// `--all` semantics; per-peer off on codex answers «use --all».
|
|
95
|
+
// Direct session surfaces OFF across the fleet BEFORE removing the
|
|
96
|
+
// declaration (ADR-009 v1.2 mirror symmetry: a dead provider's surfaces
|
|
97
|
+
// must not keep pointing at a void). Guard: only when the slot is OURS.
|
|
106
98
|
const declared = readSlot(paths.slotPath);
|
|
107
99
|
if (declared && declared.provider === SLOT_PROVIDER) {
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
100
|
+
const fleet = readFleetMap(paths.fleetMapPath);
|
|
101
|
+
if (!fleet) {
|
|
102
|
+
console.log(
|
|
103
|
+
`surfaces : fleet map missing/unreadable (${paths.fleetMapPath}) — nothing swept; ` +
|
|
104
|
+
"manual per peer: iapeer-memory unprovision-peer --cwd <cwd> --runtime <r>",
|
|
105
|
+
);
|
|
106
|
+
} else {
|
|
107
|
+
const locked = withProvisionLock({
|
|
108
|
+
stateDir: paths.stateDir,
|
|
109
|
+
fn: () => sweepUnprovision({ fleet }),
|
|
110
|
+
});
|
|
111
|
+
if (!locked.acquired) {
|
|
112
|
+
console.log(`surfaces : ${locked.detail}`);
|
|
113
|
+
failed = true;
|
|
114
|
+
} else {
|
|
115
|
+
const { results, skipped } = locked.result;
|
|
116
|
+
const bad = results.filter((r) => !r.ok);
|
|
117
|
+
console.log(
|
|
118
|
+
`surfaces : stripped from ${results.length - bad.length}/${results.length} peer-runtime(s)` +
|
|
119
|
+
(skipped.length ? ` (${skipped.length} skipped)` : ""),
|
|
120
|
+
);
|
|
121
|
+
for (const b of bad) {
|
|
122
|
+
failed = true;
|
|
123
|
+
console.log(
|
|
124
|
+
`surfaces : FAIL ${b.personality}:${b.runtime} — ${b.outcomes
|
|
125
|
+
.filter((o) => o.action === "failed")
|
|
126
|
+
.map((o) => `${o.surface}: ${o.detail ?? "failed"}`)
|
|
127
|
+
.join("; ")}`,
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Legacy v1.1 path: the slot still carries a plugin block — sweep the
|
|
134
|
+
// session plugin off via the core verb WHILE the declaration is alive
|
|
135
|
+
// (it derives the identity from it; agreed order, auto-removal).
|
|
136
|
+
if (declared.plugin) {
|
|
137
|
+
const off = applyMemoryPlugin(egress, { mode: "off", iapeerBin });
|
|
138
|
+
console.log(
|
|
139
|
+
`plugin : ${
|
|
140
|
+
off.suppressed
|
|
141
|
+
? "skipped (test sandbox — core calls suppressed)"
|
|
142
|
+
: off.ok
|
|
143
|
+
? "legacy session plugin removed across the fleet (memory-plugin off --all; codex side is host-global)"
|
|
144
|
+
: `off not applied (${off.detail.slice(0, 160)}) — manual fallback (works without the slot): per claude peer \`claude plugin uninstall iapeer-memory@agfpd --scope project\` from its cwd; codex (host-global): \`codex plugin remove iapeer-memory@agfpd\``
|
|
145
|
+
}`,
|
|
146
|
+
);
|
|
147
|
+
}
|
|
118
148
|
}
|
|
119
149
|
|
|
120
150
|
const slot = removeSlot(paths.slotPath);
|
|
@@ -127,7 +157,7 @@ export function cmdUninstall(argv: string[]): number {
|
|
|
127
157
|
|
|
128
158
|
// notifier wiring: best-effort unregister of all three triggers (not-found
|
|
129
159
|
// is soft on the notifier side; teaching replies go to the index session).
|
|
130
|
-
const unreg = unregisterWatcher({ iapeerBin });
|
|
160
|
+
const unreg = unregisterWatcher(egress, { iapeerBin });
|
|
131
161
|
console.log(
|
|
132
162
|
`watcher : ${
|
|
133
163
|
unreg.ok
|
|
@@ -136,7 +166,7 @@ export function cmdUninstall(argv: string[]): number {
|
|
|
136
166
|
}`,
|
|
137
167
|
);
|
|
138
168
|
for (const id of [SWEEP_TRIGGER_ID, DREAM_TRIGGER_ID]) {
|
|
139
|
-
const t = unregisterTimer({ id, iapeerBin });
|
|
169
|
+
const t = unregisterTimer(egress, { id, iapeerBin });
|
|
140
170
|
console.log(
|
|
141
171
|
`timer : ${
|
|
142
172
|
t.ok
|
|
@@ -146,7 +176,7 @@ export function cmdUninstall(argv: string[]): number {
|
|
|
146
176
|
);
|
|
147
177
|
}
|
|
148
178
|
|
|
149
|
-
console.log(`memoryd : ${stopMemorydByPidFile(paths.pidPath)}`);
|
|
179
|
+
console.log(`memoryd : ${stopMemorydByPidFile(egress, paths.pidPath)}`);
|
|
150
180
|
|
|
151
181
|
if (keepBinary) {
|
|
152
182
|
console.log(`binary : kept (${paths.binaryPath})`);
|