kimaki 0.3.0 → 0.3.2

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.
@@ -718,22 +718,29 @@ function formatPart(part) {
718
718
  return `▪︎ thinking: ${escapeDiscordFormatting(part.text || '')}`;
719
719
  case 'tool':
720
720
  if (part.state.status === 'completed' || part.state.status === 'error') {
721
- // console.log(part)
722
- // Escape triple backticks so Discord does not break code blocks
723
721
  let language = '';
724
722
  let outputToDisplay = '';
723
+ let summaryText = '';
725
724
  if (part.tool === 'bash') {
726
- outputToDisplay =
727
- part.state.status === 'completed'
728
- ? part.state.output
729
- : part.state.error;
730
- outputToDisplay ||= '';
725
+ const output = part.state.status === 'completed'
726
+ ? part.state.output
727
+ : part.state.error;
728
+ const lines = (output || '').split('\n').filter((l) => l.trim());
729
+ summaryText = `(${lines.length} line${lines.length === 1 ? '' : 's'})`;
731
730
  }
732
- if (part.tool === 'edit') {
733
- outputToDisplay = part.state.input?.newString || '';
734
- language = path.extname(part.state.input.filePath || '');
731
+ else if (part.tool === 'edit') {
732
+ const newString = part.state.input?.newString || '';
733
+ const oldString = part.state.input?.oldString || '';
734
+ const added = newString.split('\n').length;
735
+ const removed = oldString.split('\n').length;
736
+ summaryText = `(+${added}-${removed})`;
735
737
  }
736
- if (part.tool === 'todowrite') {
738
+ else if (part.tool === 'write') {
739
+ const content = part.state.input?.content || '';
740
+ const lines = content.split('\n').length;
741
+ summaryText = `(${lines} line${lines === 1 ? '' : 's'})`;
742
+ }
743
+ else if (part.tool === 'todowrite') {
737
744
  const todos = part.state.input?.todos || [];
738
745
  outputToDisplay = todos
739
746
  .map((todo) => {
@@ -756,20 +763,27 @@ function formatPart(part) {
756
763
  })
757
764
  .filter(Boolean)
758
765
  .join('\n');
759
- language = '';
760
766
  }
761
- if (part.tool === 'write') {
762
- outputToDisplay = part.state.input?.content || '';
763
- language = path.extname(part.state.input.filePath || '');
767
+ else if (part.tool === 'webfetch') {
768
+ const url = part.state.input?.url || '';
769
+ const urlWithoutProtocol = url.replace(/^https?:\/\//, '');
770
+ summaryText = urlWithoutProtocol ? `(${urlWithoutProtocol})` : '';
771
+ }
772
+ else if (part.state.input) {
773
+ const inputFields = Object.entries(part.state.input)
774
+ .map(([key, value]) => {
775
+ if (value === null || value === undefined)
776
+ return null;
777
+ const stringValue = typeof value === 'string' ? value : JSON.stringify(value);
778
+ const truncatedValue = stringValue.length > 100 ? stringValue.slice(0, 100) + '…' : stringValue;
779
+ return `${key}: ${truncatedValue}`;
780
+ })
781
+ .filter(Boolean);
782
+ if (inputFields.length > 0) {
783
+ outputToDisplay = inputFields.join('\n');
784
+ }
764
785
  }
765
- outputToDisplay =
766
- outputToDisplay.length > 500
767
- ? outputToDisplay.slice(0, 497) + `…`
768
- : outputToDisplay;
769
- // Escape Discord formatting characters that could break code blocks
770
- outputToDisplay = escapeDiscordFormatting(outputToDisplay);
771
786
  let toolTitle = part.state.status === 'completed' ? part.state.title || '' : 'error';
772
- // Escape backticks in the title before wrapping in backticks
773
787
  if (toolTitle) {
774
788
  toolTitle = `\`${escapeInlineCode(toolTitle)}\``;
775
789
  }
@@ -778,19 +792,10 @@ function formatPart(part) {
778
792
  : part.state.status === 'error'
779
793
  ? '⨯'
780
794
  : '';
781
- const title = `${icon} ${part.tool} ${toolTitle}`;
795
+ const title = `${icon} ${part.tool} ${toolTitle} ${summaryText}`;
782
796
  let text = title;
783
797
  if (outputToDisplay) {
784
- // Don't wrap todowrite output in code blocks
785
- if (part.tool === 'todowrite') {
786
- text += '\n\n' + outputToDisplay;
787
- }
788
- else {
789
- if (language.startsWith('.')) {
790
- language = language.slice(1);
791
- }
792
- text += '\n\n```' + language + '\n' + outputToDisplay + '\n```';
793
- }
798
+ text += '\n\n' + outputToDisplay;
794
799
  }
795
800
  return text;
796
801
  }
@@ -1168,7 +1173,14 @@ async function handleOpencodeSession(prompt, thread, projectDirectory, originalM
1168
1173
  discordLogger.log(`Could not update reaction:`, e);
1169
1174
  }
1170
1175
  }
1171
- await sendThreadMessage(thread, `✗ Unexpected bot Error: ${error instanceof Error ? error.stack || error.message : String(error)}`);
1176
+ // Always log the error's constructor name (if any) and make error reporting more readable
1177
+ const errorName = error && typeof error === 'object' && 'constructor' in error && error.constructor && typeof error.constructor.name === 'string'
1178
+ ? error.constructor.name
1179
+ : typeof error;
1180
+ const errorMsg = error instanceof Error
1181
+ ? (error.stack || error.message)
1182
+ : String(error);
1183
+ await sendThreadMessage(thread, `✗ Unexpected bot Error: [${errorName}]\n${errorMsg}`);
1172
1184
  }
1173
1185
  }
1174
1186
  }
package/dist/utils.js CHANGED
@@ -40,11 +40,12 @@ export function deduplicateByKey(arr, keyFn) {
40
40
  });
41
41
  }
42
42
  export function isAbortError(error, signal) {
43
- return (error instanceof Error &&
43
+ return ((error instanceof Error &&
44
44
  (error.name === 'AbortError' ||
45
45
  error.name === 'Aborterror' ||
46
46
  error.name === 'aborterror' ||
47
47
  error.name.toLowerCase() === 'aborterror' ||
48
48
  error.message?.includes('aborted') ||
49
- (signal?.aborted ?? false)));
49
+ (signal?.aborted ?? false))) ||
50
+ (error instanceof DOMException && error.name === 'AbortError'));
50
51
  }
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "kimaki",
3
3
  "module": "index.ts",
4
4
  "type": "module",
5
- "version": "0.3.0",
5
+ "version": "0.3.2",
6
6
  "repository": "https://github.com/remorses/kimaki",
7
7
  "bin": "bin.js",
8
8
  "files": [
@@ -45,7 +45,7 @@
45
45
  "zod": "^4.0.17"
46
46
  },
47
47
  "scripts": {
48
- "dev": "pnpm tsc && DEBUG=1 tsx --env-file .env src/cli.ts",
48
+ "dev": "pnpm tsc && tsx --env-file .env src/cli.ts",
49
49
  "dev:bun": "DEBUG=1 bun --env-file .env src/cli.ts",
50
50
  "test": "tsx scripts/test-opencode.ts",
51
51
  "watch": "tsx scripts/watch-session.ts",
package/src/discordBot.ts CHANGED
@@ -954,22 +954,28 @@ function formatPart(part: Part): string {
954
954
  return `▪︎ thinking: ${escapeDiscordFormatting(part.text || '')}`
955
955
  case 'tool':
956
956
  if (part.state.status === 'completed' || part.state.status === 'error') {
957
- // console.log(part)
958
- // Escape triple backticks so Discord does not break code blocks
959
957
  let language = ''
960
958
  let outputToDisplay = ''
959
+ let summaryText = ''
960
+
961
961
  if (part.tool === 'bash') {
962
- outputToDisplay =
962
+ const output =
963
963
  part.state.status === 'completed'
964
964
  ? part.state.output
965
965
  : part.state.error
966
- outputToDisplay ||= ''
967
- }
968
- if (part.tool === 'edit') {
969
- outputToDisplay = (part.state.input?.newString as string) || ''
970
- language = path.extname((part.state.input.filePath as string) || '')
971
- }
972
- if (part.tool === 'todowrite') {
966
+ const lines = (output || '').split('\n').filter((l) => l.trim())
967
+ summaryText = `(${lines.length} line${lines.length === 1 ? '' : 's'})`
968
+ } else if (part.tool === 'edit') {
969
+ const newString = (part.state.input?.newString as string) || ''
970
+ const oldString = (part.state.input?.oldString as string) || ''
971
+ const added = newString.split('\n').length
972
+ const removed = oldString.split('\n').length
973
+ summaryText = `(+${added}-${removed})`
974
+ } else if (part.tool === 'write') {
975
+ const content = (part.state.input?.content as string) || ''
976
+ const lines = content.split('\n').length
977
+ summaryText = `(${lines} line${lines === 1 ? '' : 's'})`
978
+ } else if (part.tool === 'todowrite') {
973
979
  const todos =
974
980
  (part.state.input?.todos as {
975
981
  content: string
@@ -996,23 +1002,26 @@ function formatPart(part: Part): string {
996
1002
  })
997
1003
  .filter(Boolean)
998
1004
  .join('\n')
999
- language = ''
1000
- }
1001
- if (part.tool === 'write') {
1002
- outputToDisplay = (part.state.input?.content as string) || ''
1003
- language = path.extname((part.state.input.filePath as string) || '')
1005
+ } else if (part.tool === 'webfetch') {
1006
+ const url = (part.state.input?.url as string) || ''
1007
+ const urlWithoutProtocol = url.replace(/^https?:\/\//, '')
1008
+ summaryText = urlWithoutProtocol ? `(${urlWithoutProtocol})` : ''
1009
+ } else if (part.state.input) {
1010
+ const inputFields = Object.entries(part.state.input)
1011
+ .map(([key, value]) => {
1012
+ if (value === null || value === undefined) return null
1013
+ const stringValue = typeof value === 'string' ? value : JSON.stringify(value)
1014
+ const truncatedValue = stringValue.length > 100 ? stringValue.slice(0, 100) + '…' : stringValue
1015
+ return `${key}: ${truncatedValue}`
1016
+ })
1017
+ .filter(Boolean)
1018
+ if (inputFields.length > 0) {
1019
+ outputToDisplay = inputFields.join('\n')
1020
+ }
1004
1021
  }
1005
- outputToDisplay =
1006
- outputToDisplay.length > 500
1007
- ? outputToDisplay.slice(0, 497) + `…`
1008
- : outputToDisplay
1009
-
1010
- // Escape Discord formatting characters that could break code blocks
1011
- outputToDisplay = escapeDiscordFormatting(outputToDisplay)
1012
1022
 
1013
1023
  let toolTitle =
1014
1024
  part.state.status === 'completed' ? part.state.title || '' : 'error'
1015
- // Escape backticks in the title before wrapping in backticks
1016
1025
  if (toolTitle) {
1017
1026
  toolTitle = `\`${escapeInlineCode(toolTitle)}\``
1018
1027
  }
@@ -1022,20 +1031,12 @@ function formatPart(part: Part): string {
1022
1031
  : part.state.status === 'error'
1023
1032
  ? '⨯'
1024
1033
  : ''
1025
- const title = `${icon} ${part.tool} ${toolTitle}`
1034
+ const title = `${icon} ${part.tool} ${toolTitle} ${summaryText}`
1026
1035
 
1027
1036
  let text = title
1028
1037
 
1029
1038
  if (outputToDisplay) {
1030
- // Don't wrap todowrite output in code blocks
1031
- if (part.tool === 'todowrite') {
1032
- text += '\n\n' + outputToDisplay
1033
- } else {
1034
- if (language.startsWith('.')) {
1035
- language = language.slice(1)
1036
- }
1037
- text += '\n\n```' + language + '\n' + outputToDisplay + '\n```'
1038
- }
1039
+ text += '\n\n' + outputToDisplay
1039
1040
  }
1040
1041
  return text
1041
1042
  }
@@ -1513,9 +1514,16 @@ async function handleOpencodeSession(
1513
1514
  discordLogger.log(`Could not update reaction:`, e)
1514
1515
  }
1515
1516
  }
1517
+ // Always log the error's constructor name (if any) and make error reporting more readable
1518
+ const errorName = error && typeof error === 'object' && 'constructor' in error && error.constructor && typeof error.constructor.name === 'string'
1519
+ ? error.constructor.name
1520
+ : typeof error
1521
+ const errorMsg = error instanceof Error
1522
+ ? (error.stack || error.message)
1523
+ : String(error)
1516
1524
  await sendThreadMessage(
1517
1525
  thread,
1518
- `✗ Unexpected bot Error: ${error instanceof Error ? error.stack || error.message : String(error)}`,
1526
+ `✗ Unexpected bot Error: [${errorName}]\n${errorMsg}`,
1519
1527
  )
1520
1528
  }
1521
1529
  }
package/src/utils.ts CHANGED
@@ -65,12 +65,13 @@ export function isAbortError(
65
65
  signal?: AbortSignal,
66
66
  ): error is Error {
67
67
  return (
68
- error instanceof Error &&
69
- (error.name === 'AbortError' ||
70
- error.name === 'Aborterror' ||
71
- error.name === 'aborterror' ||
72
- error.name.toLowerCase() === 'aborterror' ||
73
- error.message?.includes('aborted') ||
74
- (signal?.aborted ?? false))
68
+ (error instanceof Error &&
69
+ (error.name === 'AbortError' ||
70
+ error.name === 'Aborterror' ||
71
+ error.name === 'aborterror' ||
72
+ error.name.toLowerCase() === 'aborterror' ||
73
+ error.message?.includes('aborted') ||
74
+ (signal?.aborted ?? false))) ||
75
+ (error instanceof DOMException && error.name === 'AbortError')
75
76
  )
76
77
  }