@agfpd/iapeer-memory 0.1.8 → 0.1.10
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/commands/init.ts +23 -2
- package/src/commands/uninstall.ts +23 -1
- package/src/slot.ts +86 -2
- package/src/templates/roles-en.ts +13 -4
- package/src/templates/roles-ru.ts +12 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agfpd/iapeer-memory",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
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.1.
|
|
30
|
+
"@agfpd/iapeer-memory-core": "0.1.10"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@types/bun": "^1.2.0",
|
package/src/commands/init.ts
CHANGED
|
@@ -33,7 +33,7 @@ import { installBinary } from "../binary.js";
|
|
|
33
33
|
import { memoryPaths } from "../paths.js";
|
|
34
34
|
import { provisionVault, writeDefaultConfig } from "../provision.js";
|
|
35
35
|
import { writeRolesManifest, type RoleEntry } from "../roles.js";
|
|
36
|
-
import { writeSlot } from "../slot.js";
|
|
36
|
+
import { applyMemoryPlugin, writeSlot } from "../slot.js";
|
|
37
37
|
import {
|
|
38
38
|
doctrineOwnership,
|
|
39
39
|
guideText,
|
|
@@ -388,7 +388,8 @@ export async function cmdInit(argv: string[]): Promise<number> {
|
|
|
388
388
|
);
|
|
389
389
|
}
|
|
390
390
|
|
|
391
|
-
// 8. slot declaration (the contract: written by the provider, atomic)
|
|
391
|
+
// 8. slot declaration (the contract: written by the provider, atomic) —
|
|
392
|
+
// v1.1: includes the plugin block the core derives installs from.
|
|
392
393
|
const slot = writeSlot({
|
|
393
394
|
slotPath: paths.slotPath,
|
|
394
395
|
version,
|
|
@@ -402,6 +403,26 @@ export async function cmdInit(argv: string[]): Promise<number> {
|
|
|
402
403
|
slot.action !== "refused-foreign",
|
|
403
404
|
);
|
|
404
405
|
|
|
406
|
+
// 8b. session plugin across the fleet — the core verb derives the plugin
|
|
407
|
+
// from the block JUST written (order matters), installs per-peer on claude
|
|
408
|
+
// and host-globally on codex; new peers get it from the core's birth-hook.
|
|
409
|
+
// Soft-skip on an older core (verb landed in iapeer 0.2.25).
|
|
410
|
+
if (flags.skipEcosystem) {
|
|
411
|
+
step("plugin", "skipped (--skip-ecosystem)");
|
|
412
|
+
} else if (slot.action === "refused-foreign") {
|
|
413
|
+
step("plugin", "skipped (slot refused — nothing to derive the plugin from)");
|
|
414
|
+
} else {
|
|
415
|
+
const plug = applyMemoryPlugin({ mode: "on", iapeerBin: flags.iapeerBin });
|
|
416
|
+
step(
|
|
417
|
+
"plugin",
|
|
418
|
+
plug.suppressed
|
|
419
|
+
? "skipped (test sandbox — core calls suppressed)"
|
|
420
|
+
: plug.ok
|
|
421
|
+
? "session plugin rolled out across the fleet (iapeer memory-plugin on --all)"
|
|
422
|
+
: `soft-skip: ${plug.detail.slice(0, 160) || "verb failed"} — upgrade the iapeer core (≥0.2.25) or run manually: iapeer memory-plugin on --all`,
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
|
|
405
426
|
// 9. native-memory sweep — the core's lever (one home of runtime forms);
|
|
406
427
|
// soft-skip when the verb is unavailable (older core), verify re-runs later.
|
|
407
428
|
if (flags.skipEcosystem) {
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
import fs from "node:fs";
|
|
19
19
|
import { memoryPaths } from "../paths.js";
|
|
20
20
|
import { removeBinary } from "../binary.js";
|
|
21
|
-
import { removeSlot } from "../slot.js";
|
|
21
|
+
import { applyMemoryPlugin, readSlot, removeSlot, SLOT_PROVIDER } from "../slot.js";
|
|
22
22
|
import {
|
|
23
23
|
DREAM_TRIGGER_ID,
|
|
24
24
|
SWEEP_TRIGGER_ID,
|
|
@@ -95,6 +95,28 @@ export function cmdUninstall(argv: string[]): number {
|
|
|
95
95
|
const paths = memoryPaths();
|
|
96
96
|
let failed = false;
|
|
97
97
|
|
|
98
|
+
// Session plugin OFF across the fleet BEFORE removing the declaration —
|
|
99
|
+
// the core verb DERIVES the plugin identity from the slot's v1.1 block;
|
|
100
|
+
// once the declaration is gone there is nothing to derive from (agreed
|
|
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».
|
|
106
|
+
const declared = readSlot(paths.slotPath);
|
|
107
|
+
if (declared && declared.provider === SLOT_PROVIDER) {
|
|
108
|
+
const off = applyMemoryPlugin({ mode: "off", iapeerBin });
|
|
109
|
+
console.log(
|
|
110
|
+
`plugin : ${
|
|
111
|
+
off.suppressed
|
|
112
|
+
? "skipped (test sandbox — core calls suppressed)"
|
|
113
|
+
: off.ok
|
|
114
|
+
? "session plugin removed across the fleet (memory-plugin off --all; codex side is host-global)"
|
|
115
|
+
: `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\``
|
|
116
|
+
}`,
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
98
120
|
const slot = removeSlot(paths.slotPath);
|
|
99
121
|
if (slot === "refused-foreign") {
|
|
100
122
|
console.log("slot : held by a FOREIGN provider — left intact");
|
package/src/slot.ts
CHANGED
|
@@ -11,7 +11,15 @@
|
|
|
11
11
|
* - `version` = the package version (the same single source as the doctrine
|
|
12
12
|
* marker, ADR-010); our `update` re-writes it (P4 obligation);
|
|
13
13
|
* - `heartbeat` (optional) = the absolute path whose mtime memoryd touches —
|
|
14
|
-
* the core may show staleness in `iapeer status`, never acts on it
|
|
14
|
+
* the core may show staleness in `iapeer status`, never acts on it;
|
|
15
|
+
* - `plugin` (v1.1, agreed 10.06 + live in iapeer 0.2.25) = the marketplace
|
|
16
|
+
* identity of the session plugin. The core DERIVES installs from this block:
|
|
17
|
+
* birth-hook installs for new peers, `iapeer memory-plugin on|off (--peer|
|
|
18
|
+
* --all)` is the operator verb (built-in marketplace ensure + stale-cache
|
|
19
|
+
* retry). Reference reader: iapeer src/status/index.ts parsePluginBlock —
|
|
20
|
+
* all three fields required non-empty, anything less = treated as a v1
|
|
21
|
+
* declaration (no install). marketplaceRef matches iapeer onboard's
|
|
22
|
+
* MARKETPLACE_REF for the distribution default.
|
|
15
23
|
*/
|
|
16
24
|
|
|
17
25
|
import fs from "node:fs";
|
|
@@ -20,12 +28,29 @@ import path from "node:path";
|
|
|
20
28
|
export const SLOT_PROVIDER = "iapeer-memory";
|
|
21
29
|
export const SLOT_PACKAGE = "@agfpd/iapeer-memory";
|
|
22
30
|
|
|
31
|
+
/** Mirror of iapeer's MemoryProviderPlugin (src/status/index.ts). */
|
|
32
|
+
export type MemoryProviderPlugin = {
|
|
33
|
+
/** Plugin id in the marketplace (forms `<name>@<marketplace>`). */
|
|
34
|
+
name: string;
|
|
35
|
+
/** Marketplace NAME the plugin id keys on. */
|
|
36
|
+
marketplace: string;
|
|
37
|
+
/** Source ref for `plugin marketplace add` when absent on the host (owner/repo). */
|
|
38
|
+
marketplaceRef: string;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const SLOT_PLUGIN: MemoryProviderPlugin = {
|
|
42
|
+
name: "iapeer-memory",
|
|
43
|
+
marketplace: "agfpd",
|
|
44
|
+
marketplaceRef: "agfpd/agfpd-marketplace",
|
|
45
|
+
};
|
|
46
|
+
|
|
23
47
|
export type MemoryProviderSlot = {
|
|
24
48
|
provider: string;
|
|
25
49
|
package: string;
|
|
26
50
|
version: string;
|
|
27
51
|
registeredAt: string;
|
|
28
52
|
heartbeat?: string;
|
|
53
|
+
plugin?: MemoryProviderPlugin;
|
|
29
54
|
};
|
|
30
55
|
|
|
31
56
|
/** Never throws: missing / unreadable / malformed → null (empty slot). */
|
|
@@ -59,7 +84,11 @@ export function writeSlot(opts: {
|
|
|
59
84
|
existing &&
|
|
60
85
|
existing.version === opts.version &&
|
|
61
86
|
existing.heartbeat === opts.heartbeat &&
|
|
62
|
-
existing.package === SLOT_PACKAGE
|
|
87
|
+
existing.package === SLOT_PACKAGE &&
|
|
88
|
+
existing.plugin &&
|
|
89
|
+
existing.plugin.name === SLOT_PLUGIN.name &&
|
|
90
|
+
existing.plugin.marketplace === SLOT_PLUGIN.marketplace &&
|
|
91
|
+
existing.plugin.marketplaceRef === SLOT_PLUGIN.marketplaceRef
|
|
63
92
|
) {
|
|
64
93
|
return { action: "identical", existing }; // idempotent re-init: no churn
|
|
65
94
|
}
|
|
@@ -69,6 +98,7 @@ export function writeSlot(opts: {
|
|
|
69
98
|
version: opts.version,
|
|
70
99
|
registeredAt: opts.nowIso ?? new Date().toISOString(),
|
|
71
100
|
...(opts.heartbeat ? { heartbeat: opts.heartbeat } : {}),
|
|
101
|
+
plugin: SLOT_PLUGIN,
|
|
72
102
|
};
|
|
73
103
|
fs.mkdirSync(path.dirname(opts.slotPath), { recursive: true });
|
|
74
104
|
const tmp = `${opts.slotPath}.tmp`;
|
|
@@ -87,3 +117,57 @@ export function removeSlot(slotPath: string): SlotRemoveResult {
|
|
|
87
117
|
fs.unlinkSync(slotPath);
|
|
88
118
|
return "removed";
|
|
89
119
|
}
|
|
120
|
+
|
|
121
|
+
export type MemoryPluginApplyResult = {
|
|
122
|
+
ok: boolean;
|
|
123
|
+
detail: string;
|
|
124
|
+
/** True when the test-sandbox fuse blocked the call — callers report a
|
|
125
|
+
* SKIP, not a failure (same contract as watcher.ts iapSend). */
|
|
126
|
+
suppressed?: boolean;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Fleet-wide install/remove of the slot-declared session plugin via the core
|
|
131
|
+
* verb `iapeer memory-plugin <on|off> --all` (iapeer ≥0.2.25; marketplace
|
|
132
|
+
* ensure + stale-cache retry live INSIDE the verb). The verb derives the
|
|
133
|
+
* plugin identity from the slot declaration — so `on` runs AFTER writeSlot
|
|
134
|
+
* and `off` runs BEFORE removeSlot (agreed order, auto-removal: a dead
|
|
135
|
+
* provider's plugin must not keep injecting).
|
|
136
|
+
*
|
|
137
|
+
* Hard fuse first (same class as iapSend, incident 10.06): `on --all`
|
|
138
|
+
* mutates the HOST fleet — no sandbox env contains it, tests must never
|
|
139
|
+
* reach the live core. Both belts honoured.
|
|
140
|
+
*/
|
|
141
|
+
export function applyMemoryPlugin(opts: {
|
|
142
|
+
mode: "on" | "off";
|
|
143
|
+
iapeerBin?: string;
|
|
144
|
+
}): MemoryPluginApplyResult {
|
|
145
|
+
if (
|
|
146
|
+
process.env.IAPEER_MEMORY_SUPPRESS_IAP_SEND === "1" ||
|
|
147
|
+
process.env.IAPEER_TEST_SANDBOX === "1"
|
|
148
|
+
) {
|
|
149
|
+
return {
|
|
150
|
+
ok: false,
|
|
151
|
+
suppressed: true,
|
|
152
|
+
detail: "memory-plugin call suppressed (test sandbox)",
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
const bin = opts.iapeerBin ?? "iapeer";
|
|
156
|
+
try {
|
|
157
|
+
const proc = Bun.spawnSync([bin, "memory-plugin", opts.mode, "--all"], {
|
|
158
|
+
stdout: "pipe",
|
|
159
|
+
stderr: "pipe",
|
|
160
|
+
});
|
|
161
|
+
if (proc.exitCode !== 0) {
|
|
162
|
+
return {
|
|
163
|
+
ok: false,
|
|
164
|
+
detail:
|
|
165
|
+
(proc.stderr.toString().trim() || proc.stdout.toString().trim() || "").slice(0, 200) ||
|
|
166
|
+
`iapeer memory-plugin exited ${proc.exitCode}`,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
return { ok: true, detail: proc.stdout.toString().trim() };
|
|
170
|
+
} catch (err) {
|
|
171
|
+
return { ok: false, detail: `${bin} unavailable: ${String(err)}` };
|
|
172
|
+
}
|
|
173
|
+
}
|
|
@@ -42,6 +42,9 @@ different world.
|
|
|
42
42
|
vault_search, link to an active project phase when it belongs to one.
|
|
43
43
|
- rejected list — statistics for the digest only; the Scriber has
|
|
44
44
|
already pinged the authors directly, no action from you.
|
|
45
|
+
- "filtered out entirely, no substance" one-liner → NO action and no
|
|
46
|
+
reply (the Scriber must always send one outbound — it closes its
|
|
47
|
+
ephemeral window; the empty form is normal, not an error).
|
|
45
48
|
- agent-memory paths → your light curation pass (below).
|
|
46
49
|
- human-inbox results → place like drafts, then the nightly vault
|
|
47
50
|
health-check: orphan wikilinks (auto-fix via vault_search by similar
|
|
@@ -120,7 +123,7 @@ notifier delivers memoryd events straight to you, strictly one event per
|
|
|
120
123
|
fresh session; nobody else may task you. Vault root on this host:
|
|
121
124
|
\`{{VAULT_PATH}}\` — event paths arrive ABSOLUTE; NEVER guess the vault
|
|
122
125
|
location (a stale copy elsewhere on disk is a different world). Per event: filter, vet what needs
|
|
123
|
-
vetting, then
|
|
126
|
+
vetting, then exactly ONE report to the Index (plus direct pings to
|
|
124
127
|
rejected authors). Fact-checking uses your runtime's web tools, edits use
|
|
125
128
|
the native file tools; after the report, only local writes until the
|
|
126
129
|
session ends.
|
|
@@ -138,9 +141,15 @@ session ends.
|
|
|
138
141
|
canon → vet (mode permanent).
|
|
139
142
|
- \`HUMAN_INBOX_BATCH: [<absolute paths…>]\` — the human's nightly batch
|
|
140
143
|
(daily, config hour): vet as drafts.
|
|
141
|
-
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
+
- The report is UNCONDITIONAL — exactly one outbound send_to_peer per
|
|
145
|
+
session, ALWAYS, even when filtering left nothing (then it is one line
|
|
146
|
+
to the Index: "delivery filtered out entirely, no substance").
|
|
147
|
+
MECHANICS, not politeness: your ephemeral window is closed by your
|
|
148
|
+
outbound reply — a silent finish leaves the session unreaped and STALLS
|
|
149
|
+
the serial delivery queue for every event behind you (live incident
|
|
150
|
+
10.06: one silent finish parked the inbox pipeline for an hour).
|
|
151
|
+
Substance report carries: vetted results, passed-through agent-memory
|
|
152
|
+
paths, human-inbox results.
|
|
144
153
|
|
|
145
154
|
## Modes
|
|
146
155
|
|
|
@@ -34,6 +34,9 @@ locale: ru
|
|
|
34
34
|
активного проекта, если заметка из его темы.
|
|
35
35
|
- rejected-список — только статистика для дайджеста; авторов Scriber
|
|
36
36
|
уже пинганул напрямую, действий от тебя ноль.
|
|
37
|
+
- однострочник «доставка отфильтрована целиком, субстанции нет» → НОЛЬ
|
|
38
|
+
действий и без ответа (Scriber обязан отправить ровно один исходящий —
|
|
39
|
+
он закрывает его эфемерное окно; пустая форма — норма, не ошибка).
|
|
37
40
|
- пути оперативки → твой лёгкий проход курирования (ниже).
|
|
38
41
|
- human-inbox → размести как черновики, затем ночной health-check
|
|
39
42
|
vault: битые wikilinks (чини через vault_search по похожему title,
|
|
@@ -112,7 +115,7 @@ locale: ru
|
|
|
112
115
|
memoryd прямо тебе, строго по одному на свежую сессию; никто другой задач
|
|
113
116
|
не ставит. Корень vault на этом хосте: \`{{VAULT_PATH}}\` — пути в
|
|
114
117
|
событиях АБСОЛЮТНЫЕ; НИКОГДА не угадывай расположение vault (протухшая
|
|
115
|
-
копия в другом месте диска — другой мир). На событие: фильтр, вычитка нужного, затем
|
|
118
|
+
копия в другом месте диска — другой мир). На событие: фильтр, вычитка нужного, затем ровно ОДИН отчёт
|
|
116
119
|
Индексу (плюс прямые пинги авторам rejected). Фактчек — web-тулами
|
|
117
120
|
рантайма, правки — нативными файловыми тулами; после отчёта — только
|
|
118
121
|
локальные записи до конца сессии.
|
|
@@ -130,9 +133,14 @@ memoryd прямо тебе, строго по одному на свежую с
|
|
|
130
133
|
вычитывай (режим permanent).
|
|
131
134
|
- \`HUMAN_INBOX_BATCH: [<абсолютные пути…>]\` — ночная партия человека
|
|
132
135
|
(раз в сутки, час конфигом): вычитывай как черновики.
|
|
133
|
-
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
+
- Отчёт БЕЗУСЛОВЕН — ровно один исходящий send_to_peer на сессию, ВСЕГДА,
|
|
137
|
+
даже когда фильтр не оставил ничего (тогда это одна строка Индексу:
|
|
138
|
+
«доставка отфильтрована целиком, субстанции нет»). МЕХАНИКА, не
|
|
139
|
+
вежливость: твоё эфемерное окно закрывается твоим исходящим ответом —
|
|
140
|
+
молчаливый финиш оставляет сессию нереапнутой и СТАВИТ серийную очередь
|
|
141
|
+
доставки для всех событий за тобой (живой инцидент 10.06: один
|
|
142
|
+
молчаливый финиш запарковал inbox-конвейер на час). Отчёт с субстанцией
|
|
143
|
+
несёт: вычитанное, переданные пути оперативки, human-inbox.
|
|
136
144
|
|
|
137
145
|
## Режимы
|
|
138
146
|
|