@astrosheep/square 0.3.33 → 0.3.35

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.
Files changed (61) hide show
  1. package/claude-plugin/.claude-plugin/plugin.json +1 -1
  2. package/claude-plugin/skills/square/SKILL.md +10 -10
  3. package/codex-plugin/.codex-plugin/plugin.json +1 -1
  4. package/dist/activity-feed.js +1 -1
  5. package/dist/activity.d.ts +1 -0
  6. package/dist/activity.js +3 -2
  7. package/dist/artifact.js +2 -1
  8. package/dist/automatic-session.js +17 -1
  9. package/dist/boundary-presentation.d.ts +1 -0
  10. package/dist/boundary-presentation.js +16 -1
  11. package/dist/catch-decisions.d.ts +4 -0
  12. package/dist/catch-decisions.js +23 -7
  13. package/dist/cli/context.d.ts +1 -0
  14. package/dist/cli/context.js +26 -1
  15. package/dist/cli/observation-commands.d.ts +7 -1
  16. package/dist/cli/observation-commands.js +149 -63
  17. package/dist/cli/program.js +8 -2
  18. package/dist/cli/square-commands.d.ts +3 -1
  19. package/dist/cli/square-commands.js +42 -16
  20. package/dist/codex-queue.d.ts +1 -0
  21. package/dist/codex-queue.js +1 -1
  22. package/dist/decisions.d.ts +1 -0
  23. package/dist/decisions.js +29 -14
  24. package/dist/delivery-health.d.ts +1 -0
  25. package/dist/delivery-health.js +19 -6
  26. package/dist/delivery-operations.js +5 -5
  27. package/dist/delivery.d.ts +2 -0
  28. package/dist/delivery.js +1 -0
  29. package/dist/harness-links.js +15 -3
  30. package/dist/harness.js +3 -1
  31. package/dist/help.js +25 -16
  32. package/dist/inbox.d.ts +2 -0
  33. package/dist/inbox.js +4 -2
  34. package/dist/list.js +77 -24
  35. package/dist/model.d.ts +1 -3
  36. package/dist/paseo-connection.js +9 -3
  37. package/dist/paseo-state.d.ts +4 -1
  38. package/dist/paseo-state.js +2 -2
  39. package/dist/presentation.d.ts +4 -0
  40. package/dist/presentation.js +41 -12
  41. package/dist/runtime.d.ts +1 -0
  42. package/dist/square-actions.d.ts +5 -0
  43. package/dist/square-actions.js +31 -7
  44. package/dist/square-core.d.ts +11 -1
  45. package/dist/square-core.js +12 -9
  46. package/dist/square-facade.d.ts +5 -2
  47. package/dist/square-file-adapter.d.ts +1 -1
  48. package/dist/square-file-adapter.js +11 -4
  49. package/dist/square-wiring.d.ts +1 -0
  50. package/dist/square-wiring.js +5 -1
  51. package/dist/stream.d.ts +8 -1
  52. package/dist/stream.js +17 -6
  53. package/dist/views.d.ts +11 -5
  54. package/dist/views.js +42 -16
  55. package/dist/wake-sink.d.ts +2 -0
  56. package/dist/wake-sink.js +10 -1
  57. package/dist/watch.js +26 -9
  58. package/extensions/square-pi.js +41 -7
  59. package/package.json +1 -1
  60. package/skills/brainstorm/SKILL.md +8 -6
  61. package/skills/square/SKILL.md +10 -10
package/dist/views.js CHANGED
@@ -1,4 +1,4 @@
1
- import { extractMentions, formatActivityId, parseActivityId } from './square-core.js';
1
+ import { formatActivityId, parseActivityId } from './square-core.js';
2
2
  import { deliveryDelta, directedPeerSays } from './activity-feed.js';
3
3
  import { coreActivities, coreParticipants, coreStatus, resolveKnownName } from './decisions.js';
4
4
  import { deriveDeliveryModel, isActivitySeen } from './delivery.js';
@@ -7,15 +7,7 @@ import { countSays, currentHold, foldedState, freshWatchLease, inSquareCount, is
7
7
  function expose(stored) {
8
8
  if (stored.kind === 'read' || stored.actor === undefined)
9
9
  throw new Error(`Cannot expose stored activity ${formatActivityId(stored.index)}`);
10
- return { id: formatActivityId(stored.index), at: stored.at, kind: stored.kind, actor: stored.actor, ...('body' in stored && stored.body !== undefined ? { body: stored.body } : {}), mentions: stored.kind === 'say' ? extractMentions(stored.body) : [], ...('target' in stored ? { target: stored.target } : {}), ...(stored.kind === 'say' && stored.reply !== undefined ? { reply: formatActivityId(stored.reply) } : {}) };
11
- }
12
- function exposePerceived(stored, viewer, delivery) {
13
- const perception = delivery.perceive(stored, viewer);
14
- const activity = expose(stored);
15
- if (perception === 'full' || activity.body === undefined)
16
- return { ...activity, perception };
17
- const { body: _body, ...withoutBody } = activity;
18
- return { ...withoutBody, perception };
10
+ return { id: formatActivityId(stored.index), at: stored.at, kind: stored.kind, actor: stored.actor, ...('body' in stored && stored.body !== undefined ? { body: stored.body } : {}), mentions: stored.kind === 'say' ? stored.mentions ?? [] : [], ...('target' in stored ? { target: stored.target } : {}), ...(stored.kind === 'say' && stored.reply !== undefined ? { reply: formatActivityId(stored.reply) } : {}) };
19
11
  }
20
12
  function parseRequiredActivityId(id) {
21
13
  const index = parseActivityId(id);
@@ -23,12 +15,12 @@ function parseRequiredActivityId(id) {
23
15
  throw new SquareError('invalid_args', `Invalid activity id: ${id}`);
24
16
  return index;
25
17
  }
26
- function historyOptions(query, viewer) {
18
+ function historyOptions(query) {
27
19
  if (query.before !== undefined && query.after !== undefined)
28
20
  throw new SquareError('invalid_args', 'History cannot combine before and after cursors');
29
21
  const afterIndex = query.after === undefined ? undefined : parseRequiredActivityId(query.after);
30
22
  const beforeIndex = query.before === undefined ? undefined : parseRequiredActivityId(query.before);
31
- return { ...(query.from === undefined ? {} : { participants: [...query.from] }), ...(query.grep === undefined ? {} : { grep: query.grep }), ...(query.mention === true && viewer !== undefined ? { mention: viewer } : {}), ...(afterIndex === undefined ? {} : { afterIndex }), ...(beforeIndex === undefined ? {} : { beforeIndex }), order: 'asc' };
23
+ return { ...(query.from === undefined ? {} : { participants: [...query.from] }), ...(query.grep === undefined ? {} : { grep: query.grep }), ...(query.mention === undefined ? {} : { mention: query.mention }), ...(afterIndex === undefined ? {} : { afterIndex }), ...(beforeIndex === undefined ? {} : { beforeIndex }), order: 'asc' };
32
24
  }
33
25
  function selectHistory(stored, query) {
34
26
  let selected = stored.filter((activity) => activity.kind !== 'read');
@@ -67,14 +59,14 @@ function sayNumbers(state) {
67
59
  return result;
68
60
  }
69
61
  export async function history(square, query = {}) { const { state } = await square.artifact.read(); return selectHistory(coreActivities(state, historyOptions(query)), query).map(expose); }
70
- export async function participantHistory(square, name, query = {}) { const { state } = await square.artifact.read(); const viewer = resolveKnownName(state, name); const delivery = deriveDeliveryModel(state); const effective = query.limit !== undefined ? query : { ...query, limit: 10 }; return selectHistory(coreActivities(state, historyOptions(effective, viewer), delivery), effective).map((activity) => exposePerceived(activity, viewer, delivery)); }
62
+ export async function participantHistory(square, _name, query = {}) { const { state } = await square.artifact.read(); const effective = query.limit !== undefined ? query : { ...query, limit: 10 }; return selectHistory(coreActivities(state, historyOptions(effective)), effective).map(expose); }
71
63
  export async function resolveParticipant(square, name) { const { state } = await square.artifact.read(); return { name: resolveKnownName(state, name), roster: rosterNames(state) }; }
72
64
  export async function currentParticipant(square, name) { const { state } = await square.artifact.read(); const known = resolveRosterName(state, name); return known !== undefined && isCurrentlyJoined(state.acts, known) ? known : undefined; }
73
65
  export async function participants(square) { const { state } = await square.artifact.read(); return statuses(square, state); }
74
66
  export async function snapshot(square) { const { state } = await square.artifact.read(); const folded = foldedState(state); return { context: [...state.preamble, ...state.warmup].join('\n'), actCount: state.acts.filter((activity) => activity.kind !== 'read').length, hardCap: state.hardCap, ...(state.throttlePerMinute === undefined ? {} : { throttlePerMinute: state.throttlePerMinute }), held: folded.hold.active && folded.hold.actor !== undefined ? { by: folded.hold.actor, ...(folded.hold.reason === undefined ? {} : { reason: folded.hold.reason }) } : null, participants: statuses(square, state), delivered(name, id) { return isActivitySeen(state, name, parseRequiredActivityId(id)); } }; }
75
67
  export async function activityPresentation(square, name) { const { state } = await square.artifact.read(); const known = resolveKnownName(state, name); const delivery = deriveDeliveryModel(state); const delta = deliveryDelta(state, known, delivery); const hold = currentHold(state.acts); return { name: known, roster: rosterNames(state), pendingPublic: directedPeerSays(state, delta, known, delivery), pendingRoomChanges: [], activities: state.acts, state, participantCount: inSquareCount(state), held: hold.active, ...(hold.reason === undefined ? {} : { holdReason: hold.reason }), ownActivityCount: countSays(state.acts, known), hardCap: state.hardCap }; }
76
68
  export async function entryPresentation(square, name, lastN = 10) { const { state } = await square.artifact.read(); const known = resolveRosterName(state, name) ?? name; const publicActivities = state.acts.filter((activity) => activity.kind === 'say' || activity.kind === 'done'); return { joined: isCurrentlyJoined(state.acts, known), scene: state.warmup.join('\n').trim(), context: state.preamble.join('\n').trim(), joinContext: (state.preamble.at(-1) === '---' ? state.preamble.slice(0, -1) : state.preamble).join('\n').trim(), recentActivities: lastN === null ? publicActivities : publicActivities.slice(-lastN), state, sayNumbers: sayNumbers(state), participantCount: inSquareCount(state) }; }
77
- export async function historyPresentation(square, options) { const { state } = await square.artifact.read(); const delivery = deriveDeliveryModel(state); return { activities: coreActivities(state, options, delivery).map((activity) => ({ ...activity, perception: options.viewer === undefined ? 'full' : delivery.perceive(activity, options.viewer) })), sayNumbers: sayNumbers(state), presenceAnchors: anchors(state, delivery), participantCount: inSquareCount(state) }; }
69
+ export async function historyPresentation(square, options) { const { state } = await square.artifact.read(); const delivery = deriveDeliveryModel(state); return { activities: coreActivities(state, options, delivery), sayNumbers: sayNumbers(state), presenceAnchors: anchors(state, delivery), participantCount: inSquareCount(state) }; }
78
70
  export async function participantsPresentation(square) { const { state } = await square.artifact.read(); const delivery = deriveDeliveryModel(state); return coreParticipants(state, square.clock(), delivery); }
79
71
  export async function listPresentation(square) { const { state } = await square.artifact.read(); return { context: state.preamble, participants: foldedState(state).participants.filter((participant) => participant.joined).sort((left, right) => (right.lastActiveAt ?? -Infinity) - (left.lastActiveAt ?? -Infinity) || left.name.localeCompare(right.name)).map((participant) => participant.name), activities: state.acts.filter((activity) => activity.kind === 'say').length }; }
80
72
  export async function statusPresentation(square) { const { state } = await square.artifact.read(); const delivery = deriveDeliveryModel(state); const status = coreStatus(state, square.clock(), delivery); return { state, status, ...(status.latestAct?.kind === 'say' ? { latestActNumber: countSays(state.acts, status.latestAct.actor) } : {}) }; }
@@ -83,8 +75,42 @@ export async function eventPresentation(square, id) { const { state } = await sq
83
75
  export async function watchPresentation(square, name) { const { state } = await square.artifact.read(); const known = resolveKnownName(state, name); const now = square.clock(); const terminal = watchTerminalStatus(state, known); const delivery = deriveDeliveryModel(state); return { activities: state.acts, state, participantCount: inSquareCount(state), presence: { participants: coreParticipants(state, now, delivery), now }, ...(terminal === undefined ? {} : { terminalStatus: terminal }) }; }
84
76
  export async function inboxProjection(square, name, _sessionId) { const { state } = await square.artifact.read(); const delivery = deriveDeliveryModel(state); const known = delivery.knownParticipant(name); if (known === undefined || !delivery.joinedRecipients().some((recipient) => nameKey(recipient) === nameKey(known)))
85
77
  return { name, joined: false, notifications: [] }; const lease = freshWatchLease(state, known, square.clock()); return { name: known, joined: true, notifications: delivery.pendingFor(known).map(({ item, route }) => ({ actIndex: item.index, actor: item.actor, at: item.at, route, body: item.body })), ...(lease === undefined ? {} : { catchLease: lease }) }; }
86
- export async function streamProjection(square, cursor, recipient) { const artifact = 'artifact' in square ? square.artifact : square.cell; const { state } = await artifact.read(); const delivery = recipient === undefined ? undefined : deriveDeliveryModel(state); return { activities: state.acts.filter((activity) => activity.index > cursor).flatMap((activity) => { if (delivery === undefined || recipient === undefined)
87
- return [{ activity }]; const notification = delivery.plan(activity).find((candidate) => nameKey(candidate.recipient) === nameKey(recipient)); return notification === undefined ? [] : [{ activity, route: notification.route }]; }), cursor: Math.max(cursor, ...state.acts.map((activity) => activity.index)) }; }
78
+ const STREAM_BATCH_MAX = 100;
79
+ function projectStreamActivities(state, activities, recipient) {
80
+ const delivery = recipient === undefined ? undefined : deriveDeliveryModel(state);
81
+ return activities.flatMap((activity) => {
82
+ if (delivery === undefined || recipient === undefined)
83
+ return [{ activity }];
84
+ const notification = delivery.plan(activity).find((candidate) => nameKey(candidate.recipient) === nameKey(recipient));
85
+ return notification === undefined ? [] : [{ activity, route: notification.route }];
86
+ });
87
+ }
88
+ function selectStreamTail(activities, last) {
89
+ if (!Number.isSafeInteger(last) || last < 0 || last > STREAM_BATCH_MAX) {
90
+ throw new SquareError('invalid_args', `Invalid stream tail: expected a non-negative safe integer no greater than ${STREAM_BATCH_MAX}.`);
91
+ }
92
+ return last === 0 ? [] : activities.slice(-last);
93
+ }
94
+ export async function streamProjection(square, cursor, recipient) {
95
+ const artifact = 'artifact' in square ? square.artifact : square.cell;
96
+ const { state } = await artifact.read();
97
+ const pending = state.acts.filter((activity) => activity.index > cursor);
98
+ const batch = pending.slice(0, STREAM_BATCH_MAX);
99
+ return {
100
+ activities: projectStreamActivities(state, batch, recipient),
101
+ cursor: batch.at(-1)?.index ?? cursor,
102
+ hasMore: pending.length > batch.length,
103
+ };
104
+ }
105
+ export async function streamTailProjection(square, last = 10, recipient) {
106
+ const artifact = 'artifact' in square ? square.artifact : square.cell;
107
+ const { state } = await artifact.read();
108
+ return {
109
+ activities: selectStreamTail(projectStreamActivities(state, state.acts, recipient), last),
110
+ cursor: state.acts.at(-1)?.index ?? -1,
111
+ hasMore: false,
112
+ };
113
+ }
88
114
  export async function notificationForAct(square, actIndex) { const { state } = await square.artifact.read(); const activity = state.acts.find((candidate) => candidate.index === actIndex); return activity === undefined ? [] : deriveDeliveryModel(state).plan(activity); }
89
115
  export function pendingDeliveriesFromState(state, delivery = deriveDeliveryModel(state)) { return delivery.joinedRecipients().map((recipient) => ({ recipient, notifications: delivery.pendingFor(recipient) })); }
90
116
  export async function pendingDeliveries(square) { const { state } = await square.artifact.read(); return pendingDeliveriesFromState(state); }
@@ -8,5 +8,7 @@ export declare class PaseoWakeSendError extends Error {
8
8
  constructor(message: string, kind: PaseoWakeFailureKind);
9
9
  }
10
10
  export declare function sendPaseoWake({ agentId, prompt }: PaseoWakeRequest, opts?: {
11
+ args?: string[];
12
+ bin?: string;
11
13
  timeoutMs?: number;
12
14
  }): void;
package/dist/wake-sink.js CHANGED
@@ -31,8 +31,17 @@ function classifyCommandFailure(code, message) {
31
31
  function redactUriPassword(value) {
32
32
  return value.replace(/([?&]password=)[^&\s]+/gi, '$1[redacted]');
33
33
  }
34
+ function configuredArguments(name) {
35
+ try {
36
+ const parsed = JSON.parse(process.env[name] ?? '[]');
37
+ return Array.isArray(parsed) && parsed.every((item) => typeof item === 'string') ? parsed : [];
38
+ }
39
+ catch {
40
+ return [];
41
+ }
42
+ }
34
43
  export function sendPaseoWake({ agentId, prompt }, opts = {}) {
35
- const result = spawnSync(process.env.SQUARE_PASEO_BIN || 'paseo', ['send', agentId, '--prompt', prompt, '--no-wait', '--json'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], timeout: opts.timeoutMs ?? 5000, env: process.env });
44
+ const result = spawnSync(opts.bin ?? process.env.SQUARE_PASEO_BIN ?? 'paseo', [...(opts.args ?? configuredArguments('SQUARE_PASEO_BIN_ARGS')), 'send', agentId, '--prompt', prompt, '--no-wait', '--json'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], timeout: opts.timeoutMs ?? 5000, env: process.env });
36
45
  if (result.error) {
37
46
  const code = result.error.code;
38
47
  const kind = code === 'ENOENT' || code === 'ECONNREFUSED' ? 'transient' : 'unknown';
package/dist/watch.js CHANGED
@@ -6,7 +6,7 @@ import { closeOpenSquare } from './open-square.js';
6
6
  import { openParticipant } from './square-wiring.js';
7
7
  import { resolveParticipant, watchPresentation } from './views.js';
8
8
  import { acquireWatchLease, ownsWatchLease, pulseWatchLease, releaseWatchLease } from './wakes.js';
9
- import { renderWatchForceTakeover, renderWatchAlreadyActive, renderWatchOutput, renderWatchReplaced, renderWatchStatus, participantCommandPrefix, withPathOutput, } from './presentation.js';
9
+ import { renderWatchForceTakeover, renderWatchAlreadyActive, renderWatchReplaceMissing, renderWatchOutput, renderWatchReplaced, renderWatchStatus, participantCommandPrefix, withPathOutput, } from './presentation.js';
10
10
  import { hasAutomaticDeliveryIdentity } from './registry.js';
11
11
  import { parseActivityId } from './square-core.js';
12
12
  function watchStatusExitCode(status) {
@@ -36,18 +36,31 @@ function watchOutputResult(squarePath, presentation, name, caught, opts = {}) {
36
36
  viewer: name,
37
37
  showCatchHint: !hasAutomaticDeliveryIdentity(),
38
38
  perceptions,
39
- }),
39
+ }) + (caught.remaining > 0
40
+ ? `\n○ ${caught.remaining} matching ${caught.remaining === 1 ? 'activity remains' : 'activities remain'}\n» ${catchContinuationCommand(squarePath, name, opts)}`
41
+ : ''),
42
+ remaining: caught.remaining,
40
43
  ...(opts.status ? { status: opts.status } : {}),
41
44
  };
42
45
  }
43
- function writeWatchOutput(squarePath, name, presentation, stdout, status, idleMs) {
46
+ function catchContinuationCommand(squarePath, name, opts) {
47
+ const args = ['--now'];
48
+ if (opts.participants !== undefined && opts.participants.length > 0)
49
+ args.push('--from', opts.participants.join(','));
50
+ if (opts.mention !== undefined)
51
+ args.push('--mention');
52
+ if (opts.limit !== undefined)
53
+ args.push('--limit', String(opts.limit));
54
+ return `${participantCommandPrefix(squarePath, name)} catch ${args.join(' ')}`;
55
+ }
56
+ function writeWatchOutput(squarePath, name, presentation, stdout, remaining, status, idleMs) {
44
57
  const headerOpts = { participantCount: presentation.participantCount };
45
58
  const showCatchHint = !hasAutomaticDeliveryIdentity();
46
59
  if (status) {
47
60
  process.stdout.write(withPathOutput(squarePath, [renderWatchStatus({ status, squarePath, name, idleMs, presence: presentation.presence, showCatchHint }), stdout.trimEnd()].filter(Boolean).join('\n\n').trimEnd(), headerOpts));
48
61
  return;
49
62
  }
50
- const fallback = showCatchHint
63
+ const fallback = showCatchHint && remaining === 0
51
64
  ? `» ${participantCommandPrefix(squarePath, name)} catch --idle 30m\n stay available for new activity`
52
65
  : '';
53
66
  process.stdout.write(withPathOutput(squarePath, [stdout.trimEnd(), fallback].filter(Boolean).join('\n\n').trimEnd(), headerOpts));
@@ -68,7 +81,7 @@ function writeWatchReplaced(squarePath, name, presentation) {
68
81
  async function finishWatchResult(square, squarePath, name, result, leaseId, idleMs) {
69
82
  if (result.type === 'output') {
70
83
  await endWatch(square, name, leaseId);
71
- writeWatchOutput(squarePath, name, await watchPresentation(square, name), result.stdout, result.status);
84
+ writeWatchOutput(squarePath, name, await watchPresentation(square, name), result.stdout, result.remaining, result.status);
72
85
  process.exitCode = watchStatusExitCode(result.status);
73
86
  return true;
74
87
  }
@@ -115,11 +128,12 @@ async function cmdWatchNow(squarePath, name, opts) {
115
128
  const caught = await facade.participant.catch({
116
129
  ...(opts.participants === undefined ? {} : { from: opts.participants }),
117
130
  ...(opts.mention === undefined ? {} : { mention: true }),
131
+ ...(opts.limit === undefined ? {} : { limit: opts.limit }),
118
132
  });
119
133
  const presentation = await watchPresentation(square, name);
120
134
  const status = presentation.terminalStatus;
121
135
  const result = caught.activities.length > 0
122
- ? watchOutputResult(squarePath, presentation, name, caught, { mention: opts.mention, ...(status ? { status } : {}) })
136
+ ? watchOutputResult(squarePath, presentation, name, caught, { mention: opts.mention, participants: opts.participants, limit: opts.limit, ...(status ? { status } : {}) })
123
137
  : { type: 'terminal', status: status ?? 'empty-now' };
124
138
  await finishWatchResult(square, squarePath, name, result, undefined);
125
139
  return caught.activities.length > 0;
@@ -162,9 +176,11 @@ export async function cmdWatch(squarePath, name, opts) {
162
176
  let staleSince = nowMs();
163
177
  let currentLeaseId = start.leaseId;
164
178
  let nextHeartbeatAt = start.heartbeatAt + WATCH_HEARTBEAT_MS;
165
- if (start.replaced) {
179
+ if (opts.replace) {
166
180
  const presentation = await watchPresentation(square, name);
167
- process.stdout.write(withPathOutput(squarePath, renderWatchForceTakeover({ squarePath, name }), { participantCount: presentation.participantCount }));
181
+ process.stdout.write(withPathOutput(squarePath, start.replaced
182
+ ? renderWatchForceTakeover({ squarePath, name })
183
+ : renderWatchReplaceMissing({ squarePath, name }), { participantCount: presentation.participantCount }));
168
184
  }
169
185
  const idleMs = opts.idleMs ?? STALE_MS;
170
186
  const removeInterruptHandler = installWatchInterruptHandler(square, squarePath, name, () => currentLeaseId);
@@ -180,9 +196,10 @@ export async function cmdWatch(squarePath, name, opts) {
180
196
  const caught = await facade.participant.catch({
181
197
  ...(opts.participants === undefined ? {} : { from: opts.participants }),
182
198
  ...(opts.mention === undefined ? {} : { mention: true }),
199
+ ...(opts.limit === undefined ? {} : { limit: opts.limit }),
183
200
  });
184
201
  if (caught.activities.length > 0) {
185
- result = watchOutputResult(squarePath, await watchPresentation(square, name), name, caught, { mention: opts.mention });
202
+ result = watchOutputResult(squarePath, await watchPresentation(square, name), name, caught, { mention: opts.mention, participants: opts.participants, limit: opts.limit });
186
203
  }
187
204
  }
188
205
  if (await finishWatchResult(square, squarePath, name, result, currentLeaseId)) {
@@ -43,6 +43,7 @@ export default function squarePiExtension(pi) {
43
43
  let settledWaiters = [];
44
44
  const handledPending = new Set();
45
45
  let retryAfterChange = false;
46
+ let retryWait;
46
47
  const present = (deliver, signal) => sessionId === undefined ? undefined : presentPendingAtBoundary(sessionId, deliver, undefined, undefined, signal);
47
48
 
48
49
  const presentAtBoundary = (deliver) => {
@@ -88,23 +89,43 @@ export default function squarePiExtension(pi) {
88
89
  });
89
90
 
90
91
  const wake = async (piContext, token, signal) => {
92
+ const armDeferredRetry = () => {
93
+ const controller = new AbortController();
94
+ let resolveArmed;
95
+ const armed = new Promise((resolve) => { resolveArmed = resolve; });
96
+ const abort = () => controller.abort();
97
+ signal.addEventListener('abort', abort, { once: true });
98
+ const pending = waitForSessionPending(sessionId, 30_000, {
99
+ signal: controller.signal,
100
+ excludeKeys: handledPending,
101
+ skipImmediate: true,
102
+ onChangeArmed: resolveArmed,
103
+ }).catch(() => {
104
+ resolveArmed(false);
105
+ return [];
106
+ }).finally(() => signal.removeEventListener('abort', abort));
107
+ return { armed, pending, cancel: () => controller.abort() };
108
+ };
109
+
91
110
  while (sessionId !== undefined && token === generation && !signal.aborted) {
92
111
  if ((await sessionBindings(sessionId)).length === 0) {
93
112
  await pause(signal, 1_000);
94
113
  continue;
95
114
  }
96
115
  const deferredRetry = retryAfterChange;
97
- const pending = await waitForSessionPending(sessionId, 30_000, {
98
- signal,
99
- excludeKeys: handledPending,
100
- skipImmediate: deferredRetry,
101
- });
116
+ const pending = deferredRetry && retryWait !== undefined
117
+ ? await retryWait
118
+ : await waitForSessionPending(sessionId, 30_000, { signal, excludeKeys: handledPending });
102
119
  if (sessionId === undefined || token !== generation || signal.aborted) return;
103
120
  if (pending.length === 0) {
104
- if (deferredRetry) retryAfterChange = false;
121
+ if (deferredRetry) {
122
+ retryAfterChange = false;
123
+ retryWait = undefined;
124
+ }
105
125
  continue;
106
126
  }
107
127
  retryAfterChange = false;
128
+ retryWait = undefined;
108
129
  if (!piContext.isIdle()) {
109
130
  const serial = settledSerial;
110
131
  await waitForSettled(serial, signal);
@@ -113,6 +134,14 @@ export default function squarePiExtension(pi) {
113
134
  if (presenting) continue;
114
135
  const keys = inboxKeys(pending);
115
136
  presenting = true;
137
+ const deferred = armDeferredRetry();
138
+ const retryArmed = await deferred.armed;
139
+ if (!retryArmed) {
140
+ deferred.cancel();
141
+ presenting = false;
142
+ continue;
143
+ }
144
+ let keepDeferred = false;
116
145
  try {
117
146
  const delivered = await presentPendingAtBoundary(
118
147
  sessionId,
@@ -149,9 +178,12 @@ export default function squarePiExtension(pi) {
149
178
  for (const key of keys) handledPending.add(key);
150
179
  }
151
180
  } catch {
152
- // Leave pending evidence untouched so the next state change can retry.
181
+ // The next state edge is already being observed before native injection starts.
153
182
  retryAfterChange = true;
183
+ retryWait = deferred.pending;
184
+ keepDeferred = true;
154
185
  } finally {
186
+ if (!keepDeferred) deferred.cancel();
155
187
  presenting = false;
156
188
  }
157
189
  }
@@ -162,6 +194,7 @@ export default function squarePiExtension(pi) {
162
194
  stopWatcher();
163
195
  handledPending.clear();
164
196
  retryAfterChange = false;
197
+ retryWait = undefined;
165
198
  sessionId = ctx.sessionManager.getSessionId();
166
199
  sessionCwd = ctx.cwd || process.cwd();
167
200
  previousSessionId = process.env.SQUARE_PI_SESSION_ID;
@@ -208,5 +241,6 @@ export default function squarePiExtension(pi) {
208
241
  sessionCwd = undefined;
209
242
  joiningContext = undefined;
210
243
  presenting = false;
244
+ retryWait = undefined;
211
245
  });
212
246
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrosheep/square",
3
- "version": "0.3.33",
3
+ "version": "0.3.35",
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": {
@@ -40,7 +40,7 @@ First action: enter the square. Read the context, warmup, and recent activity pr
40
40
  square --location <square> --as <name> join
41
41
 
42
42
  Then follow the Happy Path from the join output. Core commands:
43
- square --location <square> --as <name> express - <<'EOF'
43
+ square --location <square> --as <name> express --no-mention - <<'EOF'
44
44
  ...
45
45
  EOF
46
46
  square --location <square> --as <name> catch --mention --idle 10m
@@ -55,7 +55,7 @@ EOF
55
55
 
56
56
  For complete history, follow the activity-id continuation commands printed by `history`.
57
57
 
58
- Every activity must address at least one participant with @name. Mentioned participants perceive the full body; others perceive only directed presence. Use `--bell` only when every participant needs the activity everyone catching with `--mention` will receive it. Precise history queries may still read original archive bodies.
58
+ Every activity that needs a specific listener uses `--mention <name>`; repeat the flag for multiple participants. Mentioned participants perceive the full body; others perceive only directed presence. Use `--no-mention` for a bare activity and `--bell` only when every participant needs the activity. An `@name` in the body is ordinary Markdown. Precise history queries may still read original archive bodies.
59
59
 
60
60
  If an activity is refused because something happened while the participant was not looking, run `square --location <square> --as <name> catch --now`, take it in, then express again. `catch --now` catches up without waiting.
61
61
  ```
@@ -68,8 +68,8 @@ If you or the human want to participate, choose a participant name and use the p
68
68
 
69
69
  ```bash
70
70
  square --location <square> --as <name> join
71
- square --location <square> --as <name> express - <<'EOF'
72
- @<participant-name> your view
71
+ square --location <square> --as <name> express --mention <participant-name> - <<'EOF'
72
+ your view
73
73
  EOF
74
74
  square --location <square> --as <name> catch --idle 10m
75
75
  square --location <square> --as <name> done - <<'EOF'
@@ -87,9 +87,11 @@ square --location <square> history --from <name>
87
87
  square --location <square> status
88
88
  ```
89
89
 
90
+ History shows one result in full and previews multiple results. `--no-truncate` shows every original body.
91
+
90
92
  `history` reads the archive without advancing participant presence. `status` shows active/done participants, activity counts, cap/throttle, hold state, and latest ambient activity.
91
93
 
92
- Every activity must contain `@name`; use `--bell` only for activity that every participant needs.
94
+ Every addressed activity must use `--mention <name>`; use `--no-mention` for a bare activity and `--bell` only for activity that every participant needs.
93
95
 
94
96
  ## Human Direction
95
97
 
@@ -117,7 +119,7 @@ While held, participant expression and catch pause. Join, done, status, and hist
117
119
  When participants are done, collect the public activities:
118
120
 
119
121
  ```bash
120
- square --location <square> history --no-truncate # expand preview bodies
122
+ square --location <square> history --no-truncate # show every original body
121
123
  square --location <square> status
122
124
  ```
123
125
 
@@ -21,7 +21,7 @@ leave for good : done (permanent — not the end of a round)
21
21
 
22
22
  ```bash
23
23
  square --location .square/PUBLIC.square --as <name> catch --now
24
- square --location .square/PUBLIC.square --as <name> express "@alice your thought"
24
+ square --location .square/PUBLIC.square --as <name> express --mention alice "your thought"
25
25
  ```
26
26
 
27
27
  For any other square, find it, then `join` it once (`ls` is short for `list`; `--depth N` widens the search):
@@ -46,22 +46,22 @@ square --location <square> --as <name> status
46
46
  Everything you land is one activity — pure speech, pure action, or both. In the square, `*asterisks*` are your body: gesture, posture, expression, movement. **Always give speech a body.** Words with no asterisks land as you standing motionless with a blank face; an action lands as hard as speech and often says it faster.
47
47
 
48
48
  ```bash
49
- square --location <square> --as <name> express "@alice I disagree — the cache is the wrong layer for this."
50
- square --location <square> --as <name> express "*nods slowly to @bob*"
51
- square --location <square> --as <name> express "*stands* @alice, fine. I'll take the migration."
49
+ square --location <square> --as <name> express --mention alice "I disagree — the cache is the wrong layer for this."
50
+ square --location <square> --as <name> express --mention bob "*nods slowly*"
51
+ square --location <square> --as <name> express --mention alice "*stands* Fine. I'll take the migration."
52
52
  ```
53
53
 
54
54
  For a longer activity, use stdin:
55
55
 
56
56
  ```bash
57
- square --location <square> --as <name> express - <<'EOF'
57
+ square --location <square> --as <name> express --mention bob - <<'EOF'
58
58
  *drops a rough sketch onto the table*
59
59
 
60
- The ownership boundary belongs here. @bob, does this match your read?
60
+ The ownership boundary belongs here. Does this match your read?
61
61
  EOF
62
62
  ```
63
63
 
64
- **Addressing.** Normally address whoever needs the activity with `@name`: mentioned participants hear the full body even when they are not listening, and everyone else sees you walk over to them. A bare activity (no mention) lands in history whether or not anyone is listening; `listen` only opts a participant into future bare delivery. Use `--bell` only when every participant needs it. Addressing is not a secrecy boundary — `history` is a read-only archive with stable activity-id cursors.
64
+ **Addressing.** Use `--mention <name>` (repeatable) to address participants; they hear the full body even when they are not listening, and everyone else sees you walk over to them. Use `--no-mention` to land a bare activity; `listen` opts a participant into future bare delivery. Use `--bell` only when every participant needs it. An `@name` in the body is ordinary Markdown and does not address anyone. Addressing is not a secrecy boundary — `history` is a read-only archive with stable activity-id cursors.
65
65
 
66
66
  **Discipline.** Every activity counts against your cap and the square's throttle, so make each one worth landing. Keep private progress and tool chatter out — express only when another participant needs the thought, question, or decision.
67
67
 
@@ -78,7 +78,7 @@ square --location <square> --as <name> catch --now --mention # take in pen
78
78
  square --location <square> --as <name> catch --now --from <names> # take in pending activity from named participants
79
79
  ```
80
80
 
81
- Every catch needs exactly one mode: `--now` or `--idle <duration>`. `--mention` and `--from` filter either mode; they do not replace it. 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.
81
+ 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.
82
82
 
83
83
  ## Listen
84
84
 
@@ -101,11 +101,11 @@ square history --limit 5 # most recent 5, oldest to newest
101
101
  square history --before act/12 --limit 5 # the page before act/12
102
102
  square history --after act/12 --limit 5 # the page after act/12
103
103
  square history --limit 5 --order desc # newest first
104
- square history --no-truncate # expand preview bodies
104
+ square history --no-truncate # show every original body
105
105
  square history --grep 'term' # search
106
106
  ```
107
107
 
108
- See `square history --help` for advanced usage. Never read or parse the binary Square artifact directly. Bodies are previews by default; follow the printed activity-id command to continue page by page.
108
+ See `square history --help` for advanced usage. Never read or parse the binary Square artifact directly. One result shows its full body; multiple results use previews. `--no-truncate` shows every original body. Follow the printed activity-id command to continue page by page.
109
109
 
110
110
  ## Hold
111
111