gm-skill 2.0.1216 → 2.0.1217
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/gm-plugkit/plugkit-wasm-wrapper.js +154 -6
- package/gm.json +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -35,7 +35,7 @@ An earlier generation fanned out fifteen per-platform downstream repos (gm-cc, g
|
|
|
35
35
|
|
|
36
36
|
## Version
|
|
37
37
|
|
|
38
|
-
`2.0.
|
|
38
|
+
`2.0.1217` — auto-bumped from the canonical `gm` repo. Every push to `AnEntrypoint/gm` (or any cascading sibling crate) republishes this package.
|
|
39
39
|
|
|
40
40
|
## Source of truth
|
|
41
41
|
|
|
@@ -124,11 +124,35 @@ function ensureSpoolPollGate(cwd) {
|
|
|
124
124
|
const gateScript = path.join(gmHooks, 'spool-poll-gate.js');
|
|
125
125
|
const want = spoolPollGateScript();
|
|
126
126
|
let need = true;
|
|
127
|
+
let oldSha = '';
|
|
128
|
+
let existed = false;
|
|
127
129
|
try {
|
|
128
130
|
const existing = fs.readFileSync(gateScript, 'utf8');
|
|
131
|
+
existed = true;
|
|
129
132
|
if (existing === want) need = false;
|
|
133
|
+
else {
|
|
134
|
+
try {
|
|
135
|
+
const _crypto = require('crypto');
|
|
136
|
+
oldSha = _crypto.createHash('sha256').update(existing).digest('hex').slice(0, 12);
|
|
137
|
+
} catch (_) {}
|
|
138
|
+
}
|
|
130
139
|
} catch (_) {}
|
|
131
|
-
if (need)
|
|
140
|
+
if (need) {
|
|
141
|
+
fs.writeFileSync(gateScript, want);
|
|
142
|
+
try {
|
|
143
|
+
const _crypto = require('crypto');
|
|
144
|
+
const newSha = _crypto.createHash('sha256').update(want).digest('hex').slice(0, 12);
|
|
145
|
+
try {
|
|
146
|
+
logEvent('bootstrap', existed ? 'gate.refreshed' : 'gate.installed', {
|
|
147
|
+
cwd,
|
|
148
|
+
path: gateScript,
|
|
149
|
+
old_sha: oldSha || null,
|
|
150
|
+
new_sha: newSha,
|
|
151
|
+
bytes: Buffer.byteLength(want, 'utf8'),
|
|
152
|
+
});
|
|
153
|
+
} catch (_) {}
|
|
154
|
+
} catch (_) {}
|
|
155
|
+
}
|
|
132
156
|
|
|
133
157
|
const claudeDir = path.join(cwd, '.claude');
|
|
134
158
|
fs.mkdirSync(claudeDir, { recursive: true });
|
|
@@ -172,6 +196,99 @@ function applyDisciplineSigil(rawBody) {
|
|
|
172
196
|
return JSON.stringify(parsed);
|
|
173
197
|
}
|
|
174
198
|
|
|
199
|
+
function isInstructionTurnStart(sess) {
|
|
200
|
+
const key = sess || '(no-session)';
|
|
201
|
+
const now = Date.now();
|
|
202
|
+
const t = _turns.get(key);
|
|
203
|
+
if (!t) return true;
|
|
204
|
+
if ((now - t.lastTs) > TURN_IDLE_MS) return true;
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function readUserPromptForRecall(cwd) {
|
|
209
|
+
const root = cwd || process.cwd();
|
|
210
|
+
try {
|
|
211
|
+
const p = path.join(root, '.gm', 'last-prompt.txt');
|
|
212
|
+
const txt = fs.readFileSync(p, 'utf8').trim();
|
|
213
|
+
if (txt) return txt;
|
|
214
|
+
} catch (_) {}
|
|
215
|
+
try {
|
|
216
|
+
const p = path.join(root, '.gm', 'turn-state.json');
|
|
217
|
+
const obj = JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
218
|
+
if (obj && typeof obj.last_prompt === 'string' && obj.last_prompt.trim()) return obj.last_prompt.trim();
|
|
219
|
+
if (obj && typeof obj.prompt === 'string' && obj.prompt.trim()) return obj.prompt.trim();
|
|
220
|
+
} catch (_) {}
|
|
221
|
+
return '';
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function dispatchVerbToWasmInternal(instance, verb, body) {
|
|
225
|
+
const dispatch = instance.exports.dispatch_verb;
|
|
226
|
+
if (!dispatch) return null;
|
|
227
|
+
const verbBytes = new TextEncoder().encode(verb);
|
|
228
|
+
const bodyBytes = new TextEncoder().encode(body || '');
|
|
229
|
+
const verbPtr = instance.exports.plugkit_alloc(verbBytes.length);
|
|
230
|
+
const bodyPtr = instance.exports.plugkit_alloc(bodyBytes.length);
|
|
231
|
+
try {
|
|
232
|
+
new Uint8Array(instance.exports.memory.buffer, verbPtr, verbBytes.length).set(verbBytes);
|
|
233
|
+
new Uint8Array(instance.exports.memory.buffer, bodyPtr, bodyBytes.length).set(bodyBytes);
|
|
234
|
+
const result = dispatch(verbPtr, verbBytes.length, bodyPtr, bodyBytes.length);
|
|
235
|
+
const ptr = Number(result & 0xffffffffn);
|
|
236
|
+
const len = Number(result >> 32n);
|
|
237
|
+
const out = new TextDecoder().decode(new Uint8Array(instance.exports.memory.buffer, ptr, len));
|
|
238
|
+
try { instance.exports.plugkit_free(ptr, len); } catch (_) {}
|
|
239
|
+
return out;
|
|
240
|
+
} finally {
|
|
241
|
+
try { instance.exports.plugkit_free(verbPtr, verbBytes.length); } catch (_) {}
|
|
242
|
+
try { instance.exports.plugkit_free(bodyPtr, bodyBytes.length); } catch (_) {}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function tryAutoRecallForTurnEntry(instance, sess, cwd) {
|
|
247
|
+
try {
|
|
248
|
+
const prompt = readUserPromptForRecall(cwd);
|
|
249
|
+
if (!prompt) return null;
|
|
250
|
+
const out = dispatchVerbToWasmInternal(instance, 'auto-recall', prompt);
|
|
251
|
+
if (!out) return null;
|
|
252
|
+
let parsed;
|
|
253
|
+
try { parsed = JSON.parse(out); } catch (_) { return null; }
|
|
254
|
+
if (!parsed || parsed.ok !== true) return null;
|
|
255
|
+
let inner = parsed.data;
|
|
256
|
+
if (typeof parsed.stdout === 'string' && parsed.stdout.length > 0) {
|
|
257
|
+
try { inner = JSON.parse(parsed.stdout); } catch (_) {}
|
|
258
|
+
}
|
|
259
|
+
if (!inner || typeof inner !== 'object') return null;
|
|
260
|
+
const hits = Array.isArray(inner.results) ? inner.results : (Array.isArray(inner.hits) ? inner.hits : []);
|
|
261
|
+
const payload = { query: inner.query || '', hits, fired_at: new Date().toISOString(), turn_entry: true };
|
|
262
|
+
logEvent('plugkit', 'auto_recall.turn-entry', { sess, query: payload.query, count: hits.length });
|
|
263
|
+
return payload;
|
|
264
|
+
} catch (e) {
|
|
265
|
+
logEvent('plugkit', 'auto_recall.error', { sess, error: String(e && e.message || e) });
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function mergeAutoRecallIntoInstructionResponse(resultStr, autoRecall) {
|
|
271
|
+
if (!autoRecall) return resultStr;
|
|
272
|
+
let parsed;
|
|
273
|
+
try { parsed = JSON.parse(resultStr); } catch (_) { return resultStr; }
|
|
274
|
+
if (!parsed || typeof parsed !== 'object') return resultStr;
|
|
275
|
+
if (parsed.data && typeof parsed.data === 'object') {
|
|
276
|
+
parsed.data.auto_recall = autoRecall;
|
|
277
|
+
} else {
|
|
278
|
+
parsed.auto_recall = autoRecall;
|
|
279
|
+
}
|
|
280
|
+
if (typeof parsed.stdout === 'string' && parsed.stdout.length > 0) {
|
|
281
|
+
try {
|
|
282
|
+
const inner = JSON.parse(parsed.stdout);
|
|
283
|
+
if (inner && typeof inner === 'object') {
|
|
284
|
+
inner.auto_recall = autoRecall;
|
|
285
|
+
parsed.stdout = JSON.stringify(inner);
|
|
286
|
+
}
|
|
287
|
+
} catch (_) {}
|
|
288
|
+
}
|
|
289
|
+
return JSON.stringify(parsed);
|
|
290
|
+
}
|
|
291
|
+
|
|
175
292
|
function turnTick(sess, verb, taskBase, phase) {
|
|
176
293
|
const key = sess || '(no-session)';
|
|
177
294
|
const now = Date.now();
|
|
@@ -1203,6 +1320,8 @@ async function runSpoolWatcher(instance, spoolDir) {
|
|
|
1203
1320
|
fs.mkdirSync(inDir, { recursive: true });
|
|
1204
1321
|
fs.mkdirSync(outDir, { recursive: true });
|
|
1205
1322
|
|
|
1323
|
+
try { ensureSpoolPollGate(process.env.CLAUDE_PROJECT_DIR || process.cwd()); } catch (_) {}
|
|
1324
|
+
|
|
1206
1325
|
const LOCK_PATH = path.join(spoolDir, '.watcher.lock');
|
|
1207
1326
|
let _ownWrapperSha12 = '';
|
|
1208
1327
|
try {
|
|
@@ -1506,18 +1625,35 @@ async function runSpoolWatcher(instance, spoolDir) {
|
|
|
1506
1625
|
prior_status: _priorStatus,
|
|
1507
1626
|
prior_status_age_ms: _priorStatus && Number.isFinite(_priorStatus.ts) ? Date.now() - _priorStatus.ts : null,
|
|
1508
1627
|
};
|
|
1509
|
-
const
|
|
1628
|
+
const _PLANNED_REASONS = new Set(['idle', 'sigterm', 'version-change', 'wrapper-change', 'peer-stale-takeover', 'version-drift', 'external-planned']);
|
|
1629
|
+
const _isPlannedBoot = _priorShutdown && _PLANNED_REASONS.has(_priorShutdown.reason);
|
|
1510
1630
|
const _isFirstBoot = !_priorShutdown && !_priorStatus;
|
|
1511
1631
|
const UNPLANNED_RESTART_MARKER = path.join(spoolDir, '.unplanned-restart.json');
|
|
1512
|
-
|
|
1632
|
+
const HEARTBEAT_RECENT_MS = 60_000;
|
|
1633
|
+
const HEARTBEAT_DEAD_MS = 5 * 60_000;
|
|
1634
|
+
let _severity = 'critical';
|
|
1635
|
+
if (_isPlannedBoot) {
|
|
1636
|
+
_severity = 'info';
|
|
1637
|
+
} else if (!_priorShutdown && _priorStatus && Number.isFinite(_priorStatus.ts)) {
|
|
1638
|
+
const _statusAge = Date.now() - _priorStatus.ts;
|
|
1639
|
+
if (_statusAge <= HEARTBEAT_RECENT_MS) _severity = 'warn';
|
|
1640
|
+
else if (_statusAge < HEARTBEAT_DEAD_MS) _severity = 'warn';
|
|
1641
|
+
else _severity = 'critical';
|
|
1642
|
+
}
|
|
1643
|
+
if (!_isFirstBoot) {
|
|
1513
1644
|
const incidentPayload = {
|
|
1514
1645
|
ts: Date.now(),
|
|
1515
1646
|
version: _bootVersion,
|
|
1516
|
-
severity:
|
|
1647
|
+
severity: _severity,
|
|
1648
|
+
planned: _isPlannedBoot,
|
|
1517
1649
|
...restartContext,
|
|
1518
1650
|
log_tail_path: path.join(spoolDir, '.watcher.log'),
|
|
1519
1651
|
gm_log_dir: GM_LOG_ROOT,
|
|
1520
|
-
instruction:
|
|
1652
|
+
instruction: _isPlannedBoot
|
|
1653
|
+
? `Planned restart: prior watcher exited with reason="${_priorShutdown.reason}". No action required.`
|
|
1654
|
+
: (_severity === 'warn'
|
|
1655
|
+
? 'Prior watcher disappeared with a recent heartbeat — likely a clean shutdown that did not write .shutdown-reason.json. Inspect .watcher.log if recurrent.'
|
|
1656
|
+
: 'Prior watcher died without a planned shutdown and without a recent heartbeat. This is treated as a critical failure. Inspect .watcher.log and gm-log/<day>/plugkit.jsonl events supervisor.watcher-exited-unexpectedly + supervisor.heartbeat-stale around the prior_status.ts timestamp to diagnose root cause.'),
|
|
1521
1657
|
};
|
|
1522
1658
|
logEvent('plugkit', 'watcher.unplanned-restart', incidentPayload);
|
|
1523
1659
|
try {
|
|
@@ -1575,6 +1711,14 @@ async function runSpoolWatcher(instance, spoolDir) {
|
|
|
1575
1711
|
console.log(`[dispatch] → verb=${verb} task=${taskBase} body=${bodyBytes.length}b`);
|
|
1576
1712
|
logEvent('plugkit', 'dispatch.start', { verb, task: taskBase, body_bytes: bodyBytes.length, cwd: process.cwd() });
|
|
1577
1713
|
|
|
1714
|
+
let autoRecallPayload = null;
|
|
1715
|
+
if (verb === 'instruction') {
|
|
1716
|
+
const sessForRecall = readCurrentSess();
|
|
1717
|
+
if (isInstructionTurnStart(sessForRecall)) {
|
|
1718
|
+
autoRecallPayload = tryAutoRecallForTurnEntry(instance, sessForRecall, process.cwd());
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1578
1722
|
const verbPtr = instance.exports.plugkit_alloc(verbBytes.length);
|
|
1579
1723
|
const bodyPtr = instance.exports.plugkit_alloc(bodyBytes.length);
|
|
1580
1724
|
new Uint8Array(instance.exports.memory.buffer, verbPtr, verbBytes.length).set(verbBytes);
|
|
@@ -1585,7 +1729,11 @@ async function runSpoolWatcher(instance, spoolDir) {
|
|
|
1585
1729
|
const ptr = Number(result & 0xffffffffn);
|
|
1586
1730
|
const len = Number(result >> 32n);
|
|
1587
1731
|
const resultBytes = new Uint8Array(instance.exports.memory.buffer, ptr, len);
|
|
1588
|
-
|
|
1732
|
+
let resultStr = new TextDecoder().decode(resultBytes);
|
|
1733
|
+
|
|
1734
|
+
if (autoRecallPayload) {
|
|
1735
|
+
resultStr = mergeAutoRecallIntoInstructionResponse(resultStr, autoRecallPayload);
|
|
1736
|
+
}
|
|
1589
1737
|
|
|
1590
1738
|
const outName = dir === '.' ? `${taskBase}.json` : `${verb}-${taskBase}.json`;
|
|
1591
1739
|
fs.writeFileSync(path.join(outDir, outName), resultStr);
|
package/gm.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gm-skill",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1217",
|
|
4
4
|
"description": "Canonical universal harness — AI-native software engineering via skill-driven orchestration; bootstraps plugkit for task execution and session isolation. Install in any AI coding agent host.",
|
|
5
5
|
"author": "AnEntrypoint",
|
|
6
6
|
"license": "MIT",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"gm.json"
|
|
40
40
|
],
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"gm-plugkit": "^2.0.
|
|
42
|
+
"gm-plugkit": "^2.0.1217"
|
|
43
43
|
},
|
|
44
44
|
"engines": {
|
|
45
45
|
"node": ">=16.0.0"
|