kimaki 0.4.19 → 0.4.20
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.
- package/dist/discordBot.js +46 -38
- package/package.json +3 -3
- package/src/discordBot.ts +53 -42
package/dist/discordBot.js
CHANGED
|
@@ -1039,14 +1039,19 @@ function formatPart(part) {
|
|
|
1039
1039
|
const isSingleLine = !command.includes('\n');
|
|
1040
1040
|
const hasBackticks = command.includes('`');
|
|
1041
1041
|
if (isSingleLine && command.length <= 120 && !hasBackticks) {
|
|
1042
|
-
toolTitle =
|
|
1042
|
+
toolTitle = `_${command}_`;
|
|
1043
1043
|
}
|
|
1044
1044
|
else {
|
|
1045
|
-
toolTitle = stateTitle ?
|
|
1045
|
+
toolTitle = stateTitle ? `_${stateTitle}_` : '';
|
|
1046
1046
|
}
|
|
1047
1047
|
}
|
|
1048
|
+
else if (part.tool === 'edit' || part.tool === 'write') {
|
|
1049
|
+
const filePath = part.state.input?.filePath || '';
|
|
1050
|
+
const fileName = filePath.split('/').pop() || filePath;
|
|
1051
|
+
toolTitle = fileName ? `_${fileName}_` : '';
|
|
1052
|
+
}
|
|
1048
1053
|
else if (stateTitle) {
|
|
1049
|
-
toolTitle =
|
|
1054
|
+
toolTitle = `_${stateTitle}_`;
|
|
1050
1055
|
}
|
|
1051
1056
|
const icon = part.state.status === 'error' ? '⨯' : '◼︎';
|
|
1052
1057
|
return `${icon} ${part.tool} ${toolTitle} ${summaryText}`;
|
|
@@ -1074,16 +1079,6 @@ async function handleOpencodeSession({ prompt, thread, projectDirectory, origina
|
|
|
1074
1079
|
voiceLogger.log(`[OPENCODE SESSION] Starting for thread ${thread.id} with prompt: "${prompt.slice(0, 50)}${prompt.length > 50 ? '...' : ''}"`);
|
|
1075
1080
|
// Track session start time
|
|
1076
1081
|
const sessionStartTime = Date.now();
|
|
1077
|
-
// Add processing reaction to original message
|
|
1078
|
-
if (originalMessage) {
|
|
1079
|
-
try {
|
|
1080
|
-
await originalMessage.react('⏳');
|
|
1081
|
-
discordLogger.log(`Added processing reaction to message`);
|
|
1082
|
-
}
|
|
1083
|
-
catch (e) {
|
|
1084
|
-
discordLogger.log(`Could not add processing reaction:`, e);
|
|
1085
|
-
}
|
|
1086
|
-
}
|
|
1087
1082
|
// Use default directory if not specified
|
|
1088
1083
|
const directory = projectDirectory || process.cwd();
|
|
1089
1084
|
sessionLogger.log(`Using directory: ${directory}`);
|
|
@@ -1134,34 +1129,32 @@ async function handleOpencodeSession({ prompt, thread, projectDirectory, origina
|
|
|
1134
1129
|
voiceLogger.log(`[ABORT] Cancelling existing request for session: ${session.id}`);
|
|
1135
1130
|
existingController.abort(new Error('New request started'));
|
|
1136
1131
|
}
|
|
1137
|
-
if (abortControllers.has(session.id)) {
|
|
1138
|
-
abortControllers.get(session.id)?.abort(new Error('new reply'));
|
|
1139
|
-
}
|
|
1140
1132
|
const abortController = new AbortController();
|
|
1141
|
-
// Store this controller for this session
|
|
1142
1133
|
abortControllers.set(session.id, abortController);
|
|
1134
|
+
if (existingController) {
|
|
1135
|
+
await new Promise((resolve) => { setTimeout(resolve, 200); });
|
|
1136
|
+
if (abortController.signal.aborted) {
|
|
1137
|
+
sessionLogger.log(`[DEBOUNCE] Request was superseded during wait, exiting`);
|
|
1138
|
+
return;
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
if (abortController.signal.aborted) {
|
|
1142
|
+
sessionLogger.log(`[DEBOUNCE] Aborted before subscribe, exiting`);
|
|
1143
|
+
return;
|
|
1144
|
+
}
|
|
1143
1145
|
const eventsResult = await getClient().event.subscribe({
|
|
1144
1146
|
signal: abortController.signal,
|
|
1145
1147
|
});
|
|
1148
|
+
if (abortController.signal.aborted) {
|
|
1149
|
+
sessionLogger.log(`[DEBOUNCE] Aborted during subscribe, exiting`);
|
|
1150
|
+
return;
|
|
1151
|
+
}
|
|
1146
1152
|
const events = eventsResult.stream;
|
|
1147
1153
|
sessionLogger.log(`Subscribed to OpenCode events`);
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
.
|
|
1152
|
-
.all(thread.id);
|
|
1153
|
-
// Pre-populate map with existing messages
|
|
1154
|
-
for (const row of existingParts) {
|
|
1155
|
-
try {
|
|
1156
|
-
const message = await thread.messages.fetch(row.message_id);
|
|
1157
|
-
if (message) {
|
|
1158
|
-
partIdToMessage.set(row.part_id, message);
|
|
1159
|
-
}
|
|
1160
|
-
}
|
|
1161
|
-
catch (error) {
|
|
1162
|
-
voiceLogger.log(`Could not fetch message ${row.message_id} for part ${row.part_id}`);
|
|
1163
|
-
}
|
|
1164
|
-
}
|
|
1154
|
+
const sentPartIds = new Set(getDatabase()
|
|
1155
|
+
.prepare('SELECT part_id FROM part_messages WHERE thread_id = ?')
|
|
1156
|
+
.all(thread.id)
|
|
1157
|
+
.map((row) => row.part_id));
|
|
1165
1158
|
let currentParts = [];
|
|
1166
1159
|
let stopTyping = null;
|
|
1167
1160
|
let usedModel;
|
|
@@ -1174,12 +1167,12 @@ async function handleOpencodeSession({ prompt, thread, projectDirectory, origina
|
|
|
1174
1167
|
return;
|
|
1175
1168
|
}
|
|
1176
1169
|
// Skip if already sent
|
|
1177
|
-
if (
|
|
1170
|
+
if (sentPartIds.has(part.id)) {
|
|
1178
1171
|
return;
|
|
1179
1172
|
}
|
|
1180
1173
|
try {
|
|
1181
1174
|
const firstMessage = await sendThreadMessage(thread, content);
|
|
1182
|
-
|
|
1175
|
+
sentPartIds.add(part.id);
|
|
1183
1176
|
// Store part-message mapping in database
|
|
1184
1177
|
getDatabase()
|
|
1185
1178
|
.prepare('INSERT OR REPLACE INTO part_messages (part_id, message_id, thread_id) VALUES (?, ?, ?)')
|
|
@@ -1275,6 +1268,10 @@ async function handleOpencodeSession({ prompt, thread, projectDirectory, origina
|
|
|
1275
1268
|
if (part.type === 'tool' && part.state.status === 'running') {
|
|
1276
1269
|
await sendPartMessage(part);
|
|
1277
1270
|
}
|
|
1271
|
+
// Send reasoning parts immediately (shows "◼︎ thinking" indicator early)
|
|
1272
|
+
if (part.type === 'reasoning') {
|
|
1273
|
+
await sendPartMessage(part);
|
|
1274
|
+
}
|
|
1278
1275
|
// Check if this is a step-finish part
|
|
1279
1276
|
if (part.type === 'step-finish') {
|
|
1280
1277
|
// Send all parts accumulated so far to Discord
|
|
@@ -1361,7 +1358,7 @@ async function handleOpencodeSession({ prompt, thread, projectDirectory, origina
|
|
|
1361
1358
|
finally {
|
|
1362
1359
|
// Send any remaining parts that weren't sent
|
|
1363
1360
|
for (const part of currentParts) {
|
|
1364
|
-
if (!
|
|
1361
|
+
if (!sentPartIds.has(part.id)) {
|
|
1365
1362
|
try {
|
|
1366
1363
|
await sendPartMessage(part);
|
|
1367
1364
|
}
|
|
@@ -1403,8 +1400,19 @@ async function handleOpencodeSession({ prompt, thread, projectDirectory, origina
|
|
|
1403
1400
|
}
|
|
1404
1401
|
};
|
|
1405
1402
|
try {
|
|
1406
|
-
// Start the event handler
|
|
1407
1403
|
const eventHandlerPromise = eventHandler();
|
|
1404
|
+
if (abortController.signal.aborted) {
|
|
1405
|
+
sessionLogger.log(`[DEBOUNCE] Aborted before prompt, exiting`);
|
|
1406
|
+
return;
|
|
1407
|
+
}
|
|
1408
|
+
if (originalMessage) {
|
|
1409
|
+
try {
|
|
1410
|
+
await originalMessage.react('⏳');
|
|
1411
|
+
}
|
|
1412
|
+
catch (e) {
|
|
1413
|
+
discordLogger.log(`Could not add processing reaction:`, e);
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1408
1416
|
let response;
|
|
1409
1417
|
if (parsedCommand?.isCommand) {
|
|
1410
1418
|
sessionLogger.log(`[COMMAND] Sending command /${parsedCommand.command} to session ${session.id} with args: "${parsedCommand.arguments.slice(0, 100)}${parsedCommand.arguments.length > 100 ? '...' : ''}"`);
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "kimaki",
|
|
3
3
|
"module": "index.ts",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "0.4.
|
|
5
|
+
"version": "0.4.20",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"dev": "tsx --env-file .env src/cli.ts",
|
|
8
8
|
"prepublishOnly": "pnpm tsc",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"bin.js"
|
|
22
22
|
],
|
|
23
23
|
"devDependencies": {
|
|
24
|
-
"@opencode-ai/plugin": "^1.0.
|
|
24
|
+
"@opencode-ai/plugin": "^1.0.193",
|
|
25
25
|
"@types/better-sqlite3": "^7.6.13",
|
|
26
26
|
"@types/bun": "latest",
|
|
27
27
|
"@types/js-yaml": "^4.0.9",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"@discordjs/opus": "^0.10.0",
|
|
36
36
|
"@discordjs/voice": "^0.19.0",
|
|
37
37
|
"@google/genai": "^1.34.0",
|
|
38
|
-
"@opencode-ai/sdk": "^1.0.
|
|
38
|
+
"@opencode-ai/sdk": "^1.0.193",
|
|
39
39
|
"@purinton/resampler": "^1.0.4",
|
|
40
40
|
"@snazzah/davey": "^0.1.6",
|
|
41
41
|
"ai": "^5.0.114",
|
package/src/discordBot.ts
CHANGED
|
@@ -1375,12 +1375,16 @@ function formatPart(part: Part): string {
|
|
|
1375
1375
|
const isSingleLine = !command.includes('\n')
|
|
1376
1376
|
const hasBackticks = command.includes('`')
|
|
1377
1377
|
if (isSingleLine && command.length <= 120 && !hasBackticks) {
|
|
1378
|
-
toolTitle =
|
|
1378
|
+
toolTitle = `_${command}_`
|
|
1379
1379
|
} else {
|
|
1380
|
-
toolTitle = stateTitle ?
|
|
1380
|
+
toolTitle = stateTitle ? `_${stateTitle}_` : ''
|
|
1381
1381
|
}
|
|
1382
|
+
} else if (part.tool === 'edit' || part.tool === 'write') {
|
|
1383
|
+
const filePath = (part.state.input?.filePath as string) || ''
|
|
1384
|
+
const fileName = filePath.split('/').pop() || filePath
|
|
1385
|
+
toolTitle = fileName ? `_${fileName}_` : ''
|
|
1382
1386
|
} else if (stateTitle) {
|
|
1383
|
-
toolTitle =
|
|
1387
|
+
toolTitle = `_${stateTitle}_`
|
|
1384
1388
|
}
|
|
1385
1389
|
|
|
1386
1390
|
const icon = part.state.status === 'error' ? '⨯' : '◼︎'
|
|
@@ -1430,16 +1434,6 @@ async function handleOpencodeSession({
|
|
|
1430
1434
|
// Track session start time
|
|
1431
1435
|
const sessionStartTime = Date.now()
|
|
1432
1436
|
|
|
1433
|
-
// Add processing reaction to original message
|
|
1434
|
-
if (originalMessage) {
|
|
1435
|
-
try {
|
|
1436
|
-
await originalMessage.react('⏳')
|
|
1437
|
-
discordLogger.log(`Added processing reaction to message`)
|
|
1438
|
-
} catch (e) {
|
|
1439
|
-
discordLogger.log(`Could not add processing reaction:`, e)
|
|
1440
|
-
}
|
|
1441
|
-
}
|
|
1442
|
-
|
|
1443
1437
|
// Use default directory if not specified
|
|
1444
1438
|
const directory = projectDirectory || process.cwd()
|
|
1445
1439
|
sessionLogger.log(`Using directory: ${directory}`)
|
|
@@ -1507,40 +1501,40 @@ async function handleOpencodeSession({
|
|
|
1507
1501
|
existingController.abort(new Error('New request started'))
|
|
1508
1502
|
}
|
|
1509
1503
|
|
|
1510
|
-
if (abortControllers.has(session.id)) {
|
|
1511
|
-
abortControllers.get(session.id)?.abort(new Error('new reply'))
|
|
1512
|
-
}
|
|
1513
1504
|
const abortController = new AbortController()
|
|
1514
|
-
// Store this controller for this session
|
|
1515
1505
|
abortControllers.set(session.id, abortController)
|
|
1516
1506
|
|
|
1507
|
+
if (existingController) {
|
|
1508
|
+
await new Promise((resolve) => { setTimeout(resolve, 200) })
|
|
1509
|
+
if (abortController.signal.aborted) {
|
|
1510
|
+
sessionLogger.log(`[DEBOUNCE] Request was superseded during wait, exiting`)
|
|
1511
|
+
return
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
if (abortController.signal.aborted) {
|
|
1516
|
+
sessionLogger.log(`[DEBOUNCE] Aborted before subscribe, exiting`)
|
|
1517
|
+
return
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1517
1520
|
const eventsResult = await getClient().event.subscribe({
|
|
1518
1521
|
signal: abortController.signal,
|
|
1519
1522
|
})
|
|
1523
|
+
|
|
1524
|
+
if (abortController.signal.aborted) {
|
|
1525
|
+
sessionLogger.log(`[DEBOUNCE] Aborted during subscribe, exiting`)
|
|
1526
|
+
return
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1520
1529
|
const events = eventsResult.stream
|
|
1521
1530
|
sessionLogger.log(`Subscribed to OpenCode events`)
|
|
1522
1531
|
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
.all(thread.id) as { part_id: string; message_id: string }[]
|
|
1530
|
-
|
|
1531
|
-
// Pre-populate map with existing messages
|
|
1532
|
-
for (const row of existingParts) {
|
|
1533
|
-
try {
|
|
1534
|
-
const message = await thread.messages.fetch(row.message_id)
|
|
1535
|
-
if (message) {
|
|
1536
|
-
partIdToMessage.set(row.part_id, message)
|
|
1537
|
-
}
|
|
1538
|
-
} catch (error) {
|
|
1539
|
-
voiceLogger.log(
|
|
1540
|
-
`Could not fetch message ${row.message_id} for part ${row.part_id}`,
|
|
1541
|
-
)
|
|
1542
|
-
}
|
|
1543
|
-
}
|
|
1532
|
+
const sentPartIds = new Set<string>(
|
|
1533
|
+
(getDatabase()
|
|
1534
|
+
.prepare('SELECT part_id FROM part_messages WHERE thread_id = ?')
|
|
1535
|
+
.all(thread.id) as { part_id: string }[])
|
|
1536
|
+
.map((row) => row.part_id)
|
|
1537
|
+
)
|
|
1544
1538
|
|
|
1545
1539
|
let currentParts: Part[] = []
|
|
1546
1540
|
let stopTyping: (() => void) | null = null
|
|
@@ -1556,13 +1550,13 @@ async function handleOpencodeSession({
|
|
|
1556
1550
|
}
|
|
1557
1551
|
|
|
1558
1552
|
// Skip if already sent
|
|
1559
|
-
if (
|
|
1553
|
+
if (sentPartIds.has(part.id)) {
|
|
1560
1554
|
return
|
|
1561
1555
|
}
|
|
1562
1556
|
|
|
1563
1557
|
try {
|
|
1564
1558
|
const firstMessage = await sendThreadMessage(thread, content)
|
|
1565
|
-
|
|
1559
|
+
sentPartIds.add(part.id)
|
|
1566
1560
|
|
|
1567
1561
|
// Store part-message mapping in database
|
|
1568
1562
|
getDatabase()
|
|
@@ -1686,6 +1680,11 @@ async function handleOpencodeSession({
|
|
|
1686
1680
|
await sendPartMessage(part)
|
|
1687
1681
|
}
|
|
1688
1682
|
|
|
1683
|
+
// Send reasoning parts immediately (shows "◼︎ thinking" indicator early)
|
|
1684
|
+
if (part.type === 'reasoning') {
|
|
1685
|
+
await sendPartMessage(part)
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1689
1688
|
// Check if this is a step-finish part
|
|
1690
1689
|
if (part.type === 'step-finish') {
|
|
1691
1690
|
|
|
@@ -1790,7 +1789,7 @@ async function handleOpencodeSession({
|
|
|
1790
1789
|
} finally {
|
|
1791
1790
|
// Send any remaining parts that weren't sent
|
|
1792
1791
|
for (const part of currentParts) {
|
|
1793
|
-
if (!
|
|
1792
|
+
if (!sentPartIds.has(part.id)) {
|
|
1794
1793
|
try {
|
|
1795
1794
|
await sendPartMessage(part)
|
|
1796
1795
|
} catch (error) {
|
|
@@ -1841,9 +1840,21 @@ async function handleOpencodeSession({
|
|
|
1841
1840
|
}
|
|
1842
1841
|
|
|
1843
1842
|
try {
|
|
1844
|
-
// Start the event handler
|
|
1845
1843
|
const eventHandlerPromise = eventHandler()
|
|
1846
1844
|
|
|
1845
|
+
if (abortController.signal.aborted) {
|
|
1846
|
+
sessionLogger.log(`[DEBOUNCE] Aborted before prompt, exiting`)
|
|
1847
|
+
return
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
if (originalMessage) {
|
|
1851
|
+
try {
|
|
1852
|
+
await originalMessage.react('⏳')
|
|
1853
|
+
} catch (e) {
|
|
1854
|
+
discordLogger.log(`Could not add processing reaction:`, e)
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1847
1858
|
let response: { data?: unknown; error?: unknown; response: Response }
|
|
1848
1859
|
if (parsedCommand?.isCommand) {
|
|
1849
1860
|
sessionLogger.log(
|