forge-remote 0.1.18 → 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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forge-remote",
3
- "version": "0.1.18",
3
+ "version": "0.1.19",
4
4
  "description": "Desktop relay for Forge Remote — monitor and control Claude Code sessions from your phone",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
@@ -726,7 +726,12 @@ async function runClaudeProcess(sessionId, prompt) {
726
726
  }
727
727
  session.isFirstPrompt = false;
728
728
 
729
- args.push(prompt);
729
+ // IMPORTANT: When --allowedTools is used, it consumes all remaining
730
+ // positional args. So we must pass the prompt via stdin instead.
731
+ const promptViaStdin = !!session.webhookMeta;
732
+ if (!promptViaStdin) {
733
+ args.push(prompt);
734
+ }
730
735
 
731
736
  log.session(sessionId, `Spawning: ${claudeBinary} ${args.join(" ")}`);
732
737
 
@@ -741,9 +746,15 @@ async function runClaudeProcess(sessionId, prompt) {
741
746
 
742
747
  session.process = claudeProcess;
743
748
 
744
- // Close stdin immediately `-p` mode reads the prompt from args, not stdin.
745
- // Leaving stdin open can cause Claude CLI to hang waiting for input.
746
- claudeProcess.stdin.end();
749
+ // When --allowedTools is used (webhook sessions), the prompt is sent via
750
+ // stdin because --allowedTools is variadic and would consume the prompt
751
+ // if passed as a positional arg. Otherwise, close stdin immediately.
752
+ if (promptViaStdin) {
753
+ claudeProcess.stdin.write(prompt);
754
+ claudeProcess.stdin.end();
755
+ } else {
756
+ claudeProcess.stdin.end();
757
+ }
747
758
 
748
759
  log.session(sessionId, `Process started — PID ${claudeProcess.pid}`);
749
760
 
@@ -361,6 +361,21 @@ async function handleWebhookPost(req, res, webhookId, sourceIp) {
361
361
  });
362
362
  }
363
363
 
364
+ // When subscribed to both message and app_mention events, Slack sends
365
+ // separate events for the same user message. Deduplicate by using the
366
+ // Slack message timestamp (ts) which is identical across both events.
367
+ if (event.ts) {
368
+ const tsKey = `slack-ts:${event.channel}:${event.ts}`;
369
+ if (recentDeliveryIds.has(tsKey)) {
370
+ log.info(`Duplicate Slack message ts ignored: ${event.ts}`);
371
+ return sendJson(res, 200, {
372
+ status: "duplicate",
373
+ reason: "same message ts",
374
+ });
375
+ }
376
+ addDeliveryId(tsKey);
377
+ }
378
+
364
379
  // Flatten the Slack event into the payload for template rendering.
365
380
  // This lets templates use {{text}}, {{user}}, {{channel}} directly.
366
381
  payload = { ...payload, ...event };