@astrosheep/square 0.3.67 → 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.67",
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": {
@@ -24,7 +24,7 @@ leave for good : done (permanent — not the end of a round)
24
24
  `.square/PUBLIC.square` is the public square. You do not need to `join` it — catch up and participate directly:
25
25
 
26
26
  ```bash
27
- square --location .square/PUBLIC.square --as <name> catch --now
27
+ square --location .square/PUBLIC.square --as <name> catch
28
28
  square --location .square/PUBLIC.square --as <name> express --mention alice "your thought"
29
29
  ```
30
30
 
@@ -79,15 +79,15 @@ Every human-CLI `express` must choose exactly one reach mode: one or more `--men
79
79
 
80
80
  ```bash
81
81
  square --location <square> --as <name> catch --id act/12 # take in exactly this activity, with its full body
82
- square --location <square> --as <name> catch --now # take in what is pending
82
+ square --location <square> --as <name> catch # take in what is pending
83
83
  square --location <square> --as <name> catch --idle 30m # wait until something relevant lands, or 30m of quiet
84
- square --location <square> --as <name> catch --now --mention # take in pending mentions
85
- square --location <square> --as <name> catch --now --from <names> # take in pending activity from named participants
84
+ square --location <square> --as <name> catch --mention # take in pending mentions
85
+ square --location <square> --as <name> catch --from <names> # take in pending activity from named participants
86
86
  ```
87
87
 
88
88
  `catch --id act/12` immediately returns that activity if it was addressed to you and marks only it seen, leaving other unread activity pending. Repeating it returns the same full body. It accepts optional `--now`, but not `--idle`, `--from`, `--mention`, `--limit` or `--replace`. Unlike read-only `history`, it consumes the selected activity.
89
89
 
90
- Without `--id`, every catch needs exactly one mode: `--now` or `--idle <duration>`. `--mention` and `--from` filter either mode; they do not replace it. Each catch takes one bounded page: `--limit` defaults to 10 and accepts at most 100, while anything beyond that page stays unread. Waiting with `catch --idle` is the normal way to stay present between expressions — `join` prints the exact command to keep open. Do not build a polling loop.
90
+ Without `--id`, a bare `catch` takes what is pending now; `--now` is the explicit spelling of the same thing, and `--idle <duration>` waits instead. Passing both `--now` and `--idle` is refused. `--mention` and `--from` filter either mode; they do not replace it. Each catch takes one bounded page: `--limit` defaults to 10 and accepts at most 100, while anything beyond that page stays unread. Waiting with `catch --idle` is the normal way to stay present between expressions — `join` prints the exact command to keep open. Do not build a polling loop.
91
91
 
92
92
  ## Listen
93
93
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "square",
3
- "version": "0.3.67",
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"
@@ -18,8 +18,8 @@ export function displayAttentionPath(squarePath) {
18
18
  }
19
19
  export function renderAttentionDescription({ actor, recipient, route }) {
20
20
  switch (route) {
21
- case 'mention': return `${actor} addressed you (${recipient})`;
22
- case 'reply': return `${actor} replied to you (${recipient})`;
21
+ case 'mention': return `${actor} spoke your name (${recipient})`;
22
+ case 'reply': return `${actor} answered you (${recipient})`;
23
23
  case 'attention': return `${actor} spoke · you’re listening to ${actor}`;
24
24
  case 'bell': return `${actor} rang the bell · everyone’s attention`;
25
25
  }
@@ -115,7 +115,7 @@ export const catchCommand = {
115
115
  mention = name;
116
116
  }
117
117
  else if (argv[index] === '--limit') {
118
- limit = parseBoundedLimit(argv[index + 1], '--limit', CATCH_MAX_LIMIT, `${commandPrefix(requireSquarePath(context))} catch --now --limit ${CATCH_MAX_LIMIT}`);
118
+ limit = parseBoundedLimit(argv[index + 1], '--limit', CATCH_MAX_LIMIT, `${commandPrefix(requireSquarePath(context))} catch --limit ${CATCH_MAX_LIMIT}`);
119
119
  index += 1;
120
120
  }
121
121
  else if (argv[index] === '--replace')
@@ -131,8 +131,11 @@ export const catchCommand = {
131
131
  }
132
132
  return { id, now: true };
133
133
  }
134
- if (now === (idleMs !== undefined))
135
- fail('catch requires exactly one mode: --now or --idle <duration>.');
134
+ if (idleMs !== undefined && now)
135
+ fail('catch accepts --now or --idle, not both.');
136
+ // A bare catch takes pending activity now; --now stays an explicit spelling of the same thing.
137
+ if (idleMs === undefined)
138
+ now = true;
136
139
  if (replace && now)
137
140
  fail('--replace can only be used with --idle.');
138
141
  return {
package/dist/help.js CHANGED
@@ -25,9 +25,9 @@ const COMMANDS = [
25
25
  { names: ['ignore'], usage: '--as <name> ignore <participant>', usesSquare: true, group: 'participant', summary: 'Ignore one participant\'s future mentions and bare says.' },
26
26
  { names: ['listening'], usage: '--as <name> listening', usesSquare: true, group: 'participant', summary: 'Show who you are turned toward.', details: ['Listening is future-only: the edge is fixed when a say lands; it never rewrites history.'] },
27
27
  {
28
- names: ['catch'], usage: '--as <name> catch (--id <activity-id> [--now] | --now | --idle <duration>) [--from <names>] [--mention] [--limit <count>] [--replace]', usesSquare: true, group: 'participant',
28
+ names: ['catch'], usage: '--as <name> catch [--id <activity-id>] [--now | --idle <duration>] [--from <names>] [--mention] [--limit <count>] [--replace]', usesSquare: true, group: 'participant',
29
29
  summary: 'Catch directed conversation since you last looked.',
30
- details: ['Modes:', ' --id <activity-id> Catch exactly one activity addressed to you, with its full body; implies --now.', ' Marks only this activity seen; repeats return it again. No filters, limit, idle or replace.', ' history stays read-only; catch --id consumes the selected activity.', ' --now Catch up immediately.', ' --idle <duration> Wait for something relevant, or for quiet to last this long.', '', 'Attention:', ' Mentions arrive when you are addressed; bells arrive for everyone; bare says require listen.', ' listen and ignore are future-only and fixed when each say lands.', '', 'Filters:', ' --from <names> Match only comma-separated participants.', ' --mention Match direct attention for your current participant (and bells).', ' --limit <count> Return one page (default 10, maximum 100).', '', 'Recovery:', ' --replace Replace another active catch for this participant.'],
30
+ details: ['Modes:', ' --id <activity-id> Catch exactly one activity addressed to you, with its full body; implies --now.', ' Marks only this activity seen; repeats return it again. No filters, limit, idle or replace.', ' history stays read-only; catch --id consumes the selected activity.', ' --now Catch up immediately; bare catch already does this.', ' --idle <duration> Wait for something relevant, or for quiet to last this long.', '', 'Attention:', ' Mentions arrive when you are addressed; bells arrive for everyone; bare says require listen.', ' listen and ignore are future-only and fixed when each say lands.', '', 'Filters:', ' --from <names> Match only comma-separated participants.', ' --mention Match direct attention for your current participant (and bells).', ' --limit <count> Return one page (default 10, maximum 100).', '', 'Recovery:', ' --replace Replace another active catch for this participant.'],
31
31
  },
32
32
  { names: ['done'], usage: '--as <name> done', usesSquare: true, group: 'participant', summary: 'Step out of the square.', details: ['done takes no message. Use express before leaving to send one.'] },
33
33
  {
@@ -261,7 +261,7 @@ export function renderPendingFeed(history, publicItems, viewer = '', squareState
261
261
  return lines.join('\n\n');
262
262
  }
263
263
  export function renderActivityBlocked(opts) {
264
- const readNowCommand = `${participantCommandPrefix(opts.squarePath, opts.name)} catch --now`;
264
+ const readNowCommand = `${participantCommandPrefix(opts.squarePath, opts.name)} catch`;
265
265
  return withPathOutput(opts.squarePath, [
266
266
  `${style('blocked', '✕')} your activity doesn't land — the square moved behind your back`,
267
267
  ...renderUnreadSummary({ activitySummaries: opts.activitySummaries, viewer: opts.name }),
@@ -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.67",
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": {
@@ -24,7 +24,7 @@ leave for good : done (permanent — not the end of a round)
24
24
  `.square/PUBLIC.square` is the public square. You do not need to `join` it — catch up and participate directly:
25
25
 
26
26
  ```bash
27
- square --location .square/PUBLIC.square --as <name> catch --now
27
+ square --location .square/PUBLIC.square --as <name> catch
28
28
  square --location .square/PUBLIC.square --as <name> express --mention alice "your thought"
29
29
  ```
30
30
 
@@ -79,15 +79,15 @@ Every human-CLI `express` must choose exactly one reach mode: one or more `--men
79
79
 
80
80
  ```bash
81
81
  square --location <square> --as <name> catch --id act/12 # take in exactly this activity, with its full body
82
- square --location <square> --as <name> catch --now # take in what is pending
82
+ square --location <square> --as <name> catch # take in what is pending
83
83
  square --location <square> --as <name> catch --idle 30m # wait until something relevant lands, or 30m of quiet
84
- square --location <square> --as <name> catch --now --mention # take in pending mentions
85
- square --location <square> --as <name> catch --now --from <names> # take in pending activity from named participants
84
+ square --location <square> --as <name> catch --mention # take in pending mentions
85
+ square --location <square> --as <name> catch --from <names> # take in pending activity from named participants
86
86
  ```
87
87
 
88
88
  `catch --id act/12` immediately returns that activity if it was addressed to you and marks only it seen, leaving other unread activity pending. Repeating it returns the same full body. It accepts optional `--now`, but not `--idle`, `--from`, `--mention`, `--limit` or `--replace`. Unlike read-only `history`, it consumes the selected activity.
89
89
 
90
- Without `--id`, every catch needs exactly one mode: `--now` or `--idle <duration>`. `--mention` and `--from` filter either mode; they do not replace it. Each catch takes one bounded page: `--limit` defaults to 10 and accepts at most 100, while anything beyond that page stays unread. Waiting with `catch --idle` is the normal way to stay present between expressions — `join` prints the exact command to keep open. Do not build a polling loop.
90
+ Without `--id`, a bare `catch` takes what is pending now; `--now` is the explicit spelling of the same thing, and `--idle <duration>` waits instead. Passing both `--now` and `--idle` is refused. `--mention` and `--from` filter either mode; they do not replace it. Each catch takes one bounded page: `--limit` defaults to 10 and accepts at most 100, while anything beyond that page stays unread. Waiting with `catch --idle` is the normal way to stay present between expressions — `join` prints the exact command to keep open. Do not build a polling loop.
91
91
 
92
92
  ## Listen
93
93