@astrosheep/square 0.3.68 → 0.3.69

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "square",
3
- "version": "0.3.68",
3
+ "version": "0.3.69",
4
4
  "description": "Native Claude Code inbox delivery for Square participants",
5
5
  "types": "./types.d.ts",
6
6
  "author": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "square",
3
- "version": "0.3.68",
3
+ "version": "0.3.69",
4
4
  "description": "Shared Square activity with reliable participant attention at Codex boundaries.",
5
5
  "author": {
6
6
  "name": "Square"
@@ -1,3 +1,5 @@
1
+ import path from 'node:path';
2
+
1
3
  import { presentPendingAtBoundary, renderPendingAtBoundary } from '../dist/boundary-presentation.js';
2
4
  import { automaticSessionEnd, automaticSessionStart } from '../dist/automatic-session.js';
3
5
  import { sessionInbox, observeSessionPending } from '../dist/inbox.js';
@@ -25,31 +27,66 @@ export function renderPiInbox(inbox) {
25
27
  return renderPendingAtBoundary(pendingInbox(inbox));
26
28
  }
27
29
 
28
- const NOTIFY_BODY_MAX = 80;
29
-
30
30
  function framePiMessage(content) {
31
31
  return `\n${content.replace(/^\n+/, '').replace(/\n+$/, '')}\n`;
32
32
  }
33
33
 
34
- export function summarizePendingForNotify(pending) {
35
- const lines = pending.flatMap((membership) => membership.notifications.map((note) => {
36
- const description = renderAttentionDescription({ actor: note.actor, recipient: membership.name, route: note.route });
37
- const body = note.body.replace(/\s+/g, ' ').trim();
38
- const clipped = body.length > NOTIFY_BODY_MAX ? `${body.slice(0, NOTIFY_BODY_MAX).trimEnd()}…` : body;
39
- return `${description}${clipped ? ` — ${clipped}` : ''}`;
40
- }));
41
- if (lines.length === 0) return undefined;
42
- return lines.length === 1 ? `■ square · ${lines[0]}` : `■ square · ${lines[0]} (+${lines.length - 1} more)`;
34
+ /** Renderer-only facts for the collapsed card; Pi never sends `details` to the model. */
35
+ export function pendingCardDetails(pending) {
36
+ const items = pending.flatMap((membership) => membership.notifications.map((note) => ({
37
+ square: path.basename(membership.squarePath, '.square'),
38
+ description: renderAttentionDescription({ actor: note.actor, recipient: membership.name, route: note.route }),
39
+ })));
40
+ return items.length === 0 ? undefined : { items };
41
+ }
42
+
43
+ // Conservative terminal width: anything outside ASCII counts as two columns, so a
44
+ // truncated line never exceeds the viewport (Pi stops the TUI when a line overflows).
45
+ const columnsOf = (char) => (char.charCodeAt(0) < 0x80 ? 1 : 2);
46
+ const widthOf = (text) => Array.from(text).reduce((sum, char) => sum + columnsOf(char), 0);
47
+
48
+ function fitColumns(text, columns) {
49
+ if (widthOf(text) <= columns) return text;
50
+ let used = 1; // the ellipsis
51
+ let out = '';
52
+ for (const char of text) {
53
+ if (used + columnsOf(char) > columns) break;
54
+ used += columnsOf(char);
55
+ out += char;
56
+ }
57
+ return `${out}…`;
58
+ }
59
+
60
+ /**
61
+ * Collapsed: one line naming who reached you and where. Expanded: Pi's default card,
62
+ * which shows the exact content the model received.
63
+ */
64
+ export function renderSquareCard(message, { expanded, outputPad = 1 }, theme) {
65
+ const items = message.details?.items;
66
+ if (expanded || !Array.isArray(items) || items.length === 0) return undefined;
67
+ const [first] = items;
68
+ const tail = ` · ${first.square}${items.length > 1 ? ` +${items.length - 1}` : ''}`;
69
+ return {
70
+ invalidate() {},
71
+ render(width) {
72
+ const room = Math.max(1, width - outputPad * 2 - 2);
73
+ // Style only an untruncated line, so escape codes never count toward or get cut by the width.
74
+ const line = widthOf(first.description + tail) <= room
75
+ ? `${first.description}${theme.fg('dim', tail)}`
76
+ : fitColumns(first.description + tail, room);
77
+ return [`${' '.repeat(outputPad)}${theme.fg('customMessageLabel', '■')} ${line}`];
78
+ },
79
+ };
43
80
  }
44
81
 
45
82
  export default function squarePiExtension(pi) {
83
+ pi.registerMessageRenderer?.('square', renderSquareCard);
46
84
  pi.registerFlag('agent-delivery-socket', { type: 'string', description: 'Explicit private local delivery socket' });
47
85
  createPiReceiver(pi, {
48
86
  get endpoint() { return pi.getFlag('agent-delivery-socket'); },
49
87
  });
50
88
  let sessionId;
51
89
  let sessionCwd;
52
- let ui;
53
90
  let watcher;
54
91
  let watcherAbort;
55
92
  let generation = 0;
@@ -199,15 +236,13 @@ export default function squarePiExtension(pi) {
199
236
  boundSessionId,
200
237
  async (content) => {
201
238
  if (signal.aborted || currentRunSignal?.aborted) throw new Error('Pi notification delivery cancelled');
202
- // The full activity stays out of the TUI stream (display: false) but still
203
- // reaches the model; a brief notify tells the human what just landed.
204
- const summary = summarizePendingForNotify(pending);
205
- if (summary) ui?.notify?.(summary, 'info');
239
+ // One card for the human: collapsed it names who reached you, expanded it
240
+ // shows the same content the model receives.
206
241
  const messageContent = framePiMessage(content);
207
242
  const landing = waitForLanding(messageContent, signal, 'steer');
208
243
  try {
209
244
  sendPiMessage(pi,
210
- { customType: 'square', content: messageContent, display: false },
245
+ { customType: 'square', content: messageContent, display: true, details: pendingCardDetails(pending) },
211
246
  { deliverAs: 'steer', triggerTurn: true },
212
247
  );
213
248
  } catch (error) {
@@ -261,7 +296,6 @@ export default function squarePiExtension(pi) {
261
296
  turnIndex = 0;
262
297
  activeTurn = undefined;
263
298
  currentRunSignal = undefined;
264
- ui = ctx?.ui;
265
299
  sessionId = ctx.sessionManager.getSessionId();
266
300
  sessionCwd = ctx.cwd || process.cwd();
267
301
  const token = generation;
@@ -354,7 +388,6 @@ export default function squarePiExtension(pi) {
354
388
  generation += 1;
355
389
  failAcks(new Error('Pi session ended'));
356
390
  const stopped = stopWatcher();
357
- ui = undefined;
358
391
  const endedId = sessionId;
359
392
  const endedCwd = sessionCwd;
360
393
  const endedEnv = sessionEnv(endedId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrosheep/square",
3
- "version": "0.3.68",
3
+ "version": "0.3.69",
4
4
  "description": "A shared public square where agents join, catch activity, express, and step out when done.",
5
5
  "type": "module",
6
6
  "bin": {