panopticon-cli 0.5.4 → 0.5.7

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 (119) hide show
  1. package/dist/{agents-HNMF52RM.js → agents-QXVDAW2M.js} +12 -9
  2. package/dist/archive-planning-U3AZAKWI.js +16 -0
  3. package/dist/{chunk-KBHRXV5T.js → chunk-43F4LDZ4.js} +3 -3
  4. package/dist/{chunk-KY2E2Q3T.js → chunk-4XR62WWV.js} +105 -46
  5. package/dist/chunk-4XR62WWV.js.map +1 -0
  6. package/dist/chunk-6OYUJ4AJ.js +146 -0
  7. package/dist/chunk-6OYUJ4AJ.js.map +1 -0
  8. package/dist/{chunk-MOPGR3CL.js → chunk-AAP4G6U7.js} +1 -1
  9. package/dist/chunk-AAP4G6U7.js.map +1 -0
  10. package/dist/{chunk-4HST45MO.js → chunk-BYWVPPAZ.js} +19 -12
  11. package/dist/chunk-BYWVPPAZ.js.map +1 -0
  12. package/dist/{chunk-CFCUOV3Q.js → chunk-DMRTN432.js} +4 -1
  13. package/dist/chunk-DMRTN432.js.map +1 -0
  14. package/dist/{chunk-HOGYHJ2G.js → chunk-DW3PKGIS.js} +2 -2
  15. package/dist/chunk-GUV2EPBG.js +692 -0
  16. package/dist/chunk-GUV2EPBG.js.map +1 -0
  17. package/dist/{chunk-44EOY2ZL.js → chunk-HHL3AWXA.js} +46 -2
  18. package/dist/chunk-HHL3AWXA.js.map +1 -0
  19. package/dist/{chunk-6N2KBSJA.js → chunk-IZIXJYXZ.js} +40 -6
  20. package/dist/chunk-IZIXJYXZ.js.map +1 -0
  21. package/dist/chunk-MJXYTGK5.js +64 -0
  22. package/dist/chunk-MJXYTGK5.js.map +1 -0
  23. package/dist/chunk-OJF4QS3S.js +269 -0
  24. package/dist/chunk-OJF4QS3S.js.map +1 -0
  25. package/dist/{chunk-FQ66DECN.js → chunk-QAJAJBFW.js} +1 -1
  26. package/dist/chunk-QAJAJBFW.js.map +1 -0
  27. package/dist/chunk-R4KPLLRB.js +36 -0
  28. package/dist/chunk-R4KPLLRB.js.map +1 -0
  29. package/dist/{chunk-DFNVHK3N.js → chunk-SUM2WVPF.js} +4 -4
  30. package/dist/{chunk-ID4OYXVH.js → chunk-TFPJD2I2.js} +112 -45
  31. package/dist/chunk-TFPJD2I2.js.map +1 -0
  32. package/dist/{chunk-T7BBPDEJ.js → chunk-UKSGE6RH.js} +45 -15
  33. package/dist/chunk-UKSGE6RH.js.map +1 -0
  34. package/dist/chunk-W2OTF6OS.js +201 -0
  35. package/dist/chunk-W2OTF6OS.js.map +1 -0
  36. package/dist/chunk-WEQW3EAT.js +78 -0
  37. package/dist/chunk-WEQW3EAT.js.map +1 -0
  38. package/dist/chunk-YAAT66RT.js +70 -0
  39. package/dist/chunk-YAAT66RT.js.map +1 -0
  40. package/dist/{chunk-RLZQB7HS.js → chunk-ZMJFEHGF.js} +13 -1
  41. package/dist/chunk-ZMJFEHGF.js.map +1 -0
  42. package/dist/{chunk-HRU7S4TA.js → chunk-ZN5RHWGR.js} +18 -208
  43. package/dist/{chunk-HRU7S4TA.js.map → chunk-ZN5RHWGR.js.map} +1 -1
  44. package/dist/{chunk-ZTYHZMEC.js → chunk-ZWZNEA26.js} +2 -2
  45. package/dist/clean-planning-7Z5YY64X.js +9 -0
  46. package/dist/cli/index.js +1338 -2226
  47. package/dist/cli/index.js.map +1 -1
  48. package/dist/close-issue-CTZK777I.js +9 -0
  49. package/dist/compact-beads-72SHALOL.js +9 -0
  50. package/dist/{config-4CJNUE3O.js → config-FFTMBVHM.js} +2 -2
  51. package/dist/dashboard/public/assets/{index-DSvt5pPn.css → index-Bx4NCn9A.css} +1 -1
  52. package/dist/dashboard/public/assets/index-C7hJ5-o1.js +756 -0
  53. package/dist/dashboard/public/index.html +3 -2
  54. package/dist/dashboard/server.js +34720 -34297
  55. package/dist/{feedback-writer-T43PI5S2.js → feedback-writer-T2WCT6EZ.js} +2 -2
  56. package/dist/{hume-CKJJ3OUU.js → hume-GVTB5BKW.js} +3 -3
  57. package/dist/index.d.ts +24 -16
  58. package/dist/index.js +4 -4
  59. package/dist/label-cleanup-4HJVX6NP.js +103 -0
  60. package/dist/label-cleanup-4HJVX6NP.js.map +1 -0
  61. package/dist/merge-agent-O3TSBTLC.js +1725 -0
  62. package/dist/merge-agent-O3TSBTLC.js.map +1 -0
  63. package/dist/{projects-KVM3MN3Y.js → projects-3CRF57ZU.js} +2 -2
  64. package/dist/{rally-RKFSWC7E.js → rally-LBY24P4C.js} +2 -2
  65. package/dist/{remote-agents-ULPD6C5U.js → remote-agents-3NZPSHYG.js} +2 -3
  66. package/dist/{remote-workspace-XX6ARE6I.js → remote-workspace-M4IULGFZ.js} +24 -49
  67. package/dist/remote-workspace-M4IULGFZ.js.map +1 -0
  68. package/dist/{review-status-XKUKZF6J.js → review-status-J2YJGL3E.js} +2 -2
  69. package/dist/{specialist-context-C66TEMXS.js → specialist-context-IKG6VMNH.js} +7 -5
  70. package/dist/{specialist-context-C66TEMXS.js.map → specialist-context-IKG6VMNH.js.map} +1 -1
  71. package/dist/{specialist-logs-CJKXM3SR.js → specialist-logs-GFKUXCFG.js} +6 -4
  72. package/dist/{specialists-NXYD4Z62.js → specialists-XMFCFGYQ.js} +6 -4
  73. package/dist/specialists-XMFCFGYQ.js.map +1 -0
  74. package/dist/tmux-X2I5SAIJ.js +31 -0
  75. package/dist/tmux-X2I5SAIJ.js.map +1 -0
  76. package/dist/{traefik-5GL3Q7DJ.js → traefik-QXLZ4PO2.js} +4 -4
  77. package/dist/traefik-QXLZ4PO2.js.map +1 -0
  78. package/dist/{tunnel-BKC7KLBX.js → tunnel-7IOSRZVH.js} +3 -3
  79. package/dist/tunnel-7IOSRZVH.js.map +1 -0
  80. package/dist/{workspace-manager-ALBR62AS.js → workspace-manager-G6TTBPC3.js} +6 -6
  81. package/dist/workspace-manager-G6TTBPC3.js.map +1 -0
  82. package/package.json +2 -2
  83. package/scripts/build-cost-script.mjs +17 -0
  84. package/scripts/heartbeat-hook +28 -8
  85. package/scripts/record-cost-event.js +46 -7
  86. package/scripts/record-cost-event.ts +2 -1
  87. package/dist/chunk-44EOY2ZL.js.map +0 -1
  88. package/dist/chunk-4HST45MO.js.map +0 -1
  89. package/dist/chunk-565HZ6VV.js +0 -159
  90. package/dist/chunk-565HZ6VV.js.map +0 -1
  91. package/dist/chunk-6N2KBSJA.js.map +0 -1
  92. package/dist/chunk-CFCUOV3Q.js.map +0 -1
  93. package/dist/chunk-FQ66DECN.js.map +0 -1
  94. package/dist/chunk-ID4OYXVH.js.map +0 -1
  95. package/dist/chunk-KY2E2Q3T.js.map +0 -1
  96. package/dist/chunk-MOPGR3CL.js.map +0 -1
  97. package/dist/chunk-RLZQB7HS.js.map +0 -1
  98. package/dist/chunk-T7BBPDEJ.js.map +0 -1
  99. package/dist/chunk-ZDNQFWR5.js +0 -650
  100. package/dist/chunk-ZDNQFWR5.js.map +0 -1
  101. package/dist/dashboard/public/assets/index-DA6pnizT.js +0 -767
  102. package/dist/remote-workspace-XX6ARE6I.js.map +0 -1
  103. /package/dist/{agents-HNMF52RM.js.map → agents-QXVDAW2M.js.map} +0 -0
  104. /package/dist/{config-4CJNUE3O.js.map → archive-planning-U3AZAKWI.js.map} +0 -0
  105. /package/dist/{chunk-KBHRXV5T.js.map → chunk-43F4LDZ4.js.map} +0 -0
  106. /package/dist/{chunk-HOGYHJ2G.js.map → chunk-DW3PKGIS.js.map} +0 -0
  107. /package/dist/{chunk-DFNVHK3N.js.map → chunk-SUM2WVPF.js.map} +0 -0
  108. /package/dist/{chunk-ZTYHZMEC.js.map → chunk-ZWZNEA26.js.map} +0 -0
  109. /package/dist/{hume-CKJJ3OUU.js.map → clean-planning-7Z5YY64X.js.map} +0 -0
  110. /package/dist/{projects-KVM3MN3Y.js.map → close-issue-CTZK777I.js.map} +0 -0
  111. /package/dist/{rally-RKFSWC7E.js.map → compact-beads-72SHALOL.js.map} +0 -0
  112. /package/dist/{remote-agents-ULPD6C5U.js.map → config-FFTMBVHM.js.map} +0 -0
  113. /package/dist/{feedback-writer-T43PI5S2.js.map → feedback-writer-T2WCT6EZ.js.map} +0 -0
  114. /package/dist/{review-status-XKUKZF6J.js.map → hume-GVTB5BKW.js.map} +0 -0
  115. /package/dist/{specialist-logs-CJKXM3SR.js.map → projects-3CRF57ZU.js.map} +0 -0
  116. /package/dist/{specialists-NXYD4Z62.js.map → rally-LBY24P4C.js.map} +0 -0
  117. /package/dist/{traefik-5GL3Q7DJ.js.map → remote-agents-3NZPSHYG.js.map} +0 -0
  118. /package/dist/{tunnel-BKC7KLBX.js.map → review-status-J2YJGL3E.js.map} +0 -0
  119. /package/dist/{workspace-manager-ALBR62AS.js.map → specialist-logs-GFKUXCFG.js.map} +0 -0
@@ -7,7 +7,6 @@ import {
7
7
  } from "./chunk-ZP6EWSZV.js";
8
8
  import {
9
9
  AGENTS_DIR,
10
- PANOPTICON_HOME,
11
10
  init_paths
12
11
  } from "./chunk-ZTFNYOC7.js";
13
12
  import {
@@ -15,213 +14,35 @@ import {
15
14
  init_esm_shims
16
15
  } from "./chunk-ZHC57RCV.js";
17
16
 
18
- // src/lib/tmux.ts
19
- import { execSync, exec } from "child_process";
20
- import { promisify } from "util";
21
- import { writeFileSync, chmodSync, appendFileSync, mkdirSync, existsSync, unlinkSync } from "fs";
22
- import { join } from "path";
23
- function ensureLogDir() {
24
- const logDir = join(PANOPTICON_HOME, "logs");
25
- if (!existsSync(logDir)) {
26
- mkdirSync(logDir, { recursive: true });
27
- }
28
- }
29
- function logSendKeys(sessionName, keys, caller) {
30
- try {
31
- ensureLogDir();
32
- const stack = new Error().stack || "";
33
- const stackLines = stack.split("\n").slice(3, 6);
34
- const callerInfo = caller || stackLines.map((l) => l.trim()).join(" <- ");
35
- const entry = {
36
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
37
- sessionName,
38
- keysLength: keys.length,
39
- keysPreview: keys.length > 200 ? keys.slice(0, 200) + "..." : keys,
40
- caller: callerInfo,
41
- pid: process.pid
42
- };
43
- appendFileSync(SENDKEYS_LOG_FILE, JSON.stringify(entry) + "\n", "utf-8");
44
- } catch {
45
- }
46
- }
47
- function listSessions() {
48
- try {
49
- const output = execSync('tmux list-sessions -F "#{session_name}|#{session_created}|#{session_attached}|#{session_windows}"', {
50
- encoding: "utf8"
51
- });
52
- return output.trim().split("\n").filter(Boolean).map((line) => {
53
- const [name, created, attached, windows] = line.split("|");
54
- return {
55
- name,
56
- created: new Date(parseInt(created) * 1e3),
57
- attached: attached === "1",
58
- windows: parseInt(windows)
59
- };
60
- });
61
- } catch {
62
- return [];
63
- }
64
- }
65
- function sessionExists(name) {
66
- try {
67
- execSync(`tmux has-session -t ${name} 2>/dev/null`);
68
- return true;
69
- } catch {
70
- return false;
71
- }
72
- }
73
- function createSession(name, cwd, initialCommand, options) {
74
- const escapedCwd = cwd.replace(/"/g, '\\"');
75
- let envFlags = "";
76
- if (options?.env) {
77
- for (const [key, value] of Object.entries(options.env)) {
78
- envFlags += ` -e ${key}="${value.replace(/"/g, '\\"')}"`;
79
- }
80
- }
81
- if (initialCommand && (initialCommand.includes("`") || initialCommand.includes("\n") || initialCommand.length > 500)) {
82
- execSync(`tmux new-session -d -s ${name} -c "${escapedCwd}"${envFlags}`);
83
- execSync("sleep 0.5");
84
- const tmpFile = `/tmp/pan-cmd-${name}.sh`;
85
- writeFileSync(tmpFile, initialCommand);
86
- chmodSync(tmpFile, "755");
87
- execSync(`tmux send-keys -t ${name} "bash ${tmpFile}"`);
88
- execSync(`tmux send-keys -t ${name} C-m`);
89
- } else if (initialCommand) {
90
- const cmd = `tmux new-session -d -s ${name} -c "${escapedCwd}"${envFlags} "${initialCommand.replace(/"/g, '\\"')}"`;
91
- execSync(cmd);
92
- } else {
93
- execSync(`tmux new-session -d -s ${name} -c "${escapedCwd}"${envFlags}`);
94
- }
95
- }
96
- function killSession(name) {
97
- execSync(`tmux kill-session -t ${name}`);
98
- }
99
- async function sendKeysAsync(sessionName, keys, caller) {
100
- logSendKeys(sessionName, keys, caller);
101
- const bufferName = `pan-${process.pid}-${Date.now()}`;
102
- const tmpFile = `/tmp/pan-sendkeys-${bufferName}.txt`;
103
- try {
104
- writeFileSync(tmpFile, keys);
105
- await execAsync(`tmux load-buffer -b ${bufferName} ${tmpFile}`);
106
- await execAsync(`tmux paste-buffer -b ${bufferName} -t ${sessionName} -d`);
107
- await new Promise((r) => setTimeout(r, 300));
108
- await execAsync(`tmux send-keys -t ${sessionName} C-m`);
109
- } finally {
110
- try {
111
- unlinkSync(tmpFile);
112
- } catch {
113
- }
114
- try {
115
- await execAsync(`tmux delete-buffer -b ${bufferName} 2>/dev/null`);
116
- } catch {
117
- }
118
- }
119
- }
120
- function sendKeys(sessionName, keys, caller) {
121
- logSendKeys(sessionName, keys, caller);
122
- const tmpFile = `/tmp/pan-sendkeys-${process.pid}-${Date.now()}.txt`;
123
- try {
124
- writeFileSync(tmpFile, keys);
125
- execSync(`tmux load-buffer ${tmpFile}`);
126
- execSync(`tmux paste-buffer -t ${sessionName}`);
127
- execSync(`sleep 0.3`);
128
- execSync(`tmux send-keys -t ${sessionName} C-m`);
129
- } finally {
130
- try {
131
- unlinkSync(tmpFile);
132
- } catch {
133
- }
134
- }
135
- }
136
- function capturePane(sessionName, lines = 50) {
137
- try {
138
- return execSync(`tmux capture-pane -t ${sessionName} -p -S -${lines}`, {
139
- encoding: "utf8"
140
- });
141
- } catch {
142
- return "";
143
- }
144
- }
145
- async function capturePaneAsync(sessionName, lines = 50) {
146
- try {
147
- const { stdout } = await execAsync(`tmux capture-pane -t ${sessionName} -p -S -${lines}`, {
148
- encoding: "utf-8"
149
- });
150
- return stdout;
151
- } catch {
152
- return "";
153
- }
154
- }
155
- async function waitForClaudePrompt(sessionName, timeoutMs = 15e3) {
156
- const start = Date.now();
157
- const POLL = 500;
158
- while (Date.now() - start < timeoutMs) {
159
- const output = await capturePaneAsync(sessionName, 10);
160
- const lines = output.split("\n").filter((l) => l.trim());
161
- const lastLine = lines[lines.length - 1] || "";
162
- if (lastLine.includes("\u276F")) return true;
163
- await new Promise((r) => setTimeout(r, POLL));
164
- }
165
- return false;
166
- }
167
- async function confirmDelivery(sessionName, outputBefore, timeoutMs = 1e4) {
168
- const start = Date.now();
169
- const POLL = 1e3;
170
- const beforeLineCount = outputBefore.split("\n").filter((l) => l.trim()).length;
171
- while (Date.now() - start < timeoutMs) {
172
- await new Promise((r) => setTimeout(r, POLL));
173
- const after = await capturePaneAsync(sessionName, 50);
174
- const afterLines = after.split("\n").filter((l) => l.trim());
175
- const afterLineCount = afterLines.length;
176
- if (afterLineCount > beforeLineCount + 1) return true;
177
- const newOutput = afterLines.slice(beforeLineCount).join("\n");
178
- if (newOutput.includes("\u25CF") || newOutput.includes("\u23BF") || newOutput.includes("Read") || newOutput.includes("\u273B") || newOutput.includes("\xB7") || newOutput.includes("\u2736") || newOutput.includes("\u273D") || newOutput.includes("\u2722") || newOutput.includes("Generating") || newOutput.includes("thinking") || newOutput.includes("thought for")) return true;
179
- }
180
- return false;
181
- }
182
- function getAgentSessions() {
183
- return listSessions().filter((s) => s.name.startsWith("agent-"));
184
- }
185
- var SENDKEYS_LOG_FILE, execAsync;
186
- var init_tmux = __esm({
187
- "src/lib/tmux.ts"() {
188
- "use strict";
189
- init_esm_shims();
190
- init_paths();
191
- SENDKEYS_LOG_FILE = join(PANOPTICON_HOME, "logs", "sendkeys.jsonl");
192
- execAsync = promisify(exec);
193
- }
194
- });
195
-
196
17
  // src/lib/hooks.ts
197
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, writeFileSync as writeFileSync2, readdirSync, unlinkSync as unlinkSync2 } from "fs";
198
- import { join as join2 } from "path";
18
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, unlinkSync } from "fs";
19
+ import { join } from "path";
199
20
  function getHookDir(agentId) {
200
- return join2(AGENTS_DIR, agentId);
21
+ return join(AGENTS_DIR, agentId);
201
22
  }
202
23
  function getHookFile(agentId) {
203
- return join2(getHookDir(agentId), "hook.json");
24
+ return join(getHookDir(agentId), "hook.json");
204
25
  }
205
26
  function getMailDir(agentId) {
206
- return join2(getHookDir(agentId), "mail");
27
+ return join(getHookDir(agentId), "mail");
207
28
  }
208
29
  function initHook(agentId) {
209
30
  const hookDir = getHookDir(agentId);
210
31
  const mailDir = getMailDir(agentId);
211
- mkdirSync2(hookDir, { recursive: true });
212
- mkdirSync2(mailDir, { recursive: true });
32
+ mkdirSync(hookDir, { recursive: true });
33
+ mkdirSync(mailDir, { recursive: true });
213
34
  const hookFile = getHookFile(agentId);
214
- if (!existsSync2(hookFile)) {
35
+ if (!existsSync(hookFile)) {
215
36
  const hook = {
216
37
  agentId,
217
38
  items: []
218
39
  };
219
- writeFileSync2(hookFile, JSON.stringify(hook, null, 2));
40
+ writeFileSync(hookFile, JSON.stringify(hook, null, 2));
220
41
  }
221
42
  }
222
43
  function getHook(agentId) {
223
44
  const hookFile = getHookFile(agentId);
224
- if (!existsSync2(hookFile)) {
45
+ if (!existsSync(hookFile)) {
225
46
  return null;
226
47
  }
227
48
  try {
@@ -240,19 +61,19 @@ function pushToHook(agentId, item) {
240
61
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
241
62
  };
242
63
  hook.items.push(newItem);
243
- writeFileSync2(getHookFile(agentId), JSON.stringify(hook, null, 2));
64
+ writeFileSync(getHookFile(agentId), JSON.stringify(hook, null, 2));
244
65
  return newItem;
245
66
  }
246
67
  function checkHook(agentId) {
247
68
  const hook = getHook(agentId);
248
69
  if (!hook || hook.items.length === 0) {
249
70
  const mailDir = getMailDir(agentId);
250
- if (existsSync2(mailDir)) {
71
+ if (existsSync(mailDir)) {
251
72
  const mails = readdirSync(mailDir).filter((f) => f.endsWith(".json"));
252
73
  if (mails.length > 0) {
253
74
  const mailItems = mails.map((file) => {
254
75
  try {
255
- const content = readFileSync(join2(mailDir, file), "utf-8");
76
+ const content = readFileSync(join(mailDir, file), "utf-8");
256
77
  return JSON.parse(content);
257
78
  } catch {
258
79
  return null;
@@ -289,7 +110,7 @@ function popFromHook(agentId, itemId) {
289
110
  if (index === -1) return false;
290
111
  hook.items.splice(index, 1);
291
112
  hook.lastChecked = (/* @__PURE__ */ new Date()).toISOString();
292
- writeFileSync2(getHookFile(agentId), JSON.stringify(hook, null, 2));
113
+ writeFileSync(getHookFile(agentId), JSON.stringify(hook, null, 2));
293
114
  return true;
294
115
  }
295
116
  function clearHook(agentId) {
@@ -297,7 +118,7 @@ function clearHook(agentId) {
297
118
  if (!hook) return;
298
119
  hook.items = [];
299
120
  hook.lastChecked = (/* @__PURE__ */ new Date()).toISOString();
300
- writeFileSync2(getHookFile(agentId), JSON.stringify(hook, null, 2));
121
+ writeFileSync(getHookFile(agentId), JSON.stringify(hook, null, 2));
301
122
  }
302
123
  function sendMail(toAgentId, from, message, priority = "normal") {
303
124
  initHook(toAgentId);
@@ -310,8 +131,8 @@ function sendMail(toAgentId, from, message, priority = "normal") {
310
131
  payload: { message },
311
132
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
312
133
  };
313
- writeFileSync2(
314
- join2(mailDir, `${mailItem.id}.json`),
134
+ writeFileSync(
135
+ join(mailDir, `${mailItem.id}.json`),
315
136
  JSON.stringify(mailItem, null, 2)
316
137
  );
317
138
  }
@@ -1000,17 +821,6 @@ var init_work_type_router = __esm({
1000
821
  });
1001
822
 
1002
823
  export {
1003
- sessionExists,
1004
- createSession,
1005
- killSession,
1006
- sendKeysAsync,
1007
- sendKeys,
1008
- capturePane,
1009
- capturePaneAsync,
1010
- waitForClaudePrompt,
1011
- confirmDelivery,
1012
- getAgentSessions,
1013
- init_tmux,
1014
824
  initHook,
1015
825
  pushToHook,
1016
826
  checkHook,
@@ -1022,4 +832,4 @@ export {
1022
832
  getModelId,
1023
833
  init_work_type_router
1024
834
  };
1025
- //# sourceMappingURL=chunk-HRU7S4TA.js.map
835
+ //# sourceMappingURL=chunk-ZN5RHWGR.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/lib/tmux.ts","../src/lib/hooks.ts","../src/lib/work-types.ts","../src/lib/model-fallback.ts","../src/lib/smart-model-selector.ts","../src/lib/work-type-router.ts"],"sourcesContent":["import { execSync, exec } from 'child_process';\nimport { promisify } from 'util';\nimport { writeFileSync, chmodSync, appendFileSync, mkdirSync, existsSync, unlinkSync } from 'fs';\nimport { join } from 'path';\nimport { PANOPTICON_HOME } from './paths.js';\n\n/**\n * Log file for tmux sendKeys operations\n * This helps debug mysterious messages appearing in agent prompts\n */\nconst SENDKEYS_LOG_FILE = join(PANOPTICON_HOME, 'logs', 'sendkeys.jsonl');\n\n/**\n * Ensure log directory exists\n */\nfunction ensureLogDir(): void {\n const logDir = join(PANOPTICON_HOME, 'logs');\n if (!existsSync(logDir)) {\n mkdirSync(logDir, { recursive: true });\n }\n}\n\n/**\n * Log a sendKeys operation for debugging\n */\nfunction logSendKeys(sessionName: string, keys: string, caller?: string): void {\n try {\n ensureLogDir();\n\n // Get call stack to identify caller if not provided\n const stack = new Error().stack || '';\n const stackLines = stack.split('\\n').slice(3, 6); // Skip Error, logSendKeys, sendKeys\n const callerInfo = caller || stackLines.map(l => l.trim()).join(' <- ');\n\n const entry = {\n timestamp: new Date().toISOString(),\n sessionName,\n keysLength: keys.length,\n keysPreview: keys.length > 200 ? keys.slice(0, 200) + '...' : keys,\n caller: callerInfo,\n pid: process.pid,\n };\n\n appendFileSync(SENDKEYS_LOG_FILE, JSON.stringify(entry) + '\\n', 'utf-8');\n } catch {\n // Silently fail - logging should never break functionality\n }\n}\n\nexport interface TmuxSession {\n name: string;\n created: Date;\n attached: boolean;\n windows: number;\n}\n\nexport function listSessions(): TmuxSession[] {\n try {\n const output = execSync('tmux list-sessions -F \"#{session_name}|#{session_created}|#{session_attached}|#{session_windows}\"', {\n encoding: 'utf8',\n });\n\n return output.trim().split('\\n').filter(Boolean).map(line => {\n const [name, created, attached, windows] = line.split('|');\n return {\n name,\n created: new Date(parseInt(created) * 1000),\n attached: attached === '1',\n windows: parseInt(windows),\n };\n });\n } catch {\n return []; // No sessions\n }\n}\n\nexport function sessionExists(name: string): boolean {\n try {\n execSync(`tmux has-session -t ${name} 2>/dev/null`);\n return true;\n } catch {\n return false;\n }\n}\n\nexport function createSession(\n name: string,\n cwd: string,\n initialCommand?: string,\n options?: { env?: Record<string, string> }\n): void {\n const escapedCwd = cwd.replace(/\"/g, '\\\\\"');\n\n // Build environment variable flags for tmux\n let envFlags = '';\n if (options?.env) {\n for (const [key, value] of Object.entries(options.env)) {\n envFlags += ` -e ${key}=\"${value.replace(/\"/g, '\\\\\"')}\"`;\n }\n }\n\n // For complex commands (with special chars), start session first then send command\n if (initialCommand && (initialCommand.includes('`') || initialCommand.includes('\\n') || initialCommand.length > 500)) {\n // Create session without command\n execSync(`tmux new-session -d -s ${name} -c \"${escapedCwd}\"${envFlags}`);\n\n // Small delay to let session initialize\n execSync('sleep 0.5');\n\n // Send the command in chunks if needed (tmux has buffer limits)\n // First, write to a temp file and source it\n const tmpFile = `/tmp/pan-cmd-${name}.sh`;\n writeFileSync(tmpFile, initialCommand);\n chmodSync(tmpFile, '755');\n\n // Execute the script\n execSync(`tmux send-keys -t ${name} \"bash ${tmpFile}\"`);\n execSync(`tmux send-keys -t ${name} C-m`);\n } else if (initialCommand) {\n // Simple command - use inline\n const cmd = `tmux new-session -d -s ${name} -c \"${escapedCwd}\"${envFlags} \"${initialCommand.replace(/\"/g, '\\\\\"')}\"`;\n execSync(cmd);\n } else {\n execSync(`tmux new-session -d -s ${name} -c \"${escapedCwd}\"${envFlags}`);\n }\n}\n\nexport function killSession(name: string): void {\n execSync(`tmux kill-session -t ${name}`);\n}\n\nconst execAsync = promisify(exec);\n\n/**\n * Send keys to a tmux session (async, non-blocking).\n * Uses load-buffer + paste-buffer for reliable delivery, with a delay before Enter.\n * MUST be used from the dashboard server and any async context.\n */\nexport async function sendKeysAsync(sessionName: string, keys: string, caller?: string): Promise<void> {\n logSendKeys(sessionName, keys, caller);\n\n // Use a unique named buffer per call to prevent race conditions.\n // The default (unnamed) paste buffer is global — concurrent load-buffer\n // calls from different specialist wakes clobber each other.\n const bufferName = `pan-${process.pid}-${Date.now()}`;\n const tmpFile = `/tmp/pan-sendkeys-${bufferName}.txt`;\n try {\n writeFileSync(tmpFile, keys);\n await execAsync(`tmux load-buffer -b ${bufferName} ${tmpFile}`);\n await execAsync(`tmux paste-buffer -b ${bufferName} -t ${sessionName} -d`);\n await new Promise(r => setTimeout(r, 300));\n await execAsync(`tmux send-keys -t ${sessionName} C-m`);\n } finally {\n try { unlinkSync(tmpFile); } catch {}\n try { await execAsync(`tmux delete-buffer -b ${bufferName} 2>/dev/null`); } catch {}\n }\n}\n\n/**\n * Send keys to a tmux session (sync, blocks event loop).\n * Only use from CLI commands — NEVER from the dashboard server.\n */\nexport function sendKeys(sessionName: string, keys: string, caller?: string): void {\n logSendKeys(sessionName, keys, caller);\n\n const tmpFile = `/tmp/pan-sendkeys-${process.pid}-${Date.now()}.txt`;\n try {\n writeFileSync(tmpFile, keys);\n execSync(`tmux load-buffer ${tmpFile}`);\n execSync(`tmux paste-buffer -t ${sessionName}`);\n execSync(`sleep 0.3`);\n execSync(`tmux send-keys -t ${sessionName} C-m`);\n } finally {\n try { unlinkSync(tmpFile); } catch {}\n }\n}\n\nexport function capturePane(sessionName: string, lines: number = 50): string {\n try {\n return execSync(`tmux capture-pane -t ${sessionName} -p -S -${lines}`, {\n encoding: 'utf8',\n });\n } catch {\n return '';\n }\n}\n\n/**\n * Capture tmux pane output (async, non-blocking).\n * MUST be used from the dashboard server and any async context.\n */\nexport async function capturePaneAsync(sessionName: string, lines: number = 50): Promise<string> {\n try {\n const { stdout } = await execAsync(`tmux capture-pane -t ${sessionName} -p -S -${lines}`, {\n encoding: 'utf-8',\n });\n return stdout;\n } catch {\n return '';\n }\n}\n\n/**\n * Wait for Claude Code to reach its interactive prompt (❯) in a tmux session.\n * Polls tmux output until the prompt appears or timeout is reached.\n *\n * @param sessionName - tmux session name\n * @param timeoutMs - maximum time to wait (default: 15s for fresh start, use 5s for already-running)\n * @returns true if prompt detected, false if timed out\n */\nexport async function waitForClaudePrompt(sessionName: string, timeoutMs: number = 15000): Promise<boolean> {\n const start = Date.now();\n const POLL = 500;\n while (Date.now() - start < timeoutMs) {\n const output = await capturePaneAsync(sessionName, 10);\n // Claude Code shows ❯ when ready for user input.\n // Check that the LAST non-empty line contains ❯ (not a stale prompt from earlier output).\n const lines = output.split('\\n').filter(l => l.trim());\n const lastLine = lines[lines.length - 1] || '';\n if (lastLine.includes('❯')) return true;\n await new Promise(r => setTimeout(r, POLL));\n }\n return false;\n}\n\n/**\n * Verify that a message sent to Claude was actually received and processing started.\n * Compares tmux output before and after to detect new activity (tool calls, responses).\n *\n * @param sessionName - tmux session name\n * @param outputBefore - tmux output snapshot taken BEFORE sending the message\n * @param timeoutMs - maximum time to wait for activity (default: 10s)\n * @returns true if new activity detected, false if timed out\n */\nexport async function confirmDelivery(\n sessionName: string,\n outputBefore: string,\n timeoutMs: number = 10000,\n): Promise<boolean> {\n const start = Date.now();\n const POLL = 1000;\n const beforeLineCount = outputBefore.split('\\n').filter(l => l.trim()).length;\n\n while (Date.now() - start < timeoutMs) {\n await new Promise(r => setTimeout(r, POLL));\n const after = await capturePaneAsync(sessionName, 50);\n const afterLines = after.split('\\n').filter(l => l.trim());\n const afterLineCount = afterLines.length;\n\n // Claude is processing if: new output lines appeared (tool calls: ●, results: ⎿, etc.)\n if (afterLineCount > beforeLineCount + 1) return true;\n\n // Or if we can see activity markers in the new output\n const newOutput = afterLines.slice(beforeLineCount).join('\\n');\n if (\n newOutput.includes('●') || newOutput.includes('⎿') || newOutput.includes('Read') ||\n newOutput.includes('✻') || newOutput.includes('·') || newOutput.includes('✶') ||\n newOutput.includes('✽') || newOutput.includes('✢') || newOutput.includes('Generating') ||\n newOutput.includes('thinking') || newOutput.includes('thought for')\n ) return true;\n }\n return false;\n}\n\nexport function getAgentSessions(): TmuxSession[] {\n return listSessions().filter(s => s.name.startsWith('agent-'));\n}\n","/**\n * FPP Hooks System - Fixed Point Principle\n *\n * \"Any runnable action is a fixed point and must resolve before the system can rest.\"\n *\n * Inspired by Doctor Who: a fixed point in time must occur — it cannot be avoided.\n *\n * Hooks are persistent work queues for agents. When an agent starts,\n * it checks its hook for pending work and executes immediately.\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, unlinkSync } from 'fs';\nimport { join } from 'path';\nimport { AGENTS_DIR } from './paths.js';\n\nexport interface HookItem {\n id: string;\n type: 'task' | 'message' | 'notification';\n priority: 'urgent' | 'high' | 'normal' | 'low';\n source: string;\n payload: {\n issueId?: string;\n message?: string;\n action?: string;\n context?: Record<string, any>;\n };\n createdAt: string;\n expiresAt?: string;\n}\n\nexport interface Hook {\n agentId: string;\n items: HookItem[];\n lastChecked?: string;\n}\n\nfunction getHookDir(agentId: string): string {\n return join(AGENTS_DIR, agentId);\n}\n\nfunction getHookFile(agentId: string): string {\n return join(getHookDir(agentId), 'hook.json');\n}\n\nfunction getMailDir(agentId: string): string {\n return join(getHookDir(agentId), 'mail');\n}\n\n/**\n * Initialize hook structure for an agent\n */\nexport function initHook(agentId: string): void {\n const hookDir = getHookDir(agentId);\n const mailDir = getMailDir(agentId);\n\n mkdirSync(hookDir, { recursive: true });\n mkdirSync(mailDir, { recursive: true });\n\n const hookFile = getHookFile(agentId);\n if (!existsSync(hookFile)) {\n const hook: Hook = {\n agentId,\n items: [],\n };\n writeFileSync(hookFile, JSON.stringify(hook, null, 2));\n }\n}\n\n/**\n * Get the hook for an agent\n */\nexport function getHook(agentId: string): Hook | null {\n const hookFile = getHookFile(agentId);\n if (!existsSync(hookFile)) {\n return null;\n }\n\n try {\n const content = readFileSync(hookFile, 'utf-8');\n return JSON.parse(content);\n } catch {\n return null;\n }\n}\n\n/**\n * Add work to an agent's hook (FPP trigger)\n */\nexport function pushToHook(agentId: string, item: Omit<HookItem, 'id' | 'createdAt'>): HookItem {\n initHook(agentId);\n\n const hook = getHook(agentId) || { agentId, items: [] };\n\n const newItem: HookItem = {\n ...item,\n id: `hook-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,\n createdAt: new Date().toISOString(),\n };\n\n hook.items.push(newItem);\n writeFileSync(getHookFile(agentId), JSON.stringify(hook, null, 2));\n\n return newItem;\n}\n\n/**\n * Check if agent has pending work (FPP check)\n */\nexport function checkHook(agentId: string): { hasWork: boolean; urgentCount: number; items: HookItem[] } {\n const hook = getHook(agentId);\n\n if (!hook || hook.items.length === 0) {\n // Also check mail directory for incoming messages\n const mailDir = getMailDir(agentId);\n if (existsSync(mailDir)) {\n const mails = readdirSync(mailDir).filter((f) => f.endsWith('.json'));\n if (mails.length > 0) {\n // Convert mail to hook items\n const mailItems: HookItem[] = mails.map((file) => {\n try {\n const content = readFileSync(join(mailDir, file), 'utf-8');\n return JSON.parse(content);\n } catch {\n return null;\n }\n }).filter(Boolean) as HookItem[];\n\n return {\n hasWork: mailItems.length > 0,\n urgentCount: mailItems.filter((i) => i.priority === 'urgent').length,\n items: mailItems,\n };\n }\n }\n\n return { hasWork: false, urgentCount: 0, items: [] };\n }\n\n // Filter out expired items\n const now = new Date();\n const activeItems = hook.items.filter((item) => {\n if (item.expiresAt) {\n return new Date(item.expiresAt) > now;\n }\n return true;\n });\n\n // Sort by priority: urgent > high > normal > low\n const priorityOrder = { urgent: 0, high: 1, normal: 2, low: 3 };\n activeItems.sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]);\n\n return {\n hasWork: activeItems.length > 0,\n urgentCount: activeItems.filter((i) => i.priority === 'urgent').length,\n items: activeItems,\n };\n}\n\n/**\n * Pop the next work item from hook (after execution)\n */\nexport function popFromHook(agentId: string, itemId: string): boolean {\n const hook = getHook(agentId);\n if (!hook) return false;\n\n const index = hook.items.findIndex((i) => i.id === itemId);\n if (index === -1) return false;\n\n hook.items.splice(index, 1);\n hook.lastChecked = new Date().toISOString();\n writeFileSync(getHookFile(agentId), JSON.stringify(hook, null, 2));\n\n return true;\n}\n\n/**\n * Clear all items from hook\n */\nexport function clearHook(agentId: string): void {\n const hook = getHook(agentId);\n if (!hook) return;\n\n hook.items = [];\n hook.lastChecked = new Date().toISOString();\n writeFileSync(getHookFile(agentId), JSON.stringify(hook, null, 2));\n}\n\n/**\n * Reorder hook items by providing a new order of item IDs\n * Used for manual queue management from dashboard\n */\nexport function reorderHookItems(agentId: string, orderedItemIds: string[]): boolean {\n const hook = getHook(agentId);\n if (!hook) return false;\n\n // Validate that all provided IDs exist in the hook\n const existingIds = new Set(hook.items.map((item) => item.id));\n const providedIds = new Set(orderedItemIds);\n\n // Check if all provided IDs exist\n for (const id of orderedItemIds) {\n if (!existingIds.has(id)) {\n console.error(`[hooks] Cannot reorder: item ${id} not found in hook`);\n return false;\n }\n }\n\n // Check if all existing IDs are provided\n if (existingIds.size !== providedIds.size) {\n console.error(`[hooks] Cannot reorder: mismatch in item count (existing: ${existingIds.size}, provided: ${providedIds.size})`);\n return false;\n }\n\n // Build a map for quick lookup\n const itemMap = new Map(hook.items.map((item) => [item.id, item]));\n\n // Reorder items based on provided IDs\n hook.items = orderedItemIds.map((id) => itemMap.get(id)!);\n\n // Write back to file\n writeFileSync(getHookFile(agentId), JSON.stringify(hook, null, 2));\n\n return true;\n}\n\n/**\n * Send a message to an agent's mailbox\n */\nexport function sendMail(\n toAgentId: string,\n from: string,\n message: string,\n priority: HookItem['priority'] = 'normal'\n): void {\n initHook(toAgentId);\n const mailDir = getMailDir(toAgentId);\n\n const mailItem: HookItem = {\n id: `mail-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,\n type: 'message',\n priority,\n source: from,\n payload: { message },\n createdAt: new Date().toISOString(),\n };\n\n writeFileSync(\n join(mailDir, `${mailItem.id}.json`),\n JSON.stringify(mailItem, null, 2)\n );\n}\n\n/**\n * Get and clear mail for an agent\n */\nexport function collectMail(agentId: string): HookItem[] {\n const mailDir = getMailDir(agentId);\n if (!existsSync(mailDir)) return [];\n\n const mails: HookItem[] = [];\n const files = readdirSync(mailDir).filter((f) => f.endsWith('.json'));\n\n for (const file of files) {\n const filePath = join(mailDir, file);\n try {\n const content = readFileSync(filePath, 'utf-8');\n mails.push(JSON.parse(content));\n unlinkSync(filePath); // Remove after reading\n } catch {\n // Skip invalid mail\n }\n }\n\n return mails;\n}\n\n/**\n * Generate Fixed Point prompt for agent startup\n */\nexport function generateFixedPointPrompt(agentId: string): string | null {\n const { hasWork, urgentCount, items } = checkHook(agentId);\n\n if (!hasWork) return null;\n\n const lines: string[] = [\n '# FPP: Work Found on Your Hook',\n '',\n '> \"Any runnable action is a fixed point and must resolve before the system can rest.\"',\n '',\n ];\n\n if (urgentCount > 0) {\n lines.push(`⚠️ **${urgentCount} URGENT item(s) require immediate attention**`);\n lines.push('');\n }\n\n lines.push(`## Pending Work Items (${items.length})`);\n lines.push('');\n\n for (const item of items) {\n const priorityEmoji = {\n urgent: '🔴',\n high: '🟠',\n normal: '🟢',\n low: '⚪',\n }[item.priority];\n\n lines.push(`### ${priorityEmoji} ${item.type.toUpperCase()}: ${item.id}`);\n lines.push(`- Source: ${item.source}`);\n lines.push(`- Created: ${item.createdAt}`);\n\n if (item.payload.issueId) {\n lines.push(`- Issue: ${item.payload.issueId}`);\n }\n if (item.payload.message) {\n lines.push(`- Message: ${item.payload.message}`);\n }\n if (item.payload.action) {\n lines.push(`- Action: ${item.payload.action}`);\n }\n lines.push('');\n }\n\n lines.push('---');\n lines.push('');\n lines.push('Execute these items in priority order. Use `bd hook pop <id>` after completing each item.');\n\n return lines.join('\\n');\n}\n","/**\n * Work Type Registry\n *\n * Central registry of all work type IDs used for model routing.\n * Each work type represents a specific context where AI agents operate,\n * allowing fine-grained control over which models handle which tasks.\n */\n\n/**\n * Metadata for each work type\n */\nexport interface WorkTypeMetadata {\n /** Broad category this work type belongs to */\n category: 'issue-agent' | 'specialist' | 'subagent' | 'convoy' | 'pre-work' | 'cli';\n /** Optional phase within the category (e.g., for issue-agent phases) */\n phase?: string;\n /** Human-readable description */\n description: string;\n}\n\n/**\n * Complete registry of all 23 work types with metadata\n */\nexport const WORK_TYPES = {\n // Issue agent phases (6)\n 'issue-agent:exploration': {\n phase: 'exploration',\n category: 'issue-agent',\n description: 'Exploring codebase and understanding requirements',\n },\n 'issue-agent:implementation': {\n phase: 'implementation',\n category: 'issue-agent',\n description: 'Writing code to implement features or fixes',\n },\n 'issue-agent:testing': {\n phase: 'testing',\n category: 'issue-agent',\n description: 'Running tests and verifying functionality',\n },\n 'issue-agent:documentation': {\n phase: 'documentation',\n category: 'issue-agent',\n description: 'Writing documentation and updating docs',\n },\n 'issue-agent:review-response': {\n phase: 'review-response',\n category: 'issue-agent',\n description: 'Responding to code review feedback',\n },\n\n // Specialist agents (3)\n 'specialist-review-agent': {\n category: 'specialist',\n description: 'Comprehensive code review specialist',\n },\n 'specialist-test-agent': {\n category: 'specialist',\n description: 'Test generation and verification specialist',\n },\n 'specialist-merge-agent': {\n category: 'specialist',\n description: 'Merge request finalization specialist',\n },\n\n // Subagents (4)\n 'subagent:explore': {\n category: 'subagent',\n description: 'Fast codebase exploration subagent',\n },\n 'subagent:plan': {\n category: 'subagent',\n description: 'Implementation planning subagent',\n },\n 'subagent:bash': {\n category: 'subagent',\n description: 'Command execution specialist subagent',\n },\n 'subagent:general-purpose': {\n category: 'subagent',\n description: 'General-purpose task subagent',\n },\n\n // Convoy members (4)\n 'convoy:security-reviewer': {\n category: 'convoy',\n description: 'Security-focused code reviewer in convoy',\n },\n 'convoy:performance-reviewer': {\n category: 'convoy',\n description: 'Performance-focused code reviewer in convoy',\n },\n 'convoy:correctness-reviewer': {\n category: 'convoy',\n description: 'Correctness-focused code reviewer in convoy',\n },\n 'convoy:synthesis-agent': {\n category: 'convoy',\n description: 'Synthesizes findings from convoy reviewers',\n },\n\n // Pre-work agents (5)\n 'planning-agent': {\n category: 'pre-work',\n description: 'Interactive planning and discovery agent',\n },\n 'prd-agent': {\n category: 'pre-work',\n description: 'Generates Product Requirement Documents',\n },\n 'decomposition-agent': {\n category: 'pre-work',\n description: 'Breaks down work into tasks',\n },\n 'triage-agent': {\n category: 'pre-work',\n description: 'Prioritizes and triages issues',\n },\n\n // CLI contexts (2)\n 'cli:interactive': {\n category: 'cli',\n description: 'Interactive CLI session',\n },\n 'cli:quick-command': {\n category: 'cli',\n description: 'Quick one-off CLI commands',\n },\n} as const;\n\n/**\n * Type-safe work type IDs\n */\nexport type WorkTypeId = keyof typeof WORK_TYPES;\n\n/**\n * Valid work type categories\n */\nexport type WorkTypeCategory = WorkTypeMetadata['category'];\n\n/**\n * Get all work type IDs\n */\nexport function getAllWorkTypes(): WorkTypeId[] {\n return Object.keys(WORK_TYPES) as WorkTypeId[];\n}\n\n/**\n * Get all work types in a specific category\n */\nexport function getWorkTypesByCategory(category: WorkTypeCategory): WorkTypeId[] {\n return getAllWorkTypes().filter((id) => WORK_TYPES[id].category === category);\n}\n\n/**\n * Check if a string is a valid work type ID\n */\nexport function isValidWorkType(id: string): id is WorkTypeId {\n return id in WORK_TYPES;\n}\n\n/**\n * Get metadata for a work type\n */\nexport function getWorkTypeMetadata(id: WorkTypeId): WorkTypeMetadata {\n return WORK_TYPES[id];\n}\n\n/**\n * Get human-readable name for a work type\n */\nexport function getWorkTypeName(id: WorkTypeId): string {\n const metadata = WORK_TYPES[id];\n if ('phase' in metadata && metadata.phase) {\n return `${metadata.category} (${metadata.phase})`;\n }\n return id;\n}\n\n/**\n * Validate work type ID and throw if invalid\n */\nexport function validateWorkType(id: string): asserts id is WorkTypeId {\n if (!isValidWorkType(id)) {\n throw new Error(\n `Invalid work type ID: ${id}. Valid types: ${getAllWorkTypes().join(', ')}`\n );\n }\n}\n","/**\n * Model Fallback Strategy\n *\n * When a non-Anthropic model is selected but its API key is missing,\n * automatically fallback to an equivalent Anthropic model. This ensures\n * Panopticon always works even without configuring external providers.\n */\n\nimport { ModelId, AnthropicModel, OpenAIModel, GoogleModel, ZAIModel } from './settings.js';\n\n/**\n * AI model provider types\n */\nexport type ModelProvider = 'anthropic' | 'openai' | 'google' | 'zai' | 'kimi';\n\n/**\n * Map of model ID to provider\n */\nconst MODEL_PROVIDERS: Record<ModelId, ModelProvider> = {\n // Anthropic models\n 'claude-opus-4-6': 'anthropic',\n 'claude-sonnet-4-6': 'anthropic',\n 'claude-sonnet-4-5': 'anthropic',\n 'claude-haiku-4-5': 'anthropic',\n\n // OpenAI models\n 'gpt-5.2-codex': 'openai',\n 'o3-deep-research': 'openai',\n 'gpt-4o': 'openai',\n 'gpt-4o-mini': 'openai',\n\n // Google models\n 'gemini-3-pro-preview': 'google',\n 'gemini-3-flash-preview': 'google',\n 'gemini-2.5-pro': 'google',\n 'gemini-2.5-flash': 'google',\n\n // Z.AI models\n 'glm-4.7': 'zai',\n 'glm-4.7-flash': 'zai',\n\n // Kimi models\n 'kimi-k2': 'kimi',\n 'kimi-k2.5': 'kimi',\n};\n\n/**\n * Fallback mapping: non-Anthropic model → Anthropic equivalent\n *\n * Mapping strategy:\n * - Premium models (GPT-5.2, O3, Gemini Pro) → Sonnet 4.6 (good balance)\n * - Economy models (GPT-4o-mini, Gemini Flash, GLM Flash) → Haiku 4.5\n * - GPT-4o → Sonnet 4.6 (similar tier)\n * - GLM-4.7 → Haiku 4.5 (economy tier)\n *\n * Note: We intentionally avoid Opus 4.6 as default fallback to keep costs reasonable.\n * Users who want Opus can explicitly set it in their config.\n */\nconst FALLBACK_MAP: Record<string, AnthropicModel> = {\n // OpenAI → Anthropic\n 'gpt-5.2-codex': 'claude-sonnet-4-6', // Premium code model → Sonnet\n 'o3-deep-research': 'claude-sonnet-4-6', // Premium research model → Sonnet\n 'gpt-4o': 'claude-sonnet-4-6', // Flagship model → Sonnet\n 'gpt-4o-mini': 'claude-haiku-4-5', // Economy model → Haiku\n\n // Google → Anthropic\n 'gemini-3-pro-preview': 'claude-sonnet-4-6', // Premium model → Sonnet\n 'gemini-3-flash-preview': 'claude-haiku-4-5', // Fast model → Haiku\n\n // Z.AI → Anthropic\n 'glm-4.7': 'claude-haiku-4-5', // Standard model → Haiku\n 'glm-4.7-flash': 'claude-haiku-4-5', // Fast model → Haiku\n\n // Kimi → Anthropic\n 'kimi-k2': 'claude-sonnet-4-6', // Good balance model → Sonnet\n 'kimi-k2.5': 'claude-sonnet-4-6', // Premium model → Sonnet\n};\n\n/**\n * Default fallback when model not in explicit mapping\n */\nconst DEFAULT_FALLBACK: AnthropicModel = 'claude-sonnet-4-6';\n\n/**\n * Get the provider for a model ID\n */\nexport function getModelProvider(modelId: ModelId): ModelProvider {\n return MODEL_PROVIDERS[modelId];\n}\n\n/**\n * Check if a model requires an external API key\n */\nexport function requiresExternalKey(modelId: ModelId): boolean {\n return getModelProvider(modelId) !== 'anthropic';\n}\n\n/**\n * Get all models for a specific provider\n */\nexport function getModelsByProvider(provider: ModelProvider): ModelId[] {\n return Object.entries(MODEL_PROVIDERS)\n .filter(([_, p]) => p === provider)\n .map(([modelId]) => modelId as ModelId);\n}\n\n/**\n * Check if a provider is enabled (has API key configured)\n *\n * @param provider Provider to check\n * @param enabledProviders Set of enabled provider names\n * @returns true if provider is enabled or is Anthropic (always enabled)\n */\nexport function isProviderEnabled(\n provider: ModelProvider,\n enabledProviders: Set<ModelProvider>\n): boolean {\n // Anthropic is always enabled (required)\n if (provider === 'anthropic') return true;\n\n return enabledProviders.has(provider);\n}\n\n/**\n * Apply fallback strategy for a model\n *\n * If the model's provider is disabled (no API key), return an Anthropic equivalent.\n * Otherwise, return the original model.\n *\n * @param modelId Requested model\n * @param enabledProviders Set of enabled provider names\n * @returns Original model if provider enabled, otherwise Anthropic fallback\n */\nexport function applyFallback(\n modelId: ModelId,\n enabledProviders: Set<ModelProvider>\n): ModelId {\n const provider = getModelProvider(modelId);\n\n // If provider is enabled, use the requested model\n if (isProviderEnabled(provider, enabledProviders)) {\n return modelId;\n }\n\n // Provider disabled - lookup fallback\n const fallback = FALLBACK_MAP[modelId] || DEFAULT_FALLBACK;\n\n // Log fallback for visibility\n console.warn(\n `Model ${modelId} requires ${provider} API key - falling back to ${fallback}`\n );\n\n return fallback;\n}\n\n/**\n * Get the fallback model for a given model (useful for preview/display)\n *\n * @param modelId Model to get fallback for\n * @returns Anthropic fallback model\n */\nexport function getFallbackModel(modelId: ModelId): AnthropicModel {\n // Anthropic models fallback to themselves\n if (getModelProvider(modelId) === 'anthropic') {\n return modelId as AnthropicModel;\n }\n\n return FALLBACK_MAP[modelId] || DEFAULT_FALLBACK;\n}\n\n/**\n * Detect enabled providers from API keys configuration\n *\n * @param apiKeys API keys object from settings\n * @returns Set of enabled provider names\n */\nexport function detectEnabledProviders(apiKeys: {\n openai?: string;\n google?: string;\n zai?: string;\n kimi?: string;\n}): Set<ModelProvider> {\n const enabled = new Set<ModelProvider>(['anthropic']); // Always enabled\n\n // Check each optional provider\n if (apiKeys.openai && apiKeys.openai.trim()) {\n enabled.add('openai');\n }\n if (apiKeys.google && apiKeys.google.trim()) {\n enabled.add('google');\n }\n if (apiKeys.zai && apiKeys.zai.trim()) {\n enabled.add('zai');\n }\n if (apiKeys.kimi && apiKeys.kimi.trim()) {\n enabled.add('kimi');\n }\n\n return enabled;\n}\n\n/**\n * Filter a list of models to only those available with enabled providers\n *\n * @param models List of models to filter\n * @param enabledProviders Set of enabled provider names\n * @returns Filtered list of models\n */\nexport function filterAvailableModels(\n models: ModelId[],\n enabledProviders: Set<ModelProvider>\n): ModelId[] {\n return models.filter((modelId) => {\n const provider = getModelProvider(modelId);\n return isProviderEnabled(provider, enabledProviders);\n });\n}\n\n/**\n * Get all available models (across all enabled providers)\n *\n * @param enabledProviders Set of enabled provider names\n * @returns List of available model IDs\n */\nexport function getAvailableModels(enabledProviders: Set<ModelProvider>): ModelId[] {\n return Object.keys(MODEL_PROVIDERS).filter((modelId) => {\n const provider = MODEL_PROVIDERS[modelId as ModelId];\n return isProviderEnabled(provider, enabledProviders);\n }) as ModelId[];\n}\n","/**\n * Smart Model Selector\n *\n * Intelligently selects the best model for each work type based on:\n * 1. What models the user has enabled (API keys configured)\n * 2. Capability scores for the required skills\n *\n * This is an opinionated system - always pick the BEST model for each job.\n * Users control cost by which providers they enable, not a sensitivity slider.\n */\n\nimport { ModelId } from './settings.js';\nimport { WorkTypeId } from './work-types.js';\nimport {\n MODEL_CAPABILITIES,\n SkillDimension,\n ModelCapability,\n getModelCapability,\n} from './model-capabilities.js';\n\n/**\n * Skill requirements for a work type\n * Higher weight = more important for this task\n */\nexport interface SkillRequirement {\n skill: SkillDimension;\n weight: number; // 0-1, how important this skill is\n}\n\n/**\n * Work type to skill mapping\n * Defines what skills each work type needs\n */\nexport const WORK_TYPE_REQUIREMENTS: Record<WorkTypeId, SkillRequirement[]> = {\n // ═══════════════════════════════════════════════════════════════════════════\n // ISSUE AGENT PHASES\n // ═══════════════════════════════════════════════════════════════════════════\n\n 'issue-agent:exploration': [\n { skill: 'speed', weight: 0.4 }, // Need fast exploration\n { skill: 'context-length', weight: 0.3 }, // Large codebases\n { skill: 'synthesis', weight: 0.3 }, // Understanding structure\n ],\n\n 'issue-agent:implementation': [\n { skill: 'code-generation', weight: 0.6 }, // Primary skill\n { skill: 'debugging', weight: 0.2 }, // Avoiding bugs\n { skill: 'testing', weight: 0.2 }, // Writing testable code\n ],\n\n 'issue-agent:testing': [\n { skill: 'testing', weight: 0.5 }, // Primary skill\n { skill: 'code-generation', weight: 0.3 }, // Writing test code\n { skill: 'debugging', weight: 0.2 }, // Finding edge cases\n ],\n\n 'issue-agent:documentation': [\n { skill: 'documentation', weight: 0.6 }, // Primary skill\n { skill: 'synthesis', weight: 0.3 }, // Summarizing\n { skill: 'speed', weight: 0.1 }, // Fast iteration\n ],\n\n 'issue-agent:review-response': [\n { skill: 'code-review', weight: 0.4 }, // Understanding feedback\n { skill: 'code-generation', weight: 0.3 }, // Making fixes\n { skill: 'debugging', weight: 0.3 }, // Finding issues\n ],\n\n // ═══════════════════════════════════════════════════════════════════════════\n // SPECIALIST AGENTS\n // ═══════════════════════════════════════════════════════════════════════════\n\n 'specialist-review-agent': [\n { skill: 'code-review', weight: 0.5 }, // Primary skill\n { skill: 'security', weight: 0.25 }, // Security awareness\n { skill: 'performance', weight: 0.25 }, // Performance awareness\n ],\n\n 'specialist-test-agent': [\n { skill: 'testing', weight: 0.5 }, // Primary skill\n { skill: 'code-generation', weight: 0.3 }, // Writing tests\n { skill: 'debugging', weight: 0.2 }, // Finding issues\n ],\n\n 'specialist-merge-agent': [\n { skill: 'code-review', weight: 0.4 }, // Understanding conflicts\n { skill: 'synthesis', weight: 0.3 }, // Merging changes\n { skill: 'debugging', weight: 0.3 }, // Resolving issues\n ],\n\n // ═══════════════════════════════════════════════════════════════════════════\n // SUBAGENTS\n // ═══════════════════════════════════════════════════════════════════════════\n\n 'subagent:explore': [\n { skill: 'speed', weight: 0.5 }, // Need speed\n { skill: 'context-length', weight: 0.3 }, // Large scope\n { skill: 'synthesis', weight: 0.2 }, // Quick understanding\n ],\n\n 'subagent:plan': [\n { skill: 'planning', weight: 0.5 }, // Primary skill\n { skill: 'synthesis', weight: 0.3 }, // Combining info\n { skill: 'speed', weight: 0.2 }, // Quick iteration\n ],\n\n 'subagent:bash': [\n { skill: 'speed', weight: 0.6 }, // Fast execution\n { skill: 'code-generation', weight: 0.3 }, // Command generation\n { skill: 'debugging', weight: 0.1 }, // Error handling\n ],\n\n 'subagent:general-purpose': [\n { skill: 'speed', weight: 0.3 }, // Balanced\n { skill: 'synthesis', weight: 0.3 }, // General understanding\n { skill: 'code-generation', weight: 0.4 }, // General tasks\n ],\n\n // ═══════════════════════════════════════════════════════════════════════════\n // CONVOY MEMBERS\n // ═══════════════════════════════════════════════════════════════════════════\n\n 'convoy:security-reviewer': [\n { skill: 'security', weight: 0.7 }, // PRIMARY - never compromise\n { skill: 'code-review', weight: 0.2 }, // Code understanding\n { skill: 'debugging', weight: 0.1 }, // Finding vulnerabilities\n ],\n\n 'convoy:performance-reviewer': [\n { skill: 'performance', weight: 0.6 }, // Primary skill\n { skill: 'code-review', weight: 0.3 }, // Code understanding\n { skill: 'debugging', weight: 0.1 }, // Finding bottlenecks\n ],\n\n 'convoy:correctness-reviewer': [\n { skill: 'code-review', weight: 0.4 }, // Primary skill\n { skill: 'debugging', weight: 0.4 }, // Finding bugs\n { skill: 'testing', weight: 0.2 }, // Test coverage\n ],\n\n 'convoy:synthesis-agent': [\n { skill: 'synthesis', weight: 0.6 }, // Primary skill\n { skill: 'documentation', weight: 0.2 }, // Clear writing\n { skill: 'planning', weight: 0.2 }, // Organizing findings\n ],\n\n // ═══════════════════════════════════════════════════════════════════════════\n // PRE-WORK AGENTS\n // ═══════════════════════════════════════════════════════════════════════════\n\n 'planning-agent': [\n { skill: 'planning', weight: 0.5 }, // Primary skill\n { skill: 'synthesis', weight: 0.3 }, // Combining requirements\n { skill: 'documentation', weight: 0.2 }, // Documenting decisions\n ],\n\n 'prd-agent': [\n { skill: 'documentation', weight: 0.5 }, // Primary skill\n { skill: 'planning', weight: 0.3 }, // Structure\n { skill: 'synthesis', weight: 0.2 }, // Combining requirements\n ],\n\n 'decomposition-agent': [\n { skill: 'planning', weight: 0.5 }, // Primary skill\n { skill: 'synthesis', weight: 0.3 }, // Breaking down\n { skill: 'documentation', weight: 0.2 }, // Clear tasks\n ],\n\n 'triage-agent': [\n { skill: 'speed', weight: 0.4 }, // Quick decisions\n { skill: 'synthesis', weight: 0.3 }, // Understanding scope\n { skill: 'planning', weight: 0.3 }, // Prioritization\n ],\n\n // ═══════════════════════════════════════════════════════════════════════════\n // CLI CONTEXTS\n // ═══════════════════════════════════════════════════════════════════════════\n\n 'cli:interactive': [\n { skill: 'speed', weight: 0.4 }, // Responsive\n { skill: 'synthesis', weight: 0.3 }, // Understanding context\n { skill: 'code-generation', weight: 0.3 }, // Quick code\n ],\n\n 'cli:quick-command': [\n { skill: 'speed', weight: 0.7 }, // Must be fast\n { skill: 'code-generation', weight: 0.2 }, // Simple generation\n { skill: 'synthesis', weight: 0.1 }, // Quick understanding\n ],\n};\n\n/**\n * Selection result with explanation\n */\nexport interface ModelSelectionResult {\n /** Selected model */\n model: ModelId;\n /** Score that led to selection (0-100) */\n score: number;\n /** Why this model was selected */\n reason: string;\n /** All candidates that were considered */\n candidates: Array<{\n model: ModelId;\n score: number;\n available: boolean;\n }>;\n}\n\n/**\n * Selection options\n */\nexport interface SelectionOptions {\n /**\n * Minimum capability threshold (0-100)\n * Models below this score are excluded\n * Default: 50\n */\n minCapability?: number;\n\n /**\n * Force a specific model (bypass selection)\n */\n forceModel?: ModelId;\n}\n\n/**\n * Calculate weighted skill score for a model given requirements\n */\nfunction calculateSkillScore(\n model: ModelId,\n requirements: SkillRequirement[]\n): number {\n const cap = getModelCapability(model);\n let totalScore = 0;\n let totalWeight = 0;\n\n for (const req of requirements) {\n totalScore += cap.skills[req.skill] * req.weight;\n totalWeight += req.weight;\n }\n\n return totalWeight > 0 ? totalScore / totalWeight : 0;\n}\n\n/**\n * Calculate final selection score - pure capability based\n *\n * We're opinionated: always pick the BEST model for the job.\n * Users control cost by which providers they enable.\n */\nfunction calculateSelectionScore(\n model: ModelId,\n skillScore: number\n): number {\n // Pure quality - just return the skill score\n // Cost control is done by which providers the user enables\n return skillScore;\n}\n\n/**\n * Select the best model for a work type from available models\n */\nexport function selectModel(\n workType: WorkTypeId,\n availableModels: ModelId[],\n options: SelectionOptions = {}\n): ModelSelectionResult {\n const { minCapability = 50, forceModel } = options;\n\n // Force model if specified and available\n if (forceModel) {\n if (availableModels.includes(forceModel)) {\n return {\n model: forceModel,\n score: 100,\n reason: `Forced selection: ${forceModel}`,\n candidates: [{ model: forceModel, score: 100, available: true }],\n };\n }\n // Fall through to normal selection if forced model not available\n }\n\n const requirements = WORK_TYPE_REQUIREMENTS[workType];\n const allModels = Object.keys(MODEL_CAPABILITIES) as ModelId[];\n\n // Calculate scores for all models - pure capability based\n // Users control cost by which providers they enable\n const candidates = allModels.map((model) => {\n const skillScore = calculateSkillScore(model, requirements);\n const selectionScore = calculateSelectionScore(model, skillScore);\n const available = availableModels.includes(model);\n\n return {\n model,\n skillScore,\n score: selectionScore,\n available,\n };\n });\n\n // Filter to available models with minimum capability\n const eligible = candidates.filter(\n (c) => c.available && c.skillScore >= minCapability\n );\n\n // Sort by selection score (descending)\n eligible.sort((a, b) => b.score - a.score);\n\n // Fallback: if no eligible models, use best available regardless of threshold\n if (eligible.length === 0) {\n const fallback = candidates\n .filter((c) => c.available)\n .sort((a, b) => b.score - a.score)[0];\n\n if (!fallback) {\n // No available models at all - use Anthropic default\n return {\n model: 'claude-sonnet-4-6',\n score: 0,\n reason: 'No models available, falling back to default',\n candidates: candidates.map((c) => ({\n model: c.model,\n score: c.score,\n available: c.available,\n })),\n };\n }\n\n return {\n model: fallback.model,\n score: fallback.score,\n reason: `Best available (below min threshold): ${fallback.model}`,\n candidates: candidates.map((c) => ({\n model: c.model,\n score: c.score,\n available: c.available,\n })),\n };\n }\n\n const selected = eligible[0];\n const cap = getModelCapability(selected.model);\n\n // Generate reason\n const topSkills = requirements\n .sort((a, b) => b.weight - a.weight)\n .slice(0, 2)\n .map((r) => r.skill);\n\n const reason = `Best for ${workType}: ${cap.displayName} (${topSkills.join(', ')}: ${Math.round(selected.skillScore)}, cost: $${cap.costPer1MTokens}/1M)`;\n\n return {\n model: selected.model,\n score: selected.score,\n reason,\n candidates: candidates.map((c) => ({\n model: c.model,\n score: c.score,\n available: c.available,\n })),\n };\n}\n\n/**\n * Select models for all work types at once\n */\nexport function selectAllModels(\n availableModels: ModelId[],\n options: SelectionOptions = {}\n): Record<WorkTypeId, ModelSelectionResult> {\n const workTypes = Object.keys(WORK_TYPE_REQUIREMENTS) as WorkTypeId[];\n const results: Record<WorkTypeId, ModelSelectionResult> = {} as Record<\n WorkTypeId,\n ModelSelectionResult\n >;\n\n for (const workType of workTypes) {\n results[workType] = selectModel(workType, availableModels, options);\n }\n\n return results;\n}\n\n/**\n * Get simple model mapping (for backward compatibility with presets)\n */\nexport function getSimpleModelMapping(\n availableModels: ModelId[],\n options: SelectionOptions = {}\n): Record<WorkTypeId, ModelId> {\n const results = selectAllModels(availableModels, options);\n const mapping: Record<WorkTypeId, ModelId> = {} as Record<WorkTypeId, ModelId>;\n\n for (const [workType, result] of Object.entries(results)) {\n mapping[workType as WorkTypeId] = result.model;\n }\n\n return mapping;\n}\n\n/**\n * Pretty print selection results for debugging\n */\nexport function formatSelectionResults(\n results: Record<WorkTypeId, ModelSelectionResult>\n): string {\n const lines: string[] = ['Model Selection Results', '='.repeat(60)];\n\n for (const [workType, result] of Object.entries(results)) {\n lines.push(`${workType}: ${result.model}`);\n lines.push(` Reason: ${result.reason}`);\n lines.push('');\n }\n\n return lines.join('\\n');\n}\n\n","/**\n * Work Type Router\n *\n * Routes work types to appropriate models using smart (capability-based) selection.\n * Picks the best model for each job based on:\n * 1. What models the user has enabled (API keys configured)\n * 2. Capability scores for the required skills\n * 3. Cost optimization (configurable)\n */\n\nimport { WorkTypeId, isValidWorkType, validateWorkType, getAllWorkTypes } from './work-types.js';\nimport { ModelId } from './settings.js';\nimport { applyFallback, ModelProvider, getModelsByProvider } from './model-fallback.js';\nimport { loadConfig, NormalizedConfig } from './config-yaml.js';\nimport { selectModel, ModelSelectionResult } from './smart-model-selector.js';\n\n// Re-export WorkTypeId for backward compatibility\nexport type { WorkTypeId } from './work-types.js';\n\n/**\n * Model resolution result with debugging info\n */\nexport interface ModelResolutionResult {\n /** Final model to use */\n model: ModelId;\n /** Work type that was resolved */\n workType: WorkTypeId;\n /** How the model was determined */\n source: 'override' | 'smart';\n /** Whether fallback was applied (provider disabled) */\n usedFallback: boolean;\n /** Original model before fallback */\n originalModel?: ModelId;\n /** Smart selection details */\n selection: {\n score: number;\n reason: string;\n };\n}\n\n/**\n * Work Type Router\n *\n * Main router class for resolving work types to models.\n */\nexport class WorkTypeRouter {\n private config: NormalizedConfig;\n private availableModels: ModelId[] | null = null;\n\n constructor(config?: NormalizedConfig) {\n this.config = config || loadConfig().config;\n }\n\n /**\n * Get list of available models based on enabled providers\n */\n private getAvailableModels(): ModelId[] {\n if (this.availableModels) {\n return this.availableModels;\n }\n\n const available: ModelId[] = [];\n for (const provider of this.config.enabledProviders) {\n available.push(...getModelsByProvider(provider));\n }\n this.availableModels = available;\n return available;\n }\n\n /**\n * Get model for a specific work type\n *\n * Resolution order:\n * 1. Per-project/global override (if configured)\n * 2. Smart selection (capability-based)\n */\n getModel(workTypeId: WorkTypeId): ModelResolutionResult {\n validateWorkType(workTypeId);\n\n let model: ModelId;\n let source: 'override' | 'smart';\n let originalModel: ModelId | undefined;\n let selection: { score: number; reason: string };\n\n // Check for override first\n if (this.config.overrides[workTypeId]) {\n model = this.config.overrides[workTypeId]!;\n source = 'override';\n selection = {\n score: 100,\n reason: `Explicit override: ${model}`,\n };\n } else {\n // Use smart (capability-based) selection\n const availableModels = this.getAvailableModels();\n const result = selectModel(workTypeId, availableModels);\n model = result.model;\n source = 'smart';\n selection = {\n score: result.score,\n reason: result.reason,\n };\n }\n\n // Apply fallback if provider is disabled\n originalModel = model;\n model = applyFallback(model, this.config.enabledProviders);\n\n return {\n model,\n workType: workTypeId,\n source,\n usedFallback: model !== originalModel,\n originalModel: model !== originalModel ? originalModel : undefined,\n selection,\n };\n }\n\n /**\n * Get just the model ID for a work type (convenience method)\n */\n getModelId(workTypeId: WorkTypeId): ModelId {\n return this.getModel(workTypeId).model;\n }\n\n /**\n * Check if a work type has an override configured\n */\n hasOverride(workTypeId: WorkTypeId): boolean {\n return workTypeId in this.config.overrides;\n }\n\n /**\n * Get the set of enabled providers\n */\n getEnabledProviders(): Set<ModelProvider> {\n return this.config.enabledProviders;\n }\n\n /**\n * Get all configured overrides\n */\n getOverrides(): Partial<Record<WorkTypeId, ModelId>> {\n return { ...this.config.overrides };\n }\n\n /**\n * Get API keys configuration\n */\n getApiKeys(): { openai?: string; google?: string; zai?: string; kimi?: string } {\n return { ...this.config.apiKeys };\n }\n\n /**\n * Get Gemini thinking level\n */\n getGeminiThinkingLevel(): 1 | 2 | 3 | 4 {\n return this.config.geminiThinkingLevel;\n }\n\n /**\n * Reload configuration from disk\n */\n reloadConfig(): void {\n this.config = loadConfig().config;\n this.availableModels = null; // Clear cache\n }\n\n /**\n * Get debug information about current configuration\n */\n getDebugInfo(): {\n enabledProviders: string[];\n availableModelCount: number;\n overrideCount: number;\n hasApiKeys: {\n openai: boolean;\n google: boolean;\n zai: boolean;\n kimi: boolean;\n };\n } {\n return {\n enabledProviders: Array.from(this.config.enabledProviders),\n availableModelCount: this.getAvailableModels().length,\n overrideCount: Object.keys(this.config.overrides).length,\n hasApiKeys: {\n openai: !!this.config.apiKeys.openai,\n google: !!this.config.apiKeys.google,\n zai: !!this.config.apiKeys.zai,\n kimi: !!this.config.apiKeys.kimi,\n },\n };\n }\n}\n\n/**\n * Global router instance\n */\nlet globalRouter: WorkTypeRouter | null = null;\n\n/**\n * Get the global work type router instance\n */\nexport function getGlobalRouter(): WorkTypeRouter {\n if (!globalRouter) {\n globalRouter = new WorkTypeRouter();\n }\n return globalRouter;\n}\n\n/**\n * Reset the global router (useful for testing)\n */\nexport function resetGlobalRouter(): void {\n globalRouter = null;\n}\n\n/**\n * Reload global router configuration\n */\nexport function reloadGlobalRouter(): void {\n if (globalRouter) {\n globalRouter.reloadConfig();\n }\n}\n\n/**\n * Get model using the global router\n */\nexport function getModel(workTypeId: WorkTypeId): ModelResolutionResult {\n return getGlobalRouter().getModel(workTypeId);\n}\n\n/**\n * Get just the model ID using the global router\n */\nexport function getModelId(workTypeId: WorkTypeId): ModelId {\n return getGlobalRouter().getModelId(workTypeId);\n}\n\n/**\n * Check for override using the global router\n */\nexport function hasOverride(workTypeId: WorkTypeId): boolean {\n return getGlobalRouter().hasOverride(workTypeId);\n}\n\n/**\n * Get debug info using the global router\n */\nexport function getDebugInfo() {\n return getGlobalRouter().getDebugInfo();\n}\n"],"mappings":"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","names":["existsSync","mkdirSync","writeFileSync","unlinkSync","join"]}
1
+ {"version":3,"sources":["../src/lib/hooks.ts","../src/lib/work-types.ts","../src/lib/model-fallback.ts","../src/lib/smart-model-selector.ts","../src/lib/work-type-router.ts"],"sourcesContent":["/**\n * FPP Hooks System - Fixed Point Principle\n *\n * \"Any runnable action is a fixed point and must resolve before the system can rest.\"\n *\n * Inspired by Doctor Who: a fixed point in time must occur — it cannot be avoided.\n *\n * Hooks are persistent work queues for agents. When an agent starts,\n * it checks its hook for pending work and executes immediately.\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, unlinkSync } from 'fs';\nimport { join } from 'path';\nimport { AGENTS_DIR } from './paths.js';\n\nexport interface HookItem {\n id: string;\n type: 'task' | 'message' | 'notification';\n priority: 'urgent' | 'high' | 'normal' | 'low';\n source: string;\n payload: {\n issueId?: string;\n message?: string;\n action?: string;\n context?: Record<string, any>;\n };\n createdAt: string;\n expiresAt?: string;\n}\n\nexport interface Hook {\n agentId: string;\n items: HookItem[];\n lastChecked?: string;\n}\n\nfunction getHookDir(agentId: string): string {\n return join(AGENTS_DIR, agentId);\n}\n\nfunction getHookFile(agentId: string): string {\n return join(getHookDir(agentId), 'hook.json');\n}\n\nfunction getMailDir(agentId: string): string {\n return join(getHookDir(agentId), 'mail');\n}\n\n/**\n * Initialize hook structure for an agent\n */\nexport function initHook(agentId: string): void {\n const hookDir = getHookDir(agentId);\n const mailDir = getMailDir(agentId);\n\n mkdirSync(hookDir, { recursive: true });\n mkdirSync(mailDir, { recursive: true });\n\n const hookFile = getHookFile(agentId);\n if (!existsSync(hookFile)) {\n const hook: Hook = {\n agentId,\n items: [],\n };\n writeFileSync(hookFile, JSON.stringify(hook, null, 2));\n }\n}\n\n/**\n * Get the hook for an agent\n */\nexport function getHook(agentId: string): Hook | null {\n const hookFile = getHookFile(agentId);\n if (!existsSync(hookFile)) {\n return null;\n }\n\n try {\n const content = readFileSync(hookFile, 'utf-8');\n return JSON.parse(content);\n } catch {\n return null;\n }\n}\n\n/**\n * Add work to an agent's hook (FPP trigger)\n */\nexport function pushToHook(agentId: string, item: Omit<HookItem, 'id' | 'createdAt'>): HookItem {\n initHook(agentId);\n\n const hook = getHook(agentId) || { agentId, items: [] };\n\n const newItem: HookItem = {\n ...item,\n id: `hook-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,\n createdAt: new Date().toISOString(),\n };\n\n hook.items.push(newItem);\n writeFileSync(getHookFile(agentId), JSON.stringify(hook, null, 2));\n\n return newItem;\n}\n\n/**\n * Check if agent has pending work (FPP check)\n */\nexport function checkHook(agentId: string): { hasWork: boolean; urgentCount: number; items: HookItem[] } {\n const hook = getHook(agentId);\n\n if (!hook || hook.items.length === 0) {\n // Also check mail directory for incoming messages\n const mailDir = getMailDir(agentId);\n if (existsSync(mailDir)) {\n const mails = readdirSync(mailDir).filter((f) => f.endsWith('.json'));\n if (mails.length > 0) {\n // Convert mail to hook items\n const mailItems: HookItem[] = mails.map((file) => {\n try {\n const content = readFileSync(join(mailDir, file), 'utf-8');\n return JSON.parse(content);\n } catch {\n return null;\n }\n }).filter(Boolean) as HookItem[];\n\n return {\n hasWork: mailItems.length > 0,\n urgentCount: mailItems.filter((i) => i.priority === 'urgent').length,\n items: mailItems,\n };\n }\n }\n\n return { hasWork: false, urgentCount: 0, items: [] };\n }\n\n // Filter out expired items\n const now = new Date();\n const activeItems = hook.items.filter((item) => {\n if (item.expiresAt) {\n return new Date(item.expiresAt) > now;\n }\n return true;\n });\n\n // Sort by priority: urgent > high > normal > low\n const priorityOrder = { urgent: 0, high: 1, normal: 2, low: 3 };\n activeItems.sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]);\n\n return {\n hasWork: activeItems.length > 0,\n urgentCount: activeItems.filter((i) => i.priority === 'urgent').length,\n items: activeItems,\n };\n}\n\n/**\n * Pop the next work item from hook (after execution)\n */\nexport function popFromHook(agentId: string, itemId: string): boolean {\n const hook = getHook(agentId);\n if (!hook) return false;\n\n const index = hook.items.findIndex((i) => i.id === itemId);\n if (index === -1) return false;\n\n hook.items.splice(index, 1);\n hook.lastChecked = new Date().toISOString();\n writeFileSync(getHookFile(agentId), JSON.stringify(hook, null, 2));\n\n return true;\n}\n\n/**\n * Clear all items from hook\n */\nexport function clearHook(agentId: string): void {\n const hook = getHook(agentId);\n if (!hook) return;\n\n hook.items = [];\n hook.lastChecked = new Date().toISOString();\n writeFileSync(getHookFile(agentId), JSON.stringify(hook, null, 2));\n}\n\n/**\n * Reorder hook items by providing a new order of item IDs\n * Used for manual queue management from dashboard\n */\nexport function reorderHookItems(agentId: string, orderedItemIds: string[]): boolean {\n const hook = getHook(agentId);\n if (!hook) return false;\n\n // Validate that all provided IDs exist in the hook\n const existingIds = new Set(hook.items.map((item) => item.id));\n const providedIds = new Set(orderedItemIds);\n\n // Check if all provided IDs exist\n for (const id of orderedItemIds) {\n if (!existingIds.has(id)) {\n console.error(`[hooks] Cannot reorder: item ${id} not found in hook`);\n return false;\n }\n }\n\n // Check if all existing IDs are provided\n if (existingIds.size !== providedIds.size) {\n console.error(`[hooks] Cannot reorder: mismatch in item count (existing: ${existingIds.size}, provided: ${providedIds.size})`);\n return false;\n }\n\n // Build a map for quick lookup\n const itemMap = new Map(hook.items.map((item) => [item.id, item]));\n\n // Reorder items based on provided IDs\n hook.items = orderedItemIds.map((id) => itemMap.get(id)!);\n\n // Write back to file\n writeFileSync(getHookFile(agentId), JSON.stringify(hook, null, 2));\n\n return true;\n}\n\n/**\n * Send a message to an agent's mailbox\n */\nexport function sendMail(\n toAgentId: string,\n from: string,\n message: string,\n priority: HookItem['priority'] = 'normal'\n): void {\n initHook(toAgentId);\n const mailDir = getMailDir(toAgentId);\n\n const mailItem: HookItem = {\n id: `mail-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,\n type: 'message',\n priority,\n source: from,\n payload: { message },\n createdAt: new Date().toISOString(),\n };\n\n writeFileSync(\n join(mailDir, `${mailItem.id}.json`),\n JSON.stringify(mailItem, null, 2)\n );\n}\n\n/**\n * Get and clear mail for an agent\n */\nexport function collectMail(agentId: string): HookItem[] {\n const mailDir = getMailDir(agentId);\n if (!existsSync(mailDir)) return [];\n\n const mails: HookItem[] = [];\n const files = readdirSync(mailDir).filter((f) => f.endsWith('.json'));\n\n for (const file of files) {\n const filePath = join(mailDir, file);\n try {\n const content = readFileSync(filePath, 'utf-8');\n mails.push(JSON.parse(content));\n unlinkSync(filePath); // Remove after reading\n } catch {\n // Skip invalid mail\n }\n }\n\n return mails;\n}\n\n/**\n * Generate Fixed Point prompt for agent startup\n */\nexport function generateFixedPointPrompt(agentId: string): string | null {\n const { hasWork, urgentCount, items } = checkHook(agentId);\n\n if (!hasWork) return null;\n\n const lines: string[] = [\n '# FPP: Work Found on Your Hook',\n '',\n '> \"Any runnable action is a fixed point and must resolve before the system can rest.\"',\n '',\n ];\n\n if (urgentCount > 0) {\n lines.push(`⚠️ **${urgentCount} URGENT item(s) require immediate attention**`);\n lines.push('');\n }\n\n lines.push(`## Pending Work Items (${items.length})`);\n lines.push('');\n\n for (const item of items) {\n const priorityEmoji = {\n urgent: '🔴',\n high: '🟠',\n normal: '🟢',\n low: '⚪',\n }[item.priority];\n\n lines.push(`### ${priorityEmoji} ${item.type.toUpperCase()}: ${item.id}`);\n lines.push(`- Source: ${item.source}`);\n lines.push(`- Created: ${item.createdAt}`);\n\n if (item.payload.issueId) {\n lines.push(`- Issue: ${item.payload.issueId}`);\n }\n if (item.payload.message) {\n lines.push(`- Message: ${item.payload.message}`);\n }\n if (item.payload.action) {\n lines.push(`- Action: ${item.payload.action}`);\n }\n lines.push('');\n }\n\n lines.push('---');\n lines.push('');\n lines.push('Execute these items in priority order. Use `bd hook pop <id>` after completing each item.');\n\n return lines.join('\\n');\n}\n","/**\n * Work Type Registry\n *\n * Central registry of all work type IDs used for model routing.\n * Each work type represents a specific context where AI agents operate,\n * allowing fine-grained control over which models handle which tasks.\n */\n\n/**\n * Metadata for each work type\n */\nexport interface WorkTypeMetadata {\n /** Broad category this work type belongs to */\n category: 'issue-agent' | 'specialist' | 'subagent' | 'convoy' | 'pre-work' | 'cli';\n /** Optional phase within the category (e.g., for issue-agent phases) */\n phase?: string;\n /** Human-readable description */\n description: string;\n}\n\n/**\n * Complete registry of all 23 work types with metadata\n */\nexport const WORK_TYPES = {\n // Issue agent phases (6)\n 'issue-agent:exploration': {\n phase: 'exploration',\n category: 'issue-agent',\n description: 'Exploring codebase and understanding requirements',\n },\n 'issue-agent:implementation': {\n phase: 'implementation',\n category: 'issue-agent',\n description: 'Writing code to implement features or fixes',\n },\n 'issue-agent:testing': {\n phase: 'testing',\n category: 'issue-agent',\n description: 'Running tests and verifying functionality',\n },\n 'issue-agent:documentation': {\n phase: 'documentation',\n category: 'issue-agent',\n description: 'Writing documentation and updating docs',\n },\n 'issue-agent:review-response': {\n phase: 'review-response',\n category: 'issue-agent',\n description: 'Responding to code review feedback',\n },\n\n // Specialist agents (3)\n 'specialist-review-agent': {\n category: 'specialist',\n description: 'Comprehensive code review specialist',\n },\n 'specialist-test-agent': {\n category: 'specialist',\n description: 'Test generation and verification specialist',\n },\n 'specialist-merge-agent': {\n category: 'specialist',\n description: 'Merge request finalization specialist',\n },\n\n // Subagents (4)\n 'subagent:explore': {\n category: 'subagent',\n description: 'Fast codebase exploration subagent',\n },\n 'subagent:plan': {\n category: 'subagent',\n description: 'Implementation planning subagent',\n },\n 'subagent:bash': {\n category: 'subagent',\n description: 'Command execution specialist subagent',\n },\n 'subagent:general-purpose': {\n category: 'subagent',\n description: 'General-purpose task subagent',\n },\n\n // Convoy members (4)\n 'convoy:security-reviewer': {\n category: 'convoy',\n description: 'Security-focused code reviewer in convoy',\n },\n 'convoy:performance-reviewer': {\n category: 'convoy',\n description: 'Performance-focused code reviewer in convoy',\n },\n 'convoy:correctness-reviewer': {\n category: 'convoy',\n description: 'Correctness-focused code reviewer in convoy',\n },\n 'convoy:synthesis-agent': {\n category: 'convoy',\n description: 'Synthesizes findings from convoy reviewers',\n },\n\n // Pre-work agents (5)\n 'planning-agent': {\n category: 'pre-work',\n description: 'Interactive planning and discovery agent',\n },\n 'prd-agent': {\n category: 'pre-work',\n description: 'Generates Product Requirement Documents',\n },\n 'decomposition-agent': {\n category: 'pre-work',\n description: 'Breaks down work into tasks',\n },\n 'triage-agent': {\n category: 'pre-work',\n description: 'Prioritizes and triages issues',\n },\n\n // CLI contexts (2)\n 'cli:interactive': {\n category: 'cli',\n description: 'Interactive CLI session',\n },\n 'cli:quick-command': {\n category: 'cli',\n description: 'Quick one-off CLI commands',\n },\n} as const;\n\n/**\n * Type-safe work type IDs\n */\nexport type WorkTypeId = keyof typeof WORK_TYPES;\n\n/**\n * Valid work type categories\n */\nexport type WorkTypeCategory = WorkTypeMetadata['category'];\n\n/**\n * Get all work type IDs\n */\nexport function getAllWorkTypes(): WorkTypeId[] {\n return Object.keys(WORK_TYPES) as WorkTypeId[];\n}\n\n/**\n * Get all work types in a specific category\n */\nexport function getWorkTypesByCategory(category: WorkTypeCategory): WorkTypeId[] {\n return getAllWorkTypes().filter((id) => WORK_TYPES[id].category === category);\n}\n\n/**\n * Check if a string is a valid work type ID\n */\nexport function isValidWorkType(id: string): id is WorkTypeId {\n return id in WORK_TYPES;\n}\n\n/**\n * Get metadata for a work type\n */\nexport function getWorkTypeMetadata(id: WorkTypeId): WorkTypeMetadata {\n return WORK_TYPES[id];\n}\n\n/**\n * Get human-readable name for a work type\n */\nexport function getWorkTypeName(id: WorkTypeId): string {\n const metadata = WORK_TYPES[id];\n if ('phase' in metadata && metadata.phase) {\n return `${metadata.category} (${metadata.phase})`;\n }\n return id;\n}\n\n/**\n * Validate work type ID and throw if invalid\n */\nexport function validateWorkType(id: string): asserts id is WorkTypeId {\n if (!isValidWorkType(id)) {\n throw new Error(\n `Invalid work type ID: ${id}. Valid types: ${getAllWorkTypes().join(', ')}`\n );\n }\n}\n","/**\n * Model Fallback Strategy\n *\n * When a non-Anthropic model is selected but its API key is missing,\n * automatically fallback to an equivalent Anthropic model. This ensures\n * Panopticon always works even without configuring external providers.\n */\n\nimport { ModelId, AnthropicModel, OpenAIModel, GoogleModel, ZAIModel } from './settings.js';\n\n/**\n * AI model provider types\n */\nexport type ModelProvider = 'anthropic' | 'openai' | 'google' | 'zai' | 'kimi';\n\n/**\n * Map of model ID to provider\n */\nconst MODEL_PROVIDERS: Record<ModelId, ModelProvider> = {\n // Anthropic models\n 'claude-opus-4-6': 'anthropic',\n 'claude-sonnet-4-6': 'anthropic',\n 'claude-sonnet-4-5': 'anthropic',\n 'claude-haiku-4-5': 'anthropic',\n\n // OpenAI models\n 'gpt-5.2-codex': 'openai',\n 'o3-deep-research': 'openai',\n 'gpt-4o': 'openai',\n 'gpt-4o-mini': 'openai',\n\n // Google models\n 'gemini-3-pro-preview': 'google',\n 'gemini-3-flash-preview': 'google',\n 'gemini-2.5-pro': 'google',\n 'gemini-2.5-flash': 'google',\n\n // Z.AI models\n 'glm-4.7': 'zai',\n 'glm-4.7-flash': 'zai',\n\n // Kimi models\n 'kimi-k2': 'kimi',\n 'kimi-k2.5': 'kimi',\n};\n\n/**\n * Fallback mapping: non-Anthropic model → Anthropic equivalent\n *\n * Mapping strategy:\n * - Premium models (GPT-5.2, O3, Gemini Pro) → Sonnet 4.6 (good balance)\n * - Economy models (GPT-4o-mini, Gemini Flash, GLM Flash) → Haiku 4.5\n * - GPT-4o → Sonnet 4.6 (similar tier)\n * - GLM-4.7 → Haiku 4.5 (economy tier)\n *\n * Note: We intentionally avoid Opus 4.6 as default fallback to keep costs reasonable.\n * Users who want Opus can explicitly set it in their config.\n */\nconst FALLBACK_MAP: Record<string, AnthropicModel> = {\n // OpenAI → Anthropic\n 'gpt-5.2-codex': 'claude-sonnet-4-6', // Premium code model → Sonnet\n 'o3-deep-research': 'claude-sonnet-4-6', // Premium research model → Sonnet\n 'gpt-4o': 'claude-sonnet-4-6', // Flagship model → Sonnet\n 'gpt-4o-mini': 'claude-haiku-4-5', // Economy model → Haiku\n\n // Google → Anthropic\n 'gemini-3-pro-preview': 'claude-sonnet-4-6', // Premium model → Sonnet\n 'gemini-3-flash-preview': 'claude-haiku-4-5', // Fast model → Haiku\n\n // Z.AI → Anthropic\n 'glm-4.7': 'claude-haiku-4-5', // Standard model → Haiku\n 'glm-4.7-flash': 'claude-haiku-4-5', // Fast model → Haiku\n\n // Kimi → Anthropic\n 'kimi-k2': 'claude-sonnet-4-6', // Good balance model → Sonnet\n 'kimi-k2.5': 'claude-sonnet-4-6', // Premium model → Sonnet\n};\n\n/**\n * Default fallback when model not in explicit mapping\n */\nconst DEFAULT_FALLBACK: AnthropicModel = 'claude-sonnet-4-6';\n\n/**\n * Get the provider for a model ID\n */\nexport function getModelProvider(modelId: ModelId): ModelProvider {\n return MODEL_PROVIDERS[modelId];\n}\n\n/**\n * Check if a model requires an external API key\n */\nexport function requiresExternalKey(modelId: ModelId): boolean {\n return getModelProvider(modelId) !== 'anthropic';\n}\n\n/**\n * Get all models for a specific provider\n */\nexport function getModelsByProvider(provider: ModelProvider): ModelId[] {\n return Object.entries(MODEL_PROVIDERS)\n .filter(([_, p]) => p === provider)\n .map(([modelId]) => modelId as ModelId);\n}\n\n/**\n * Check if a provider is enabled (has API key configured)\n *\n * @param provider Provider to check\n * @param enabledProviders Set of enabled provider names\n * @returns true if provider is enabled or is Anthropic (always enabled)\n */\nexport function isProviderEnabled(\n provider: ModelProvider,\n enabledProviders: Set<ModelProvider>\n): boolean {\n // Anthropic is always enabled (required)\n if (provider === 'anthropic') return true;\n\n return enabledProviders.has(provider);\n}\n\n/**\n * Apply fallback strategy for a model\n *\n * If the model's provider is disabled (no API key), return an Anthropic equivalent.\n * Otherwise, return the original model.\n *\n * @param modelId Requested model\n * @param enabledProviders Set of enabled provider names\n * @returns Original model if provider enabled, otherwise Anthropic fallback\n */\nexport function applyFallback(\n modelId: ModelId,\n enabledProviders: Set<ModelProvider>\n): ModelId {\n const provider = getModelProvider(modelId);\n\n // If provider is enabled, use the requested model\n if (isProviderEnabled(provider, enabledProviders)) {\n return modelId;\n }\n\n // Provider disabled - lookup fallback\n const fallback = FALLBACK_MAP[modelId] || DEFAULT_FALLBACK;\n\n // Log fallback for visibility\n console.warn(\n `Model ${modelId} requires ${provider} API key - falling back to ${fallback}`\n );\n\n return fallback;\n}\n\n/**\n * Get the fallback model for a given model (useful for preview/display)\n *\n * @param modelId Model to get fallback for\n * @returns Anthropic fallback model\n */\nexport function getFallbackModel(modelId: ModelId): AnthropicModel {\n // Anthropic models fallback to themselves\n if (getModelProvider(modelId) === 'anthropic') {\n return modelId as AnthropicModel;\n }\n\n return FALLBACK_MAP[modelId] || DEFAULT_FALLBACK;\n}\n\n/**\n * Detect enabled providers from API keys configuration\n *\n * @param apiKeys API keys object from settings\n * @returns Set of enabled provider names\n */\nexport function detectEnabledProviders(apiKeys: {\n openai?: string;\n google?: string;\n zai?: string;\n kimi?: string;\n}): Set<ModelProvider> {\n const enabled = new Set<ModelProvider>(['anthropic']); // Always enabled\n\n // Check each optional provider\n if (apiKeys.openai && apiKeys.openai.trim()) {\n enabled.add('openai');\n }\n if (apiKeys.google && apiKeys.google.trim()) {\n enabled.add('google');\n }\n if (apiKeys.zai && apiKeys.zai.trim()) {\n enabled.add('zai');\n }\n if (apiKeys.kimi && apiKeys.kimi.trim()) {\n enabled.add('kimi');\n }\n\n return enabled;\n}\n\n/**\n * Filter a list of models to only those available with enabled providers\n *\n * @param models List of models to filter\n * @param enabledProviders Set of enabled provider names\n * @returns Filtered list of models\n */\nexport function filterAvailableModels(\n models: ModelId[],\n enabledProviders: Set<ModelProvider>\n): ModelId[] {\n return models.filter((modelId) => {\n const provider = getModelProvider(modelId);\n return isProviderEnabled(provider, enabledProviders);\n });\n}\n\n/**\n * Get all available models (across all enabled providers)\n *\n * @param enabledProviders Set of enabled provider names\n * @returns List of available model IDs\n */\nexport function getAvailableModels(enabledProviders: Set<ModelProvider>): ModelId[] {\n return Object.keys(MODEL_PROVIDERS).filter((modelId) => {\n const provider = MODEL_PROVIDERS[modelId as ModelId];\n return isProviderEnabled(provider, enabledProviders);\n }) as ModelId[];\n}\n","/**\n * Smart Model Selector\n *\n * Intelligently selects the best model for each work type based on:\n * 1. What models the user has enabled (API keys configured)\n * 2. Capability scores for the required skills\n *\n * This is an opinionated system - always pick the BEST model for each job.\n * Users control cost by which providers they enable, not a sensitivity slider.\n */\n\nimport { ModelId } from './settings.js';\nimport { WorkTypeId } from './work-types.js';\nimport {\n MODEL_CAPABILITIES,\n SkillDimension,\n ModelCapability,\n getModelCapability,\n} from './model-capabilities.js';\n\n/**\n * Skill requirements for a work type\n * Higher weight = more important for this task\n */\nexport interface SkillRequirement {\n skill: SkillDimension;\n weight: number; // 0-1, how important this skill is\n}\n\n/**\n * Work type to skill mapping\n * Defines what skills each work type needs\n */\nexport const WORK_TYPE_REQUIREMENTS: Record<WorkTypeId, SkillRequirement[]> = {\n // ═══════════════════════════════════════════════════════════════════════════\n // ISSUE AGENT PHASES\n // ═══════════════════════════════════════════════════════════════════════════\n\n 'issue-agent:exploration': [\n { skill: 'speed', weight: 0.4 }, // Need fast exploration\n { skill: 'context-length', weight: 0.3 }, // Large codebases\n { skill: 'synthesis', weight: 0.3 }, // Understanding structure\n ],\n\n 'issue-agent:implementation': [\n { skill: 'code-generation', weight: 0.6 }, // Primary skill\n { skill: 'debugging', weight: 0.2 }, // Avoiding bugs\n { skill: 'testing', weight: 0.2 }, // Writing testable code\n ],\n\n 'issue-agent:testing': [\n { skill: 'testing', weight: 0.5 }, // Primary skill\n { skill: 'code-generation', weight: 0.3 }, // Writing test code\n { skill: 'debugging', weight: 0.2 }, // Finding edge cases\n ],\n\n 'issue-agent:documentation': [\n { skill: 'documentation', weight: 0.6 }, // Primary skill\n { skill: 'synthesis', weight: 0.3 }, // Summarizing\n { skill: 'speed', weight: 0.1 }, // Fast iteration\n ],\n\n 'issue-agent:review-response': [\n { skill: 'code-review', weight: 0.4 }, // Understanding feedback\n { skill: 'code-generation', weight: 0.3 }, // Making fixes\n { skill: 'debugging', weight: 0.3 }, // Finding issues\n ],\n\n // ═══════════════════════════════════════════════════════════════════════════\n // SPECIALIST AGENTS\n // ═══════════════════════════════════════════════════════════════════════════\n\n 'specialist-review-agent': [\n { skill: 'code-review', weight: 0.5 }, // Primary skill\n { skill: 'security', weight: 0.25 }, // Security awareness\n { skill: 'performance', weight: 0.25 }, // Performance awareness\n ],\n\n 'specialist-test-agent': [\n { skill: 'testing', weight: 0.5 }, // Primary skill\n { skill: 'code-generation', weight: 0.3 }, // Writing tests\n { skill: 'debugging', weight: 0.2 }, // Finding issues\n ],\n\n 'specialist-merge-agent': [\n { skill: 'code-review', weight: 0.4 }, // Understanding conflicts\n { skill: 'synthesis', weight: 0.3 }, // Merging changes\n { skill: 'debugging', weight: 0.3 }, // Resolving issues\n ],\n\n // ═══════════════════════════════════════════════════════════════════════════\n // SUBAGENTS\n // ═══════════════════════════════════════════════════════════════════════════\n\n 'subagent:explore': [\n { skill: 'speed', weight: 0.5 }, // Need speed\n { skill: 'context-length', weight: 0.3 }, // Large scope\n { skill: 'synthesis', weight: 0.2 }, // Quick understanding\n ],\n\n 'subagent:plan': [\n { skill: 'planning', weight: 0.5 }, // Primary skill\n { skill: 'synthesis', weight: 0.3 }, // Combining info\n { skill: 'speed', weight: 0.2 }, // Quick iteration\n ],\n\n 'subagent:bash': [\n { skill: 'speed', weight: 0.6 }, // Fast execution\n { skill: 'code-generation', weight: 0.3 }, // Command generation\n { skill: 'debugging', weight: 0.1 }, // Error handling\n ],\n\n 'subagent:general-purpose': [\n { skill: 'speed', weight: 0.3 }, // Balanced\n { skill: 'synthesis', weight: 0.3 }, // General understanding\n { skill: 'code-generation', weight: 0.4 }, // General tasks\n ],\n\n // ═══════════════════════════════════════════════════════════════════════════\n // CONVOY MEMBERS\n // ═══════════════════════════════════════════════════════════════════════════\n\n 'convoy:security-reviewer': [\n { skill: 'security', weight: 0.7 }, // PRIMARY - never compromise\n { skill: 'code-review', weight: 0.2 }, // Code understanding\n { skill: 'debugging', weight: 0.1 }, // Finding vulnerabilities\n ],\n\n 'convoy:performance-reviewer': [\n { skill: 'performance', weight: 0.6 }, // Primary skill\n { skill: 'code-review', weight: 0.3 }, // Code understanding\n { skill: 'debugging', weight: 0.1 }, // Finding bottlenecks\n ],\n\n 'convoy:correctness-reviewer': [\n { skill: 'code-review', weight: 0.4 }, // Primary skill\n { skill: 'debugging', weight: 0.4 }, // Finding bugs\n { skill: 'testing', weight: 0.2 }, // Test coverage\n ],\n\n 'convoy:synthesis-agent': [\n { skill: 'synthesis', weight: 0.6 }, // Primary skill\n { skill: 'documentation', weight: 0.2 }, // Clear writing\n { skill: 'planning', weight: 0.2 }, // Organizing findings\n ],\n\n // ═══════════════════════════════════════════════════════════════════════════\n // PRE-WORK AGENTS\n // ═══════════════════════════════════════════════════════════════════════════\n\n 'planning-agent': [\n { skill: 'planning', weight: 0.5 }, // Primary skill\n { skill: 'synthesis', weight: 0.3 }, // Combining requirements\n { skill: 'documentation', weight: 0.2 }, // Documenting decisions\n ],\n\n 'prd-agent': [\n { skill: 'documentation', weight: 0.5 }, // Primary skill\n { skill: 'planning', weight: 0.3 }, // Structure\n { skill: 'synthesis', weight: 0.2 }, // Combining requirements\n ],\n\n 'decomposition-agent': [\n { skill: 'planning', weight: 0.5 }, // Primary skill\n { skill: 'synthesis', weight: 0.3 }, // Breaking down\n { skill: 'documentation', weight: 0.2 }, // Clear tasks\n ],\n\n 'triage-agent': [\n { skill: 'speed', weight: 0.4 }, // Quick decisions\n { skill: 'synthesis', weight: 0.3 }, // Understanding scope\n { skill: 'planning', weight: 0.3 }, // Prioritization\n ],\n\n // ═══════════════════════════════════════════════════════════════════════════\n // CLI CONTEXTS\n // ═══════════════════════════════════════════════════════════════════════════\n\n 'cli:interactive': [\n { skill: 'speed', weight: 0.4 }, // Responsive\n { skill: 'synthesis', weight: 0.3 }, // Understanding context\n { skill: 'code-generation', weight: 0.3 }, // Quick code\n ],\n\n 'cli:quick-command': [\n { skill: 'speed', weight: 0.7 }, // Must be fast\n { skill: 'code-generation', weight: 0.2 }, // Simple generation\n { skill: 'synthesis', weight: 0.1 }, // Quick understanding\n ],\n};\n\n/**\n * Selection result with explanation\n */\nexport interface ModelSelectionResult {\n /** Selected model */\n model: ModelId;\n /** Score that led to selection (0-100) */\n score: number;\n /** Why this model was selected */\n reason: string;\n /** All candidates that were considered */\n candidates: Array<{\n model: ModelId;\n score: number;\n available: boolean;\n }>;\n}\n\n/**\n * Selection options\n */\nexport interface SelectionOptions {\n /**\n * Minimum capability threshold (0-100)\n * Models below this score are excluded\n * Default: 50\n */\n minCapability?: number;\n\n /**\n * Force a specific model (bypass selection)\n */\n forceModel?: ModelId;\n}\n\n/**\n * Calculate weighted skill score for a model given requirements\n */\nfunction calculateSkillScore(\n model: ModelId,\n requirements: SkillRequirement[]\n): number {\n const cap = getModelCapability(model);\n let totalScore = 0;\n let totalWeight = 0;\n\n for (const req of requirements) {\n totalScore += cap.skills[req.skill] * req.weight;\n totalWeight += req.weight;\n }\n\n return totalWeight > 0 ? totalScore / totalWeight : 0;\n}\n\n/**\n * Calculate final selection score - pure capability based\n *\n * We're opinionated: always pick the BEST model for the job.\n * Users control cost by which providers they enable.\n */\nfunction calculateSelectionScore(\n model: ModelId,\n skillScore: number\n): number {\n // Pure quality - just return the skill score\n // Cost control is done by which providers the user enables\n return skillScore;\n}\n\n/**\n * Select the best model for a work type from available models\n */\nexport function selectModel(\n workType: WorkTypeId,\n availableModels: ModelId[],\n options: SelectionOptions = {}\n): ModelSelectionResult {\n const { minCapability = 50, forceModel } = options;\n\n // Force model if specified and available\n if (forceModel) {\n if (availableModels.includes(forceModel)) {\n return {\n model: forceModel,\n score: 100,\n reason: `Forced selection: ${forceModel}`,\n candidates: [{ model: forceModel, score: 100, available: true }],\n };\n }\n // Fall through to normal selection if forced model not available\n }\n\n const requirements = WORK_TYPE_REQUIREMENTS[workType];\n const allModels = Object.keys(MODEL_CAPABILITIES) as ModelId[];\n\n // Calculate scores for all models - pure capability based\n // Users control cost by which providers they enable\n const candidates = allModels.map((model) => {\n const skillScore = calculateSkillScore(model, requirements);\n const selectionScore = calculateSelectionScore(model, skillScore);\n const available = availableModels.includes(model);\n\n return {\n model,\n skillScore,\n score: selectionScore,\n available,\n };\n });\n\n // Filter to available models with minimum capability\n const eligible = candidates.filter(\n (c) => c.available && c.skillScore >= minCapability\n );\n\n // Sort by selection score (descending)\n eligible.sort((a, b) => b.score - a.score);\n\n // Fallback: if no eligible models, use best available regardless of threshold\n if (eligible.length === 0) {\n const fallback = candidates\n .filter((c) => c.available)\n .sort((a, b) => b.score - a.score)[0];\n\n if (!fallback) {\n // No available models at all - use Anthropic default\n return {\n model: 'claude-sonnet-4-6',\n score: 0,\n reason: 'No models available, falling back to default',\n candidates: candidates.map((c) => ({\n model: c.model,\n score: c.score,\n available: c.available,\n })),\n };\n }\n\n return {\n model: fallback.model,\n score: fallback.score,\n reason: `Best available (below min threshold): ${fallback.model}`,\n candidates: candidates.map((c) => ({\n model: c.model,\n score: c.score,\n available: c.available,\n })),\n };\n }\n\n const selected = eligible[0];\n const cap = getModelCapability(selected.model);\n\n // Generate reason\n const topSkills = requirements\n .sort((a, b) => b.weight - a.weight)\n .slice(0, 2)\n .map((r) => r.skill);\n\n const reason = `Best for ${workType}: ${cap.displayName} (${topSkills.join(', ')}: ${Math.round(selected.skillScore)}, cost: $${cap.costPer1MTokens}/1M)`;\n\n return {\n model: selected.model,\n score: selected.score,\n reason,\n candidates: candidates.map((c) => ({\n model: c.model,\n score: c.score,\n available: c.available,\n })),\n };\n}\n\n/**\n * Select models for all work types at once\n */\nexport function selectAllModels(\n availableModels: ModelId[],\n options: SelectionOptions = {}\n): Record<WorkTypeId, ModelSelectionResult> {\n const workTypes = Object.keys(WORK_TYPE_REQUIREMENTS) as WorkTypeId[];\n const results: Record<WorkTypeId, ModelSelectionResult> = {} as Record<\n WorkTypeId,\n ModelSelectionResult\n >;\n\n for (const workType of workTypes) {\n results[workType] = selectModel(workType, availableModels, options);\n }\n\n return results;\n}\n\n/**\n * Get simple model mapping (for backward compatibility with presets)\n */\nexport function getSimpleModelMapping(\n availableModels: ModelId[],\n options: SelectionOptions = {}\n): Record<WorkTypeId, ModelId> {\n const results = selectAllModels(availableModels, options);\n const mapping: Record<WorkTypeId, ModelId> = {} as Record<WorkTypeId, ModelId>;\n\n for (const [workType, result] of Object.entries(results)) {\n mapping[workType as WorkTypeId] = result.model;\n }\n\n return mapping;\n}\n\n/**\n * Pretty print selection results for debugging\n */\nexport function formatSelectionResults(\n results: Record<WorkTypeId, ModelSelectionResult>\n): string {\n const lines: string[] = ['Model Selection Results', '='.repeat(60)];\n\n for (const [workType, result] of Object.entries(results)) {\n lines.push(`${workType}: ${result.model}`);\n lines.push(` Reason: ${result.reason}`);\n lines.push('');\n }\n\n return lines.join('\\n');\n}\n\n","/**\n * Work Type Router\n *\n * Routes work types to appropriate models using smart (capability-based) selection.\n * Picks the best model for each job based on:\n * 1. What models the user has enabled (API keys configured)\n * 2. Capability scores for the required skills\n * 3. Cost optimization (configurable)\n */\n\nimport { WorkTypeId, isValidWorkType, validateWorkType, getAllWorkTypes } from './work-types.js';\nimport { ModelId } from './settings.js';\nimport { applyFallback, ModelProvider, getModelsByProvider } from './model-fallback.js';\nimport { loadConfig, NormalizedConfig } from './config-yaml.js';\nimport { selectModel, ModelSelectionResult } from './smart-model-selector.js';\n\n// Re-export WorkTypeId for backward compatibility\nexport type { WorkTypeId } from './work-types.js';\n\n/**\n * Model resolution result with debugging info\n */\nexport interface ModelResolutionResult {\n /** Final model to use */\n model: ModelId;\n /** Work type that was resolved */\n workType: WorkTypeId;\n /** How the model was determined */\n source: 'override' | 'smart';\n /** Whether fallback was applied (provider disabled) */\n usedFallback: boolean;\n /** Original model before fallback */\n originalModel?: ModelId;\n /** Smart selection details */\n selection: {\n score: number;\n reason: string;\n };\n}\n\n/**\n * Work Type Router\n *\n * Main router class for resolving work types to models.\n */\nexport class WorkTypeRouter {\n private config: NormalizedConfig;\n private availableModels: ModelId[] | null = null;\n\n constructor(config?: NormalizedConfig) {\n this.config = config || loadConfig().config;\n }\n\n /**\n * Get list of available models based on enabled providers\n */\n private getAvailableModels(): ModelId[] {\n if (this.availableModels) {\n return this.availableModels;\n }\n\n const available: ModelId[] = [];\n for (const provider of this.config.enabledProviders) {\n available.push(...getModelsByProvider(provider));\n }\n this.availableModels = available;\n return available;\n }\n\n /**\n * Get model for a specific work type\n *\n * Resolution order:\n * 1. Per-project/global override (if configured)\n * 2. Smart selection (capability-based)\n */\n getModel(workTypeId: WorkTypeId): ModelResolutionResult {\n validateWorkType(workTypeId);\n\n let model: ModelId;\n let source: 'override' | 'smart';\n let originalModel: ModelId | undefined;\n let selection: { score: number; reason: string };\n\n // Check for override first\n if (this.config.overrides[workTypeId]) {\n model = this.config.overrides[workTypeId]!;\n source = 'override';\n selection = {\n score: 100,\n reason: `Explicit override: ${model}`,\n };\n } else {\n // Use smart (capability-based) selection\n const availableModels = this.getAvailableModels();\n const result = selectModel(workTypeId, availableModels);\n model = result.model;\n source = 'smart';\n selection = {\n score: result.score,\n reason: result.reason,\n };\n }\n\n // Apply fallback if provider is disabled\n originalModel = model;\n model = applyFallback(model, this.config.enabledProviders);\n\n return {\n model,\n workType: workTypeId,\n source,\n usedFallback: model !== originalModel,\n originalModel: model !== originalModel ? originalModel : undefined,\n selection,\n };\n }\n\n /**\n * Get just the model ID for a work type (convenience method)\n */\n getModelId(workTypeId: WorkTypeId): ModelId {\n return this.getModel(workTypeId).model;\n }\n\n /**\n * Check if a work type has an override configured\n */\n hasOverride(workTypeId: WorkTypeId): boolean {\n return workTypeId in this.config.overrides;\n }\n\n /**\n * Get the set of enabled providers\n */\n getEnabledProviders(): Set<ModelProvider> {\n return this.config.enabledProviders;\n }\n\n /**\n * Get all configured overrides\n */\n getOverrides(): Partial<Record<WorkTypeId, ModelId>> {\n return { ...this.config.overrides };\n }\n\n /**\n * Get API keys configuration\n */\n getApiKeys(): { openai?: string; google?: string; zai?: string; kimi?: string } {\n return { ...this.config.apiKeys };\n }\n\n /**\n * Get Gemini thinking level\n */\n getGeminiThinkingLevel(): 1 | 2 | 3 | 4 {\n return this.config.geminiThinkingLevel;\n }\n\n /**\n * Reload configuration from disk\n */\n reloadConfig(): void {\n this.config = loadConfig().config;\n this.availableModels = null; // Clear cache\n }\n\n /**\n * Get debug information about current configuration\n */\n getDebugInfo(): {\n enabledProviders: string[];\n availableModelCount: number;\n overrideCount: number;\n hasApiKeys: {\n openai: boolean;\n google: boolean;\n zai: boolean;\n kimi: boolean;\n };\n } {\n return {\n enabledProviders: Array.from(this.config.enabledProviders),\n availableModelCount: this.getAvailableModels().length,\n overrideCount: Object.keys(this.config.overrides).length,\n hasApiKeys: {\n openai: !!this.config.apiKeys.openai,\n google: !!this.config.apiKeys.google,\n zai: !!this.config.apiKeys.zai,\n kimi: !!this.config.apiKeys.kimi,\n },\n };\n }\n}\n\n/**\n * Global router instance\n */\nlet globalRouter: WorkTypeRouter | null = null;\n\n/**\n * Get the global work type router instance\n */\nexport function getGlobalRouter(): WorkTypeRouter {\n if (!globalRouter) {\n globalRouter = new WorkTypeRouter();\n }\n return globalRouter;\n}\n\n/**\n * Reset the global router (useful for testing)\n */\nexport function resetGlobalRouter(): void {\n globalRouter = null;\n}\n\n/**\n * Reload global router configuration\n */\nexport function reloadGlobalRouter(): void {\n if (globalRouter) {\n globalRouter.reloadConfig();\n }\n}\n\n/**\n * Get model using the global router\n */\nexport function getModel(workTypeId: WorkTypeId): ModelResolutionResult {\n return getGlobalRouter().getModel(workTypeId);\n}\n\n/**\n * Get just the model ID using the global router\n */\nexport function getModelId(workTypeId: WorkTypeId): ModelId {\n return getGlobalRouter().getModelId(workTypeId);\n}\n\n/**\n * Check for override using the global router\n */\nexport function hasOverride(workTypeId: WorkTypeId): boolean {\n return getGlobalRouter().hasOverride(workTypeId);\n}\n\n/**\n * Get debug info using the global router\n */\nexport function getDebugInfo() {\n return getGlobalRouter().getDebugInfo();\n}\n"],"mappings":"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","names":[]}