@pcircle/memesh 4.7.3 → 4.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/AGENTS.md +3 -1
- package/README.de.md +42 -16
- package/README.md +46 -19
- package/README.zh-TW.md +42 -17
- package/dashboard/dist/index.html +1 -1
- package/dist/core/agent-message-storage.d.ts +76 -0
- package/dist/core/agent-message-storage.d.ts.map +1 -0
- package/dist/core/agent-message-storage.js +359 -0
- package/dist/core/agent-message-storage.js.map +1 -0
- package/dist/core/agent-messaging.d.ts +224 -0
- package/dist/core/agent-messaging.d.ts.map +1 -0
- package/dist/core/agent-messaging.js +881 -0
- package/dist/core/agent-messaging.js.map +1 -0
- package/dist/core/agent-router.d.ts +194 -0
- package/dist/core/agent-router.d.ts.map +1 -0
- package/dist/core/agent-router.js +1138 -0
- package/dist/core/agent-router.js.map +1 -0
- package/dist/core/briefing.d.ts.map +1 -1
- package/dist/core/briefing.js +8 -1
- package/dist/core/briefing.js.map +1 -1
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js.map +1 -1
- package/dist/core/doctor.d.ts +19 -0
- package/dist/core/doctor.d.ts.map +1 -1
- package/dist/core/doctor.js +176 -2
- package/dist/core/doctor.js.map +1 -1
- package/dist/core/dreamer.d.ts +8 -5
- package/dist/core/dreamer.d.ts.map +1 -1
- package/dist/core/dreamer.js +88 -3
- package/dist/core/dreamer.js.map +1 -1
- package/dist/core/product-improvements.d.ts +61 -0
- package/dist/core/product-improvements.d.ts.map +1 -0
- package/dist/core/product-improvements.js +216 -0
- package/dist/core/product-improvements.js.map +1 -0
- package/dist/core/repo-state.d.ts +11 -0
- package/dist/core/repo-state.d.ts.map +1 -0
- package/dist/core/repo-state.js +70 -0
- package/dist/core/repo-state.js.map +1 -0
- package/dist/core/schema-export.d.ts.map +1 -1
- package/dist/core/schema-export.js +70 -0
- package/dist/core/schema-export.js.map +1 -1
- package/dist/core/task-state.d.ts.map +1 -1
- package/dist/core/task-state.js +3 -3
- package/dist/core/task-state.js.map +1 -1
- package/dist/core/updater.d.ts +2 -0
- package/dist/core/updater.d.ts.map +1 -1
- package/dist/core/updater.js +11 -5
- package/dist/core/updater.js.map +1 -1
- package/dist/core/work-topology.d.ts.map +1 -1
- package/dist/core/work-topology.js +1 -0
- package/dist/core/work-topology.js.map +1 -1
- package/dist/host-adapters/acp-client.d.ts +147 -0
- package/dist/host-adapters/acp-client.d.ts.map +1 -0
- package/dist/host-adapters/acp-client.js +793 -0
- package/dist/host-adapters/acp-client.js.map +1 -0
- package/dist/host-adapters/claude-channel.d.ts +75 -0
- package/dist/host-adapters/claude-channel.d.ts.map +1 -0
- package/dist/host-adapters/claude-channel.js +255 -0
- package/dist/host-adapters/claude-channel.js.map +1 -0
- package/dist/host-adapters/codex-app-server.d.ts +83 -0
- package/dist/host-adapters/codex-app-server.d.ts.map +1 -0
- package/dist/host-adapters/codex-app-server.js +360 -0
- package/dist/host-adapters/codex-app-server.js.map +1 -0
- package/dist/host-adapters/codex-cli-queue.d.ts +17 -0
- package/dist/host-adapters/codex-cli-queue.d.ts.map +1 -0
- package/dist/host-adapters/codex-cli-queue.js +85 -0
- package/dist/host-adapters/codex-cli-queue.js.map +1 -0
- package/dist/host-runtime/acp.d.ts +57 -0
- package/dist/host-runtime/acp.d.ts.map +1 -0
- package/dist/host-runtime/acp.js +330 -0
- package/dist/host-runtime/acp.js.map +1 -0
- package/dist/host-runtime/claude.d.ts +41 -0
- package/dist/host-runtime/claude.d.ts.map +1 -0
- package/dist/host-runtime/claude.js +213 -0
- package/dist/host-runtime/claude.js.map +1 -0
- package/dist/host-runtime/codex-session.d.ts +24 -0
- package/dist/host-runtime/codex-session.d.ts.map +1 -0
- package/dist/host-runtime/codex-session.js +98 -0
- package/dist/host-runtime/codex-session.js.map +1 -0
- package/dist/host-runtime/codex.d.ts +33 -0
- package/dist/host-runtime/codex.d.ts.map +1 -0
- package/dist/host-runtime/codex.js +215 -0
- package/dist/host-runtime/codex.js.map +1 -0
- package/dist/host-runtime/config.d.ts +9 -0
- package/dist/host-runtime/config.d.ts.map +1 -0
- package/dist/host-runtime/config.js +91 -0
- package/dist/host-runtime/config.js.map +1 -0
- package/dist/host-runtime/router-client.d.ts +38 -0
- package/dist/host-runtime/router-client.d.ts.map +1 -0
- package/dist/host-runtime/router-client.js +463 -0
- package/dist/host-runtime/router-client.js.map +1 -0
- package/dist/host-runtime/router.d.ts +3 -0
- package/dist/host-runtime/router.d.ts.map +1 -0
- package/dist/host-runtime/router.js +43 -0
- package/dist/host-runtime/router.js.map +1 -0
- package/dist/mcp/server.js +2 -2
- package/dist/mcp/server.js.map +1 -1
- package/dist/skills-manifest.json +22 -17
- package/dist/storage/conflicts.d.ts.map +1 -1
- package/dist/storage/conflicts.js +8 -1
- package/dist/storage/conflicts.js.map +1 -1
- package/dist/storage/schema.d.ts +1 -1
- package/dist/storage/schema.d.ts.map +1 -1
- package/dist/storage/schema.js +255 -0
- package/dist/storage/schema.js.map +1 -1
- package/dist/transports/agent-messaging.d.ts +11 -0
- package/dist/transports/agent-messaging.d.ts.map +1 -0
- package/dist/transports/agent-messaging.js +273 -0
- package/dist/transports/agent-messaging.js.map +1 -0
- package/dist/transports/cli/cli.d.ts +1 -1
- package/dist/transports/cli/cli.d.ts.map +1 -1
- package/dist/transports/cli/cli.js +376 -12
- package/dist/transports/cli/cli.js.map +1 -1
- package/dist/transports/http/server.d.ts.map +1 -1
- package/dist/transports/http/server.js +31 -2
- package/dist/transports/http/server.js.map +1 -1
- package/dist/transports/mcp/handlers.d.ts +152 -1
- package/dist/transports/mcp/handlers.d.ts.map +1 -1
- package/dist/transports/mcp/handlers.js +108 -3
- package/dist/transports/mcp/handlers.js.map +1 -1
- package/dist/transports/schemas.d.ts +106 -1
- package/dist/transports/schemas.d.ts.map +1 -1
- package/dist/transports/schemas.js +86 -0
- package/dist/transports/schemas.js.map +1 -1
- package/docs/platforms/README.md +167 -0
- package/docs/platforms/agent-messaging.md +290 -0
- package/hooks/hooks.json +11 -0
- package/llms-install.md +101 -25
- package/package.json +14 -5
- package/scripts/hooks/_generated/repo-state.js +77 -0
- package/scripts/hooks/_generated/schema.js +255 -0
- package/scripts/hooks/_generated/task-state.js +3 -3
- package/scripts/hooks/_generated/work-topology.js +1 -0
- package/scripts/hooks/_shared.js +77 -97
- package/scripts/hooks/auto-update-runner.mjs +243 -0
- package/scripts/hooks/session-start.js +12 -2
- package/scripts/hooks/session-summary.js +7 -6
- package/skills/memesh/SKILL.md +40 -1
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
closeSync,
|
|
5
|
+
linkSync,
|
|
6
|
+
openSync,
|
|
7
|
+
readFileSync,
|
|
8
|
+
realpathSync,
|
|
9
|
+
unlinkSync,
|
|
10
|
+
writeFileSync,
|
|
11
|
+
writeSync,
|
|
12
|
+
} from 'fs';
|
|
13
|
+
import { createHash, randomBytes } from 'crypto';
|
|
14
|
+
import { pathToFileURL } from 'url';
|
|
15
|
+
import { runGlobalUpdate } from '../../dist/core/updater.js';
|
|
16
|
+
|
|
17
|
+
export const AUTO_UPDATE_LOCK_TTL_MS = 10 * 60 * 1000;
|
|
18
|
+
export const AUTO_UPDATE_RECOVERY_TTL_MS = 30 * 1000;
|
|
19
|
+
const MAX_RECOVERY_GENERATIONS = 64;
|
|
20
|
+
|
|
21
|
+
function ownerToken() {
|
|
22
|
+
return `${process.pid}-${Date.now()}-${randomBytes(8).toString('hex')}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function readLock(lockPath) {
|
|
26
|
+
try {
|
|
27
|
+
const [token, pidRaw, startedAtRaw, version] = readFileSync(lockPath, 'utf8').split('\n');
|
|
28
|
+
const pid = Number(pidRaw);
|
|
29
|
+
const startedAt = Number(startedAtRaw);
|
|
30
|
+
if (!token || !Number.isSafeInteger(pid) || pid <= 0 || !Number.isFinite(startedAt)) return null;
|
|
31
|
+
return { token, pid, startedAt, version: version || null };
|
|
32
|
+
} catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function processIsAlive(pid) {
|
|
38
|
+
try {
|
|
39
|
+
process.kill(pid, 0);
|
|
40
|
+
return true;
|
|
41
|
+
} catch (err) {
|
|
42
|
+
return err?.code !== 'ESRCH';
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function createOwnedFile(filePath, token, payload) {
|
|
47
|
+
const fd = openSync(filePath, 'wx', 0o600);
|
|
48
|
+
try {
|
|
49
|
+
writeFileSync(fd, payload);
|
|
50
|
+
} finally {
|
|
51
|
+
closeSync(fd);
|
|
52
|
+
}
|
|
53
|
+
return token;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function tryCreateLock(lockPath, version) {
|
|
57
|
+
const token = ownerToken();
|
|
58
|
+
const startedAt = Date.now();
|
|
59
|
+
try {
|
|
60
|
+
createOwnedFile(
|
|
61
|
+
lockPath,
|
|
62
|
+
token,
|
|
63
|
+
`${token}\n${process.pid}\n${startedAt}\n${version}\n`,
|
|
64
|
+
);
|
|
65
|
+
return { acquired: true, lockPath, ownerToken: token, recoveredStale: false };
|
|
66
|
+
} catch (err) {
|
|
67
|
+
if (err?.code === 'EEXIST') {
|
|
68
|
+
return { acquired: false, lockPath, ownerToken: null, recoveredStale: false };
|
|
69
|
+
}
|
|
70
|
+
throw err;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function autoUpdateRecoveryClaimPath(lockPath, staleToken, generation = 0) {
|
|
75
|
+
const tokenHash = createHash('sha256').update(staleToken).digest('hex').slice(0, 24);
|
|
76
|
+
return `${lockPath}.recover.${tokenHash}.${generation}`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function tryCreateRecoveryClaim(claimPath) {
|
|
80
|
+
const token = ownerToken();
|
|
81
|
+
const candidatePath = `${claimPath}.candidate.${token}`;
|
|
82
|
+
createOwnedFile(
|
|
83
|
+
candidatePath,
|
|
84
|
+
token,
|
|
85
|
+
`${token}\n${process.pid}\n${Date.now()}\nrecovery\n`,
|
|
86
|
+
);
|
|
87
|
+
try {
|
|
88
|
+
// The candidate is complete before the fixed claim path appears. linkSync
|
|
89
|
+
// gives the destination O_EXCL semantics, so a crash cannot leave a
|
|
90
|
+
// partially written claim that blocks recovery forever.
|
|
91
|
+
linkSync(candidatePath, claimPath);
|
|
92
|
+
return { acquired: true, claimPath, ownerToken: token };
|
|
93
|
+
} catch (err) {
|
|
94
|
+
if (err?.code === 'EEXIST') {
|
|
95
|
+
return { acquired: false, claimPath, ownerToken: null };
|
|
96
|
+
}
|
|
97
|
+
throw err;
|
|
98
|
+
} finally {
|
|
99
|
+
try { unlinkSync(candidatePath); } catch { /* crash-only candidate orphan */ }
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function tryAcquireRecoveryClaim(lockPath, staleToken) {
|
|
104
|
+
for (let generation = 0; generation < MAX_RECOVERY_GENERATIONS; generation += 1) {
|
|
105
|
+
const claimPath = autoUpdateRecoveryClaimPath(lockPath, staleToken, generation);
|
|
106
|
+
const created = tryCreateRecoveryClaim(claimPath);
|
|
107
|
+
if (created.acquired) return created;
|
|
108
|
+
|
|
109
|
+
const existing = readLock(claimPath);
|
|
110
|
+
if (!existing) return null;
|
|
111
|
+
if (Date.now() - existing.startedAt <= AUTO_UPDATE_RECOVERY_TTL_MS) return null;
|
|
112
|
+
if (processIsAlive(existing.pid)) return null;
|
|
113
|
+
// A complete claim whose owner crashed remains immutable. All contenders
|
|
114
|
+
// derive the same successor generation, where O_EXCL elects one recovery
|
|
115
|
+
// owner without deleting or replacing the orphaned claim.
|
|
116
|
+
}
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function releaseAutoUpdateLock(lockPath, token) {
|
|
121
|
+
const current = readLock(lockPath);
|
|
122
|
+
if (!current || current.token !== token) return false;
|
|
123
|
+
try {
|
|
124
|
+
unlinkSync(lockPath);
|
|
125
|
+
return true;
|
|
126
|
+
} catch {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function recoverObservedStaleAutoUpdateLock(lockPath, observed) {
|
|
132
|
+
const recovery = tryAcquireRecoveryClaim(lockPath, observed.token);
|
|
133
|
+
if (!recovery?.acquired) return false;
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
const current = readLock(lockPath);
|
|
137
|
+
if (!current || current.token !== observed.token) return false;
|
|
138
|
+
if (Date.now() - current.startedAt <= AUTO_UPDATE_LOCK_TTL_MS) return false;
|
|
139
|
+
if (processIsAlive(current.pid)) return false;
|
|
140
|
+
try {
|
|
141
|
+
unlinkSync(lockPath);
|
|
142
|
+
return true;
|
|
143
|
+
} catch (err) {
|
|
144
|
+
if (err?.code === 'ENOENT') return false;
|
|
145
|
+
throw err;
|
|
146
|
+
}
|
|
147
|
+
} finally {
|
|
148
|
+
releaseAutoUpdateLock(recovery.claimPath, recovery.ownerToken);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function tryAcquireAutoUpdateLock(lockPath, version) {
|
|
153
|
+
const created = tryCreateLock(lockPath, version);
|
|
154
|
+
if (created.acquired) return created;
|
|
155
|
+
|
|
156
|
+
const observed = readLock(lockPath);
|
|
157
|
+
if (!observed) return created;
|
|
158
|
+
if (Date.now() - observed.startedAt <= AUTO_UPDATE_LOCK_TTL_MS) return created;
|
|
159
|
+
if (processIsAlive(observed.pid)) return created;
|
|
160
|
+
|
|
161
|
+
// Recovery never installs. It removes only the exact stale token observed
|
|
162
|
+
// under a separate O_EXCL claim, then defers so a later trigger must acquire
|
|
163
|
+
// the normal update lock. No delayed stale contender touches a fresh lock.
|
|
164
|
+
const recoveredStale = recoverObservedStaleAutoUpdateLock(lockPath, observed);
|
|
165
|
+
return { ...created, recoveredStale };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function writeLine(fd, line) {
|
|
169
|
+
try {
|
|
170
|
+
writeSync(fd, `[memesh auto-update] ${line}\n`);
|
|
171
|
+
return true;
|
|
172
|
+
} catch {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function errorMessage(err) {
|
|
178
|
+
const raw = err instanceof Error ? err.message : String(err);
|
|
179
|
+
return raw.replace(/[\r\n]+/g, ' ').slice(0, 500);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function runAutoUpdate(targetVersion, lockPath) {
|
|
183
|
+
let lock;
|
|
184
|
+
try {
|
|
185
|
+
lock = tryAcquireAutoUpdateLock(lockPath, targetVersion);
|
|
186
|
+
} catch (err) {
|
|
187
|
+
writeLine(2, `FAILED target=${targetVersion} stage=lock error=${errorMessage(err)}`);
|
|
188
|
+
return 1;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (!lock.acquired) {
|
|
192
|
+
const state = lock.recoveredStale ? 'RECOVERED_STALE' : 'IN_PROGRESS';
|
|
193
|
+
writeLine(1, `${state} target=${targetVersion} lock=${lockPath}`);
|
|
194
|
+
return 0;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (!writeLine(1, `START target=${targetVersion} pid=${process.pid}`)) {
|
|
198
|
+
releaseAutoUpdateLock(lockPath, lock.ownerToken);
|
|
199
|
+
return 1;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
let installedVersion = null;
|
|
203
|
+
let updateError = null;
|
|
204
|
+
try {
|
|
205
|
+
installedVersion = runGlobalUpdate(targetVersion).installedVersion;
|
|
206
|
+
} catch (err) {
|
|
207
|
+
updateError = err;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const released = releaseAutoUpdateLock(lockPath, lock.ownerToken);
|
|
211
|
+
if (updateError) {
|
|
212
|
+
writeLine(
|
|
213
|
+
2,
|
|
214
|
+
`FAILED target=${targetVersion} stage=install-or-readback error=${errorMessage(updateError)}`,
|
|
215
|
+
);
|
|
216
|
+
if (!released) writeLine(2, `FAILED target=${targetVersion} stage=lock-release`);
|
|
217
|
+
return 1;
|
|
218
|
+
}
|
|
219
|
+
if (!released) {
|
|
220
|
+
writeLine(2, `FAILED target=${targetVersion} stage=lock-release installed=${installedVersion}`);
|
|
221
|
+
return 1;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
writeLine(1, `SUCCESS target=${targetVersion} installed=${installedVersion}`);
|
|
225
|
+
return 0;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async function main() {
|
|
229
|
+
const [targetVersion, lockPath] = process.argv.slice(2);
|
|
230
|
+
if (!targetVersion || !lockPath) {
|
|
231
|
+
writeLine(2, 'FAILED stage=arguments error=expected target version and lock path');
|
|
232
|
+
process.exitCode = 1;
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
process.exitCode = runAutoUpdate(targetVersion, lockPath);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const invokedPath = process.argv[1]
|
|
239
|
+
? pathToFileURL(realpathSync(process.argv[1])).href
|
|
240
|
+
: null;
|
|
241
|
+
if (invokedPath === import.meta.url) {
|
|
242
|
+
await main();
|
|
243
|
+
}
|
|
@@ -24,7 +24,9 @@ import {
|
|
|
24
24
|
// home resolver the update-check cache itself uses.
|
|
25
25
|
memeshDir as memeshHomeDir,
|
|
26
26
|
parseTaskState,
|
|
27
|
+
readRepoState,
|
|
27
28
|
readUpdateCheckCache,
|
|
29
|
+
repoStateLines,
|
|
28
30
|
resolvePluginRoot,
|
|
29
31
|
resolveSessionLimit,
|
|
30
32
|
taskStateLines,
|
|
@@ -396,8 +398,8 @@ function spawnFreshUpdateCheck(installedVersion) {
|
|
|
396
398
|
// racy — both peers' renames are destructive, so each could
|
|
397
399
|
// read its own token back and both would spawn a refresh.
|
|
398
400
|
// O_EXCL is the standard POSIX/libuv primitive that lets at
|
|
399
|
-
// most one process succeed.
|
|
400
|
-
//
|
|
401
|
+
// most one process succeed. The updater runner uses the same O_EXCL
|
|
402
|
+
// ownership primitive for its separate update lock.
|
|
401
403
|
const token = `${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
|
402
404
|
const claim = () => {
|
|
403
405
|
try {
|
|
@@ -976,8 +978,16 @@ process.stdin.on('end', async () => {
|
|
|
976
978
|
try { process.stderr.write(`[memesh session-start] memory-context: ${err?.message || err}\n`); } catch {}
|
|
977
979
|
}
|
|
978
980
|
|
|
981
|
+
// Same prefix, same rule, as `assembleBriefing`: repository facts are
|
|
982
|
+
// context for memories, never a briefing on their own. Inside the
|
|
983
|
+
// emptiness gate so a project with nothing recorded still injects
|
|
984
|
+
// nothing — a fenced block containing only a branch name tells the
|
|
985
|
+
// agent something it can already see. briefing.test.ts's parity case
|
|
986
|
+
// is what keeps this identical to the tool side.
|
|
979
987
|
let memoryContext = '';
|
|
980
988
|
if (memoryLines.length > 0) {
|
|
989
|
+
const repoLines = repoStateLines(readRepoState(data.cwd));
|
|
990
|
+
if (repoLines.length > 0) memoryLines.unshift(...repoLines, '');
|
|
981
991
|
// Same wrapper pre-edit-recall uses: an explicit "background data,
|
|
982
992
|
// not instructions" preamble plus a fenced block. Memory content is
|
|
983
993
|
// attacker-influenced in the general case (anything the agent has
|
|
@@ -60,9 +60,10 @@ try {
|
|
|
60
60
|
} catch { /* best-effort */ }
|
|
61
61
|
|
|
62
62
|
/**
|
|
63
|
-
* Run auto-update at Stop hook: reads cache, evaluates policy, and
|
|
64
|
-
*
|
|
65
|
-
* avoiding the TOCTOU race where install would overwrite dist/
|
|
63
|
+
* Run auto-update at Stop hook: reads cache, evaluates policy, and dispatches
|
|
64
|
+
* the detached updater runner if warranted. Runs after all session work
|
|
65
|
+
* completes, avoiding the TOCTOU race where install would overwrite dist/
|
|
66
|
+
* mid-session.
|
|
66
67
|
*/
|
|
67
68
|
async function runAutoUpdateAtStop() {
|
|
68
69
|
try {
|
|
@@ -75,7 +76,7 @@ async function runAutoUpdateAtStop() {
|
|
|
75
76
|
const policy = resolveAutoUpdatePolicy(process.env);
|
|
76
77
|
const decision = decideAutoUpdateHook(installedVersion, cache, policy);
|
|
77
78
|
if (decision.run) {
|
|
78
|
-
spawnAutoUpdate(decision.latest,
|
|
79
|
+
await spawnAutoUpdate(decision.latest, _installChannelMod);
|
|
79
80
|
}
|
|
80
81
|
} catch {
|
|
81
82
|
// Best-effort — never crash the hook.
|
|
@@ -648,8 +649,8 @@ process.stdin.on('end', async () => {
|
|
|
648
649
|
try { process.stderr.write(`[memesh session-summary] ${err?.message || err}\n`); } catch {}
|
|
649
650
|
}
|
|
650
651
|
|
|
651
|
-
//
|
|
652
|
-
// so
|
|
652
|
+
// Dispatch auto-update if policy + cache permit. Runs after all session work
|
|
653
|
+
// so the runner cannot overwrite dist/ while peer hooks are reading it.
|
|
653
654
|
await runAutoUpdateAtStop();
|
|
654
655
|
|
|
655
656
|
// Emit NOTHING on success — not `{"suppressOutput": true}`.
|
package/skills/memesh/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: memesh
|
|
3
|
-
description: Use MeMesh to remember, recall, and manage AI knowledge across sessions. Triggers when the user asks to remember something, recall past decisions, forget outdated info, learn from mistakes,
|
|
3
|
+
description: Use MeMesh to remember, recall, and manage AI knowledge across sessions, and to exchange durable task-focused messages with local agents. Triggers when the user asks to remember something, recall past decisions, forget outdated info, learn from mistakes, analyze work patterns, contact another agent, or handle a memesh_message_available marker. Also triggers when the user asks "what do you remember", "where did we leave off", or wants to catch up on a project; when a session starts and project context is needed; and proactively when you make important decisions, fix bugs, learn lessons worth preserving, or owe another agent a requested result or disposition.
|
|
4
4
|
user-invocable: true
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -27,6 +27,44 @@ All examples below use CLI. MCP tools accept the same parameters as JSON objects
|
|
|
27
27
|
|
|
28
28
|
Four moments. Everything else in this file is detail.
|
|
29
29
|
|
|
30
|
+
## Durable messages and active-host delivery
|
|
31
|
+
|
|
32
|
+
Use the `message` tool when another local agent needs a durable, exact-recipient handoff rather than an inferred memory. `send`, `poll`, `fetch`, `intake`, `ack`, `disposition`, `activation`, and `receipts` are independent lifecycle actions: fetching or host acceptance never implies acknowledgement or workflow acceptance.
|
|
33
|
+
|
|
34
|
+
### Handle messages to a result
|
|
35
|
+
|
|
36
|
+
- A `memesh_message_available` marker is routing metadata, not the payload. Call `message` with `action: "fetch"` using its exact `project`, `recipient`, and `message_id`; never answer from the marker or guess missing IDs.
|
|
37
|
+
- Reply when the payload asks for work, a decision, review, feedback, missing information, status, or an explicit response. An FYI with no requested action needs no reply unless it asks for a receipt.
|
|
38
|
+
- Do not leave requested work silently pending. If the result is not immediate, send one concise acceptance or blocker with the owner and next action; send the result when available. Do not send recurring progress chatter.
|
|
39
|
+
- Reply with `action: "send"` to the original sender, in the same project. Preserve the original `correlation_id` (or use the original `message_id` when none exists), set `reply_to` to the original `message_id`, and use a stable idempotency key. Route to the sender's stable principal unless the message explicitly requires an exact session.
|
|
40
|
+
- A useful reply states the outcome, decision or findings, essential evidence, any unresolved blocker, and the owner or next action. One result-oriented reply is enough; omit greetings, thanks, and conversational acknowledgements. Ask a follow-up only when missing information prevents a responsible result.
|
|
41
|
+
- `ack` means the recipient explicitly acknowledges the message; `disposition` records workflow state such as `accepted`, `deferred`, or `completed`. Record only facts that occurred. Neither replaces a requested substantive reply.
|
|
42
|
+
|
|
43
|
+
Use the routing and identity fields returned by `fetch`. A reply has this shape (replace placeholders with fetched or caller-stable values):
|
|
44
|
+
|
|
45
|
+
```json
|
|
46
|
+
{
|
|
47
|
+
"action": "send",
|
|
48
|
+
"project": "<original project>",
|
|
49
|
+
"sender": "<this agent's stable principal>",
|
|
50
|
+
"recipient": "<original sender>",
|
|
51
|
+
"target_kind": "principal",
|
|
52
|
+
"idempotency_key": "reply:<original message_id>:result",
|
|
53
|
+
"correlation_id": "<original correlation_id or message_id>",
|
|
54
|
+
"reply_to": "<original message_id>",
|
|
55
|
+
"content_type": "application/json",
|
|
56
|
+
"payload": {
|
|
57
|
+
"outcome": "<result, decision, or blocker>",
|
|
58
|
+
"evidence": ["<only the evidence needed by the recipient>"],
|
|
59
|
+
"next": "<owner and next action, if any>"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
An active compatible managed host can receive a native push, which removes polling for that live delivery. One-time provider enablement and a MeMesh-managed Codex app-server, Claude Channel, or Gemini ACP session may be required; ordinary unattached sessions are presence-only/inbound-unavailable. Adapter imports and a live router socket do not prove host registration or `host_accept`. Do not promise that a stopped, missing, or replaced session will wake up: it is not resumed or silently rerouted. Use the stable principal for logical routing, and an exact session/generation only when delivery must not move to a replacement connection. Local owns durable storage and host-native delivery; Cloud relay, A2A, SSE, discovery, or fetch is not host delivery.
|
|
65
|
+
|
|
66
|
+
Durable audit does not mean unbounded silent growth. Owners can inspect it with `memesh message storage report --cutoff <ISO timestamp>`, preview bounded terminal-payload tombstones with `memesh message storage prune --cutoff <ISO timestamp>`, and explicitly add `--apply`. Never prune unresolved/offline-pending work. `MEMESH_AGENT_MESSAGE_STORAGE_QUOTA_BYTES` is an optional owner policy; there is no default quota or automatic pruning.
|
|
67
|
+
|
|
30
68
|
**SESSION START → load the briefing (once).**
|
|
31
69
|
Call the `briefing` MCP tool or run `memesh briefing`. It returns the assembled
|
|
32
70
|
work topology: where the work was left off (goal / next / blocked / done),
|
|
@@ -79,6 +117,7 @@ If MeMesh is installed as a Claude Code plugin, these happen **without any actio
|
|
|
79
117
|
| **Stop** | Session ends | Auto-captures session knowledge + runs LLM failure analysis → lessons |
|
|
80
118
|
| **PreCompact** | Before context compaction | Saves important knowledge before history is compressed |
|
|
81
119
|
| **PreToolUse (Bash)** | Before a command runs | Fires accepted lesson-guards — warns when a recorded mistake is about to repeat |
|
|
120
|
+
| **SessionStart (Codex, async)** | A configured Codex session starts or resumes | Registers that exact live thread for metadata-only MeMesh message wakeups; exits without registering outside the configured workspace |
|
|
82
121
|
|
|
83
122
|
Because of the SessionStart hook: **in Claude Code, do NOT call `briefing` at
|
|
84
123
|
session start — it is already in your context.** Call it only mid-session
|