agent-companion 0.1.1 → 0.1.2

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/bridge/server.mjs +189 -10
  2. package/package.json +1 -1
package/bridge/server.mjs CHANGED
@@ -151,6 +151,11 @@ function normalizeState(value) {
151
151
  return allowed.includes(value) ? value : "RUNNING";
152
152
  }
153
153
 
154
+ function isDirectSessionId(value) {
155
+ const id = safeTrimmedText(value, 160);
156
+ return id.startsWith("codex:") || id.startsWith("claude:");
157
+ }
158
+
154
159
  function normalizeCategory(value) {
155
160
  const allowed = ["INFO", "ACTION", "INPUT", "ERROR"];
156
161
  return allowed.includes(value) ? value : "INFO";
@@ -627,6 +632,170 @@ function extractClaudeSessionIdFromRun(run) {
627
632
  return "";
628
633
  }
629
634
 
635
+ function normalizeClaudeSessionId(value) {
636
+ const candidate = safeTrimmedText(value, 120).toLowerCase();
637
+ if (!candidate) return "";
638
+ if (/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(candidate)) {
639
+ return candidate;
640
+ }
641
+ return "";
642
+ }
643
+
644
+ function getLauncherRunUpdatedAt(run) {
645
+ return Math.max(
646
+ safeNumber(run?.endedAt, 0),
647
+ safeNumber(run?.startedAt, 0),
648
+ safeNumber(run?.createdAt, 0)
649
+ );
650
+ }
651
+
652
+ function resolveCanonicalSessionIdForDirectSession(sessionId) {
653
+ const directSessionId = safeTrimmedText(sessionId, 160);
654
+ if (!isDirectSessionId(directSessionId)) return directSessionId;
655
+
656
+ if (directSessionId.startsWith("codex:")) {
657
+ const threadId = directSessionId.slice("codex:".length).trim().toLowerCase();
658
+ if (!threadId) return directSessionId;
659
+
660
+ const mappedRun = [...launcherRuns.values()]
661
+ .slice()
662
+ .sort((a, b) => getLauncherRunUpdatedAt(b) - getLauncherRunUpdatedAt(a))
663
+ .find((run) => !isDirectSessionId(run?.sessionId) && extractCodexThreadIdFromRun(run) === threadId);
664
+ if (mappedRun?.sessionId) return mappedRun.sessionId;
665
+
666
+ const mappedThread = (Array.isArray(state.sessionThreads) ? state.sessionThreads : [])
667
+ .slice()
668
+ .sort((a, b) => safeNumber(b.updatedAt, 0) - safeNumber(a.updatedAt, 0))
669
+ .find(
670
+ (thread) =>
671
+ !isDirectSessionId(thread?.id) &&
672
+ safeTrimmedText(thread?.codexThreadId, 120).toLowerCase() === threadId
673
+ );
674
+ if (mappedThread?.id) return mappedThread.id;
675
+
676
+ return directSessionId;
677
+ }
678
+
679
+ const claudeSessionId = normalizeClaudeSessionId(directSessionId.slice("claude:".length));
680
+ if (!claudeSessionId) return directSessionId;
681
+
682
+ const mappedRun = [...launcherRuns.values()]
683
+ .slice()
684
+ .sort((a, b) => getLauncherRunUpdatedAt(b) - getLauncherRunUpdatedAt(a))
685
+ .find((run) => !isDirectSessionId(run?.sessionId) && extractClaudeSessionIdFromRun(run) === claudeSessionId);
686
+ if (mappedRun?.sessionId) return mappedRun.sessionId;
687
+
688
+ const mappedThread = (Array.isArray(state.sessionThreads) ? state.sessionThreads : [])
689
+ .slice()
690
+ .sort((a, b) => safeNumber(b.updatedAt, 0) - safeNumber(a.updatedAt, 0))
691
+ .find(
692
+ (thread) =>
693
+ !isDirectSessionId(thread?.id) &&
694
+ normalizeClaudeSessionId(thread?.claudeSessionId) === claudeSessionId
695
+ );
696
+ if (mappedThread?.id) return mappedThread.id;
697
+
698
+ return directSessionId;
699
+ }
700
+
701
+ function rewriteDirectScopedId(id, originalSessionId, canonicalSessionId) {
702
+ const text = safeTrimmedText(id, 240);
703
+ if (!text || !originalSessionId || !canonicalSessionId || originalSessionId === canonicalSessionId) {
704
+ return text || id;
705
+ }
706
+
707
+ if (text === `pending:${originalSessionId}`) {
708
+ return `pending:${canonicalSessionId}`;
709
+ }
710
+ if (text === `event:${originalSessionId}`) {
711
+ return `event:${canonicalSessionId}`;
712
+ }
713
+
714
+ const directPrefix = `direct:${originalSessionId}:`;
715
+ if (text.startsWith(directPrefix)) {
716
+ return `direct:${canonicalSessionId}:${text.slice(directPrefix.length)}`;
717
+ }
718
+
719
+ return text;
720
+ }
721
+
722
+ function canonicalizeDirectSnapshot(snapshot) {
723
+ if (!snapshot || typeof snapshot !== "object") return snapshot;
724
+
725
+ const sessionIdMap = new Map();
726
+ const sourceDirectSessionIds = new Set();
727
+
728
+ const remapSessionId = (value) => {
729
+ const sessionId = safeTrimmedText(value, 160);
730
+ if (!isDirectSessionId(sessionId)) return sessionId;
731
+ sourceDirectSessionIds.add(sessionId);
732
+
733
+ const existing = sessionIdMap.get(sessionId);
734
+ if (existing) return existing;
735
+
736
+ const canonical = resolveCanonicalSessionIdForDirectSession(sessionId) || sessionId;
737
+ sessionIdMap.set(sessionId, canonical);
738
+ return canonical;
739
+ };
740
+
741
+ const sessions = (Array.isArray(snapshot.sessions) ? snapshot.sessions : []).map((item) => {
742
+ const originalSessionId = safeTrimmedText(item?.id, 160);
743
+ const canonicalSessionId = remapSessionId(originalSessionId);
744
+ if (!originalSessionId || !canonicalSessionId || canonicalSessionId === originalSessionId) {
745
+ return item;
746
+ }
747
+ return { ...item, id: canonicalSessionId };
748
+ });
749
+
750
+ const pendingInputs = (Array.isArray(snapshot.pendingInputs) ? snapshot.pendingInputs : []).map((item) => {
751
+ const originalSessionId = safeTrimmedText(item?.sessionId, 160);
752
+ const canonicalSessionId = remapSessionId(originalSessionId);
753
+ if (!originalSessionId || !canonicalSessionId || canonicalSessionId === originalSessionId) {
754
+ return item;
755
+ }
756
+ return {
757
+ ...item,
758
+ id: rewriteDirectScopedId(item?.id, originalSessionId, canonicalSessionId),
759
+ sessionId: canonicalSessionId
760
+ };
761
+ });
762
+
763
+ const events = (Array.isArray(snapshot.events) ? snapshot.events : []).map((item) => {
764
+ const originalSessionId = safeTrimmedText(item?.sessionId, 160);
765
+ const canonicalSessionId = remapSessionId(originalSessionId);
766
+ if (!originalSessionId || !canonicalSessionId || canonicalSessionId === originalSessionId) {
767
+ return item;
768
+ }
769
+ return {
770
+ ...item,
771
+ id: rewriteDirectScopedId(item?.id, originalSessionId, canonicalSessionId),
772
+ sessionId: canonicalSessionId
773
+ };
774
+ });
775
+
776
+ const chatTurns = (Array.isArray(snapshot.chatTurns) ? snapshot.chatTurns : []).map((item) => {
777
+ const originalSessionId = safeTrimmedText(item?.sessionId, 160);
778
+ const canonicalSessionId = remapSessionId(originalSessionId);
779
+ if (!originalSessionId || !canonicalSessionId || canonicalSessionId === originalSessionId) {
780
+ return item;
781
+ }
782
+ return {
783
+ ...item,
784
+ id: rewriteDirectScopedId(item?.id, originalSessionId, canonicalSessionId),
785
+ sessionId: canonicalSessionId
786
+ };
787
+ });
788
+
789
+ return {
790
+ ...snapshot,
791
+ sessions,
792
+ pendingInputs,
793
+ events,
794
+ chatTurns,
795
+ directSourceSessionIds: [...sourceDirectSessionIds]
796
+ };
797
+ }
798
+
630
799
  function buildContinueCommandFromThread(thread, prompt) {
631
800
  const safePrompt = safeTrimmedText(prompt, 1500);
632
801
  if (!safePrompt || !thread) return null;
@@ -1937,21 +2106,28 @@ function stopManagedService(service, signalInput) {
1937
2106
  function mergeDirectSnapshot(snapshot) {
1938
2107
  if (!snapshot || typeof snapshot !== "object") return;
1939
2108
 
2109
+ snapshot = canonicalizeDirectSnapshot(snapshot);
2110
+
1940
2111
  const incomingDirectSessionIds = new Set(
1941
2112
  (Array.isArray(snapshot.sessions) ? snapshot.sessions : [])
1942
2113
  .map((item) => item?.id)
1943
- .filter((id) => typeof id === "string")
2114
+ .filter((id) => typeof id === "string" && isDirectSessionId(id))
2115
+ );
2116
+ const incomingDirectSourceSessionIds = new Set(
2117
+ (Array.isArray(snapshot.directSourceSessionIds) ? snapshot.directSourceSessionIds : [])
2118
+ .map((item) => safeTrimmedText(item, 160))
2119
+ .filter(Boolean)
1944
2120
  );
1945
2121
 
1946
- if (incomingDirectSessionIds.size > 0) {
2122
+ if (incomingDirectSessionIds.size > 0 || incomingDirectSourceSessionIds.size > 0) {
1947
2123
  state.sessions = state.sessions.filter((item) => {
1948
2124
  const id = String(item?.id || "");
1949
- if (!id.startsWith("codex:") && !id.startsWith("claude:")) return true;
2125
+ if (!isDirectSessionId(id)) return true;
1950
2126
  return incomingDirectSessionIds.has(id);
1951
2127
  });
1952
2128
  state.sessionThreads = (Array.isArray(state.sessionThreads) ? state.sessionThreads : []).filter((item) => {
1953
2129
  const id = String(item?.id || "");
1954
- if (!id.startsWith("codex:") && !id.startsWith("claude:")) return true;
2130
+ if (!isDirectSessionId(id)) return true;
1955
2131
  return incomingDirectSessionIds.has(id);
1956
2132
  });
1957
2133
  }
@@ -2802,11 +2978,14 @@ app.post("/api/import/snapshot", (req, res) => {
2802
2978
  return res.json({ ok: true });
2803
2979
  });
2804
2980
 
2981
+ function refreshDirectSnapshot() {
2982
+ withPersist(() => {
2983
+ const snapshot = collectDirectSnapshot();
2984
+ mergeDirectSnapshot(snapshot);
2985
+ });
2986
+ }
2987
+
2805
2988
  app.listen(PORT, () => {
2806
- setInterval(() => {
2807
- withPersist(() => {
2808
- const snapshot = collectDirectSnapshot();
2809
- mergeDirectSnapshot(snapshot);
2810
- });
2811
- }, DIRECT_SNAPSHOT_POLL_INTERVAL_MS);
2989
+ refreshDirectSnapshot();
2990
+ setInterval(refreshDirectSnapshot, DIRECT_SNAPSHOT_POLL_INTERVAL_MS);
2812
2991
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-companion",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Phone-to-computer companion for Codex and Claude Code.",