handmux 0.29.2 → 0.31.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.
Files changed (116) hide show
  1. package/dist/connectors/claude/index.js +33 -562
  2. package/dist/connectors/codebuddy/index.js +49 -0
  3. package/dist/connectors/hookBridge.js +560 -0
  4. package/dist/hooks/handmux-codebuddy-notify.sh +116 -0
  5. package/dist/hooks/handmux-notify.sh +2 -0
  6. package/dist/hooks/handmux-write.cjs +72 -5
  7. package/dist/package.json +13 -13
  8. package/dist/public/assets/_basePickBy-BzC0P9Wg.js +1 -0
  9. package/dist/public/assets/_baseUniq-DNOfPYVY.js +1 -0
  10. package/dist/public/assets/abnfDiagram-O67JEVCF-BsRDIQEB.js +1 -0
  11. package/dist/public/assets/arc-DLtLfMnX.js +1 -0
  12. package/dist/public/assets/architectureDiagram-NJMV4G6O-DoIXhWqZ.js +36 -0
  13. package/dist/public/assets/blockDiagram-BEXU5L5S-W0HiOcVA.js +140 -0
  14. package/dist/public/assets/c4Diagram-YGBWAQC7-72uAUMvX.js +38 -0
  15. package/dist/public/assets/channel-CzALSnb0.js +1 -0
  16. package/dist/public/assets/chunk-2Q5K7J3B-B9wSRFQ0.js +1 -0
  17. package/dist/public/assets/chunk-3FUC2YCW-ryphp4s0.js +2 -0
  18. package/dist/public/assets/chunk-5VM5RSS4-Cm4KeKby.js +15 -0
  19. package/dist/public/assets/chunk-GTNCS2PH-Du4_i7r3.js +88 -0
  20. package/dist/public/assets/chunk-GWA4HPMP-CL1biVIA.js +1 -0
  21. package/dist/public/assets/chunk-JWPE2WC7-B8eAVfuB.js +1 -0
  22. package/dist/public/assets/chunk-NETBCI7D-D273GzYg.js +1 -0
  23. package/dist/public/assets/chunk-WEXAMYUT-DipTfRbc.js +1 -0
  24. package/dist/public/assets/chunk-XXDRQBXY-D4SqXrQ8.js +1 -0
  25. package/dist/public/assets/classDiagram-v2-NBCMYWYE-PrpnIXcA.js +217 -0
  26. package/dist/public/assets/cose-bilkent-JH36ORCC-BfcYMYTa.js +1 -0
  27. package/dist/public/assets/cynefin-OW5HDTMX-Du659bmz.js +178 -0
  28. package/dist/public/assets/cynefinDiagram-VND7K2PF-DR_2Copy.js +62 -0
  29. package/dist/public/assets/cytoscape.esm-C5JX3QLV.js +331 -0
  30. package/dist/public/assets/dagre-6A5THRUB-COsChaV5.js +4 -0
  31. package/dist/public/assets/defaultLocale-CrowFXzY.js +1 -0
  32. package/dist/public/assets/diagram-22UHCM2B-Cx2T1HvL.js +200 -0
  33. package/dist/public/assets/diagram-3UASUU5V-Cp04LOka.js +24 -0
  34. package/dist/public/assets/diagram-ATOU4E4O-DlqFSshY.js +3 -0
  35. package/dist/public/assets/diagram-CDSNMT55-goq40ctA.js +30 -0
  36. package/dist/public/assets/diagram-MLGK6HIB-Bdzjsy4r.js +24 -0
  37. package/dist/public/assets/diagram-MPIPVDR6-DtU7cM6P.js +41 -0
  38. package/dist/public/assets/ebnfDiagram-ZINNZB2B-XyVCfmED.js +1 -0
  39. package/dist/public/assets/elk-276RUBZZ-aHVIw-2x.js +27 -0
  40. package/dist/public/assets/erDiagram-OPXOYQCR-Bq_PhTIL.js +99 -0
  41. package/dist/public/assets/flowDiagram-KWPJA3E3-WuqnDSiF.js +213 -0
  42. package/dist/public/assets/ganttDiagram-FUAMR5RP-DZ4lXzkC.js +292 -0
  43. package/dist/public/assets/gitGraphDiagram-X574FWY7-t6l07b9g.js +106 -0
  44. package/dist/public/assets/graph-jWsD5Ro-.js +1 -0
  45. package/dist/public/assets/index-DiYff8oR.css +32 -0
  46. package/dist/public/assets/index-YZNK723o.js +203 -0
  47. package/dist/public/assets/infoDiagram-VRGFBTTK-iUGqoOFp.js +2 -0
  48. package/dist/public/assets/init-Gi6I4Gst.js +1 -0
  49. package/dist/public/assets/ishikawaDiagram-OU5B5YK6-B0TmoUOK.js +70 -0
  50. package/dist/public/assets/journeyDiagram-ZHPQQLJL-CQE61MCu.js +139 -0
  51. package/dist/public/assets/kanban-definition-PNTS6WVX-D2MWCUyS.js +89 -0
  52. package/dist/public/assets/katex-C5jXJg4s.js +257 -0
  53. package/dist/public/assets/linear-WKGxVNP1.js +1 -0
  54. package/dist/public/assets/mermaid.core-DPEwUsaj.js +311 -0
  55. package/dist/public/assets/mindmap-definition-NLK3R4M7-DsYEsBsI.js +96 -0
  56. package/dist/public/assets/ordinal-Cboi1Yqb.js +1 -0
  57. package/dist/public/assets/pegDiagram-GJSIUBJH-BS52Fbxt.js +1 -0
  58. package/dist/public/assets/pieDiagram-5QR66LMP-D4o8g7nW.js +39 -0
  59. package/dist/public/assets/quadrantDiagram-O4NWA36T-DnYUxWll.js +7 -0
  60. package/dist/public/assets/railroadDiagram-XR7U4H2S-BKy_7ULV.js +1 -0
  61. package/dist/public/assets/requirementDiagram-PLB6GJNP-Ce5F4u8K.js +84 -0
  62. package/dist/public/assets/sankeyDiagram-IPEJSGJF-CCJDrut_.js +40 -0
  63. package/dist/public/assets/sequenceDiagram-PO4LG4MO-D5E8ISM1.js +169 -0
  64. package/dist/public/assets/sizeCapture-INFHLROL-BW3KmxDN.js +1 -0
  65. package/dist/public/assets/stateDiagram-v2-GCMORJYK-Bw2ZzQOE.js +291 -0
  66. package/dist/public/assets/swimlanes-2SLR337P-CLJ-6Ex_.js +1 -0
  67. package/dist/public/assets/swimlanesDiagram-TC7HE7FX-BD8rQfV7.js +8 -0
  68. package/dist/public/assets/timeline-definition-EJHVYXUP-C_lFJ-4W.js +120 -0
  69. package/dist/public/assets/usecaseDiagram-POWQR4AR-DWXawL2A.js +370 -0
  70. package/dist/public/assets/vennDiagram-UO4OBE2U-D4O5oZal.js +34 -0
  71. package/dist/public/assets/wardleyDiagram-VNRHLVJA-B6HElk__.js +78 -0
  72. package/dist/public/assets/xychartDiagram-PMCCYNJV-C5OAXnJy.js +7 -0
  73. package/dist/public/index.html +2 -2
  74. package/dist/src/agent-runtime/adapter.js +54 -10
  75. package/dist/src/agent-runtime/builtinRuntime.js +67 -6
  76. package/dist/src/agent-runtime/conversation.js +49 -9
  77. package/dist/src/agent-runtime/interaction.js +26 -12
  78. package/dist/src/agent-runtime/interactionLiveHub.js +4 -0
  79. package/dist/src/agent-runtime/runtime.js +83 -27
  80. package/dist/src/agent-runtime/tmuxRuntime.js +36 -6
  81. package/dist/src/agents/claude.js +2 -19
  82. package/dist/src/agents/claudeInteraction.js +2 -132
  83. package/dist/src/agents/claudePaneInput.js +22 -22
  84. package/dist/src/agents/codebuddy.js +100 -0
  85. package/dist/src/agents/codebuddyConversation.js +414 -0
  86. package/dist/src/agents/codebuddyInteraction.js +4 -0
  87. package/dist/src/agents/codebuddyNativeTail.js +206 -0
  88. package/dist/src/agents/codebuddyPaneInput.js +76 -0
  89. package/dist/src/agents/codebuddyTranscriptParse.js +178 -0
  90. package/dist/src/agents/hookEvents.js +143 -0
  91. package/dist/src/agents/hookInteraction.js +178 -0
  92. package/dist/src/agents/index.js +2 -1
  93. package/dist/src/claudeEvents.js +4 -5
  94. package/dist/src/cli/agentIntegration.js +31 -2
  95. package/dist/src/cli/codebuddyHooks.js +286 -0
  96. package/dist/src/cli/hookScaffold.js +25 -12
  97. package/dist/src/cli/i18n/en.js +1 -1
  98. package/dist/src/cli/i18n/zh.js +1 -1
  99. package/dist/src/cli/state.js +4 -0
  100. package/dist/src/cli/supervisor.js +2 -1
  101. package/dist/src/codebuddyEvents.js +151 -0
  102. package/dist/src/docs.js +21 -7
  103. package/dist/src/logDetail.js +27 -0
  104. package/dist/src/paneInput.js +107 -10
  105. package/dist/src/pendingPrompt.js +21 -5
  106. package/dist/src/routes/files.js +4 -1
  107. package/dist/src/routes/system.js +12 -2
  108. package/dist/src/server.js +69 -8
  109. package/dist/src/trimCapture.js +1 -1
  110. package/dist/src/workspace/checkpointer.js +48 -4
  111. package/dist/src/workspace/lock.js +54 -10
  112. package/dist/src/workspace/runtime.js +23 -8
  113. package/dist/src/workspaceProtocol.js +1 -0
  114. package/package.json +4 -4
  115. package/dist/public/assets/index-2ghkmGbT.js +0 -356
  116. package/dist/public/assets/index-BjnnFTLC.css +0 -32
@@ -1,134 +1,4 @@
1
- import { createHash } from 'node:crypto';
2
- import { parsePendingPrompt } from '../pendingPrompt.js';
3
- function normalizedPrompt(text, pendingKind) {
4
- const prompt = parsePendingPrompt(text);
5
- if (!prompt) {
6
- if (pendingKind !== 'permission')
7
- return null;
8
- const tail = text.split('\n').map((line) => line.trim()).filter(Boolean).slice(-3).join('\n');
9
- const promptText = tail.slice(0, 2_000) || 'Claude is waiting for permission in the terminal.';
10
- const id = `claude-permission:${createHash('sha256').update(promptText).digest('hex').slice(0, 24)}`;
11
- return {
12
- id, type: 'local_only', prompt: promptText,
13
- };
14
- }
15
- const signature = JSON.stringify(prompt);
16
- const id = `claude-prompt:${createHash('sha256').update(signature).digest('hex').slice(0, 24)}`;
17
- return {
18
- id,
19
- type: 'select',
20
- prompt: prompt.leadIn ? `${prompt.leadIn}\n${prompt.title}` : prompt.title,
21
- options: prompt.options.map((option) => ({
22
- id: `choice:${option.n}`,
23
- label: option.description ? `${option.label} — ${option.description}` : option.label,
24
- })),
25
- };
26
- }
1
+ import { createHookInteractionAdapter } from './hookInteraction.js';
27
2
  export function createClaudeInteractionAdapter(control, pollMs = 750, reportHealth = () => { }) {
28
- if (!control || typeof control.capturePlain !== 'function'
29
- || typeof control.sendChoice !== 'function' || !Number.isFinite(pollMs) || pollMs <= 0) {
30
- throw new TypeError('Claude Interaction adapter requires pane capture and choice control');
31
- }
32
- const active = new Map();
33
- return {
34
- apiVersion: 1,
35
- async observeNative(run, sink) {
36
- let closed = false;
37
- let cursor = 0;
38
- let pending = normalizedPrompt(await control.capturePlain(run.ref.paneId), control.pendingKind?.(run.ref.paneId));
39
- let timer;
40
- let tail = Promise.resolve();
41
- let failures = 0;
42
- const binding = { run, pending };
43
- active.set(run.ref.runId, binding);
44
- const close = () => {
45
- closed = true;
46
- if (timer)
47
- clearTimeout(timer);
48
- timer = undefined;
49
- if (active.get(run.ref.runId) === binding)
50
- active.delete(run.ref.runId);
51
- };
52
- const schedule = (delay = pollMs) => {
53
- if (closed || run.signal.aborted)
54
- return;
55
- timer = setTimeout(() => {
56
- tail = tail.then(async () => {
57
- if (closed || run.signal.aborted)
58
- return;
59
- const next = normalizedPrompt(await control.capturePlain(run.ref.paneId), control.pendingKind?.(run.ref.paneId));
60
- if (failures > 0)
61
- reportHealth('ready');
62
- failures = 0;
63
- if (pending?.id === next?.id)
64
- return;
65
- if (pending)
66
- await sink({
67
- type: 'resolved', sourceCursor: `claude-interaction:${++cursor}`,
68
- interactionId: pending.id,
69
- });
70
- pending = next;
71
- binding.pending = next;
72
- if (next)
73
- await sink({
74
- type: 'opened', sourceCursor: `claude-interaction:${++cursor}`, interaction: next,
75
- });
76
- }).catch(async () => {
77
- failures += 1;
78
- if (failures >= 2) {
79
- reportHealth('degraded', 'Interaction polling is temporarily unavailable');
80
- if (pending) {
81
- const abandoned = pending;
82
- pending = null;
83
- binding.pending = null;
84
- await Promise.resolve(sink({
85
- type: 'cancelled', sourceCursor: `claude-interaction:${++cursor}`,
86
- interactionId: abandoned.id, reason: 'temporarily_unavailable',
87
- })).catch(close);
88
- }
89
- }
90
- }).finally(() => schedule(Math.min(10_000, pollMs * (2 ** Math.min(failures, 4)))));
91
- }, delay);
92
- timer.unref?.();
93
- };
94
- const onAbort = () => close();
95
- run.signal.addEventListener('abort', onAbort, { once: true });
96
- schedule();
97
- return {
98
- checkpoint: {
99
- sourceCursor: `claude-interaction:${cursor}`,
100
- pending: pending ? [pending] : [],
101
- },
102
- close() {
103
- run.signal.removeEventListener('abort', onAbort);
104
- close();
105
- },
106
- };
107
- },
108
- async dispatchResponse(run, request) {
109
- const binding = active.get(run.ref.runId);
110
- if (!binding || binding.run !== run || run.signal.aborted)
111
- return { status: 'stale_run' };
112
- if (!binding.pending || binding.pending.id !== request.interactionId) {
113
- return { status: 'already_resolved' };
114
- }
115
- const optionIds = request.value.type === 'selection' ? request.value.optionIds
116
- : request.value.type === 'approval' ? [request.value.optionId] : [];
117
- if (optionIds.length !== 1) {
118
- return { status: 'rejected', reason: 'invalid_value' };
119
- }
120
- const optionId = optionIds[0];
121
- const match = optionId.match(/^choice:(\d+)$/);
122
- if (!match || !binding.pending.options?.some((option) => option.id === optionId)) {
123
- return { status: 'rejected', reason: 'invalid_value' };
124
- }
125
- try {
126
- await control.sendChoice(run.ref.paneId, match[1]);
127
- return { status: 'accepted' };
128
- }
129
- catch {
130
- return { status: 'unknown', reason: 'temporarily_unavailable' };
131
- }
132
- },
133
- };
3
+ return createHookInteractionAdapter(control, { id: 'claude', label: 'Claude' }, pollMs, reportHealth);
134
4
  }
@@ -1,42 +1,42 @@
1
- import { serializePaneInput } from '../paneInput.js';
1
+ import { describeEditorArea, serializePaneInput, singleLineDraft } from '../paneInput.js';
2
2
  const SUBMIT_GAP_MS = 120;
3
3
  const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
4
- // Read the entire one-line native editor, bounded by its two rules. Wrapped input, attachments,
5
- // selection menus and unknown layouts fail closed. End is sent first: placeholder text is painted
6
- // after the cursor but isn't part of the editor; actual text places the cursor at its end.
4
+ // Claude's editor is the shared one-line shape, with `❯` as its prompt.
7
5
  export function claudeSingleLineDraft(screen, cursor) {
8
- const lines = screen.split('\n');
9
- const line = lines[cursor.cursorY];
10
- if (!line || !/^─{3,}$/.test(lines[cursor.cursorY - 1]?.trim() ?? '')
11
- || !/^─{3,}$/.test(lines[cursor.cursorY + 1]?.trim() ?? '')
12
- || !/^❯[ \u00a0]/.test(line))
13
- return null;
14
- const value = line.slice(2).trimEnd();
15
- if (cursor.cursorX === 2)
16
- return ''; // Native suggestions are painted here but End does not enter them.
17
- if (!value || /[\x00-\x1f\x7f]/.test(value) || cursor.cursorX <= 2)
18
- return null;
19
- return value;
6
+ return singleLineDraft(screen, cursor);
20
7
  }
21
- export function sendClaudePanePrompt(commands, paneId, text, restoredPrompt, guard) {
8
+ export function sendClaudePanePrompt(commands, paneId, text, restoredPrompt, guard, reportUnreadable) {
22
9
  return serializePaneInput(paneId, async () => {
23
10
  if (guard && !await guard.validate())
24
11
  return { nativeMutation: false };
25
12
  await commands.exitCopyModeIfActive(paneId);
26
13
  await commands.sendKey(paneId, 'End');
27
14
  await delay(SUBMIT_GAP_MS);
28
- const readDraft = async () => claudeSingleLineDraft(await commands.capturePlain(paneId), await commands.paneInfo(paneId));
29
- const draft = await readDraft();
30
- if (draft === null || (draft !== '' && draft !== restoredPrompt())) {
15
+ const readEditor = async () => {
16
+ const screen = await commands.capturePlain(paneId);
17
+ const cursor = await commands.paneInfo(paneId);
18
+ return { screen, cursor, draft: claudeSingleLineDraft(screen, cursor) };
19
+ };
20
+ const draft = await readEditor();
21
+ if (draft.draft === null) {
22
+ // Unreadable: record the shape, never the text we could not read (see describeEditorArea).
23
+ reportUnreadable?.(describeEditorArea(draft.screen, draft.cursor));
24
+ return { nativeMutation: false, reason: 'terminal_draft_conflict' };
25
+ }
26
+ if (draft.draft !== '' && draft.draft !== restoredPrompt()) {
31
27
  return { nativeMutation: false, reason: 'terminal_draft_conflict' };
32
28
  }
33
29
  if (guard && !await guard.validate())
34
30
  return { nativeMutation: false };
35
- if (draft) {
31
+ if (draft.draft) {
36
32
  await commands.sendKey(paneId, 'C-u');
37
33
  await delay(SUBMIT_GAP_MS);
38
- if (await readDraft() !== '')
34
+ const cleared = await readEditor();
35
+ if (cleared.draft !== '') {
36
+ if (cleared.draft === null)
37
+ reportUnreadable?.(describeEditorArea(cleared.screen, cleared.cursor));
39
38
  return { nativeMutation: false, reason: 'terminal_draft_conflict' };
39
+ }
40
40
  }
41
41
  await commands.sendText(paneId, text);
42
42
  if (text)
@@ -0,0 +1,100 @@
1
+ // The CodeBuddy Code agent adapter. Everything here is what Handmux knows that is SPECIFIC to CodeBuddy;
2
+ // the shared engine consumes this descriptor like any other provider.
3
+ //
4
+ // CodeBuddy is npm-first. `codebuddy`, `codebuddy-code` and `cbc` are symlinks onto one bin/codebuddy entry,
5
+ // so tmux only reports a stable name for the native build. In the documented npm layout the CLI runs INSIDE
6
+ // a Node process (`node /usr/local/bin/codebuddy` — verified live on 2.154.0), which makes three of the usual
7
+ // identity routes unusable:
8
+ // - `#{pane_current_command}` is a bare "node", so no exact command can ever own the pane;
9
+ // - the process's real executable (lsof/proc) is the Node binary, so executable-backed proof — the Claude
10
+ // and Codex route — cannot name CodeBuddy;
11
+ // - the Agent never spawns a native child binary, so the single-leaf foreground view, which deliberately
12
+ // prefers a non-launcher descendant for ambiguous launchers, can hand back a transient tool child
13
+ // (an npm/ruby update check) instead of the Agent itself.
14
+ // Identity therefore comes from the pane's whole foreground group, matched at a path boundary against the
15
+ // published launch entry points. We only ever answer "yes" from positive evidence, and we stay inconclusive
16
+ // rather than negative when the group was not actually read.
17
+ import os from 'node:os';
18
+ import path from 'node:path';
19
+ // The three entries the CodeBuddy CLI installs, plus the package that owns the npm layout.
20
+ const LAUNCH_NAMES = new Set(['codebuddy', 'codebuddy-code', 'cbc']);
21
+ const NPM_PACKAGE_MARKER = '/@tencent-ai/codebuddy-code/';
22
+ const NODE_PROGRAM_RE = /^(\S*\/)?node$/;
23
+ const basename = (value) => value.split('/').at(-1) ?? '';
24
+ // CodeBuddy's own on-disk layout, in one place because three readers need the same answer: the conversation
25
+ // adapter reads a session's JSONL, the native tail resolves a pane's session from its registry row, and the
26
+ // session binding reads the hook state file. A session log is
27
+ // `<projects>/<cwd with its slashes replaced by dashes>/<session-id>.jsonl`; a running session publishes
28
+ // `~/.codebuddy/sessions/<pid>.json` (pid, sessionId, cwd, heartbeat — never a status).
29
+ export const codebuddyProjectsDir = (home = os.homedir()) => path.join(home, '.codebuddy', 'projects');
30
+ export const codebuddySessionsDir = (home = os.homedir()) => path.join(home, '.codebuddy', 'sessions');
31
+ // Node's entry argument is the first non-flag token after the program. Nothing beyond it may be searched:
32
+ // the agent's own arguments (a prompt, a file path, a session id) are free to contain any word, and matching
33
+ // those would turn "some Node script that was passed the word codebuddy" into a false identity.
34
+ function nodeEntryArgument(rest) {
35
+ return rest.split(/\s+/).find((token) => token.length > 0 && !token.startsWith('-')) ?? '';
36
+ }
37
+ // Exported for the contract tests that freeze the accepted launch shapes.
38
+ export function isCodeBuddyCommandLine(value) {
39
+ const trimmed = value.trim();
40
+ if (!trimmed)
41
+ return false;
42
+ const segments = trimmed.split(/\s+/);
43
+ const program = (segments[0] ?? '').replace(/^-/, '');
44
+ // Native build: the launcher itself is the program token.
45
+ if (LAUNCH_NAMES.has(basename(program)))
46
+ return true;
47
+ // npm / Node launcher: `node <entry> …`. The entry must carry one of the published names AND sit in a
48
+ // recognised install layout — a bare `node codebuddy`, or an unrelated file that merely shares the name,
49
+ // is not proof.
50
+ if (!NODE_PROGRAM_RE.test(program))
51
+ return false;
52
+ const entry = nodeEntryArgument(segments.slice(1).join(' '));
53
+ if (!entry || !LAUNCH_NAMES.has(basename(entry)))
54
+ return false;
55
+ return entry.includes(NPM_PACKAGE_MARKER) || /(?:^|\/)(?:\.bin|bin)\//.test(entry);
56
+ }
57
+ // Returns the matched row rather than a bare `true`: that row is CodeBuddy's process, and the Runtime needs
58
+ // it as the lease anchor precisely because the pane's foreground leaf is NOT CodeBuddy. A row whose start time
59
+ // could not be resolved is still returned — the Runtime then declines to publish a lease until it can name a
60
+ // process generation, which is the same rule it applies to every other Agent.
61
+ export async function verifyCodeBuddyProcess(pane, context) {
62
+ const group = await context.inspectForegroundGroup?.(pane);
63
+ // A host that cannot supply the group cannot confirm a CodeBuddy pane (the single-leaf view may return a
64
+ // transient tool child), so CodeBuddy goes unidentified there. That is deliberately a plain `false` and not
65
+ // a thrown error: an inconclusive verdict is contagious — resolveAgentIdentity downgrades the WHOLE pane to
66
+ // `unknown` when any ambiguous candidate is unresolved, which would poison Codex/Pi on those hosts. Every
67
+ // production host builds its ProcessContext from createLocalAgentProcessContext, which does supply the group.
68
+ if (!group)
69
+ return false;
70
+ return group.find((entry) => isCodeBuddyCommandLine(entry.commandLine ?? '')) ?? false;
71
+ }
72
+ export const codebuddy = {
73
+ adapterApiVersion: 1,
74
+ id: 'codebuddy',
75
+ label: 'CodeBuddy',
76
+ process: {
77
+ commands: ['codebuddy', 'codebuddy-code', 'cbc'],
78
+ ambiguousCommands: ['node'],
79
+ // Process presence alone establishes the sessionless run. Beyond being the base every later capability
80
+ // builds on, this is what puts CodeBuddy on the pane roster the phone reads: the per-window Agent badge
81
+ // for windows you are NOT looking at comes from the Runtime's active runs (`/states`), not from pane
82
+ // identity — the pane you have selected is the only one resolved by `identifyPanes`.
83
+ runtimeAttach: true,
84
+ verify: verifyCodeBuddyProcess,
85
+ },
86
+ presentation: { iconId: 'codebuddy' },
87
+ // Only what is actually implemented. Inbox is delivered by CodeBuddy's Claude-compatible Hooks through
88
+ // its own Connector (connectors/codebuddy) + the Bridge durable lane; Conversation (with composer and
89
+ // interrupt, like Claude's) reads the session JSONL through ../../agents/codebuddyConversation.ts;
90
+ // Interaction reads the pending menu off the pane through ../../agents/codebuddyInteraction.ts — its
91
+ // question, review and permission screens were captured from a live 2.156.0 pane and all three parse with
92
+ // the shared parser, with the option digit as the key that drives them.
93
+ // The classification of its `src`→kind events lives in ../../codebuddyEvents.ts, next to the shared
94
+ // Hook vocabulary, because that is the module the Connector consumes.
95
+ capabilities: {
96
+ inbox: { apiVersion: 1 },
97
+ conversation: { apiVersion: 1, experimental: true },
98
+ interaction: { apiVersion: 1 },
99
+ },
100
+ };