agent-companion 0.1.6 → 0.1.7

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.
@@ -28,6 +28,8 @@ const MAX_JSONL_TAIL_BYTES = 1_500_000;
28
28
  const MAX_JSONL_TAIL_LINES = 1200;
29
29
  const DIRECT_RUNNING_FRESH_WINDOW_SEC = 20;
30
30
  const DIRECT_RUNNING_STALE_TIMEOUT_SEC = 180;
31
+ const INTERNAL_PLAN_MODE_SUFFIX =
32
+ "You are in PLAN MODE. Do not implement changes yet. Return only a concrete plan, then end with a single explicit approval question asking whether to proceed with implementation.";
31
33
  const PENDING_PATTERN =
32
34
  /input required|needs input|waiting for input|approval[_\s-]*required|awaiting approval|please approve|approve (?:this|the) plan|should i (?:proceed|implement|execute)|would you like me to (?:proceed|implement|execute)|ready to (?:implement|execute)|want me to (?:implement|execute)|proceed with implementation/i;
33
35
 
@@ -217,7 +219,7 @@ function parseCodexFile(file, nowMs) {
217
219
  }
218
220
 
219
221
  if (record.type === "event_msg" && record.payload?.type === "user_message") {
220
- const message = String(record.payload?.message || "").trim();
222
+ const message = normalizeVisibleUserText(record.payload?.message || "");
221
223
  flushPendingAssistant();
222
224
  if (message && !isNoisePrompt(message) && !firstPrompt) firstPrompt = message;
223
225
  appendDirectTurn(chatTurns, {
@@ -271,7 +273,7 @@ function parseCodexFile(file, nowMs) {
271
273
  if (record.type === "response_item" && record.payload?.type === "message") {
272
274
  const role = record.payload?.role;
273
275
  if (role === "user") {
274
- const text = extractCodexText(record.payload?.content);
276
+ const text = normalizeVisibleUserText(extractCodexText(record.payload?.content));
275
277
  flushPendingAssistant();
276
278
  if (text && !isNoisePrompt(text) && !firstPrompt) firstPrompt = text;
277
279
  appendDirectTurn(chatTurns, {
@@ -622,7 +624,7 @@ function formatCodexToolCall(item) {
622
624
  function extractClaudeUserText(message) {
623
625
  if (!message) return "";
624
626
  if (typeof message.content === "string") {
625
- const text = sanitizeDirectText(message.content);
627
+ const text = normalizeVisibleUserText(message.content);
626
628
  return shouldIncludeDirectUserText(text) ? text : "";
627
629
  }
628
630
  if (!Array.isArray(message.content)) return "";
@@ -630,7 +632,7 @@ function extractClaudeUserText(message) {
630
632
  const collected = [];
631
633
  for (const part of message.content) {
632
634
  if (typeof part === "string") {
633
- const text = sanitizeDirectText(part);
635
+ const text = normalizeVisibleUserText(part);
634
636
  if (shouldIncludeDirectUserText(text)) {
635
637
  collected.push(text);
636
638
  }
@@ -639,20 +641,20 @@ function extractClaudeUserText(message) {
639
641
  const partType = String(part?.type || "").trim().toLowerCase();
640
642
  if (partType && partType !== "text" && partType !== "input_text") continue;
641
643
  if (typeof part?.text === "string") {
642
- const text = sanitizeDirectText(part.text);
644
+ const text = normalizeVisibleUserText(part.text);
643
645
  if (shouldIncludeDirectUserText(text)) {
644
646
  collected.push(text);
645
647
  }
646
648
  }
647
649
  if (typeof part?.content === "string" && part.content.trim()) {
648
- const text = sanitizeDirectText(part.content);
650
+ const text = normalizeVisibleUserText(part.content);
649
651
  if (shouldIncludeDirectUserText(text)) {
650
652
  collected.push(text);
651
653
  }
652
654
  }
653
655
  }
654
656
 
655
- return collected.join("\n\n").trim();
657
+ return dedupeCollectedText(collected).join("\n\n").trim();
656
658
  }
657
659
 
658
660
  function extractClaudeAssistantText(message) {
@@ -869,6 +871,37 @@ function sanitizeDirectText(value) {
869
871
  .trim();
870
872
  }
871
873
 
874
+ function normalizeVisibleUserText(value) {
875
+ const text = sanitizeDirectText(value);
876
+ if (!text) return "";
877
+
878
+ return text
879
+ .replace(new RegExp(`(?:\\n\\s*)*${escapeRegExp(INTERNAL_PLAN_MODE_SUFFIX)}\\s*$`, "i"), "")
880
+ .trim();
881
+ }
882
+
883
+ function dedupeCollectedText(values) {
884
+ const deduped = [];
885
+ const seen = new Set();
886
+
887
+ for (const value of values) {
888
+ const text = String(value || "").trim();
889
+ if (!text) continue;
890
+
891
+ const normalized = normalizeComparableText(text);
892
+ if (!normalized || seen.has(normalized)) continue;
893
+
894
+ seen.add(normalized);
895
+ deduped.push(text);
896
+ }
897
+
898
+ return deduped;
899
+ }
900
+
901
+ function escapeRegExp(value) {
902
+ return String(value || "").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
903
+ }
904
+
872
905
  function getRecentJsonlFiles(rootDir, limit) {
873
906
  const now = Date.now();
874
907
  const cached = recentFilesCache.get(rootDir);
package/bridge/server.mjs CHANGED
@@ -1333,11 +1333,7 @@ function normalizeCommandList(input, agentType, prompt) {
1333
1333
  }
1334
1334
 
1335
1335
  function buildPlanPrompt(prompt) {
1336
- const base = safeTrimmedText(prompt, 1500);
1337
- if (!base) return "";
1338
- const suffix =
1339
- "\n\nYou are in PLAN MODE. Do not implement changes yet. Return only a concrete plan, then end with a single explicit approval question asking whether to proceed with implementation.";
1340
- return safeTrimmedText(`${base}${suffix}`, 1900);
1336
+ return safeTrimmedText(prompt, 1500);
1341
1337
  }
1342
1338
 
1343
1339
  function createLauncherRun(input) {
@@ -2527,13 +2523,19 @@ function mergeDirectSnapshot(snapshot) {
2527
2523
  .map((item) => item?.id)
2528
2524
  .filter((id) => typeof id === "string")
2529
2525
  );
2526
+ const incomingDirectTurnSessionIds = new Set(
2527
+ (Array.isArray(snapshot.chatTurns) ? snapshot.chatTurns : [])
2528
+ .map((item) => safeTrimmedText(item?.sessionId, 160))
2529
+ .filter(Boolean)
2530
+ );
2530
2531
  state.chatTurns = (Array.isArray(state.chatTurns) ? state.chatTurns : []).filter((item) => {
2531
2532
  const sessionId = String(item?.sessionId || "");
2532
- const isDirectSession = sessionId.startsWith("codex:") || sessionId.startsWith("claude:");
2533
2533
  const isDirectTurn = safeTrimmedText(item?.source, 48).toUpperCase() === "DIRECT" || String(item?.id || "").startsWith("direct:");
2534
- if (!isDirectSession || !isDirectTurn) return true;
2535
- if (!incomingDirectSessionIds.has(sessionId)) return false;
2536
- return incomingDirectTurnIds.has(item.id);
2534
+ if (!isDirectTurn) return true;
2535
+ if (incomingDirectTurnIds.has(item.id)) return true;
2536
+ if (incomingDirectTurnSessionIds.has(sessionId)) return false;
2537
+ if (incomingDirectSessionIds.has(sessionId)) return false;
2538
+ return true;
2537
2539
  });
2538
2540
 
2539
2541
  const existingTurns = new Map((Array.isArray(state.chatTurns) ? state.chatTurns : []).map((item) => [item.id, item]));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-companion",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Phone-to-computer companion for Codex and Claude Code.",