@jhizzard/termdeck 0.12.0 → 0.14.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.
@@ -15,6 +15,8 @@ const os = require('os');
15
15
  const path = require('path');
16
16
  const { resolveTheme } = require('./theme-resolver');
17
17
  const flashbackDiag = require('./flashback-diag');
18
+ const geminiAdapter = require('./agent-adapters/gemini');
19
+ const { detectAdapter, getAdapterForSessionType } = require('./agent-adapters');
18
20
 
19
21
  // Strip ANSI escape codes for pattern matching
20
22
  function stripAnsi(str) {
@@ -25,18 +27,33 @@ function stripAnsi(str) {
25
27
  .replace(/\x1b[>=<]/g, ''); // Keypad/cursor modes
26
28
  }
27
29
 
28
- // Pattern matchers for detecting terminal type and status
30
+ // Pattern matchers for detecting terminal type and status.
31
+ //
32
+ // Sprint 45 T4 removed the Sprint 44 T3 Claude shim (`PATTERNS.claudeCode.*`
33
+ // and `PATTERNS.errorLineStart`). Claude-specific regexes now live exclusively
34
+ // at ./agent-adapters/claude.js — read via `claudeAdapter.patterns.*`. The
35
+ // `_detectErrors` and `_updateStatus` paths route through `getAdapterForSessionType`
36
+ // for any registered adapter.
37
+ //
38
+ // Sprint 45 T2 retains `PATTERNS.geminiCli` as a shim into the Gemini adapter
39
+ // for the one-release deprecation horizon — same pattern Sprint 44 T3 used.
40
+ // What stays in this file:
41
+ // • geminiCli — Sprint 45 T2 shim into ./agent-adapters/gemini.js
42
+ // • pythonServer — server SUBTYPE detection (no adapter; status-badge only)
43
+ // • shell — default fallback (no adapter)
44
+ // • error — cross-agent prose-shape primary error fallback (used
45
+ // by `_detectErrors` when the active adapter has no
46
+ // `patterns.error`, and exported for tests)
47
+ // • shellError — cross-agent Unix shell-error shapes (always tried as
48
+ // the secondary fallback in `_detectErrors`)
29
49
  const PATTERNS = {
30
- claudeCode: {
31
- prompt: /^[>❯]\s/m,
32
- thinking: /\b(thinking|Thinking)\b/,
33
- editing: /^(Edit|Create|Update|Delete)\s/m,
34
- tool: /^⏺\s/m,
35
- idle: /^>\s*$/m
36
- },
50
+ // Sprint 45 T2: geminiCli patterns are owned by the Gemini adapter at
51
+ // ./agent-adapters/gemini.js. Shim preserves the legacy
52
+ // `PATTERNS.geminiCli.{prompt,thinking}` shape — same regex objects, so
53
+ // any external reference equality holds.
37
54
  geminiCli: {
38
- prompt: /^gemini>\s/m,
39
- thinking: /\b(Generating|Working)\b/,
55
+ prompt: geminiAdapter.patterns.prompt,
56
+ thinking: geminiAdapter.patterns.thinking,
40
57
  },
41
58
  pythonServer: {
42
59
  uvicorn: /Uvicorn running on/,
@@ -80,13 +97,12 @@ const PATTERNS = {
80
97
  // child-process error reporting fire without depending on the line ALSO
81
98
  // containing the `No such file or directory` prose phrase.
82
99
  error: /(?:^|\n)\s*(?:Error:\s+\S|error:\s+\S|ERROR:\s+\S|Traceback \(most recent call last\):|npm ERR!|error\[E\d+\]:|Uncaught Exception|Fatal:|ENOENT:\s+\S|EACCES:\s+\S|ECONNREFUSED:\s+\S)/m,
83
- // Stricter line-anchored variant for Claude Code, whose tool output (grep
84
- // results, test logs, file contents) routinely mentions "Error" mid-line
85
- // without representing an actual failure of the agent itself.
86
- // Sprint 40 T2: added mixed-case `Fatal` (mirrors `fatal` / `FATAL`) and
87
- // the `npm ERR!` shape (special-cased outside the alternation because
88
- // `!` is not a word character so `\b` after `npm ERR!` doesn't match).
89
- errorLineStart: /^\s*(?:(?:error|Error|ERROR|exception|Exception|Traceback|fatal|Fatal|FATAL|segmentation fault|panic|EACCES|ECONNREFUSED|ENOENT|command not found|undefined reference|cannot find module|failed with exit code|No such file or directory|Permission denied)\b|npm ERR!)/m,
100
+ // Sprint 45 T4: the Claude-specific line-anchored variant
101
+ // (formerly `PATTERNS.errorLineStart`) is owned by the Claude adapter at
102
+ // ./agent-adapters/claude.js read via `claudeAdapter.patterns.error`.
103
+ // _detectErrors below routes through `getAdapterForSessionType` for
104
+ // claude-code sessions and falls through to PATTERNS.error / shellError
105
+ // for non-adapter sessions.
90
106
  // Sprint 33: PATTERNS.error misses the most common Unix shell errors —
91
107
  // `cat: /foo: No such file or directory`, `bash: foo: command not found`,
92
108
  // `rm: cannot remove ...: Permission denied`. These have a colon-prefix
@@ -241,11 +257,17 @@ class Session {
241
257
  }
242
258
 
243
259
  _detectType(data) {
244
- if (PATTERNS.claudeCode.prompt.test(data) || /claude/i.test(this.meta.command)) {
245
- this.meta.type = 'claude-code';
246
- } else if (PATTERNS.geminiCli.prompt.test(data) || /gemini/i.test(this.meta.command)) {
247
- this.meta.type = 'gemini';
248
- } else if (
260
+ // Sprint 44 T3 + Sprint 45: registry-aware detection. detectAdapter()
261
+ // iterates AGENT_ADAPTERS in declaration order and returns the first
262
+ // hit by prompt regex OR command-string match. Claude / Codex /
263
+ // Gemini / Grok all live in the registry now; only python-server
264
+ // (a non-CLI-agent type) stays here as in-file fall-through.
265
+ const adapter = detectAdapter(data, this.meta.command);
266
+ if (adapter) {
267
+ this.meta.type = adapter.sessionType;
268
+ return;
269
+ }
270
+ if (
249
271
  PATTERNS.pythonServer.uvicorn.test(data) ||
250
272
  PATTERNS.pythonServer.flask.test(data) ||
251
273
  PATTERNS.pythonServer.django.test(data) ||
@@ -259,34 +281,20 @@ class Session {
259
281
  const p = PATTERNS;
260
282
  const oldStatus = this.meta.status;
261
283
 
262
- switch (this.meta.type) {
263
- case 'claude-code':
264
- if (p.claudeCode.thinking.test(data)) {
265
- this.meta.status = 'thinking';
266
- this.meta.statusDetail = 'Claude is reasoning...';
267
- } else if (p.claudeCode.editing.test(data)) {
268
- this.meta.status = 'editing';
269
- const match = data.match(/^(Edit|Create|Update|Delete)\s+(.+)$/m);
270
- this.meta.statusDetail = match ? `${match[1]} ${match[2]}` : 'Editing files';
271
- } else if (p.claudeCode.tool.test(data)) {
272
- this.meta.status = 'active';
273
- this.meta.statusDetail = 'Using tools';
274
- } else if (p.claudeCode.idle.test(data)) {
275
- this.meta.status = 'idle';
276
- this.meta.statusDetail = 'Waiting for input';
277
- }
278
- break;
279
-
280
- case 'gemini':
281
- if (p.geminiCli.thinking.test(data)) {
282
- this.meta.status = 'thinking';
283
- this.meta.statusDetail = 'Gemini is generating...';
284
- } else if (p.geminiCli.prompt.test(data)) {
285
- this.meta.status = 'idle';
286
- this.meta.statusDetail = 'Waiting for input';
287
- }
288
- break;
289
-
284
+ // Sprint 44 T3 + Sprint 45: per-agent status detection lives in each
285
+ // adapter's `statusFor(data)` method. Returns { status, statusDetail }
286
+ // on a match, null on no-change — preserves the original switch's
287
+ // "leave status untouched if no pattern fires" semantics. Only
288
+ // non-CLI-agent types (python-server + default shell) stay in-file.
289
+ const adapter = getAdapterForSessionType(this.meta.type);
290
+ if (adapter && typeof adapter.statusFor === 'function') {
291
+ const result = adapter.statusFor(data);
292
+ if (result && result.status) {
293
+ this.meta.status = result.status;
294
+ this.meta.statusDetail = result.statusDetail || '';
295
+ }
296
+ } else {
297
+ switch (this.meta.type) {
290
298
  case 'python-server':
291
299
  if (p.pythonServer.request.test(data)) {
292
300
  this.meta.status = 'active';
@@ -309,6 +317,7 @@ class Session {
309
317
  } else {
310
318
  this.meta.status = 'active';
311
319
  }
320
+ }
312
321
  }
313
322
 
314
323
  // Debounce status change events (3s) to avoid flooding RAG with active↔idle flaps
@@ -387,10 +396,20 @@ class Session {
387
396
  // Claude Code's tool output frequently contains "error"/"Error" mid-line
388
397
  // (grep matches, test results, log dumps). Use a line-anchored pattern
389
398
  // for that session type so we don't flag content as failure.
390
- const primaryPattern = this.meta.type === 'claude-code'
391
- ? PATTERNS.errorLineStart
399
+ //
400
+ // Sprint 44 T3 / Sprint 45 T4: per-agent primary error pattern is read
401
+ // off the adapter (`patterns.error` + `patternNames.error`). Falls back
402
+ // to the generic prose-shape PATTERNS.error when no adapter has claimed
403
+ // the session type. (Sprint 44 retained a `PATTERNS.errorLineStart` shim
404
+ // that pointed at the Claude adapter's regex; Sprint 45 T4 removed the
405
+ // shim — read `claudeAdapter.patterns.error` directly when needed.)
406
+ const adapter = getAdapterForSessionType(this.meta.type);
407
+ const primaryPattern = adapter && adapter.patterns && adapter.patterns.error
408
+ ? adapter.patterns.error
392
409
  : PATTERNS.error;
393
- const primaryName = this.meta.type === 'claude-code' ? 'errorLineStart' : 'error';
410
+ const primaryName = adapter && adapter.patternNames && adapter.patternNames.error
411
+ ? adapter.patternNames.error
412
+ : 'error';
394
413
  // Sprint 33 fix: the structured patterns above miss `cat: /foo: No such
395
414
  // file or directory` and friends — the most common Unix shell error
396
415
  // shapes Josh hits day-to-day. Fall through to PATTERNS.shellError so