gm-plugkit 2.0.1506 → 2.0.1508
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/SKILL.md +1 -1
- package/package.json +1 -1
- package/plugkit-wasm-wrapper.js +43 -7
package/SKILL.md
CHANGED
|
@@ -6,7 +6,7 @@ allowed-tools: Skill, Read, Write, Bash(bun *), Bash(npx *)
|
|
|
6
6
|
|
|
7
7
|
# gm
|
|
8
8
|
|
|
9
|
-
**A turn that ends without a tool call is a stop, and stopping mid-chain is the cardinal failure.** Every programming agent reads only tool calls and their outputs; an assistant message that ends in prose with no tool call IS the turn ending, and the session halts there. So while the chain is in-flight (phase ≠ COMPLETE OR prd_pending_count > 0) you NEVER end a turn in prose, every turn ends in a tool call that advances the chain (the next `instruction` dispatch, the next verb the prose named, the next `transition`). You do NOT summarize, you do NOT write a "here's what I did" wrap-up, you do NOT narrate closure, summary is a stop, and stopping is only authorized when plugkit itself returns `phase=COMPLETE` AND `prd_pending_count=0`. Before you are ever tempted to stop
|
|
9
|
+
**A turn that ends without a tool call is a stop, and stopping mid-chain is the cardinal failure.** Every programming agent reads only tool calls and their outputs; an assistant message that ends in prose with no tool call IS the turn ending, and the session halts there. So while the chain is in-flight (phase ≠ COMPLETE OR prd_pending_count > 0) you NEVER end a turn in prose, every turn ends in a tool call that advances the chain (the next `instruction` dispatch, the next verb the prose named, the next `transition`). You do NOT summarize, you do NOT write a "here's what I did" wrap-up, you do NOT narrate closure, summary is a stop, and stopping is only authorized when plugkit itself returns `phase=COMPLETE` AND `prd_pending_count=0`. A turn-final sentence that names the next move instead of making it is the same stop facing forward — announcing a read, a verb, a re-dispatch is not doing it, and the chain strands exactly where the prose pointed. Take the move you were about to describe. Surfacing a decision is a tool call too (`AskUserQuestion` or `prd-add`), never a prose-only "confirming direction." Before you are ever tempted to stop, you dispatch `phase-status` and read it: if it is not terminal, that temptation was a drift signal, dispatch `instruction` and keep walking. This is tool-agnostic by construction: it depends on nothing but the verb spool, so it holds identically on every agent, with no hook and no tool-specific feature. The one and only thing that authorizes the prose-only turn is plugkit's COMPLETE pronouncement; until then, the answer to "am I done?" is another tool call.
|
|
10
10
|
|
|
11
11
|
**Done is what plugkit says is done, never your claim.** The COMPLETE gate is the single arbiter. If the chain is not at COMPLETE, there is a next transition to seek; idle mid-chain is a deviation.
|
|
12
12
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gm-plugkit",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1508",
|
|
4
4
|
"description": "Bootstrap and daemon-spawn tool for gm plugkit binary. Downloads the correct platform binary, verifies SHA256, and starts the spool watcher daemon. Includes plugkit-wasm-wrapper for WASM-based spool watching.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
package/plugkit-wasm-wrapper.js
CHANGED
|
@@ -330,7 +330,7 @@ function endTurn(sess, t, idleSpanned) {
|
|
|
330
330
|
});
|
|
331
331
|
}
|
|
332
332
|
|
|
333
|
-
function turnTick(sess, verb, taskBase, phase) {
|
|
333
|
+
function turnTick(sess, verb, taskBase, phase, prdPending) {
|
|
334
334
|
const key = sess || '(no-session)';
|
|
335
335
|
const now = Date.now();
|
|
336
336
|
let t = _turns.get(key);
|
|
@@ -349,14 +349,46 @@ function turnTick(sess, verb, taskBase, phase) {
|
|
|
349
349
|
if (verb !== 'instruction') return;
|
|
350
350
|
const idx = ((_turns.get(key + ':lastIdx') || 0) + 1);
|
|
351
351
|
_turns.set(key + ':lastIdx', idx);
|
|
352
|
-
t = { idx, startTs: now, lastTs: now, dispatches: 0, verbs: new Map(), phases: new Set(), deviations: 0, lastPhase: phase };
|
|
352
|
+
t = { idx, startTs: now, lastTs: now, dispatches: 0, verbs: new Map(), phases: new Set(), deviations: 0, lastPhase: phase, prdPending: null, stallEmitted: false };
|
|
353
353
|
_turns.set(key, t);
|
|
354
354
|
logEvent('plugkit', 'turn.start', { sess, turn_idx: idx, phase: phase || null });
|
|
355
355
|
}
|
|
356
356
|
t.lastTs = now;
|
|
357
357
|
t.dispatches++;
|
|
358
|
+
// A verb arriving resumes the turn — clear any prior stall flag so a later re-stall
|
|
359
|
+
// is a fresh episode, not silently suppressed by the one-shot guard.
|
|
360
|
+
t.stallEmitted = false;
|
|
358
361
|
t.verbs.set(verb, (t.verbs.get(verb) || 0) + 1);
|
|
359
362
|
if (phase) { t.phases.add(phase); t.lastPhase = phase; }
|
|
363
|
+
if (typeof prdPending === 'number') t.prdPending = prdPending;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// turn.end fires only when a NEW verb arrives after idle, so a turn that simply never
|
|
367
|
+
// receives another verb stays open forever and emits no signal — a permanent stall is
|
|
368
|
+
// silence, not an event, which is how a mid-EXECUTE stop stays invisible for days. The
|
|
369
|
+
// heartbeat scan closes that hole: for each open turn idle past STALL_MS whose last phase
|
|
370
|
+
// is non-terminal (or carries open PRD rows), emit turn.stalled once. One-shot per episode
|
|
371
|
+
// (stallEmitted), reset when a verb resumes the turn. A COMPLETE turn with no open rows
|
|
372
|
+
// idling is the authorized prose-only state and never stalls.
|
|
373
|
+
const STALL_MS = 300_000;
|
|
374
|
+
function scanStalledTurns() {
|
|
375
|
+
const now = Date.now();
|
|
376
|
+
for (const [key, t] of _turns) {
|
|
377
|
+
if (!t || typeof t !== 'object' || !Number.isFinite(t.startTs)) continue;
|
|
378
|
+
if (t.stallEmitted) continue;
|
|
379
|
+
if ((now - t.lastTs) < STALL_MS) continue;
|
|
380
|
+
const terminal = t.lastPhase === 'COMPLETE' && (t.prdPending === 0 || t.prdPending == null);
|
|
381
|
+
if (terminal) continue;
|
|
382
|
+
t.stallEmitted = true;
|
|
383
|
+
logEvent('hook', 'deviation.mid-chain-stall', {
|
|
384
|
+
sess: key,
|
|
385
|
+
turn_idx: t.idx,
|
|
386
|
+
ended_in_phase: t.lastPhase || null,
|
|
387
|
+
prd_pending: t.prdPending,
|
|
388
|
+
idle_ms: now - t.lastTs,
|
|
389
|
+
dispatches: t.dispatches,
|
|
390
|
+
});
|
|
391
|
+
}
|
|
360
392
|
}
|
|
361
393
|
|
|
362
394
|
let __sessCache = { value: '', mtimeMs: 0, readAt: 0, srcMtimeMs: 0 };
|
|
@@ -504,7 +536,7 @@ function emitOrchestratorEvents(verb, taskBase, resultStr) {
|
|
|
504
536
|
}
|
|
505
537
|
const data = parsed.data || {};
|
|
506
538
|
const sess = readCurrentSess();
|
|
507
|
-
turnTick(sess, verb, taskBase, data.phase);
|
|
539
|
+
turnTick(sess, verb, taskBase, data.phase, typeof data.prd_pending_count === 'number' ? data.prd_pending_count : undefined);
|
|
508
540
|
switch (verb) {
|
|
509
541
|
case 'transition':
|
|
510
542
|
logEvent('plugkit', 'phase.transitioned', { task: taskBase, phase: data.phase, next_skill: data.nextSkill, recall_count: Array.isArray(data.recall_hits) ? data.recall_hits.length : 0 });
|
|
@@ -2900,10 +2932,13 @@ async function runSpoolWatcher(instance, spoolDir) {
|
|
|
2900
2932
|
// (the VERB ABORT). Stamp a busy_until window before the synchronous dispatch so the
|
|
2901
2933
|
// supervisor's heartbeat-stale check honors it, exactly as the browser runner does.
|
|
2902
2934
|
// codesearch is the longest synchronous verb: a cold first call loads the 133MB bge-small
|
|
2903
|
-
// bert model AND re-indexes the tree
|
|
2904
|
-
// the supervisor
|
|
2905
|
-
// respawn-thrash that never completes
|
|
2906
|
-
|
|
2935
|
+
// bert model AND re-indexes the tree. A cold build was witnessed at ~252s (dispatch log
|
|
2936
|
+
// codesearch ms=251772), so a 180s window let the supervisor reap the watcher mid-index and
|
|
2937
|
+
// respawn it, cold-loading again = respawn-thrash that never completes (the codeinsight-stale
|
|
2938
|
+
// symptom). codesearch gets a 360s window; the bounded git verbs keep 180s.
|
|
2939
|
+
if (verb === 'codesearch') {
|
|
2940
|
+
try { _writeStatusBusy(360000); } catch (_) {}
|
|
2941
|
+
} else if (verb === 'git_finalize' || verb === 'git_push' || verb === 'git_fetch') {
|
|
2907
2942
|
try { _writeStatusBusy(180000); } catch (_) {}
|
|
2908
2943
|
}
|
|
2909
2944
|
|
|
@@ -3062,6 +3097,7 @@ async function runSpoolWatcher(instance, spoolDir) {
|
|
|
3062
3097
|
}
|
|
3063
3098
|
_writeStatusBusy = (ms) => { try { writeStatus(ms); } catch (_) {} };
|
|
3064
3099
|
setInterval(() => writeStatus(), 5000);
|
|
3100
|
+
setInterval(() => { try { scanStalledTurns(); } catch (_) {} }, 30000);
|
|
3065
3101
|
writeStatus();
|
|
3066
3102
|
|
|
3067
3103
|
const TURN_SUMMARY_PATH = path.join(spoolDir, '.turn-summary.json');
|