openzca 0.1.17 → 0.1.19

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 (2) hide show
  1. package/dist/cli.js +164 -7
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -806,6 +806,159 @@ async function refreshCacheForProfile(profile, api) {
806
806
  groups: groups.length
807
807
  };
808
808
  }
809
+ function parsePositiveIntFromEnv(name, fallback) {
810
+ const raw = process.env[name]?.trim();
811
+ if (!raw) return fallback;
812
+ const parsed = Number.parseInt(raw, 10);
813
+ if (!Number.isFinite(parsed) || parsed <= 0) return fallback;
814
+ return parsed;
815
+ }
816
+ function isListenerAlreadyStarted(error) {
817
+ if (!(error instanceof Error)) return false;
818
+ return /already started/i.test(error.message);
819
+ }
820
+ function toErrorText(error) {
821
+ return error instanceof Error ? error.message : String(error);
822
+ }
823
+ async function withTimeout(task, timeoutMs, message) {
824
+ let timeoutId;
825
+ try {
826
+ const timeout = new Promise((_, reject) => {
827
+ timeoutId = setTimeout(() => reject(new Error(message)), timeoutMs);
828
+ });
829
+ return await Promise.race([task, timeout]);
830
+ } finally {
831
+ if (timeoutId) clearTimeout(timeoutId);
832
+ }
833
+ }
834
+ async function stopUploadListenerSafely(api, command, waitClosedMs = 1500) {
835
+ await new Promise((resolve) => {
836
+ let settled = false;
837
+ let timeoutId;
838
+ const finish = () => {
839
+ if (settled) return;
840
+ settled = true;
841
+ if (timeoutId) clearTimeout(timeoutId);
842
+ api.listener.off("closed", onClosed);
843
+ resolve();
844
+ };
845
+ const onClosed = () => {
846
+ finish();
847
+ };
848
+ api.listener.on("closed", onClosed);
849
+ timeoutId = setTimeout(finish, waitClosedMs);
850
+ try {
851
+ api.listener.stop();
852
+ writeDebugLine("msg.upload.listener.stop", void 0, command);
853
+ } catch {
854
+ finish();
855
+ }
856
+ });
857
+ }
858
+ async function withUploadListener(api, command, task) {
859
+ const connectTimeoutMs = parsePositiveIntFromEnv(
860
+ "OPENZCA_UPLOAD_LISTENER_CONNECT_TIMEOUT_MS",
861
+ 8e3
862
+ );
863
+ const uploadTimeoutMs = parsePositiveIntFromEnv(
864
+ "OPENZCA_UPLOAD_TIMEOUT_MS",
865
+ 12e4
866
+ );
867
+ let startedHere = false;
868
+ const sinkError = (error) => {
869
+ writeDebugLine(
870
+ "msg.upload.listener.error",
871
+ {
872
+ message: toErrorText(error)
873
+ },
874
+ command
875
+ );
876
+ };
877
+ const sinkClosed = (code, reason) => {
878
+ writeDebugLine(
879
+ "msg.upload.listener.closed",
880
+ {
881
+ code,
882
+ reason: reason || void 0
883
+ },
884
+ command
885
+ );
886
+ };
887
+ api.listener.on("error", sinkError);
888
+ api.listener.on("closed", sinkClosed);
889
+ try {
890
+ await new Promise((resolve, reject) => {
891
+ let settled = false;
892
+ let timeoutId;
893
+ const cleanup = () => {
894
+ if (timeoutId) clearTimeout(timeoutId);
895
+ api.listener.off("connected", onConnected);
896
+ api.listener.off("error", onConnectError);
897
+ api.listener.off("closed", onConnectClosed);
898
+ };
899
+ const finish = (error) => {
900
+ if (settled) return;
901
+ settled = true;
902
+ cleanup();
903
+ if (error) {
904
+ reject(error);
905
+ return;
906
+ }
907
+ resolve();
908
+ };
909
+ const onConnected = () => {
910
+ writeDebugLine("msg.upload.listener.connected", void 0, command);
911
+ finish();
912
+ };
913
+ const onConnectError = (error) => {
914
+ finish(new Error(`Upload listener connection error: ${toErrorText(error)}`));
915
+ };
916
+ const onConnectClosed = (code, reason) => {
917
+ finish(
918
+ new Error(
919
+ `Upload listener closed before ready (code=${code}${reason ? `, reason=${reason}` : ""}).`
920
+ )
921
+ );
922
+ };
923
+ timeoutId = setTimeout(() => {
924
+ finish(new Error(`Timed out waiting ${connectTimeoutMs}ms for upload listener connection.`));
925
+ }, connectTimeoutMs);
926
+ api.listener.on("connected", onConnected);
927
+ api.listener.on("error", onConnectError);
928
+ api.listener.on("closed", onConnectClosed);
929
+ try {
930
+ api.listener.start();
931
+ startedHere = true;
932
+ writeDebugLine(
933
+ "msg.upload.listener.start",
934
+ {
935
+ connectTimeoutMs,
936
+ uploadTimeoutMs
937
+ },
938
+ command
939
+ );
940
+ } catch (error) {
941
+ if (isListenerAlreadyStarted(error)) {
942
+ writeDebugLine("msg.upload.listener.already_started", void 0, command);
943
+ finish();
944
+ return;
945
+ }
946
+ finish(error);
947
+ }
948
+ });
949
+ return await withTimeout(
950
+ task(),
951
+ uploadTimeoutMs,
952
+ `Timed out waiting ${uploadTimeoutMs}ms for file upload completion.`
953
+ );
954
+ } finally {
955
+ if (startedHere) {
956
+ await stopUploadListenerSafely(api, command);
957
+ }
958
+ api.listener.off("error", sinkError);
959
+ api.listener.off("closed", sinkClosed);
960
+ }
961
+ }
809
962
  async function fetchRecentMessagesViaListener(api, threadId, threadType, count) {
810
963
  return new Promise((resolve, reject) => {
811
964
  let settled = false;
@@ -2137,13 +2290,17 @@ msg.command("upload <arg1> [arg2]").option("-u, --url <url>", "File URL (repeata
2137
2290
  );
2138
2291
  }
2139
2292
  await assertFilesExist(attachments);
2140
- const response = await api.sendMessage(
2141
- {
2142
- msg: "",
2143
- attachments
2144
- },
2145
- threadId,
2146
- asThreadType(opts.group)
2293
+ const response = await withUploadListener(
2294
+ api,
2295
+ command,
2296
+ async () => api.sendMessage(
2297
+ {
2298
+ msg: "",
2299
+ attachments
2300
+ },
2301
+ threadId,
2302
+ asThreadType(opts.group)
2303
+ )
2147
2304
  );
2148
2305
  output(response, false);
2149
2306
  } finally {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openzca",
3
- "version": "0.1.17",
3
+ "version": "0.1.19",
4
4
  "description": "Open-source zca-compatible CLI to integrate Zalo with OpenClaw",
5
5
  "type": "module",
6
6
  "bin": {