codeam-cli 1.0.9 → 1.1.0

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/index.js +64 -46
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -110,7 +110,7 @@ var import_picocolors = __toESM(require("picocolors"));
110
110
  // package.json
111
111
  var package_default = {
112
112
  name: "codeam-cli",
113
- version: "1.0.9",
113
+ version: "1.1.0",
114
114
  description: "Remote control Claude Code from your mobile device",
115
115
  main: "dist/index.js",
116
116
  bin: {
@@ -745,68 +745,87 @@ var OutputService = class _OutputService {
745
745
  }
746
746
  sessionId;
747
747
  pluginId;
748
+ // Buffer that accumulates PTY text (ANSI stripped, growing during response)
748
749
  buffer = "";
749
- flushTimer = null;
750
- streamTimer = null;
750
+ lastSentBuffer = "";
751
+ stableCount = 0;
752
+ pollTimer = null;
753
+ startTime = 0;
751
754
  active = false;
752
- static STREAM_INTERVAL_MS = 1e3;
753
- static DONE_DEBOUNCE_MS = 2500;
755
+ // After this many stable polls (POLL_MS each) with content, mark done
756
+ static POLL_MS = 1e3;
757
+ static STABLE_THRESHOLD = 3;
758
+ // Give up after 2 minutes (absolute) or 30 seconds with no content at all
759
+ static MAX_MS = 12e4;
760
+ static EMPTY_TIMEOUT_MS = 3e4;
754
761
  /**
755
762
  * Call before sending a command from mobile.
756
- * Clears previous output and pushes a new_turn event so the mobile app
757
- * shows the typing indicator.
763
+ * Clears previous output, sends new_turn event, starts polling.
758
764
  */
759
- async newTurn() {
760
- this.stopTimers();
765
+ newTurn() {
766
+ this.stopPoll();
761
767
  this.buffer = "";
768
+ this.lastSentBuffer = "";
769
+ this.stableCount = 0;
762
770
  this.active = true;
763
- await this.postChunk({ clear: true });
764
- await this.postChunk({ type: "new_turn", content: "", done: false });
765
- this.startStreamTimer();
771
+ this.startTime = Date.now();
772
+ this.postChunk({ clear: true }).then(() => this.postChunk({ type: "new_turn", content: "", done: false })).catch(() => {
773
+ });
774
+ this.pollTimer = setInterval(() => this.checkStability(), _OutputService.POLL_MS);
766
775
  }
767
776
  /** Feed raw terminal output from Claude (called on every stdout chunk). */
768
777
  push(raw) {
769
778
  if (!this.active) return;
770
779
  const text = stripAnsi(raw).replace(/[ \t]+\n/g, "\n").replace(/\n{3,}/g, "\n\n");
771
- if (!text.trim()) return;
772
- this.buffer += text;
773
- this.resetDoneDebounce();
780
+ if (text) this.buffer += text;
774
781
  }
775
782
  dispose() {
776
- this.stopTimers();
783
+ this.stopPoll();
777
784
  this.active = false;
778
785
  }
779
- startStreamTimer() {
780
- this.streamTimer = setInterval(() => {
781
- const text = this.buffer;
782
- if (text.trim()) {
783
- this.postChunk({ type: "text", content: text, done: false }).catch(() => {
784
- });
786
+ /**
787
+ * Stability check runs every POLL_MS while active.
788
+ *
789
+ * Only finalises when we have content that has stopped changing.
790
+ * This avoids premature done signals while Claude is still thinking
791
+ * (which would set active=false and silently drop the real response).
792
+ */
793
+ checkStability() {
794
+ if (!this.active) return;
795
+ if (Date.now() - this.startTime > _OutputService.MAX_MS) {
796
+ this.finalize();
797
+ return;
798
+ }
799
+ const current = this.buffer;
800
+ if (!current.trim()) {
801
+ if (Date.now() - this.startTime > _OutputService.EMPTY_TIMEOUT_MS) {
802
+ this.finalize();
785
803
  }
786
- }, _OutputService.STREAM_INTERVAL_MS);
787
- }
788
- resetDoneDebounce() {
789
- if (this.flushTimer) clearTimeout(this.flushTimer);
790
- this.flushTimer = setTimeout(() => {
791
- this.flushTimer = null;
792
- this.stopTimers();
793
- const text = this.buffer;
794
- this.buffer = "";
795
- this.active = false;
796
- if (text.trim()) {
797
- this.postChunk({ type: "text", content: text, done: true }).catch(() => {
798
- });
804
+ return;
805
+ }
806
+ if (current === this.lastSentBuffer) {
807
+ this.stableCount++;
808
+ if (this.stableCount >= _OutputService.STABLE_THRESHOLD) {
809
+ this.finalize();
799
810
  }
800
- }, _OutputService.DONE_DEBOUNCE_MS);
801
- }
802
- stopTimers() {
803
- if (this.flushTimer) {
804
- clearTimeout(this.flushTimer);
805
- this.flushTimer = null;
811
+ } else {
812
+ this.stableCount = 0;
813
+ this.lastSentBuffer = current;
814
+ this.postChunk({ type: "text", content: current, done: false }).catch(() => {
815
+ });
806
816
  }
807
- if (this.streamTimer) {
808
- clearInterval(this.streamTimer);
809
- this.streamTimer = null;
817
+ }
818
+ finalize() {
819
+ const text = this.buffer;
820
+ this.stopPoll();
821
+ this.active = false;
822
+ this.postChunk({ type: "text", content: text, done: true }).catch(() => {
823
+ });
824
+ }
825
+ stopPoll() {
826
+ if (this.pollTimer) {
827
+ clearInterval(this.pollTimer);
828
+ this.pollTimer = null;
810
829
  }
811
830
  }
812
831
  postChunk(body) {
@@ -862,8 +881,7 @@ async function start() {
862
881
  const ws = new WebSocketService(session.id, pluginId);
863
882
  const outputSvc = new OutputService(session.id, pluginId);
864
883
  function sendPrompt(prompt) {
865
- outputSvc.newTurn().catch(() => {
866
- });
884
+ outputSvc.newTurn();
867
885
  claude.sendCommand(prompt);
868
886
  }
869
887
  const relay = new CommandRelayService(pluginId, (cmd) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeam-cli",
3
- "version": "1.0.9",
3
+ "version": "1.1.0",
4
4
  "description": "Remote control Claude Code from your mobile device",
5
5
  "main": "dist/index.js",
6
6
  "bin": {