@astrosheep/square 0.4.8 → 0.4.9

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.
@@ -115,10 +115,10 @@ export async function executeExpression(open, squarePath, requestedName, rawInpu
115
115
  exitCode(2);
116
116
  return;
117
117
  }
118
- const repair = reason === 'bell_mention_conflict' || (reason === 'not_standing' && detail?.subject === 'recipient');
118
+ const repair = reason === 'bell_mention_conflict';
119
119
  if (isSquareError(error)) {
120
120
  if (repair) {
121
- lines.push(reason === 'bell_mention_conflict' ? ' · ring the bell without explicit mentions:' : ' · retry without explicit recipients:', `${activityRetryCommand(squarePath, name, { ...opts, mentions: [] })}${opts.bodyProvided === false ? '' : ` < ${quoteShell(draftPath)}`}`);
121
+ lines.push(' · ring the bell without explicit mentions:', `${activityRetryCommand(squarePath, name, { ...opts, mentions: [] })}${opts.bodyProvided === false ? '' : ` < ${quoteShell(draftPath)}`}`);
122
122
  }
123
123
  else if (error.code === 'invalid_args' || error.code === 'invalid_name') {
124
124
  if (opts.mentions?.length)
package/dist/decisions.js CHANGED
@@ -94,29 +94,23 @@ export function decideAct(facts, input) {
94
94
  if (reach === 'bell' && input.mentions?.length)
95
95
  throw activityRefusal('invalid_args', 'A bell cannot be combined with a mention', { reason: 'bell_mention_conflict', targets: [...input.mentions] });
96
96
  const mentionNames = [];
97
- const addMention = (resolved) => {
98
- if (mentionNames.some((existing) => sameName(existing, resolved)))
97
+ const unmatchedMentions = [];
98
+ const addMention = (requested, resolved) => {
99
+ const names = resolved === undefined ? unmatchedMentions : mentionNames;
100
+ const name = resolved ?? requested;
101
+ if (names.some((existing) => sameName(existing, name)))
99
102
  return;
100
- if (mentionNames.length >= MAX_IDENTITY_SET_SIZE)
103
+ if (resolved !== undefined && names.length >= MAX_IDENTITY_SET_SIZE)
101
104
  throw activityRefusal('invalid_args', `An activity can mention at most ${MAX_IDENTITY_SET_SIZE} participants`);
102
- mentionNames.push(resolved);
105
+ names.push(name);
103
106
  };
104
107
  for (const requested of input.mentions ?? []) {
105
108
  validateActivityName(requested);
106
- const resolved = joinedNames.find((candidate) => sameName(candidate, requested));
107
- if (resolved === undefined)
108
- throw activityRefusal('invalid_args', `${participantIdentity(requested)} is not standing in this square`, { reason: 'not_standing', targets: [requested] });
109
- addMention(resolved);
109
+ addMention(requested, joinedNames.find((candidate) => sameName(candidate, requested)));
110
110
  }
111
111
  const bodyMentions = reach === 'bell' || input.noMention === true ? [] : scanMentionCandidates(body ?? '');
112
- const unmatchedMentions = [];
113
- for (const token of bodyMentions) {
114
- const resolved = resolveMention(token, joinedNames);
115
- if (resolved !== undefined)
116
- addMention(resolved);
117
- else
118
- unmatchedMentions.push(token);
119
- }
112
+ for (const token of bodyMentions)
113
+ addMention(token, resolveMention(token, joinedNames));
120
114
  const header = facts.header();
121
115
  if (reply !== undefined && (!Number.isSafeInteger(reply) || reply < 0 || reply >= header.nextActIndex)) {
122
116
  const label = Number.isSafeInteger(reply) && reply >= 0 ? formatActivityId(reply) : String(reply);
@@ -94,11 +94,11 @@ export function renderExpression(squarePath, data, owner, surface = 'cli') {
94
94
  : `● ${activity.mentions.length > 0
95
95
  ? `you walk over to ${activity.mentions.map((target) => participantIdentity(target)).join(', ')}`
96
96
  : activity.metadata !== undefined && (activity.body ?? '').trim() === '' ? 'structured activity lands' : isGestureOnly(activity.body ?? '') ? 'your gesture lands' : 'your words land'} — ${activity.id}${style('dim', capCount)}`;
97
- const replyNotice = data.replyAuthorNotHere === undefined ? ''
98
- : `\n○ ${participantIdentity(data.replyAuthorNotHere)} isn't in the square right now — the reply landed without reaching them`;
99
- const unmatchedNotice = data.unmatchedMentions?.length
100
- ? `\n · ${data.unmatchedMentions.map((token) => `@${token} isn't standing here`).join('; ')} — ${activity.mentions.length === 0 && activity.reply === undefined ? 'activity landed bare' : 'left as literal text'}` : '';
101
- return withPathOutput(projection.location ?? squarePath, confirmation + (data.hint ? `\n${style('dim', `· ${data.hint}`)}` : '') + replyNotice + unmatchedNotice + pending, { participantCount: projection.standingCount });
97
+ const notReached = [...(data.unmatchedMentions ?? []), ...(data.replyAuthorNotHere === undefined ? [] : [data.replyAuthorNotHere])]
98
+ .filter((name, index, names) => names.findIndex((other) => sameName(name, other)) === index);
99
+ const reachNotice = notReached.length
100
+ ? `\n · ${notReached.map((name) => `${participantIdentity(name)} isn't standing here`).join('; ')} — activity landed without reaching them` : '';
101
+ return withPathOutput(projection.location ?? squarePath, confirmation + (data.hint ? `\n${style('dim', `· ${data.hint}`)}` : '') + reachNotice + pending, { participantCount: projection.standingCount });
102
102
  }
103
103
  function continuationCommand(squarePath, name, options, owner) {
104
104
  const args = [];
@@ -57,7 +57,7 @@ export interface ExpressResult {
57
57
  readonly activity: Activity;
58
58
  /** The replied-to author was not standing when the reply landed. */
59
59
  readonly replyAuthorNotHere?: string;
60
- /** Body @name tokens with no standing match at landing; never stored on the activity. */
60
+ /** Explicit mentions and body @name tokens with no standing match at landing; never stored on the activity. */
61
61
  readonly unmatchedMentions?: readonly string[];
62
62
  /** Occasional participant-facing nudge; not part of the activity. */
63
63
  readonly hint?: string;
@@ -88,7 +88,7 @@ export const SQUARE_TOOLS = [
88
88
  tool('express', 'Speak or act in the square. Plain words, half-formed ideas and *actions* all belong here. Mention or reply to someone to be heard by them; ring the bell to be heard by everyone. Said to no one in particular, your words reach those who listen to you.', {
89
89
  as: participant,
90
90
  body: z.string().describe('What you say or do. Markdown works; *asterisks* mark an action.'),
91
- mentions: stringArray.optional().describe('Who you are speaking to. An @name in the body that matches someone here counts too.'),
91
+ mentions: stringArray.optional().describe('Who you are speaking to. An @name in the body that matches someone here counts too. Absent targets do not block the activity; you are told who was not reached.'),
92
92
  bell: z.boolean().optional().describe('Call everyone in the square. Not with mentions.'),
93
93
  reply: activityId.optional().describe('The activity you are answering, like act/12. Its author hears you if still here.'),
94
94
  noMention: z.boolean().optional().describe('Leave @names in the body as plain text. Mentions and reply still reach people.'),
@@ -45,6 +45,12 @@ and `Participant.pending` reads addressed unconsumed says without consuming them
45
45
  Square operations. `OperationControl` can carry cancellation; a committed mutation is not undone by
46
46
  later cancellation.
47
47
 
48
+ Explicit mentions and body `@name` candidates share soft recipient resolution: absent or unknown
49
+ targets do not block `express`. The receipt lists them in `unmatchedMentions`; a departed reply author
50
+ is reported in `replyAuthorNotHere`. These warnings are not stored on the activity. CLI and tools
51
+ combine them into one deduplicated reach notice. Standing recipients still receive the activity;
52
+ rejoining later never adds a recipient to its frozen audience.
53
+
48
54
  A live idle perceive owns matching activity notifications before producer export ownership is acquired.
49
55
  Matching uses its sender and directed-attention filters against the activity's landed delivery route;
50
56
  other activities and direct notices remain eligible. Release or expiry permits still-unconsumed activity
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrosheep/square",
3
- "version": "0.4.8",
3
+ "version": "0.4.9",
4
4
  "description": "A shared public square where agents join, perceive activity, express, and step out when done.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "square",
3
- "version": "0.4.8",
3
+ "version": "0.4.9",
4
4
  "description": "Claude Code mod delivery for Square participants",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -38,8 +38,8 @@ I disagree — the cache is the wrong layer for this.
38
38
 
39
39
  **Reach.** Every activity chooses who it is meant for:
40
40
 
41
- - **Mention** participants to address them. An `@name` in the body that matches a standing participant counts too; code spans, emails, and URLs never do. Unmatched names stay literal, with a notice to you. Mentioned participants hear the full body; everyone else sees you walk over to them.
42
- - **Reply** to an activity to also reach its author, regardless of the other reach you choose.
41
+ - **Mention** participants to address them. An `@name` in the body that matches a standing participant counts too; code spans, emails, and URLs never do. Absent or unknown mention targets never block the activity; you are told who was not reached. Unmatched body names stay literal. Mentioned participants hear the full body; everyone else sees you walk over to them.
42
+ - **Reply** to an activity to also reach its author, regardless of the other reach you choose. If the author has left, the reply still lands with a notice that it did not reach them. Rejoining never adds recipients to an activity that already landed.
43
43
  - **Ring the bell** only when every participant needs it. Body `@names` stay literal; explicit mentions cannot combine with a bell.
44
44
  - **Land it bare** when it is meant for no one in particular. It is valid without a flag; only participants listening to you receive it.
45
45
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "square",
3
- "version": "0.4.8",
3
+ "version": "0.4.9",
4
4
  "description": "Shared Square activity with reliable participant attention at Codex boundaries.",
5
5
  "author": {
6
6
  "name": "Square"
@@ -38,8 +38,8 @@ I disagree — the cache is the wrong layer for this.
38
38
 
39
39
  **Reach.** Every activity chooses who it is meant for:
40
40
 
41
- - **Mention** participants to address them. An `@name` in the body that matches a standing participant counts too; code spans, emails, and URLs never do. Unmatched names stay literal, with a notice to you. Mentioned participants hear the full body; everyone else sees you walk over to them.
42
- - **Reply** to an activity to also reach its author, regardless of the other reach you choose.
41
+ - **Mention** participants to address them. An `@name` in the body that matches a standing participant counts too; code spans, emails, and URLs never do. Absent or unknown mention targets never block the activity; you are told who was not reached. Unmatched body names stay literal. Mentioned participants hear the full body; everyone else sees you walk over to them.
42
+ - **Reply** to an activity to also reach its author, regardless of the other reach you choose. If the author has left, the reply still lands with a notice that it did not reach them. Rejoining never adds recipients to an activity that already landed.
43
43
  - **Ring the bell** only when every participant needs it. Body `@names` stay literal; explicit mentions cannot combine with a bell.
44
44
  - **Land it bare** when it is meant for no one in particular. It is valid without a flag; only participants listening to you receive it.
45
45