@lovelybunch/api 1.0.75-alpha.10 → 1.0.75-alpha.12

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 (114) hide show
  1. package/dist/lib/jobs/job-scheduler.js +21 -0
  2. package/dist/lib/mail/mail-runner.d.ts +19 -0
  3. package/dist/lib/mail/mail-runner.js +223 -0
  4. package/dist/lib/slack/slack-service.d.ts +2 -0
  5. package/dist/lib/slack/slack-service.js +3 -0
  6. package/dist/routes/api/v1/ai/route.js +23 -3
  7. package/dist/routes/api/v1/git/index.js +23 -0
  8. package/dist/routes/api/v1/mail/index.d.ts +3 -0
  9. package/dist/routes/api/v1/mail/index.js +20 -0
  10. package/dist/routes/api/v1/mail/route.d.ts +252 -0
  11. package/dist/routes/api/v1/mail/route.js +261 -0
  12. package/dist/routes/api/v1/slack/index.d.ts +3 -0
  13. package/dist/routes/api/v1/slack/index.js +15 -0
  14. package/dist/routes/api/v1/slack/route.d.ts +124 -0
  15. package/dist/routes/api/v1/slack/route.js +192 -0
  16. package/dist/routes/api/v1/tasks/[id]/route.js +10 -0
  17. package/dist/routes/api/v1/tasks/route.d.ts +1 -0
  18. package/dist/routes/api/v1/tasks/route.js +18 -2
  19. package/dist/server-with-static.js +2 -0
  20. package/dist/server.js +4 -0
  21. package/package.json +4 -4
  22. package/static/assets/{ActivityPage-CbmEnYhg.js → ActivityPage-nSSbSFAB.js} +1 -1
  23. package/static/assets/{ApiKeysSettingsPage-DLxQIqTT.js → ApiKeysSettingsPage-xrhhroQ_.js} +1 -1
  24. package/static/assets/{ArchitectureEditPage-CbtzgIQv.js → ArchitectureEditPage-CHFEQNgk.js} +1 -1
  25. package/static/assets/{ArchitecturePage-CcAMfFZ8.js → ArchitecturePage-EudLa-GY.js} +1 -1
  26. package/static/assets/{AuthSettingsPage-4Prb7nAt.js → AuthSettingsPage-C3YTYgj_.js} +1 -1
  27. package/static/assets/{CallbackPage-CmWv_5Nh.js → CallbackPage-VI4m1EWU.js} +1 -1
  28. package/static/assets/{CodePage-COC7rcwM.js → CodePage-CcH6EB2S.js} +1 -1
  29. package/static/assets/{CollapsibleSection-C_VVeVSc.js → CollapsibleSection-4LvowJQN.js} +1 -1
  30. package/static/assets/{DashboardPage-BACFVIaW.js → DashboardPage-Dj2GNTQg.js} +1 -1
  31. package/static/assets/{GitPage-rFngEr_A.js → GitPage-E9px87JY.js} +2 -2
  32. package/static/assets/{GitSettingsPage-CDQovOqx.js → GitSettingsPage-BepF5Dpg.js} +1 -1
  33. package/static/assets/{IdentityPage-CS4REq2E.js → IdentityPage-Ccarv5Tz.js} +2 -7
  34. package/static/assets/{ImplementationStepsEditor-CBIgppkZ.js → ImplementationStepsEditor-BPeqMfAd.js} +1 -1
  35. package/static/assets/{IntegrationsSettingsPage-B7HoraBH.js → IntegrationsSettingsPage-Dp3FRWNn.js} +1 -1
  36. package/static/assets/{JobDetailPage-QUhWsaWV.js → JobDetailPage-DOJKC2Oq.js} +1 -1
  37. package/static/assets/{KnowledgeDetailPage-DCJstmIr.js → KnowledgeDetailPage-BT0czLqw.js} +1 -1
  38. package/static/assets/{KnowledgeEditPage-EYpFPfcJ.js → KnowledgeEditPage-D7gBI6r-.js} +1 -1
  39. package/static/assets/{KnowledgePage-D-7skvn5.js → KnowledgePage-CcfftMdn.js} +1 -1
  40. package/static/assets/{LoginPage-L2aoqSDT.js → LoginPage-nYfkZp1m.js} +1 -1
  41. package/static/assets/MailInboxPage-DvD2sH0g.js +1 -0
  42. package/static/assets/MailReadPage-CZ9stBHd.js +1 -0
  43. package/static/assets/MailSentPage-BqtUkhvw.js +1 -0
  44. package/static/assets/{McpSettingsPage-CLwqDjw_.js → McpSettingsPage-CrNaaXtj.js} +1 -1
  45. package/static/assets/{NewKnowledgePage-Fq_QD8um.js → NewKnowledgePage-BsiP9u8K.js} +1 -1
  46. package/static/assets/{NewSkillPage-DRcgovk0.js → NewSkillPage-BuwLNwdi.js} +1 -1
  47. package/static/assets/{NewTaskPage-CleA8rH5.js → NewTaskPage-DVh5Xv1O.js} +1 -1
  48. package/static/assets/NotificationsSettingsPage-BbDASNoh.js +1 -0
  49. package/static/assets/{ProjectEditPage-GIMOKgmh.js → ProjectEditPage-DdCvF5RR.js} +1 -1
  50. package/static/assets/{ProjectPage-DlWZOqnb.js → ProjectPage-2ujqpmN5.js} +1 -1
  51. package/static/assets/{PromptsSettingsPage-DFagGfo-.js → PromptsSettingsPage-xmsIGnw7.js} +1 -1
  52. package/static/assets/{ResourceDetailPage-CnFRpDec.js → ResourceDetailPage--4tTrZYy.js} +1 -1
  53. package/static/assets/{ResourcesPage-Cw3fu0JE.js → ResourcesPage-Nrgt2hCS.js} +1 -1
  54. package/static/assets/{RoleEditPage-ARXq_eCs.js → RoleEditPage-D8XSVw9S.js} +1 -1
  55. package/static/assets/{RolePage--XBBIrq8.js → RolePage-hS7J_Bhy.js} +1 -1
  56. package/static/assets/{RulesSettingsPage-DEqaRH96.js → RulesSettingsPage-D4K8SBOu.js} +1 -1
  57. package/static/assets/{SchedulePage-C_B2k9P6.js → SchedulePage-BiJmkZ3Q.js} +1 -1
  58. package/static/assets/{SkillDetailPage-Dr6xyKAX.js → SkillDetailPage-DXtASGaY.js} +1 -1
  59. package/static/assets/{SkillEditPage-BsHXmfEZ.js → SkillEditPage-D75sp5rI.js} +1 -1
  60. package/static/assets/{SkillsPage-BrDdAAPx.js → SkillsPage-CsutZlkM.js} +1 -1
  61. package/static/assets/{SkillsSettingsPage-Cvi7xaDE.js → SkillsSettingsPage-B8z_IMlS.js} +1 -1
  62. package/static/assets/{SourceInput-DxF_GGj8.js → SourceInput-DkQR44OP.js} +1 -1
  63. package/static/assets/{TagInput-DYvHbVZq.js → TagInput-BkkvY_kU.js} +1 -1
  64. package/static/assets/{TaskDetailPage-DzcD6t03.js → TaskDetailPage-Bh3Cb6ce.js} +1 -1
  65. package/static/assets/{TaskEditPage-C7a6FOeJ.js → TaskEditPage-CnKiBPvU.js} +1 -1
  66. package/static/assets/{TasksPage-ChWRSO_S.js → TasksPage-3UvOASrt.js} +1 -1
  67. package/static/assets/{TerminalPage-CoPwn8cU.js → TerminalPage-DIXOrK6a.js} +1 -1
  68. package/static/assets/{TerminalSessionPage-BQBZrOJa.js → TerminalSessionPage-Bu0_HyWE.js} +3 -8
  69. package/static/assets/{UserPreferencesPage-DjtU7veO.js → UserPreferencesPage-_ZVEA1Ec.js} +1 -1
  70. package/static/assets/{UserSettingsPage-CfU8boJQ.js → UserSettingsPage-hOWbcJ8X.js} +1 -1
  71. package/static/assets/{UtilitiesPage-sP1Crg-X.js → UtilitiesPage-D4Ae45jX.js} +1 -1
  72. package/static/assets/{alert-BqZa-crG.js → alert-oIIrVTSO.js} +1 -1
  73. package/static/assets/{arrow-down-3faV_GyO.js → arrow-down-Bzd7RaRH.js} +1 -1
  74. package/static/assets/{arrow-left-FD3wQmzH.js → arrow-left-BjR0fDgf.js} +1 -1
  75. package/static/assets/{arrow-up-BzP0YNVk.js → arrow-up-C7p7WCEm.js} +1 -1
  76. package/static/assets/{badge-DRyeFib9.js → badge-C2DkFMMB.js} +1 -1
  77. package/static/assets/{browser-modal-vnePkRfO.js → browser-modal-C6te-uYZ.js} +1 -1
  78. package/static/assets/{card-CuQs3dpy.js → card-WnET8BI8.js} +1 -1
  79. package/static/assets/{chevron-left-QZIoYcVa.js → chevron-left-BIRokVSR.js} +1 -1
  80. package/static/assets/{chevron-up-DreyvhRd.js → chevron-up-Cn_oyEAo.js} +1 -1
  81. package/static/assets/{chevrons-up-CsAkc9vE.js → chevrons-up-CupaYlEL.js} +1 -1
  82. package/static/assets/{circle-alert-ewz28SE3.js → circle-alert-aIhIWzZ9.js} +1 -1
  83. package/static/assets/{circle-check-2TuD-EHs.js → circle-check-Cji8oWtr.js} +1 -1
  84. package/static/assets/{circle-check-big-1xNuBPkR.js → circle-check-big-CfzDBM2L.js} +1 -1
  85. package/static/assets/{circle-play-C_w-qCn4.js → circle-play-BAAWTmz3.js} +1 -1
  86. package/static/assets/{circle-x-B_d4wjG-.js → circle-x-DUrreDIn.js} +1 -1
  87. package/static/assets/{clipboard-B6vBm1BP.js → clipboard-BM4PIYFM.js} +1 -1
  88. package/static/assets/{clock-BIzEsx1g.js → clock-PqCucnPP.js} +1 -1
  89. package/static/assets/{download-Dv-RIvKK.js → download-BfRAPZa0.js} +1 -1
  90. package/static/assets/{external-link-Cr8wjV6X.js → external-link-BHVp3obJ.js} +1 -1
  91. package/static/assets/{eye-DN958vyL.js → eye-DSRHxLqT.js} +1 -1
  92. package/static/assets/{folder-git-2-BproRzAR.js → folder-git-2-C2qrNCQi.js} +1 -1
  93. package/static/assets/index-CkH7-cOr.css +2 -0
  94. package/static/assets/{index-Co_SJV3n.js → index-Cv-tURje.js} +115 -95
  95. package/static/assets/{info-CzKk8mbR.js → info-yTHl9iJb.js} +1 -1
  96. package/static/assets/{label-h5GIKGcJ.js → label-DjRbZW_E.js} +1 -1
  97. package/static/assets/{markdown-editor-C6il4XWv.js → markdown-editor-CKaviYH9.js} +1 -1
  98. package/static/assets/message-square-D-mJ-M1p.js +6 -0
  99. package/static/assets/paperclip-BSIzwgES.js +6 -0
  100. package/static/assets/{pause-BDsjEmXM.js → pause-DJ-9PRz9.js} +1 -1
  101. package/static/assets/{play-CGsVQUJG.js → play-WwrfRXB7.js} +1 -1
  102. package/static/assets/{radio-group-Bsd75ahK.js → radio-group-Cahzon6Q.js} +1 -1
  103. package/static/assets/{refresh-cw-qE1iNkL_.js → refresh-cw-DNc78_Y9.js} +1 -1
  104. package/static/assets/{search-dvi0J4Dr.js → search-k0E_myGY.js} +1 -1
  105. package/static/assets/{select-DylRS99W.js → select-DXmoGuE-.js} +1 -1
  106. package/static/assets/{switch-CsB3wpq9.js → switch-D_BwNkIO.js} +1 -1
  107. package/static/assets/{tabs-Bw_4k2Rs.js → tabs-Bc7iwFOR.js} +1 -1
  108. package/static/assets/{tag-Dh5PraRd.js → tag-C0GUie9u.js} +1 -1
  109. package/static/assets/{terminal-preview-CfOb7xMx.js → terminal-preview-COHnoVSQ.js} +1 -1
  110. package/static/assets/{use-terminal-T_tdJTCU.js → use-terminal-Cnylz5l4.js} +1 -1
  111. package/static/assets/{video-bO6uuAjA.js → video-BC93G8Yl.js} +1 -1
  112. package/static/assets/{zap-DHZ91NcK.js → zap-Bf22dXda.js} +1 -1
  113. package/static/index.html +2 -2
  114. package/static/assets/index-GFQ5RqVh.css +0 -2
@@ -195,6 +195,18 @@ export class JobScheduler {
195
195
  catch (logError) {
196
196
  console.error('Error logging job run end:', logError);
197
197
  }
198
+ // Send Slack notification for job completion (non-blocking)
199
+ const notificationType = result.status === 'succeeded' ? 'job.completed' : 'job.failed';
200
+ import('../slack/slack-service.js').then(({ getSlackService }) => {
201
+ const duration = runRecord.finishedAt.getTime() - start.getTime();
202
+ getSlackService().sendNotification({
203
+ type: notificationType,
204
+ jobId: job.id,
205
+ jobName: job.name || job.id,
206
+ duration,
207
+ error: result.error,
208
+ }).catch(err => console.warn('[jobs] Slack notification failed:', err));
209
+ }).catch(() => { });
198
210
  }
199
211
  catch (error) {
200
212
  runRecord.status = 'failed';
@@ -223,6 +235,15 @@ export class JobScheduler {
223
235
  catch (logError) {
224
236
  console.error('Error logging job run error:', logError);
225
237
  }
238
+ // Send Slack notification for job failure (non-blocking)
239
+ import('../slack/slack-service.js').then(({ getSlackService }) => {
240
+ getSlackService().sendNotification({
241
+ type: 'job.failed',
242
+ jobId: job.id,
243
+ jobName: job.name || job.id,
244
+ error: error?.message || 'Unknown error',
245
+ }).catch(err => console.warn('[jobs] Slack notification failed:', err));
246
+ }).catch(() => { });
226
247
  }
227
248
  try {
228
249
  await this.store.saveJob(job);
@@ -0,0 +1,19 @@
1
+ interface MailRunResult {
2
+ status: 'succeeded' | 'failed';
3
+ summary?: string;
4
+ outputPath?: string;
5
+ error?: string;
6
+ cliCommand: string;
7
+ }
8
+ export declare class MailRunner {
9
+ private projectRootPromise;
10
+ constructor();
11
+ private ensureCliAvailable;
12
+ private ensureLogPath;
13
+ private loadSystemPrompt;
14
+ private loadConfigModel;
15
+ private buildInstruction;
16
+ run(mailId: string, mailFilePath: string): Promise<MailRunResult>;
17
+ }
18
+ export declare function getMailRunner(): MailRunner;
19
+ export {};
@@ -0,0 +1,223 @@
1
+ import { spawn, spawnSync } from 'child_process';
2
+ import { createWriteStream } from 'fs';
3
+ import { promises as fs } from 'fs';
4
+ import path from 'path';
5
+ import { getProjectRoot } from '../project-paths.js';
6
+ import { getInjectedEnv } from '../env-injection.js';
7
+ import { setMailProcessing } from '@lovelybunch/core';
8
+ function shellQuote(value) {
9
+ if (value === '')
10
+ return "''";
11
+ return `'${value.replace(/'/g, "'\\''")}'`;
12
+ }
13
+ function resolveAgent(model) {
14
+ if (!model)
15
+ return 'claude';
16
+ const lower = model.toLowerCase();
17
+ if (lower.includes('gemini'))
18
+ return 'gemini';
19
+ if (lower.includes('codex') || lower.includes('gpt') || lower.includes('openai'))
20
+ return 'codex';
21
+ if (lower.includes('droid') || lower.includes('factory'))
22
+ return 'droid';
23
+ return 'claude';
24
+ }
25
+ function buildCommand(agent, instruction, runningAsRoot) {
26
+ const quotedInstruction = shellQuote(instruction);
27
+ let mainCommand = '';
28
+ switch (agent) {
29
+ case 'gemini':
30
+ mainCommand = `gemini --yolo -i ${quotedInstruction}`;
31
+ break;
32
+ case 'codex': {
33
+ const baseCmd = `codex ${quotedInstruction} --dangerously-bypass-approvals-and-sandbox`.trim();
34
+ const needsPseudoTty = runningAsRoot && process.platform !== 'win32';
35
+ mainCommand = needsPseudoTty
36
+ ? `script -q -e -c ${shellQuote(baseCmd)} /dev/null`
37
+ : baseCmd;
38
+ break;
39
+ }
40
+ case 'droid':
41
+ mainCommand = `droid exec --auto high ${quotedInstruction}`.trim();
42
+ break;
43
+ case 'claude':
44
+ default: {
45
+ const prefix = runningAsRoot ? 'IS_SANDBOX=1 ' : '';
46
+ mainCommand = `${prefix}claude ${quotedInstruction} --dangerously-skip-permissions`.trim();
47
+ break;
48
+ }
49
+ }
50
+ return { command: agent === 'claude' ? 'claude' : agent, shellCommand: mainCommand };
51
+ }
52
+ const CLI_AGENT_LABEL = {
53
+ claude: 'Claude',
54
+ gemini: 'Gemini',
55
+ codex: 'Codex',
56
+ droid: 'Factory Droid'
57
+ };
58
+ const CLI_AGENT_BINARY = {
59
+ claude: 'claude',
60
+ gemini: 'gemini',
61
+ codex: 'codex',
62
+ droid: 'droid'
63
+ };
64
+ const DEFAULT_MAX_RUNTIME_MS = 15 * 60 * 1000; // 15 minutes for mail processing
65
+ function getMaxRuntime() {
66
+ const raw = process.env.COCONUT_MAIL_MAX_RUNTIME_MS;
67
+ if (!raw)
68
+ return DEFAULT_MAX_RUNTIME_MS;
69
+ const parsed = Number(raw);
70
+ if (!Number.isFinite(parsed) || parsed <= 0) {
71
+ return DEFAULT_MAX_RUNTIME_MS;
72
+ }
73
+ return parsed;
74
+ }
75
+ export class MailRunner {
76
+ projectRootPromise;
77
+ constructor() {
78
+ this.projectRootPromise = getProjectRoot();
79
+ }
80
+ ensureCliAvailable(agent) {
81
+ const binary = CLI_AGENT_BINARY[agent];
82
+ const result = spawnSync('bash', ['-lc', `command -v ${binary}`], { stdio: 'ignore' });
83
+ if (result.status !== 0) {
84
+ throw new Error(`${CLI_AGENT_LABEL[agent]} CLI ("${binary}") is not installed or not on PATH.`);
85
+ }
86
+ }
87
+ async ensureLogPath(mailId) {
88
+ const projectRoot = await this.projectRootPromise;
89
+ const logsDir = path.join(projectRoot, '.nut', 'mail', 'logs', mailId);
90
+ await fs.mkdir(logsDir, { recursive: true });
91
+ const runId = `run-${Date.now()}`;
92
+ return path.join(logsDir, `${runId}.log`);
93
+ }
94
+ async loadSystemPrompt() {
95
+ const projectRoot = await this.projectRootPromise;
96
+ // Look for the system prompt in the shared package (relative to project root)
97
+ const promptPath = path.join(projectRoot, 'packages', 'shared', 'system-prompts', 'mail-processor.md');
98
+ try {
99
+ return await fs.readFile(promptPath, 'utf-8');
100
+ }
101
+ catch (err) {
102
+ throw new Error(`Failed to load mail processor system prompt from ${promptPath}: ${err.message}`);
103
+ }
104
+ }
105
+ async loadConfigModel() {
106
+ const projectRoot = await this.projectRootPromise;
107
+ const configPath = path.join(projectRoot, '.nut', 'config.json');
108
+ try {
109
+ const raw = await fs.readFile(configPath, 'utf-8');
110
+ const config = JSON.parse(raw);
111
+ return config?.ai?.model;
112
+ }
113
+ catch {
114
+ return undefined;
115
+ }
116
+ }
117
+ async buildInstruction(mailId, mailFilePath) {
118
+ const systemPrompt = await this.loadSystemPrompt();
119
+ // Substitute placeholders in the system prompt
120
+ const instruction = systemPrompt
121
+ .replace(/\{\{mailFilePath\}\}/g, mailFilePath)
122
+ .replace(/\{\{mailId\}\}/g, mailId);
123
+ return instruction;
124
+ }
125
+ async run(mailId, mailFilePath) {
126
+ const model = await this.loadConfigModel();
127
+ const agent = resolveAgent(model);
128
+ const instruction = await this.buildInstruction(mailId, mailFilePath);
129
+ const runningAsRoot = typeof process.getuid === 'function' && process.getuid() === 0;
130
+ const { shellCommand } = buildCommand(agent, instruction, runningAsRoot);
131
+ const projectRoot = await this.projectRootPromise;
132
+ const logPath = await this.ensureLogPath(mailId);
133
+ const logStream = createWriteStream(logPath, { flags: 'a' });
134
+ const summaryChunks = [];
135
+ logStream.write(`[${new Date().toISOString()}] Starting mail processing for ${mailId} using ${agent} CLI\n`);
136
+ logStream.write(`Mail file: ${mailFilePath}\n`);
137
+ logStream.write(`Command: ${shellCommand}\n`);
138
+ return new Promise((resolve) => {
139
+ let cliMissingError = null;
140
+ try {
141
+ this.ensureCliAvailable(agent);
142
+ }
143
+ catch (error) {
144
+ cliMissingError = error instanceof Error ? error : new Error(String(error));
145
+ }
146
+ if (cliMissingError) {
147
+ const message = cliMissingError.message;
148
+ logStream.write(`${message}\n`);
149
+ logStream.end();
150
+ setMailProcessing(mailId, false).catch(err => console.warn('[mail] failed to clear processing:', err));
151
+ resolve({
152
+ status: 'failed',
153
+ error: message,
154
+ summary: message,
155
+ outputPath: path.relative(projectRoot, logPath),
156
+ cliCommand: shellCommand,
157
+ });
158
+ return;
159
+ }
160
+ const injectedEnv = getInjectedEnv();
161
+ const child = spawn('bash', ['-lc', shellCommand], {
162
+ cwd: projectRoot,
163
+ env: {
164
+ ...process.env,
165
+ ...injectedEnv
166
+ },
167
+ stdio: ['ignore', 'pipe', 'pipe'],
168
+ });
169
+ const maxRuntime = getMaxRuntime();
170
+ const abortTimeout = setTimeout(() => {
171
+ logStream.write(`\n[${new Date().toISOString()}] Max runtime ${maxRuntime}ms exceeded. Sending SIGTERM...\n`);
172
+ child.kill('SIGTERM');
173
+ setTimeout(() => child.kill('SIGKILL'), 10_000);
174
+ }, maxRuntime);
175
+ child.stdout?.on('data', (chunk) => {
176
+ const text = chunk.toString();
177
+ logStream.write(text);
178
+ summaryChunks.push(text);
179
+ });
180
+ child.stderr?.on('data', (chunk) => {
181
+ const text = chunk.toString();
182
+ logStream.write(text);
183
+ summaryChunks.push(text);
184
+ });
185
+ child.on('error', (error) => {
186
+ const message = `Failed to start CLI command: ${error.message}`;
187
+ logStream.write(`${message}\n`);
188
+ logStream.end();
189
+ clearTimeout(abortTimeout);
190
+ setMailProcessing(mailId, false).catch(err => console.warn('[mail] failed to clear processing:', err));
191
+ resolve({
192
+ status: 'failed',
193
+ error: message,
194
+ summary: summaryChunks.join('').slice(-600),
195
+ outputPath: path.relative(projectRoot, logPath),
196
+ cliCommand: shellCommand,
197
+ });
198
+ });
199
+ child.on('close', (code) => {
200
+ const status = code === 0 ? 'succeeded' : 'failed';
201
+ logStream.write(`\n[${new Date().toISOString()}] Mail processing for ${mailId} completed with exit code ${code}\n`);
202
+ logStream.end();
203
+ clearTimeout(abortTimeout);
204
+ setMailProcessing(mailId, false).catch(err => console.warn('[mail] failed to clear processing:', err));
205
+ const summary = summaryChunks.join('');
206
+ resolve({
207
+ status,
208
+ summary: summary.slice(Math.max(0, summary.length - 2000)),
209
+ outputPath: path.relative(projectRoot, logPath),
210
+ error: code === 0 ? undefined : `CLI exited with code ${code}`,
211
+ cliCommand: shellCommand,
212
+ });
213
+ });
214
+ });
215
+ }
216
+ }
217
+ let mailRunnerInstance = null;
218
+ export function getMailRunner() {
219
+ if (!mailRunnerInstance) {
220
+ mailRunnerInstance = new MailRunner();
221
+ }
222
+ return mailRunnerInstance;
223
+ }
@@ -0,0 +1,2 @@
1
+ export { SlackService, getSlackService, DEFAULT_SLACK_CONFIG, } from '@lovelybunch/core';
2
+ export type { SlackConfig, SlackNotificationSettings, SlackChannel, ProposalNotificationPayload, JobNotificationPayload, GitNotificationPayload, NotificationPayload, NotificationType, FreeformMessageOptions, } from '@lovelybunch/core';
@@ -0,0 +1,3 @@
1
+ // Re-export from @lovelybunch/core for backward compatibility
2
+ // All Slack logic now lives in packages/core/src/slack.ts
3
+ export { SlackService, getSlackService, DEFAULT_SLACK_CONFIG, } from '@lovelybunch/core';
@@ -188,12 +188,32 @@ async function executeTasksToolDirect(args, _storage) {
188
188
  try {
189
189
  switch (operation) {
190
190
  case 'list': {
191
+ const DEFAULT_LIST_LIMIT = 20;
192
+ const requestedLimit = filters?.limit;
193
+ const effectiveLimit = requestedLimit ?? DEFAULT_LIST_LIMIT;
191
194
  const tasks = await listTasks(filters || {});
195
+ const totalCount = tasks.length;
196
+ const limited = tasks.slice(0, effectiveLimit);
197
+ // Return lightweight summaries to keep token usage manageable.
198
+ // The AI can use "get" with a specific task ID to retrieve full details.
199
+ const summaries = limited.map((t) => ({
200
+ id: t.id,
201
+ title: t.title,
202
+ status: t.status,
203
+ priority: t.metadata?.priority,
204
+ tags: t.metadata?.tags,
205
+ author: t.author ? { name: t.author.name, type: t.author.type } : undefined,
206
+ createdAt: t.metadata?.createdAt,
207
+ updatedAt: t.metadata?.updatedAt,
208
+ }));
192
209
  return {
193
210
  success: true,
194
- data: tasks,
195
- count: tasks.length,
196
- message: `Found ${tasks.length} tasks`
211
+ data: summaries,
212
+ count: summaries.length,
213
+ totalCount,
214
+ message: totalCount > effectiveLimit
215
+ ? `Showing ${summaries.length} of ${totalCount} tasks (limit: ${effectiveLimit}). Use filters or increase limit to see more. Use "get" with a task ID for full details.`
216
+ : `Found ${totalCount} tasks. Use "get" with a task ID for full details.`
197
217
  };
198
218
  }
199
219
  case 'get': {
@@ -331,6 +331,22 @@ app.post('/branches/:branch/merge', async (c) => {
331
331
  catch { }
332
332
  const mergeStrategy = strategy?.strategy === 'squash' || strategy?.strategy === 'rebase' ? strategy.strategy : 'merge';
333
333
  const result = await mergeBranch(name, mergeStrategy);
334
+ // Determine target branch for notification
335
+ try {
336
+ const { runGit } = await import('../../../../lib/git.js');
337
+ const { stdout: branchOutput } = await runGit(['branch', '--show-current']);
338
+ const targetBranch = branchOutput.trim();
339
+ // Send Slack notification (non-blocking)
340
+ import('../../../../lib/slack/slack-service.js').then(({ getSlackService }) => {
341
+ getSlackService().sendNotification({
342
+ type: 'git.merge',
343
+ branch: name,
344
+ targetBranch,
345
+ message: `Merged ${name} into ${targetBranch} using ${mergeStrategy}`,
346
+ }).catch(err => console.warn('[git] Slack notification failed:', err));
347
+ }).catch(() => { });
348
+ }
349
+ catch { }
334
350
  return c.json({ success: true, data: { branch: name, strategy: mergeStrategy, result } });
335
351
  }
336
352
  catch (e) {
@@ -466,6 +482,13 @@ app.post('/push', async (c) => {
466
482
  remote: remoteName,
467
483
  }
468
484
  });
485
+ // Send Slack notification (non-blocking)
486
+ import('../../../../lib/slack/slack-service.js').then(({ getSlackService }) => {
487
+ getSlackService().sendNotification({
488
+ type: 'git.push',
489
+ branch: currentBranch,
490
+ }).catch(err => console.warn('[git] Slack notification failed:', err));
491
+ }).catch(() => { });
469
492
  }
470
493
  catch (logError) {
471
494
  console.error('Error logging push:', logError);
@@ -0,0 +1,3 @@
1
+ import { Hono } from 'hono';
2
+ declare const mailRoutes: Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
3
+ export { mailRoutes };
@@ -0,0 +1,20 @@
1
+ import { Hono } from 'hono';
2
+ import { listMailHandler, getMailHandler, setMailStatusHandler, setMailActionHandler, replyMailHandler, sendMailHandler, deleteMailHandler, inboundWebhookHandler, } from './route.js';
3
+ const mailRoutes = new Hono();
4
+ // Resend inbound webhook
5
+ mailRoutes.post('/inbound', inboundWebhookHandler);
6
+ // Send email (coming soon)
7
+ mailRoutes.post('/send', sendMailHandler);
8
+ // Set email status (read/unread)
9
+ mailRoutes.put('/:id/status', setMailStatusHandler);
10
+ // Set agent action summary
11
+ mailRoutes.put('/:id/action', setMailActionHandler);
12
+ // Reply to email
13
+ mailRoutes.post('/:id/reply', replyMailHandler);
14
+ // List emails in folder
15
+ mailRoutes.get('/:folder', listMailHandler);
16
+ // Get specific email in folder
17
+ mailRoutes.get('/:folder/:id', getMailHandler);
18
+ // Delete email from folder
19
+ mailRoutes.delete('/:folder/:id', deleteMailHandler);
20
+ export { mailRoutes };
@@ -0,0 +1,252 @@
1
+ import { Context } from 'hono';
2
+ import type { MailFolder } from '@lovelybunch/types';
3
+ /**
4
+ * GET /api/v1/mail/:folder
5
+ * List emails in a folder
6
+ */
7
+ export declare function listMailHandler(c: Context): Promise<(Response & import("hono").TypedResponse<{
8
+ success: false;
9
+ error: string;
10
+ }, 400, "json">) | (Response & import("hono").TypedResponse<{
11
+ success: true;
12
+ data: {
13
+ messages: {
14
+ id: string;
15
+ emailId: string;
16
+ from: string;
17
+ to: string[];
18
+ cc: string[];
19
+ bcc: string[];
20
+ subject: string;
21
+ messageId: string;
22
+ attachments: {
23
+ id: string;
24
+ filename: string;
25
+ contentType: string;
26
+ contentDisposition?: string;
27
+ contentId?: string;
28
+ }[];
29
+ receivedAt: string;
30
+ content: string;
31
+ folder: MailFolder;
32
+ action?: string;
33
+ processing?: boolean;
34
+ }[];
35
+ };
36
+ }, import("hono/utils/http-status").ContentfulStatusCode, "json">) | (Response & import("hono").TypedResponse<{
37
+ success: false;
38
+ error: any;
39
+ }, 500, "json">)>;
40
+ /**
41
+ * GET /api/v1/mail/:folder/:id
42
+ * Get a specific email
43
+ */
44
+ export declare function getMailHandler(c: Context): Promise<(Response & import("hono").TypedResponse<{
45
+ success: false;
46
+ error: string;
47
+ }, 400, "json">) | (Response & import("hono").TypedResponse<{
48
+ success: false;
49
+ error: string;
50
+ }, 404, "json">) | (Response & import("hono").TypedResponse<{
51
+ success: true;
52
+ data: {
53
+ message: {
54
+ id: string;
55
+ emailId: string;
56
+ from: string;
57
+ to: string[];
58
+ cc: string[];
59
+ bcc: string[];
60
+ subject: string;
61
+ messageId: string;
62
+ attachments: {
63
+ id: string;
64
+ filename: string;
65
+ contentType: string;
66
+ contentDisposition?: string;
67
+ contentId?: string;
68
+ }[];
69
+ receivedAt: string;
70
+ content: string;
71
+ folder: MailFolder;
72
+ action?: string;
73
+ processing?: boolean;
74
+ };
75
+ };
76
+ }, import("hono/utils/http-status").ContentfulStatusCode, "json">) | (Response & import("hono").TypedResponse<{
77
+ success: false;
78
+ error: any;
79
+ }, 500, "json">)>;
80
+ /**
81
+ * PUT /api/v1/mail/:id/status
82
+ * Set email read/unread status
83
+ */
84
+ export declare function setMailStatusHandler(c: Context): Promise<(Response & import("hono").TypedResponse<{
85
+ success: false;
86
+ error: string;
87
+ }, 400, "json">) | (Response & import("hono").TypedResponse<{
88
+ success: true;
89
+ data: {
90
+ message: {
91
+ id: string;
92
+ emailId: string;
93
+ from: string;
94
+ to: string[];
95
+ cc: string[];
96
+ bcc: string[];
97
+ subject: string;
98
+ messageId: string;
99
+ attachments: {
100
+ id: string;
101
+ filename: string;
102
+ contentType: string;
103
+ contentDisposition?: string;
104
+ contentId?: string;
105
+ }[];
106
+ receivedAt: string;
107
+ content: string;
108
+ folder: MailFolder;
109
+ action?: string;
110
+ processing?: boolean;
111
+ };
112
+ };
113
+ }, import("hono/utils/http-status").ContentfulStatusCode, "json">) | (Response & import("hono").TypedResponse<{
114
+ success: false;
115
+ error: any;
116
+ }, 404 | 500, "json">)>;
117
+ /**
118
+ * POST /api/v1/mail/:id/reply
119
+ * Reply to an email
120
+ */
121
+ export declare function replyMailHandler(c: Context): Promise<(Response & import("hono").TypedResponse<{
122
+ success: false;
123
+ error: string;
124
+ }, 400, "json">) | (Response & import("hono").TypedResponse<{
125
+ success: true;
126
+ data: {
127
+ message: {
128
+ id: string;
129
+ emailId: string;
130
+ from: string;
131
+ to: string[];
132
+ cc: string[];
133
+ bcc: string[];
134
+ subject: string;
135
+ messageId: string;
136
+ attachments: {
137
+ id: string;
138
+ filename: string;
139
+ contentType: string;
140
+ contentDisposition?: string;
141
+ contentId?: string;
142
+ }[];
143
+ receivedAt: string;
144
+ content: string;
145
+ folder: MailFolder;
146
+ action?: string;
147
+ processing?: boolean;
148
+ };
149
+ };
150
+ }, import("hono/utils/http-status").ContentfulStatusCode, "json">) | (Response & import("hono").TypedResponse<{
151
+ success: false;
152
+ error: any;
153
+ }, 404 | 500, "json">)>;
154
+ /**
155
+ * POST /api/v1/mail/send
156
+ * Send an email (coming soon)
157
+ */
158
+ export declare function sendMailHandler(c: Context): Promise<Response & import("hono").TypedResponse<{
159
+ success: false;
160
+ error: string;
161
+ }, 501, "json">>;
162
+ /**
163
+ * DELETE /api/v1/mail/:folder/:id
164
+ * Delete an email
165
+ */
166
+ export declare function deleteMailHandler(c: Context): Promise<(Response & import("hono").TypedResponse<{
167
+ success: false;
168
+ error: string;
169
+ }, 400, "json">) | (Response & import("hono").TypedResponse<{
170
+ success: false;
171
+ error: string;
172
+ }, 404, "json">) | (Response & import("hono").TypedResponse<{
173
+ success: true;
174
+ message: string;
175
+ }, import("hono/utils/http-status").ContentfulStatusCode, "json">) | (Response & import("hono").TypedResponse<{
176
+ success: false;
177
+ error: any;
178
+ }, 500, "json">)>;
179
+ /**
180
+ * PUT /api/v1/mail/:id/action
181
+ * Set agent action summary on an email
182
+ */
183
+ export declare function setMailActionHandler(c: Context): Promise<(Response & import("hono").TypedResponse<{
184
+ success: false;
185
+ error: string;
186
+ }, 400, "json">) | (Response & import("hono").TypedResponse<{
187
+ success: true;
188
+ data: {
189
+ message: {
190
+ id: string;
191
+ emailId: string;
192
+ from: string;
193
+ to: string[];
194
+ cc: string[];
195
+ bcc: string[];
196
+ subject: string;
197
+ messageId: string;
198
+ attachments: {
199
+ id: string;
200
+ filename: string;
201
+ contentType: string;
202
+ contentDisposition?: string;
203
+ contentId?: string;
204
+ }[];
205
+ receivedAt: string;
206
+ content: string;
207
+ folder: MailFolder;
208
+ action?: string;
209
+ processing?: boolean;
210
+ };
211
+ };
212
+ }, import("hono/utils/http-status").ContentfulStatusCode, "json">) | (Response & import("hono").TypedResponse<{
213
+ success: false;
214
+ error: any;
215
+ }, 404 | 500, "json">)>;
216
+ /**
217
+ * POST /api/v1/mail/inbound
218
+ * Receive email via Resend webhook
219
+ */
220
+ export declare function inboundWebhookHandler(c: Context): Promise<(Response & import("hono").TypedResponse<{
221
+ success: false;
222
+ error: string;
223
+ }, 400, "json">) | (Response & import("hono").TypedResponse<{
224
+ success: true;
225
+ data: {
226
+ message: {
227
+ id: string;
228
+ emailId: string;
229
+ from: string;
230
+ to: string[];
231
+ cc: string[];
232
+ bcc: string[];
233
+ subject: string;
234
+ messageId: string;
235
+ attachments: {
236
+ id: string;
237
+ filename: string;
238
+ contentType: string;
239
+ contentDisposition?: string;
240
+ contentId?: string;
241
+ }[];
242
+ receivedAt: string;
243
+ content: string;
244
+ folder: MailFolder;
245
+ action?: string;
246
+ processing?: boolean;
247
+ };
248
+ };
249
+ }, 201, "json">) | (Response & import("hono").TypedResponse<{
250
+ success: false;
251
+ error: any;
252
+ }, 500, "json">)>;