@botcord/daemon 0.2.72 → 0.2.73

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.
@@ -836,7 +836,7 @@ function normalizeBlockForHub(block, seq) {
836
836
  }
837
837
  if (kind === "tool_use") {
838
838
  // Claude Code: assistant message w/ content[].type === "tool_use" → {id,name,input}
839
- // Codex: item.started / item.completed for command_execution, file_change, mcp_tool_call, web_search
839
+ // Codex: item.started for command_execution, file_change, mcp_tool_call, web_search
840
840
  const contents = Array.isArray(raw?.message?.content) ? raw.message.content : [];
841
841
  const tu = contents.find((c) => c?.type === "tool_use");
842
842
  if (tu) {
@@ -848,12 +848,19 @@ function normalizeBlockForHub(block, seq) {
848
848
  }
849
849
  else if (raw?.item && typeof raw.item === "object") {
850
850
  payload.name = typeof raw.item.type === "string" ? raw.item.type : "tool";
851
- payload.params = raw.item;
851
+ const params = codexToolParams(raw.item);
852
+ if (Object.keys(params).length > 0)
853
+ payload.params = params;
854
+ if (typeof raw.item.id === "string")
855
+ payload.id = raw.item.id;
856
+ if (typeof raw.item.status === "string")
857
+ payload.status = raw.item.status;
852
858
  }
853
859
  return { kind: "tool_call", seq, payload };
854
860
  }
855
861
  if (kind === "tool_result") {
856
862
  // Claude Code: {type:"user", message:{content:[{type:"tool_result",tool_use_id,content}]}}
863
+ // Codex: item.completed for command_execution, file_change, mcp_tool_call, web_search
857
864
  const contents = Array.isArray(raw?.message?.content) ? raw.message.content : [];
858
865
  const tr = contents.find((c) => c?.type === "tool_result");
859
866
  if (tr) {
@@ -870,6 +877,14 @@ function normalizeBlockForHub(block, seq) {
870
877
  if (typeof tr.tool_use_id === "string")
871
878
  payload.tool_use_id = tr.tool_use_id;
872
879
  }
880
+ else if (raw?.item && typeof raw.item === "object") {
881
+ payload.name = typeof raw.item.type === "string" ? raw.item.type : "tool";
882
+ if (typeof raw.item.id === "string")
883
+ payload.tool_use_id = raw.item.id;
884
+ const result = codexToolResult(raw.item);
885
+ if (result)
886
+ payload.result = result;
887
+ }
873
888
  return { kind: "tool_result", seq, payload };
874
889
  }
875
890
  if (kind === "system") {
@@ -928,6 +943,54 @@ function formatBlockDetails(raw) {
928
943
  return String(raw);
929
944
  }
930
945
  }
946
+ function codexToolParams(item) {
947
+ const params = {};
948
+ for (const key of [
949
+ "command",
950
+ "cmd",
951
+ "args",
952
+ "path",
953
+ "query",
954
+ "url",
955
+ "name",
956
+ "input",
957
+ "arguments",
958
+ "action",
959
+ "changes",
960
+ ]) {
961
+ const value = item[key];
962
+ if (value !== undefined && value !== null && value !== "")
963
+ params[key] = value;
964
+ }
965
+ const action = item.action;
966
+ if (action && typeof action === "object") {
967
+ for (const key of ["query", "url", "command", "path"]) {
968
+ const value = action[key];
969
+ if (value !== undefined && value !== null && value !== "")
970
+ params[key] = value;
971
+ }
972
+ }
973
+ return params;
974
+ }
975
+ function codexToolResult(item) {
976
+ const parts = [];
977
+ const status = typeof item.status === "string" ? item.status : "";
978
+ const exitCode = item.exit_code ?? item.exitCode;
979
+ if (status)
980
+ parts.push(`status: ${status}`);
981
+ if (typeof exitCode === "number" || typeof exitCode === "string")
982
+ parts.push(`exit_code: ${exitCode}`);
983
+ for (const key of ["output", "stdout", "stderr", "aggregated_output", "result", "summary"]) {
984
+ const value = item[key];
985
+ if (typeof value === "string" && value.trim())
986
+ parts.push(value.trim());
987
+ }
988
+ const results = item.results;
989
+ if (Array.isArray(results) && results.length > 0) {
990
+ parts.push(JSON.stringify(results, null, 2));
991
+ }
992
+ return parts.join("\n");
993
+ }
931
994
  function extractContentText(content) {
932
995
  if (!content)
933
996
  return "";
@@ -380,7 +380,7 @@ function normalizeBlock(obj, seq) {
380
380
  itemType === "file_change" ||
381
381
  itemType === "mcp_tool_call" ||
382
382
  itemType === "web_search") {
383
- kind = "tool_use";
383
+ kind = type === "item.completed" ? "tool_result" : "tool_use";
384
384
  }
385
385
  }
386
386
  return { raw: obj, kind, seq };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botcord/daemon",
3
- "version": "0.2.72",
3
+ "version": "0.2.73",
4
4
  "description": "BotCord local daemon — bridges Hub inbox push to local Claude Code / Codex / Gemini CLIs",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,7 +1,11 @@
1
1
  import { afterEach, describe, expect, it, vi } from "vitest";
2
2
  import { WebSocketServer, type WebSocket as WsType } from "ws";
3
3
  import type { AddressInfo } from "node:net";
4
- import { createBotCordChannel, type BotCordChannelClient } from "../channels/botcord.js";
4
+ import {
5
+ createBotCordChannel,
6
+ __normalizeBlockForHubForTests,
7
+ type BotCordChannelClient,
8
+ } from "../channels/botcord.js";
5
9
  import type { ChannelStartContext, GatewayInboundEnvelope } from "../types.js";
6
10
  import type { GatewayLogger } from "../log.js";
7
11
  import type { InboxMessage } from "@botcord/protocol-core";
@@ -649,6 +653,89 @@ describe("createBotCordChannel — ack + dedup", () => {
649
653
  // ---------------------------------------------------------------------------
650
654
 
651
655
  describe("createBotCordChannel — streamBlock()", () => {
656
+ it("normalizes Codex tool items without using internal ids as params", () => {
657
+ expect(
658
+ __normalizeBlockForHubForTests(
659
+ {
660
+ kind: "tool_use",
661
+ seq: 1,
662
+ raw: {
663
+ type: "item.started",
664
+ item: { id: "item_26", type: "command_execution", command: "rg stream-block" },
665
+ },
666
+ },
667
+ 1,
668
+ ),
669
+ ).toEqual({
670
+ kind: "tool_call",
671
+ seq: 1,
672
+ payload: {
673
+ name: "command_execution",
674
+ id: "item_26",
675
+ params: { command: "rg stream-block" },
676
+ },
677
+ });
678
+
679
+ expect(
680
+ __normalizeBlockForHubForTests(
681
+ {
682
+ kind: "tool_use",
683
+ seq: 2,
684
+ raw: {
685
+ type: "item.started",
686
+ item: {
687
+ id: "ws_abc",
688
+ type: "web_search",
689
+ action: { type: "search", query: "codex stream response" },
690
+ },
691
+ },
692
+ },
693
+ 2,
694
+ ),
695
+ ).toEqual({
696
+ kind: "tool_call",
697
+ seq: 2,
698
+ payload: {
699
+ name: "web_search",
700
+ id: "ws_abc",
701
+ params: {
702
+ action: { type: "search", query: "codex stream response" },
703
+ query: "codex stream response",
704
+ },
705
+ },
706
+ });
707
+ });
708
+
709
+ it("normalizes Codex completed tool items as results", () => {
710
+ expect(
711
+ __normalizeBlockForHubForTests(
712
+ {
713
+ kind: "tool_result",
714
+ seq: 3,
715
+ raw: {
716
+ type: "item.completed",
717
+ item: {
718
+ id: "item_26",
719
+ type: "command_execution",
720
+ status: "completed",
721
+ exit_code: 0,
722
+ output: "found 3 matches",
723
+ },
724
+ },
725
+ },
726
+ 3,
727
+ ),
728
+ ).toEqual({
729
+ kind: "tool_result",
730
+ seq: 3,
731
+ payload: {
732
+ name: "command_execution",
733
+ tool_use_id: "item_26",
734
+ result: "status: completed\nexit_code: 0\nfound 3 matches",
735
+ },
736
+ });
737
+ });
738
+
652
739
  it("POSTs to /hub/stream-block with the right trace_id + block", async () => {
653
740
  const fetchSpy = vi.fn().mockResolvedValue(new Response(null, { status: 204 }));
654
741
  const realFetch = globalThis.fetch;
@@ -77,13 +77,14 @@ process.exit(0);
77
77
  expect(res.error).toBeUndefined();
78
78
  });
79
79
 
80
- it("emits tool_use StreamBlock for command_execution items", async () => {
80
+ it("emits tool_use/tool_result StreamBlocks for command_execution items", async () => {
81
81
  const script = makeScript(
82
82
  "toolblock.js",
83
83
  `
84
84
  const lines = [
85
85
  {type:"thread.started", thread_id:"01234567-89ab-7def-8123-456789abcde0"},
86
86
  {type:"item.started", item:{id:"i0", type:"command_execution", command:"ls"}},
87
+ {type:"item.completed", item:{id:"i0", type:"command_execution", status:"completed", output:"ok"}},
87
88
  {type:"item.completed", item:{id:"i1", type:"agent_message", text:"done"}},
88
89
  ];
89
90
  for (const l of lines) process.stdout.write(JSON.stringify(l) + "\\n");
@@ -103,6 +104,7 @@ for (const l of lines) process.stdout.write(JSON.stringify(l) + "\\n");
103
104
  });
104
105
  expect(res.text).toBe("done");
105
106
  expect(seen).toContain("tool_use");
107
+ expect(seen).toContain("tool_result");
106
108
  expect(seen).toContain("assistant_text");
107
109
  expect(seen).toContain("system");
108
110
  });
@@ -979,7 +979,7 @@ function normalizeBlockForHub(
979
979
 
980
980
  if (kind === "tool_use") {
981
981
  // Claude Code: assistant message w/ content[].type === "tool_use" → {id,name,input}
982
- // Codex: item.started / item.completed for command_execution, file_change, mcp_tool_call, web_search
982
+ // Codex: item.started for command_execution, file_change, mcp_tool_call, web_search
983
983
  const contents = Array.isArray(raw?.message?.content) ? raw.message.content : [];
984
984
  const tu = contents.find((c: any) => c?.type === "tool_use");
985
985
  if (tu) {
@@ -988,13 +988,17 @@ function normalizeBlockForHub(
988
988
  if (typeof tu.id === "string") payload.id = tu.id;
989
989
  } else if (raw?.item && typeof raw.item === "object") {
990
990
  payload.name = typeof raw.item.type === "string" ? raw.item.type : "tool";
991
- payload.params = raw.item;
991
+ const params = codexToolParams(raw.item);
992
+ if (Object.keys(params).length > 0) payload.params = params;
993
+ if (typeof raw.item.id === "string") payload.id = raw.item.id;
994
+ if (typeof raw.item.status === "string") payload.status = raw.item.status;
992
995
  }
993
996
  return { kind: "tool_call", seq, payload };
994
997
  }
995
998
 
996
999
  if (kind === "tool_result") {
997
1000
  // Claude Code: {type:"user", message:{content:[{type:"tool_result",tool_use_id,content}]}}
1001
+ // Codex: item.completed for command_execution, file_change, mcp_tool_call, web_search
998
1002
  const contents = Array.isArray(raw?.message?.content) ? raw.message.content : [];
999
1003
  const tr = contents.find((c: any) => c?.type === "tool_result");
1000
1004
  if (tr) {
@@ -1008,6 +1012,11 @@ function normalizeBlockForHub(
1008
1012
  }
1009
1013
  payload.result = resultStr;
1010
1014
  if (typeof tr.tool_use_id === "string") payload.tool_use_id = tr.tool_use_id;
1015
+ } else if (raw?.item && typeof raw.item === "object") {
1016
+ payload.name = typeof raw.item.type === "string" ? raw.item.type : "tool";
1017
+ if (typeof raw.item.id === "string") payload.tool_use_id = raw.item.id;
1018
+ const result = codexToolResult(raw.item);
1019
+ if (result) payload.result = result;
1011
1020
  }
1012
1021
  return { kind: "tool_result", seq, payload };
1013
1022
  }
@@ -1062,6 +1071,56 @@ function formatBlockDetails(raw: unknown): string {
1062
1071
  }
1063
1072
  }
1064
1073
 
1074
+ function codexToolParams(item: Record<string, unknown>): Record<string, unknown> {
1075
+ const params: Record<string, unknown> = {};
1076
+ for (const key of [
1077
+ "command",
1078
+ "cmd",
1079
+ "args",
1080
+ "path",
1081
+ "query",
1082
+ "url",
1083
+ "name",
1084
+ "input",
1085
+ "arguments",
1086
+ "action",
1087
+ "changes",
1088
+ ]) {
1089
+ const value = item[key];
1090
+ if (value !== undefined && value !== null && value !== "") params[key] = value;
1091
+ }
1092
+
1093
+ const action = item.action as Record<string, unknown> | undefined;
1094
+ if (action && typeof action === "object") {
1095
+ for (const key of ["query", "url", "command", "path"]) {
1096
+ const value = action[key];
1097
+ if (value !== undefined && value !== null && value !== "") params[key] = value;
1098
+ }
1099
+ }
1100
+
1101
+ return params;
1102
+ }
1103
+
1104
+ function codexToolResult(item: Record<string, unknown>): string {
1105
+ const parts: string[] = [];
1106
+ const status = typeof item.status === "string" ? item.status : "";
1107
+ const exitCode = item.exit_code ?? item.exitCode;
1108
+ if (status) parts.push(`status: ${status}`);
1109
+ if (typeof exitCode === "number" || typeof exitCode === "string") parts.push(`exit_code: ${exitCode}`);
1110
+
1111
+ for (const key of ["output", "stdout", "stderr", "aggregated_output", "result", "summary"]) {
1112
+ const value = item[key];
1113
+ if (typeof value === "string" && value.trim()) parts.push(value.trim());
1114
+ }
1115
+
1116
+ const results = item.results;
1117
+ if (Array.isArray(results) && results.length > 0) {
1118
+ parts.push(JSON.stringify(results, null, 2));
1119
+ }
1120
+
1121
+ return parts.join("\n");
1122
+ }
1123
+
1065
1124
  function extractContentText(content: unknown): string {
1066
1125
  if (!content) return "";
1067
1126
  if (typeof content === "string") return content;
@@ -420,7 +420,7 @@ function normalizeBlock(obj: any, seq: number): StreamBlock {
420
420
  itemType === "mcp_tool_call" ||
421
421
  itemType === "web_search"
422
422
  ) {
423
- kind = "tool_use";
423
+ kind = type === "item.completed" ? "tool_result" : "tool_use";
424
424
  }
425
425
  }
426
426
  return { raw: obj, kind, seq };