@adhdev/daemon-core 0.6.57 → 0.6.58

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.
Files changed (37) hide show
  1. package/dist/index.d.ts +17 -0
  2. package/dist/index.js +252 -78
  3. package/dist/index.js.map +1 -1
  4. package/package.json +1 -1
  5. package/providers/_builtin/cli/aider-cli/scripts/1.0/parse_output.js +51 -3
  6. package/providers/_builtin/cli/claude-cli/provider.json +18 -6
  7. package/providers/_builtin/cli/claude-cli/scripts/1.0/detect_status.js +68 -16
  8. package/providers/_builtin/cli/claude-cli/scripts/1.0/parse_approval.js +81 -22
  9. package/providers/_builtin/cli/claude-cli/scripts/1.0/parse_output.js +347 -94
  10. package/providers/_builtin/cli/codex-cli/provider.json +2 -0
  11. package/providers/_builtin/cli/codex-cli/scripts/1.0/detect_status.js +44 -10
  12. package/providers/_builtin/cli/codex-cli/scripts/1.0/parse_approval.js +83 -7
  13. package/providers/_builtin/cli/codex-cli/scripts/1.0/parse_output.js +501 -47
  14. package/providers/_builtin/cli/cursor-cli/scripts/1.0/parse_output.js +1 -1
  15. package/providers/_builtin/cli/github-copilot-cli/scripts/1.0/parse_output.js +1 -1
  16. package/providers/_builtin/cli/goose-cli/scripts/1.0/parse_output.js +1 -1
  17. package/providers/_builtin/cli/opencode-cli/scripts/1.0/parse_output.js +1 -1
  18. package/providers/_builtin/ide/vscode/provider.json +5 -1
  19. package/providers/_builtin/ide/vscode/scripts/1.0/focus_editor.js +1 -0
  20. package/providers/_builtin/ide/vscode/scripts/1.0/list_models.js +1 -0
  21. package/providers/_builtin/ide/vscode/scripts/1.0/list_sessions.js +1 -0
  22. package/providers/_builtin/ide/vscode/scripts/1.0/new_session.js +1 -0
  23. package/providers/_builtin/ide/vscode/scripts/1.0/open_panel.js +1 -0
  24. package/providers/_builtin/ide/vscode/scripts/1.0/read_chat.js +1 -0
  25. package/providers/_builtin/ide/vscode/scripts/1.0/resolve_action.js +1 -0
  26. package/providers/_builtin/ide/vscode/scripts/1.0/scripts.js +25 -0
  27. package/providers/_builtin/ide/vscode/scripts/1.0/send_message.js +1 -0
  28. package/providers/_builtin/ide/vscode/scripts/1.0/set_model.js +1 -0
  29. package/providers/_builtin/ide/vscode/scripts/1.0/switch_session.js +1 -0
  30. package/providers/_builtin/registry.json +1 -1
  31. package/src/cli-adapters/provider-cli-adapter.ts +223 -48
  32. package/src/commands/chat-commands.ts +7 -1
  33. package/src/config/chat-history.ts +53 -1
  34. package/src/daemon/dev-server.ts +7 -9
  35. package/src/providers/cli-provider-instance.ts +10 -23
  36. package/src/providers/provider-instance.ts +1 -0
  37. package/src/providers/version-archive.ts +4 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/daemon-core",
3
- "version": "0.6.57",
3
+ "version": "0.6.58",
4
4
  "description": "ADHDev daemon core — CDP, IDE detection, providers, command execution",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -35,13 +35,61 @@ function splitTurns(buffer) {
35
35
  return messages.length > 50 ? messages.slice(-50) : messages;
36
36
  }
37
37
 
38
+ function mergeMessages(priorMessages, parsedMessages, status) {
39
+ const base = Array.isArray(priorMessages)
40
+ ? priorMessages
41
+ .filter(message => message && (message.role === 'user' || message.role === 'assistant'))
42
+ .map(message => ({
43
+ role: message.role,
44
+ content: typeof message.content === 'string' ? message.content : String(message.content || ''),
45
+ timestamp: message.timestamp,
46
+ }))
47
+ : [];
48
+ if (!parsedMessages.length) return base.map((message, index) => ({
49
+ id: `msg_${index}`,
50
+ role: message.role,
51
+ content: message.content,
52
+ index,
53
+ kind: 'standard',
54
+ }));
55
+
56
+ const latestAssistant = [...parsedMessages].reverse().find(message => message.role === 'assistant' && message.content);
57
+ if (!latestAssistant) {
58
+ return base.map((message, index) => ({
59
+ id: `msg_${index}`,
60
+ role: message.role,
61
+ content: message.content,
62
+ index,
63
+ kind: 'standard',
64
+ }));
65
+ }
66
+
67
+ const last = base[base.length - 1];
68
+ if (last && last.role === 'assistant') {
69
+ last.content = latestAssistant.content;
70
+ } else {
71
+ base.push({ role: 'assistant', content: latestAssistant.content });
72
+ }
73
+
74
+ return base.slice(-50).map((message, index, slice) => ({
75
+ id: `msg_${index}`,
76
+ role: message.role,
77
+ content: typeof message.content === 'string' ? message.content.slice(0, 6000) : '',
78
+ index,
79
+ kind: 'standard',
80
+ ...(status === 'generating' && index === slice.length - 1 && message.role === 'assistant'
81
+ ? { meta: { streaming: true } }
82
+ : {}),
83
+ }));
84
+ }
85
+
38
86
  module.exports = function parseOutput(input) {
39
- const { buffer, recentBuffer, partialResponse, screenText } = input;
40
- const transcript = screenText || buffer;
87
+ const { buffer, recentBuffer, partialResponse, screenText, messages: priorMessages } = input;
88
+ const transcript = buffer || screenText;
41
89
  const tail = recentBuffer || (transcript || '').slice(-500);
42
90
  const status = detectStatus({ tail });
43
91
  const activeModal = status === 'waiting_approval' ? parseApproval({ buffer: transcript, tail }) : null;
44
- const messages = splitTurns(transcript);
92
+ const messages = mergeMessages(priorMessages, splitTurns(transcript), status);
45
93
  if (status === 'generating' && partialResponse && partialResponse.trim().length > 2) {
46
94
  messages.push({ id: 'msg_partial', role: 'assistant', content: partialResponse.trim().slice(0, 6000), index: messages.length, kind: 'standard', meta: { streaming: true } });
47
95
  }
@@ -58,9 +58,14 @@
58
58
  }
59
59
  },
60
60
  "binary": "claude",
61
+ "sendDelayMs": 700,
62
+ "submitStrategy": "immediate",
61
63
  "spawn": {
62
64
  "command": "claude",
63
- "args": [],
65
+ "args": [
66
+ "--permission-mode",
67
+ "acceptEdits"
68
+ ],
64
69
  "shell": true,
65
70
  "env": {}
66
71
  },
@@ -69,14 +74,21 @@
69
74
  { "source": "Type your message", "flags": "i" },
70
75
  { "source": "for\\s*shortcuts", "flags": "i" },
71
76
  { "source": "\\?\\s*for\\s*help", "flags": "i" },
72
- { "source": "Press enter", "flags": "i" }
77
+ { "source": "Press enter", "flags": "i" },
78
+ { "source": "[›❯]\\s*$", "flags": "m" },
79
+ { "source": "medium\\s+·\\s+/effort", "flags": "i" }
73
80
  ],
74
81
  "generating": [
75
82
  { "source": "[\\u2800-\\u28ff]", "flags": "" },
76
83
  { "source": "esc to (cancel|interrupt|stop)", "flags": "i" },
77
84
  { "source": "generating\\.\\.\\.", "flags": "i" },
78
85
  { "source": "Claude is (?:thinking|processing|working)", "flags": "i" },
79
- { "source": "Flummoxing", "flags": "i" }
86
+ { "source": "Flummoxing", "flags": "i" },
87
+ { "source": "Finagling", "flags": "i" },
88
+ { "source": "Scurrying", "flags": "i" },
89
+ { "source": "Bloviating", "flags": "i" },
90
+ { "source": "Whatchamacallit(?:ing)?", "flags": "i" },
91
+ { "source": "(Thinking|Processing|Working|Analyzing|Planning|Drafting|Synthesizing|Inspecting|Reading|Searching)…?", "flags": "i" }
80
92
  ],
81
93
  "approval": [
82
94
  { "source": "Allow\\s*once", "flags": "i" },
@@ -95,9 +107,9 @@
95
107
  ]
96
108
  },
97
109
  "approvalKeys": {
98
- "0": "1",
99
- "1": "2",
100
- "2": "3"
110
+ "0": "\r",
111
+ "1": "\u001b[B\r",
112
+ "2": "\u001b[B\u001b[B\r"
101
113
  },
102
114
  "compatibility": [
103
115
  {
@@ -1,31 +1,83 @@
1
1
  /**
2
2
  * Claude Code — detect_status
3
3
  *
4
- * Lightweight status detection for high-frequency polling.
5
- * Input: { tail: string } — last ~500 chars of ANSI-stripped PTY output
6
- * Output: 'idle' | 'generating' | 'waiting_approval'
4
+ * Uses the current visible PTY screen when available.
5
+ * `tail` is still used as a fallback for older runtimes.
7
6
  */
8
7
 
9
8
  'use strict';
10
9
 
10
+ function splitLines(text) {
11
+ return String(text || '')
12
+ .replace(/\u0007/g, '')
13
+ .split(/\r\n|\n|\r/g)
14
+ .map(line => line.replace(/\s+$/, ''));
15
+ }
16
+
17
+ function normalize(line) {
18
+ return String(line || '')
19
+ .replace(/\u0007/g, '')
20
+ .replace(/^\d+;/, '')
21
+ .trim();
22
+ }
23
+
24
+ function isVisiblePrompt(line) {
25
+ return /^❯\s*$/.test(normalize(line));
26
+ }
27
+
28
+ function isVisibleApproval(line) {
29
+ const trimmed = normalize(line);
30
+ return /Allow\s*once/i.test(trimmed)
31
+ || /Always\s*allow/i.test(trimmed)
32
+ || /This command requires approval/i.test(trimmed)
33
+ || /Do you want to (?:proceed|allow|run)/i.test(trimmed)
34
+ || /Deny|Reject|Cancel/i.test(trimmed)
35
+ || /\(y\/n\)/i.test(trimmed)
36
+ || /\[Y\/n\]/i.test(trimmed)
37
+ || /^([❯›>]\s*)?\d+[.)]\s+/.test(trimmed);
38
+ }
39
+
40
+ function isVisibleSpinner(line) {
41
+ const trimmed = normalize(line);
42
+ if (!trimmed) return false;
43
+ if (/^[✻✶✳✢✽⠂⠐⠒⠓⠦⠴⠶⠷⠿]+$/.test(trimmed)) return true;
44
+ if (/esc to (cancel|interrupt|stop)/i.test(trimmed)) return true;
45
+ if (/Running(?:\u2026|\.{3})?$/i.test(trimmed)) return true;
46
+ if (/(?:Finagling|Scurrying|Bloviating|Whatchamacallit(?:ing)?|Hatching|Thinking|Processing|Working|Analyzing|Planning|Drafting|Synthesizing|Inspecting|Reading|Searching|Tinkering|Canoodling|Whirring|Infusing|Accomplishing|Deliberating)\u2026?$/i.test(trimmed)) return true;
47
+ if (/^[A-Z][a-z]+ing\u2026?$/.test(trimmed)) return true;
48
+ return false;
49
+ }
50
+
11
51
  module.exports = function detectStatus(input) {
12
- const { tail } = input;
13
- if (!tail) return 'idle';
52
+ const tail = String(input?.tail || '');
53
+ const screenText = String(input?.screenText || '');
54
+ const visibleLines = splitLines(screenText);
55
+ const visibleText = visibleLines.map(normalize).filter(Boolean).join('\n');
56
+ if (visibleText) {
57
+ if (visibleLines.some(isVisibleApproval)) return 'waiting_approval';
58
+ } else if (tail) {
59
+ const hasApproval = /Allow\s*once/i.test(tail)
60
+ || /Always\s*allow/i.test(tail)
61
+ || /This command requires approval/i.test(tail)
62
+ || /Do you want to (?:proceed|allow|run)/i.test(tail)
63
+ || /\(y\/n\)|\[Y\/n\]/i.test(tail)
64
+ || /^([❯›>]\s*)?\d+[.)]\s+/m.test(tail);
65
+ if (hasApproval) return 'waiting_approval';
66
+ }
67
+
68
+ if (visibleLines.length > 0) {
69
+ const hasSpinner = visibleLines.some(isVisibleSpinner);
70
+ if (hasSpinner) return 'generating';
14
71
 
15
- // ─── waiting_approval (highest priority) ───
16
- // Claude Code approval menu items
17
- if (/Allow\s*once/i.test(tail) || /Always\s*allow/i.test(tail)) return 'waiting_approval';
18
- if (/\(y\/n\)/i.test(tail) || /\[Y\/n\]/i.test(tail)) return 'waiting_approval';
72
+ const hasPrompt = visibleLines.some(isVisiblePrompt);
73
+ if (hasPrompt) return 'idle';
74
+ }
19
75
 
20
- // ─── generating ───
21
- // Braille spinner characters (universal TUI spinner)
22
76
  if (/[\u2800-\u28ff]/.test(tail)) return 'generating';
23
- // Status line indicators
24
77
  if (/esc to (cancel|interrupt|stop)/i.test(tail)) return 'generating';
25
- if (/generating\.\.\./i.test(tail)) return 'generating';
26
- if (/Claude is (?:thinking|processing|working)/i.test(tail)) return 'generating';
27
- if (/Flummoxing/i.test(tail)) return 'generating';
78
+ if (/Running(?:\u2026|\.{3})?$/im.test(tail)) return 'generating';
79
+ if (/(?:Finagling|Scurrying|Bloviating|Whatchamacallit(?:ing)?|Hatching|Thinking|Processing|Working|Analyzing|Planning|Drafting|Synthesizing|Inspecting|Reading|Searching|Tinkering|Canoodling|Whirring|Infusing|Accomplishing|Deliberating)\u2026?$/i.test(tail)) return 'generating';
80
+ if (/^[A-Z][a-z]+ing\u2026?$/m.test(tail)) return 'generating';
28
81
 
29
- // ─── idle ───
30
82
  return 'idle';
31
83
  };
@@ -1,38 +1,97 @@
1
1
  /**
2
2
  * Claude Code — parse_approval
3
- *
4
- * Extract approval modal info from PTY output.
5
- * Input: { buffer: string, tail: string }
6
- * Output: { message: string, buttons: string[] } | null
7
3
  */
8
4
 
9
5
  'use strict';
10
6
 
7
+ function splitLines(text) {
8
+ return String(text || '')
9
+ .replace(/\u0007/g, '')
10
+ .split(/\r\n|\n|\r/g)
11
+ .map(line => line.replace(/\s+$/, ''));
12
+ }
13
+
14
+ function normalize(line) {
15
+ return String(line || '')
16
+ .replace(/\u0007/g, '')
17
+ .replace(/^\d+;/, '')
18
+ .trim();
19
+ }
20
+
21
+ function isNoise(line) {
22
+ const trimmed = normalize(line);
23
+ if (!trimmed) return true;
24
+ if (/^[─═╭╮╰╯│┌┐└┘├┤┬┴┼]+$/.test(trimmed)) return true;
25
+ if (/^❯\s*$/.test(trimmed)) return true;
26
+ if (/^➜\s+\S+/.test(trimmed)) return true;
27
+ if (/^Update available!/i.test(trimmed)) return true;
28
+ if (/^Claude Code v\d/i.test(trimmed)) return true;
29
+ if (/^(Sonnet|Opus|Haiku)\b/i.test(trimmed)) return true;
30
+ return false;
31
+ }
32
+
33
+ function normalizeButtonLabel(line) {
34
+ return normalize(line)
35
+ .replace(/^[❯›>]\s*/, '')
36
+ .replace(/^[([{]?\d+[)\].:\]-]?\s*/, '')
37
+ .replace(/\s+/g, ' ')
38
+ .trim();
39
+ }
40
+
41
+ function isButtonLine(line) {
42
+ const raw = normalize(line);
43
+ const trimmed = normalizeButtonLabel(line);
44
+ if (/^Esc to cancel/i.test(raw)) return false;
45
+ return /^([❯›>]\s*)?\d+[.)]\s+/.test(raw)
46
+ || /^(Allow\s*once|Always\s*allow.*|Deny|Reject|Yes|No)$/i.test(trimmed);
47
+ }
48
+
49
+ function stripContextPrefix(line) {
50
+ return normalize(line)
51
+ .replace(/^[⏺•]\s+/, '')
52
+ .replace(/\s+/g, ' ')
53
+ .trim();
54
+ }
55
+
56
+ function findLastIndex(lines, predicate) {
57
+ for (let i = lines.length - 1; i >= 0; i--) {
58
+ if (predicate(lines[i])) return i;
59
+ }
60
+ return -1;
61
+ }
62
+
11
63
  module.exports = function parseApproval(input) {
12
- const { buffer, tail } = input;
13
- const check = tail || (buffer || '').slice(-800);
14
- if (!check) return null;
64
+ const primary = String(input?.buffer || '');
65
+ const fallback = String(input?.tail || '');
66
+ const lines = splitLines(primary || fallback);
67
+ if (lines.length === 0) return null;
15
68
 
16
- // Claude Code approval patterns
17
- const hasApproval =
18
- /Allow\s*once/i.test(check) ||
19
- /Always\s*allow/i.test(check) ||
20
- /\(y\/n\)/i.test(check) ||
21
- /\[Y\/n\]/i.test(check);
69
+ const buttons = [];
70
+ for (const line of lines.slice(-40)) {
71
+ if (!isButtonLine(line)) continue;
72
+ const label = normalizeButtonLabel(line);
73
+ if (label && !buttons.includes(label)) buttons.push(label);
74
+ }
22
75
 
76
+ const hasApproval = buttons.length > 0
77
+ || /Allow\s*once|Always\s*allow|\(y\/n\)|\[Y\/n\]/i.test(primary || fallback);
23
78
  if (!hasApproval) return null;
24
79
 
25
- // Extract context message (last few meaningful lines before the approval)
26
- const lines = check.split('\n')
27
- .map(l => l.trim())
28
- .filter(l => l && !/^[─═╭╮╰╯│]+$/.test(l));
80
+ const questionIndex = findLastIndex(lines, line => /Do you want to (?:proceed|make this edit|run this command|allow)/i.test(normalize(line)));
81
+ const actionIndex = findLastIndex(lines, line => /^(?:[⏺•]\s+)?(?:Bash|Write|Edit|MultiEdit|Read|Task|Glob|Grep|LS|NotebookEdit)\(/.test(stripContextPrefix(line)));
82
+ const startIndex = Math.max(0, (actionIndex >= 0 ? actionIndex : questionIndex >= 0 ? questionIndex - 4 : lines.length - 8));
83
+ const endIndex = questionIndex >= 0 ? questionIndex + 1 : lines.length;
29
84
 
30
- const message = lines.slice(-5).join(' ').slice(0, 200) || 'Approval required';
85
+ const context = [];
86
+ for (const line of lines.slice(startIndex, endIndex)) {
87
+ if (isNoise(line) || isButtonLine(line)) continue;
88
+ const trimmed = stripContextPrefix(line);
89
+ if (!trimmed) continue;
90
+ if (context[context.length - 1] !== trimmed) context.push(trimmed);
91
+ }
31
92
 
32
- // Claude Code-specific button labels
33
- // These map to approvalKeys in provider.json: { "0": "1", "1": "2", "2": "3" }
34
93
  return {
35
- message,
36
- buttons: ['Yes (y)', 'Always allow (a)', 'Deny (Esc)'],
94
+ message: context.slice(-3).join(' ').slice(0, 240) || 'Claude Code approval required',
95
+ buttons: buttons.length > 0 ? buttons : ['Allow once', 'Always allow', 'Deny'],
37
96
  };
38
97
  };