@1presence/bridge 0.24.0 → 0.26.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.
@@ -7,15 +7,25 @@ function makeBridgeAccumulator() {
7
7
  assistantText: '',
8
8
  toolCalls: [],
9
9
  toolResults: {},
10
+ turns: [],
10
11
  };
11
- let textEmitted = false;
12
- let turnTextEmitted = false;
12
+ // Returns the current open API-turn bucket, creating one lazily if a text
13
+ // event arrives before any `{type:'assistant'}` event (shouldn't normally
14
+ // happen with Claude Code's stream-json, but keep it robust).
15
+ function currentTurn() {
16
+ if (state.turns.length === 0)
17
+ state.turns.push({ text: '', toolUseIds: [] });
18
+ return state.turns[state.turns.length - 1];
19
+ }
13
20
  function appendText(text) {
14
- if (textEmitted && !turnTextEmitted)
21
+ const turn = currentTurn();
22
+ turn.text += text;
23
+ // Mirror to the flat string with `\n\n` between turns for convenience.
24
+ if (state.assistantText && state.turns.length > 1 && turn.text === text) {
25
+ // First text emission of a new turn — separate from prior turn.
15
26
  state.assistantText += '\n\n';
27
+ }
16
28
  state.assistantText += text;
17
- turnTextEmitted = true;
18
- textEmitted = true;
19
29
  }
20
30
  return {
21
31
  consume(event) {
@@ -27,9 +37,9 @@ function makeBridgeAccumulator() {
27
37
  return;
28
38
  }
29
39
  if (type === 'assistant') {
30
- // New API turn — reset the per-turn flag so the next text emission
31
- // gets a `\n\n` separator iff text was already emitted in a prior turn.
32
- turnTextEmitted = false;
40
+ // New API turn — open a fresh bucket. All text and tool_use blocks
41
+ // in this `assistant` event belong to this single API turn.
42
+ state.turns.push({ text: '', toolUseIds: [] });
33
43
  const msg = event['message'];
34
44
  const content = msg?.['content'];
35
45
  if (!Array.isArray(content))
@@ -48,6 +58,7 @@ function makeBridgeAccumulator() {
48
58
  continue;
49
59
  const bareName = name.replace(/^mcp__1presence__/, '');
50
60
  state.toolCalls.push({ id, name: bareName, input });
61
+ currentTurn().toolUseIds.push(id);
51
62
  if (bareName === 'set_conversation_title') {
52
63
  const raw = String(input['title'] ?? '').trim();
53
64
  if (raw)
@@ -95,6 +106,7 @@ async function postSaveTurn(gatewayHttp, token, record) {
95
106
  assistantText: record.assistantText,
96
107
  toolCalls: record.toolCalls,
97
108
  toolResults: record.toolResults,
109
+ ...(record.apiTurns ? { apiTurns: record.apiTurns } : {}),
98
110
  ...(record.title ? { title: record.title } : {}),
99
111
  ...(record.usage ? { usage: record.usage } : {}),
100
112
  }),
package/dist/claude.js CHANGED
@@ -70,14 +70,17 @@ function spawnClaude(params) {
70
70
  existing.kill('SIGTERM');
71
71
  active.delete(conversationId);
72
72
  }
73
- const ctxParts = [];
74
- if (vaultFileOpen)
75
- ctxParts.push(`vault_file_open: ${vaultFileOpen}`);
76
- if (clientCapabilities?.length)
77
- ctxParts.push(`client_capabilities: ${clientCapabilities.join(', ')}`);
78
- if (syncedFolders?.length)
79
- ctxParts.push(`synced_folders: ${syncedFolders.join(', ')}`);
80
- const userMessageText = ctxParts.length > 0 ? `[${ctxParts.join(' | ')}]\n\n${text}` : text;
73
+ // Note: ephemeral context (vault_file_open / client_capabilities / synced_folders)
74
+ // is injected into the last user message by the gateway BEFORE history is
75
+ // sent over the WS. The bridge no longer constructs `userMessageText`
76
+ // `history` is the authoritative stream and already contains the new user
77
+ // prompt with prefix prepended. The `text`, `vaultFileOpen`,
78
+ // `clientCapabilities`, `syncedFolders` SpawnParams are retained for
79
+ // backward-compatible logging / spool correlation only.
80
+ void vaultFileOpen;
81
+ void clientCapabilities;
82
+ void syncedFolders;
83
+ void text;
81
84
  // Lockdown rationale:
82
85
  // - `--tools ""` disables ALL built-in tools (Bash/Read/Write/Edit/Glob/Grep/
83
86
  // WebFetch/etc.). MCP tools are not "built-in" so the 1Presence MCP surface
@@ -130,11 +133,14 @@ function spawnClaude(params) {
130
133
  stdio: ['pipe', 'pipe', 'pipe'],
131
134
  });
132
135
  active.set(conversationId, proc);
133
- // Feed prior turns + the new user message via stdin as stream-json.
134
- // Each line is a JSON object: `{ type, message: { role, content } }`.
135
- // Sanitisation (orphan tool_use stripping, displayOnly filtering) already
136
- // happened on the gateway via @presence/shared.toModelMessages replay
137
- // the history verbatim and append the fresh user turn.
136
+ // Feed the full conversation via stdin as stream-json. The gateway's
137
+ // early-save committed the new user message to Firestore BEFORE building
138
+ // `history`, so `history` already ends with the new user prompt (with the
139
+ // ephemeral context prefix prepended by the gateway). The bridge no longer
140
+ // appends a separate `newTurn` doing so would duplicate the user prompt.
141
+ // Sanitisation (orphan tool_use stripping, displayOnly filtering, consecutive
142
+ // same-role merging) already happened on the gateway via
143
+ // @presence/shared.toModelMessages — replay the history verbatim.
138
144
  try {
139
145
  const stdin = proc.stdin;
140
146
  if (!stdin) {
@@ -144,8 +150,6 @@ function spawnClaude(params) {
144
150
  const wrapped = { type: msg.role, message: { role: msg.role, content: msg.content } };
145
151
  stdin.write(JSON.stringify(wrapped) + '\n');
146
152
  }
147
- const newTurn = { type: 'user', message: { role: 'user', content: userMessageText } };
148
- stdin.write(JSON.stringify(newTurn) + '\n');
149
153
  stdin.end();
150
154
  }
151
155
  catch (err) {
package/dist/index.js CHANGED
@@ -188,6 +188,7 @@ async function handleMessage(conversationId, text, sessionId, history, auth, vau
188
188
  assistantText: s.assistantText,
189
189
  toolCalls: s.toolCalls,
190
190
  toolResults: s.toolResults,
191
+ apiTurns: s.turns,
191
192
  ...(s.title ? { title: s.title } : {}),
192
193
  usage: usage
193
194
  ? { ...usage, ...(model ? { model } : {}) }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@1presence/bridge",
3
- "version": "0.24.0",
3
+ "version": "0.26.0",
4
4
  "description": "Run 1Presence on your Mac and use your Claude.ai Pro subscription from any device",
5
5
  "bin": {
6
6
  "1presence-bridge": "dist/index.js"