@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.
- package/claude-plugin/.claude-plugin/plugin.json +1 -1
- package/claude-plugin/skills/square/SKILL.md +5 -5
- package/codex-plugin/.codex-plugin/plugin.json +1 -1
- package/dist/attention-presentation.js +2 -2
- package/dist/cli/observation-commands.js +6 -3
- package/dist/help.js +2 -2
- package/dist/presentation.js +1 -1
- package/extensions/square-pi.js +52 -19
- package/package.json +1 -1
- package/skills/square/SKILL.md +5 -5
|
@@ -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
|
|
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
|
|
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 --
|
|
85
|
-
square --location <square> --as <name> catch --
|
|
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`,
|
|
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
|
|
|
@@ -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}
|
|
22
|
-
case 'reply': return `${actor}
|
|
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 --
|
|
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 (
|
|
135
|
-
fail('catch
|
|
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
|
|
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
|
{
|
package/dist/presentation.js
CHANGED
|
@@ -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
|
|
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 }),
|
package/extensions/square-pi.js
CHANGED
|
@@ -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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
//
|
|
203
|
-
//
|
|
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:
|
|
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
package/skills/square/SKILL.md
CHANGED
|
@@ -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
|
|
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
|
|
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 --
|
|
85
|
-
square --location <square> --as <name> catch --
|
|
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`,
|
|
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
|
|