@oh-my-pi/pi-coding-agent 15.12.1 → 15.12.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.
@@ -92,11 +92,17 @@ export declare function rewriteEnvelopePeer(data: Uint8Array, peerId: number): v
92
92
  export declare function generateRoomId(): string;
93
93
  /**
94
94
  * Render the shareable link. Compact forms: the default relay collapses to
95
- * `<roomId>#<key>`, other wss relays drop the scheme (`host[:port]/r/…`);
95
+ * `<roomId>.<key>`, other wss relays drop the scheme (`host[:port]/r/…`);
96
96
  * only localhost ws:// links keep their full URL so parsing cannot
97
97
  * mis-infer wss.
98
98
  *
99
- * Full links append the write token to the key in the fragment
99
+ * The room secret is dot-joined (`<roomId>.<key>`) rather than `#`-joined:
100
+ * RFC 3986 forbids a raw `#` inside a fragment, so strict URL stacks (macOS
101
+ * Foundation behind terminal click-to-open) percent-encode a second `#` to
102
+ * `%23` and break the link. Parsers still accept the legacy `#` form and the
103
+ * mangled `%23` form.
104
+ *
105
+ * Full links append the write token to the key
100
106
  * (`base64url(key ∥ writeToken)`); read-only (view) links carry the bare
101
107
  * 32-byte key, which is also the pre-token link format.
102
108
  */
@@ -1401,7 +1401,7 @@ export declare const SETTINGS_SCHEMA: {
1401
1401
  readonly tab: "interaction";
1402
1402
  readonly group: "Collab";
1403
1403
  readonly label: "Relay URL";
1404
- readonly description: "Relay used by /collab (wss://host[:port]; self-host with the omp-collab-relay service)";
1404
+ readonly description: "Relay used by /collab (wss://host[:port])";
1405
1405
  };
1406
1406
  };
1407
1407
  readonly "collab.displayName": {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-coding-agent",
4
- "version": "15.12.1",
4
+ "version": "15.12.2",
5
5
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": "Can Boluk",
@@ -47,17 +47,17 @@
47
47
  "@agentclientprotocol/sdk": "0.22.1",
48
48
  "@babel/parser": "^7.29.7",
49
49
  "@mozilla/readability": "^0.6.0",
50
- "@oh-my-pi/hashline": "15.12.1",
51
- "@oh-my-pi/omp-stats": "15.12.1",
52
- "@oh-my-pi/pi-agent-core": "15.12.1",
53
- "@oh-my-pi/pi-ai": "15.12.1",
54
- "@oh-my-pi/pi-catalog": "15.12.1",
55
- "@oh-my-pi/pi-mnemopi": "15.12.1",
56
- "@oh-my-pi/pi-natives": "15.12.1",
57
- "@oh-my-pi/pi-tui": "15.12.1",
58
- "@oh-my-pi/pi-utils": "15.12.1",
59
- "@oh-my-pi/pi-wire": "15.12.1",
60
- "@oh-my-pi/snapcompact": "15.12.1",
50
+ "@oh-my-pi/hashline": "15.12.2",
51
+ "@oh-my-pi/omp-stats": "15.12.2",
52
+ "@oh-my-pi/pi-agent-core": "15.12.2",
53
+ "@oh-my-pi/pi-ai": "15.12.2",
54
+ "@oh-my-pi/pi-catalog": "15.12.2",
55
+ "@oh-my-pi/pi-mnemopi": "15.12.2",
56
+ "@oh-my-pi/pi-natives": "15.12.2",
57
+ "@oh-my-pi/pi-tui": "15.12.2",
58
+ "@oh-my-pi/pi-utils": "15.12.2",
59
+ "@oh-my-pi/pi-wire": "15.12.2",
60
+ "@oh-my-pi/snapcompact": "15.12.2",
61
61
  "@opentelemetry/api": "^1.9.1",
62
62
  "@opentelemetry/context-async-hooks": "^2.7.1",
63
63
  "@opentelemetry/exporter-trace-otlp-proto": "^0.218.0",
@@ -29,6 +29,7 @@ import {
29
29
  COLLAB_PROTO,
30
30
  type CollabFrame,
31
31
  type CollabParticipant,
32
+ type CollabPromptDetails,
32
33
  type CollabSessionState,
33
34
  formatCollabLink,
34
35
  formatCollabWebLink,
@@ -364,13 +365,24 @@ export class CollabHost {
364
365
  const name = peer.name;
365
366
  const content: string | (TextContent | ImageContent)[] =
366
367
  images && images.length > 0 ? [{ type: "text", text }, ...images] : text;
368
+ const details: CollabPromptDetails & { __pendingDisplayTag?: string } = { from: name };
369
+ if (this.#ctx.session.isStreaming) {
370
+ // Mid-turn guest prompts are steered: register the pending-display twin
371
+ // so queuedMessageCount reflects the queued steer (host pending bar +
372
+ // guests' "queued ×N" badge). The tag dequeues the entry when the agent
373
+ // consumes the message (mirrors the skill-prompt path).
374
+ details.__pendingDisplayTag = this.#ctx.session.enqueueCustomMessageDisplay(text, "steer");
375
+ this.#ctx.updatePendingMessagesDisplay();
376
+ this.#ctx.ui.requestRender();
377
+ this.#scheduleStateBroadcast();
378
+ }
367
379
  this.#ctx.session
368
380
  .promptCustomMessage(
369
381
  {
370
382
  customType: COLLAB_PROMPT_MESSAGE_TYPE,
371
383
  content,
372
384
  display: true,
373
- details: { from: name },
385
+ details,
374
386
  attribution: "user",
375
387
  },
376
388
  { streamingBehavior: "steer" },
@@ -108,11 +108,11 @@ export function rewriteEnvelopePeer(data: Uint8Array, peerId: number): void {
108
108
  }
109
109
 
110
110
  // ═══════════════════════════════════════════════════════════════════════════
111
- // Link format: wss://<host[:port]>/r/<roomId>#<base64url-32-byte-key>
111
+ // Link format: wss://<host[:port]>/r/<roomId>.<base64url-32-byte-key>
112
112
  // ═══════════════════════════════════════════════════════════════════════════
113
113
 
114
- const ROOM_PATH_RE = /^\/r\/([A-Za-z0-9_-]{10,64})$/;
115
- const BARE_LINK_RE = /^([A-Za-z0-9_-]{10,64})#([A-Za-z0-9_-]+)$/;
114
+ const ROOM_PATH_RE = /^\/r\/([A-Za-z0-9_-]{10,64})(?:\.([A-Za-z0-9_-]+))?$/;
115
+ const BARE_LINK_RE = /^([A-Za-z0-9_-]{10,64})[#.]([A-Za-z0-9_-]+)$/;
116
116
  const B64URL_RE = /^[A-Za-z0-9_-]+$/;
117
117
  const LOCAL_HOSTNAMES: Record<string, true> = { localhost: true, "127.0.0.1": true, "::1": true, "[::1]": true };
118
118
 
@@ -152,11 +152,17 @@ function normalizeRelayOrigin(relayUrl: string): { origin: string } | { error: s
152
152
 
153
153
  /**
154
154
  * Render the shareable link. Compact forms: the default relay collapses to
155
- * `<roomId>#<key>`, other wss relays drop the scheme (`host[:port]/r/…`);
155
+ * `<roomId>.<key>`, other wss relays drop the scheme (`host[:port]/r/…`);
156
156
  * only localhost ws:// links keep their full URL so parsing cannot
157
157
  * mis-infer wss.
158
158
  *
159
- * Full links append the write token to the key in the fragment
159
+ * The room secret is dot-joined (`<roomId>.<key>`) rather than `#`-joined:
160
+ * RFC 3986 forbids a raw `#` inside a fragment, so strict URL stacks (macOS
161
+ * Foundation behind terminal click-to-open) percent-encode a second `#` to
162
+ * `%23` and break the link. Parsers still accept the legacy `#` form and the
163
+ * mangled `%23` form.
164
+ *
165
+ * Full links append the write token to the key
160
166
  * (`base64url(key ∥ writeToken)`); read-only (view) links carry the bare
161
167
  * 32-byte key, which is also the pre-token link format.
162
168
  */
@@ -165,11 +171,11 @@ export function formatCollabLink(relayUrl: string, roomId: string, key: Uint8Arr
165
171
  if ("error" in normalized) throw new Error(normalized.error);
166
172
  const secret = writeToken ? Buffer.concat([key, writeToken]) : Buffer.from(key);
167
173
  const keyText = secret.toString("base64url");
168
- if (normalized.origin === DEFAULT_RELAY_URL) return `${roomId}#${keyText}`;
174
+ if (normalized.origin === DEFAULT_RELAY_URL) return `${roomId}.${keyText}`;
169
175
  const compact = normalized.origin.startsWith("wss://")
170
176
  ? normalized.origin.slice("wss://".length)
171
177
  : normalized.origin;
172
- return `${compact}/r/${roomId}#${keyText}`;
178
+ return `${compact}/r/${roomId}.${keyText}`;
173
179
  }
174
180
 
175
181
  /**
@@ -193,10 +199,12 @@ export function formatCollabWebLink(
193
199
  }
194
200
 
195
201
  export function parseCollabLink(link: string): ParsedCollabLink | { error: string } {
196
- let text = link.trim();
197
- // Bare `<roomId>#<key>` default relay.
202
+ // Lenient input: terminals that open OSC 8 links through strict URL stacks
203
+ // (macOS Foundation) percent-encode the legacy second `#` to `%23`.
204
+ let text = link.trim().replace(/%23/gi, "#");
205
+ // Bare `<roomId>.<key>` (legacy `<roomId>#<key>`) → default relay.
198
206
  const bare = BARE_LINK_RE.exec(text);
199
- if (bare) text = `${DEFAULT_RELAY_URL}/r/${bare[1]}#${bare[2]}`;
207
+ if (bare) text = `${DEFAULT_RELAY_URL}/r/${bare[1]}.${bare[2]}`;
200
208
  // Scheme-less `host[:port]/r/…` → wss.
201
209
  else if (!text.includes("://")) text = `wss://${text}`;
202
210
  let url: URL;
@@ -210,17 +218,18 @@ export function parseCollabLink(link: string): ParsedCollabLink | { error: strin
210
218
  const match = ROOM_PATH_RE.exec(url.pathname);
211
219
  if (!match) {
212
220
  // Web deep link: `http(s)://<relay>/#<collab-link>` — the fragment holds
213
- // the whole link (which itself contains another `#`). URL.hash spans to
214
- // the end of the string, so recurse on it; the inner fragment is just
215
- // the key (no `#`), which bounds the recursion.
221
+ // the whole link, so recurse on it. The recursion terminates because
222
+ // the inner text is a strict suffix of the input.
216
223
  const inner = url.hash.startsWith("#") ? url.hash.slice(1) : url.hash;
217
- if (inner.includes("#")) return parseCollabLink(inner);
224
+ if (inner) return parseCollabLink(inner);
218
225
  return { error: "Collab link must contain a /r/<roomId> path" };
219
226
  }
220
227
  const roomId = match[1]!;
221
- const fragment = url.hash.startsWith("#") ? url.hash.slice(1) : url.hash;
228
+ // Key rides dot-joined in the path (`/r/<roomId>.<key>`); legacy links
229
+ // carry it in the fragment (`/r/<roomId>#<key>`).
230
+ const fragment = match[2] ?? (url.hash.startsWith("#") ? url.hash.slice(1) : url.hash);
222
231
  if (!fragment) {
223
- return { error: "Collab link is missing the #<key> fragment" };
232
+ return { error: "Collab link is missing the <key> part" };
224
233
  }
225
234
  const secret = B64URL_RE.test(fragment) ? new Uint8Array(Buffer.from(fragment, "base64url")) : null;
226
235
  if (!secret || (secret.byteLength !== ROOM_KEY_BYTES && secret.byteLength !== ROOM_KEY_BYTES + WRITE_TOKEN_BYTES)) {
@@ -1339,7 +1339,7 @@ export const SETTINGS_SCHEMA = {
1339
1339
  tab: "interaction",
1340
1340
  group: "Collab",
1341
1341
  label: "Relay URL",
1342
- description: "Relay used by /collab (wss://host[:port]; self-host with the omp-collab-relay service)",
1342
+ description: "Relay used by /collab (wss://host[:port])",
1343
1343
  },
1344
1344
  },
1345
1345
 
@@ -3948,9 +3948,6 @@ export const SETTINGS_SCHEMA = {
3948
3948
 
3949
3949
  "dev.autoqaPush.endpoint": {
3950
3950
  type: "string",
3951
- // Bundled QA collector — runs `/work/pi-www/autoqa` behind qa.omp.sh.
3952
- // Override via `PI_AUTO_QA_PUSH_URL` or `dev.autoqaPush.endpoint`
3953
- // in `config.yml` to point at a self-hosted instance.
3954
3951
  default: "https://qa.omp.sh/v1/grievances" as const,
3955
3952
  ui: {
3956
3953
  tab: "tools",