@plexor-dev/claude-code-plugin-staging 0.1.0-beta.28 → 0.1.0-beta.29

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.
@@ -232,8 +232,7 @@ async function main() {
232
232
  // Phase 1 supervisor UX: concise single-line routing summary
233
233
  supervisor.emit(response, plexorMeta);
234
234
 
235
- // Proactive compact warning: check prompt token count against provider limits
236
- emitCompactWarning(response, plexorMeta);
235
+ // Context warnings now handled by supervisor.emit() (Phase 5) to avoid duplicates
237
236
 
238
237
  // Issue #701: Track ALL responses, not just when enabled
239
238
  // This ensures session stats are always accurate
package/lib/supervisor.js CHANGED
@@ -24,6 +24,9 @@
24
24
  * decisions to the developer without requiring them to parse verbose logs.
25
25
  */
26
26
 
27
+ const fs = require('fs');
28
+ const path = require('path');
29
+
27
30
  const CYAN = '\x1b[36m';
28
31
  const YELLOW = '\x1b[33m';
29
32
  const RED = '\x1b[31m';
@@ -32,6 +35,8 @@ const RESET = '\x1b[0m';
32
35
 
33
36
  const CONTEXT_WARNING_THRESHOLD = 70000;
34
37
  const CONTEXT_COMPACT_THRESHOLD = 80000;
38
+ const SESSION_STATE_PATH = path.join(process.env.HOME || '', '.plexor', 'supervisor-session.json');
39
+ const SESSION_TIMEOUT_MS = 30 * 60 * 1000; // 30 min — start fresh if idle
35
40
 
36
41
  class SupervisorEmitter {
37
42
  /**
@@ -46,11 +51,54 @@ class SupervisorEmitter {
46
51
  this.enabled = opts.enabled !== false;
47
52
  }
48
53
 
49
- // Phase 5: Session-level state
54
+ // Phase 5: Session-level state — restored from disk across hook invocations
55
+ this._loadSessionState();
56
+ }
57
+
58
+ /**
59
+ * Load persisted session state from disk. Resets if stale (>30 min idle).
60
+ */
61
+ _loadSessionState() {
62
+ try {
63
+ const raw = fs.readFileSync(SESSION_STATE_PATH, 'utf8');
64
+ const state = JSON.parse(raw);
65
+ const lastTs = state.lastTimestamp || 0;
66
+ if (Date.now() - lastTs > SESSION_TIMEOUT_MS) {
67
+ // Session expired — start fresh
68
+ this._resetSessionState();
69
+ return;
70
+ }
71
+ this._turnCount = state.turnCount || 0;
72
+ this._contextTokens = state.contextTokens || 0; // current context window size
73
+ this._cumulativeCost = state.cumulativeCost || 0;
74
+ this._providerHistory = state.providerHistory || [];
75
+ } catch {
76
+ this._resetSessionState();
77
+ }
78
+ }
79
+
80
+ _resetSessionState() {
50
81
  this._turnCount = 0;
51
- this._cumulativeTokens = 0;
82
+ this._contextTokens = 0;
52
83
  this._cumulativeCost = 0;
53
- this._providerHistory = []; // last N entries: { provider, hadToolCalls }
84
+ this._providerHistory = [];
85
+ }
86
+
87
+ /**
88
+ * Persist session state to disk for cross-process continuity.
89
+ */
90
+ _saveSessionState() {
91
+ try {
92
+ const dir = path.dirname(SESSION_STATE_PATH);
93
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
94
+ fs.writeFileSync(SESSION_STATE_PATH, JSON.stringify({
95
+ turnCount: this._turnCount,
96
+ contextTokens: this._contextTokens,
97
+ cumulativeCost: this._cumulativeCost,
98
+ providerHistory: this._providerHistory.slice(-20), // keep last 20
99
+ lastTimestamp: Date.now(),
100
+ }));
101
+ } catch { /* best-effort persistence */ }
54
102
  }
55
103
 
56
104
  /**
@@ -253,11 +301,13 @@ class SupervisorEmitter {
253
301
  _accumulateSessionState(response, plexorMeta) {
254
302
  this._turnCount++;
255
303
 
256
- // Cumulative token counterread prompt_tokens from usage block
304
+ // Context window size — prompt_tokens IS the current context size (not cumulative).
305
+ // Each turn's prompt_tokens includes the full conversation context.
257
306
  const usage = response?.usage || response?.plexor?.usage || {};
258
307
  const promptTokens = Number(usage.prompt_tokens) || 0;
259
- const completionTokens = Number(usage.completion_tokens) || 0;
260
- this._cumulativeTokens += promptTokens + completionTokens;
308
+ if (promptTokens > 0) {
309
+ this._contextTokens = promptTokens; // replace, not accumulate
310
+ }
261
311
 
262
312
  // Cumulative cost
263
313
  const costUsd = Number(
@@ -290,12 +340,12 @@ class SupervisorEmitter {
290
340
  */
291
341
  _emitSessionNarration() {
292
342
  // Session summary line — every turn
293
- const tokenStr = this._formatTokenCount(this._cumulativeTokens);
343
+ const tokenStr = this._formatTokenCount(this._contextTokens);
294
344
  const costStr = this._cumulativeCost < 0.01
295
345
  ? `$${this._cumulativeCost.toFixed(4)}`
296
346
  : `$${this._cumulativeCost.toFixed(2)}`;
297
347
  process.stderr.write(
298
- `${MAGENTA}[PLEXOR: Session: ${this._turnCount} turns, ${costStr} cost, ${tokenStr} tokens]${RESET}\n`
348
+ `${MAGENTA}[PLEXOR: Session: ${this._turnCount} turns, ${costStr} cost, ${tokenStr} context]${RESET}\n`
299
349
  );
300
350
 
301
351
  // Provider reliability digest — every 5th turn
@@ -306,18 +356,21 @@ class SupervisorEmitter {
306
356
  }
307
357
  }
308
358
 
309
- // Context warning at 70K tokens
310
- if (this._cumulativeTokens >= CONTEXT_COMPACT_THRESHOLD) {
311
- const kTokens = Math.round(this._cumulativeTokens / 1000);
359
+ // Context warning at 70K tokens (uses real context window size, not cumulative)
360
+ if (this._contextTokens >= CONTEXT_COMPACT_THRESHOLD) {
361
+ const kTokens = Math.round(this._contextTokens / 1000);
312
362
  process.stderr.write(
313
363
  `${YELLOW}[PLEXOR: Context at ${kTokens}K \u2014 recommend /compact to prevent errors]${RESET}\n`
314
364
  );
315
- } else if (this._cumulativeTokens >= CONTEXT_WARNING_THRESHOLD) {
316
- const kTokens = Math.round(this._cumulativeTokens / 1000);
365
+ } else if (this._contextTokens >= CONTEXT_WARNING_THRESHOLD) {
366
+ const kTokens = Math.round(this._contextTokens / 1000);
317
367
  process.stderr.write(
318
368
  `${YELLOW}[PLEXOR: Context at ${kTokens}K tokens \u2014 approaching provider limits]${RESET}\n`
319
369
  );
320
370
  }
371
+
372
+ // Persist state for next hook invocation
373
+ this._saveSessionState();
321
374
  }
322
375
 
323
376
  /**
@@ -358,7 +411,7 @@ class SupervisorEmitter {
358
411
  // ---- Phase 5 accessors (for testing) ----
359
412
 
360
413
  get turnCount() { return this._turnCount; }
361
- get cumulativeTokens() { return this._cumulativeTokens; }
414
+ get cumulativeTokens() { return this._contextTokens; }
362
415
  get cumulativeCost() { return this._cumulativeCost; }
363
416
 
364
417
  // ---- private helpers ----
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plexor-dev/claude-code-plugin-staging",
3
- "version": "0.1.0-beta.28",
3
+ "version": "0.1.0-beta.29",
4
4
  "description": "STAGING - LLM cost optimization plugin for Claude Code (internal testing)",
5
5
  "main": "lib/constants.js",
6
6
  "bin": {