@astrosheep/square 0.3.78 → 0.3.80

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 (70) hide show
  1. package/dist/attention-presentation.d.ts +2 -0
  2. package/dist/attention-presentation.js +39 -4
  3. package/dist/automatic-session.js +1 -1
  4. package/dist/boundary-presentation.js +4 -7
  5. package/dist/claude-mod.js +1 -1
  6. package/dist/cli/catch-presentation.d.ts +2 -2
  7. package/dist/cli/catch-presentation.js +3 -3
  8. package/dist/cli/context.d.ts +11 -1
  9. package/dist/cli/context.js +14 -3
  10. package/dist/cli/express-presentation.d.ts +6 -5
  11. package/dist/cli/express-presentation.js +42 -43
  12. package/dist/cli/maintenance-commands.js +6 -6
  13. package/dist/cli/mcp-server-command.js +1 -1
  14. package/dist/cli/observation-commands.d.ts +4 -4
  15. package/dist/cli/observation-commands.js +20 -16
  16. package/dist/cli/program.d.ts +1 -1
  17. package/dist/cli/program.js +5 -5
  18. package/dist/cli/square-commands.js +35 -23
  19. package/dist/decisions.d.ts +3 -5
  20. package/dist/decisions.js +10 -9
  21. package/dist/delivery.d.ts +1 -1
  22. package/dist/help.js +15 -2
  23. package/dist/inbox.js +3 -3
  24. package/dist/index.d.ts +1 -2
  25. package/dist/index.js +1 -2
  26. package/dist/ledger-maintenance.d.ts +1 -1
  27. package/dist/ledger-maintenance.js +1 -1
  28. package/dist/list.d.ts +3 -3
  29. package/dist/list.js +1 -1
  30. package/dist/mcp.d.ts +2 -2
  31. package/dist/mcp.js +12 -7
  32. package/dist/mention-parse.d.ts +2 -8
  33. package/dist/mention-parse.js +6 -9
  34. package/dist/model.d.ts +2 -2
  35. package/dist/notifications.js +24 -8
  36. package/dist/participant-host.js +5 -0
  37. package/dist/plugins/opencode/index.js +1 -1
  38. package/dist/square-actions.d.ts +22 -9
  39. package/dist/square-actions.js +26 -19
  40. package/dist/square-api.d.ts +43 -0
  41. package/dist/square-api.js +471 -0
  42. package/dist/square-facade.d.ts +123 -203
  43. package/dist/square-tools.d.ts +4 -3
  44. package/dist/square-tools.js +21 -15
  45. package/dist/stream.d.ts +2 -2
  46. package/dist/views.d.ts +16 -12
  47. package/dist/views.js +13 -27
  48. package/guides/application-demand-optimization.md +5 -0
  49. package/guides/application-facade-delivery-evidence.md +7 -2
  50. package/guides/mcp-connection-lifetime.md +2 -2
  51. package/guides/mcp.md +1 -1
  52. package/guides/operation-cancellation.md +2 -0
  53. package/guides/primitive-catch-consolidation.md +5 -0
  54. package/guides/primitive-history-consolidation.md +5 -0
  55. package/guides/primitive-status-consolidation.md +5 -0
  56. package/guides/primitive-storage-consolidation.md +5 -0
  57. package/guides/row-storage-design.md +1 -1
  58. package/guides/square-api.md +107 -0
  59. package/package.json +1 -1
  60. package/plugins/claude/.claude-plugin/plugin.json +1 -1
  61. package/plugins/claude/skills/square/SKILL.md +4 -4
  62. package/plugins/codex/.codex-plugin/plugin.json +1 -1
  63. package/plugins/opencode/index.ts +1 -1
  64. package/plugins/paseo/client/markdown.tsx +16 -6
  65. package/plugins/pi/square-pi.js +7 -6
  66. package/skills/square/SKILL.md +4 -4
  67. package/dist/square-application.d.ts +0 -38
  68. package/dist/square-application.js +0 -683
  69. package/guides/application-boundary.md +0 -32
  70. package/guides/application-facade-design.md +0 -98
@@ -1,7 +1,7 @@
1
1
  import { helpRequest } from '../help.js';
2
2
  import { isSquareError } from '../model.js';
3
3
  import { commandPrefix, formatRefusal, refusalText, joinRecoveryCommand, participantIdentity, participantsRecoveryCommand, takeoverRecoveryLines, } from '../presentation.js';
4
- import { applicationFor, defaultContext, parseGlobalArgs } from './context.js';
4
+ import { withSquare, defaultContext, parseGlobalArgs } from './context.js';
5
5
  import { executeRegisteredCommand, findCommand } from './registry.js';
6
6
  async function handleSquareError(error, context) {
7
7
  if (!isSquareError(error))
@@ -9,8 +9,8 @@ async function handleSquareError(error, context) {
9
9
  const detail = error.operation;
10
10
  const squarePath = context?.squarePath;
11
11
  const recovery = error.facts?.reason === 'migration_required' || context === undefined || squarePath === undefined ? undefined
12
- : await applicationFor({ ...context, name: detail?.participant ?? context.name }).recovery().catch(() => undefined);
13
- const name = detail?.participant ?? recovery?.data.participant ?? context?.name;
12
+ : await withSquare({ ...context, name: detail?.participant ?? context.name }, (square) => square.recovery(detail?.participant ?? context.name)).catch(() => undefined);
13
+ const name = detail?.participant ?? recovery?.participant ?? context?.name;
14
14
  const existing = [refusalText(error)];
15
15
  if (error.facts?.reason === 'migration_required') {
16
16
  if (error.facts.ledgerRoot !== undefined)
@@ -40,11 +40,11 @@ async function handleSquareError(error, context) {
40
40
  process.stderr.write(`${existing.join('\n')}\n`);
41
41
  else
42
42
  process.stderr.write(formatRefusal(squarePath, existing, {
43
- participantCount: recovery?.data.standingCount, held: recovery?.data.held,
43
+ participantCount: recovery?.standingCount, held: recovery?.held,
44
44
  }));
45
45
  process.exit(error.code === 'not_found' ? 1 : 2);
46
46
  }
47
- /** Parse global flags, select an executable adapter, and leave operations to the application. */
47
+ /** Parse global flags, select an adapter, and execute through Square/Participant. */
48
48
  export async function runCli(rawArgs = process.argv.slice(2)) {
49
49
  let context;
50
50
  try {
@@ -1,9 +1,9 @@
1
1
  import { formatHardCap } from '../model.js';
2
2
  import { commandPrefix, participantCommandPrefix, participantIdentity, renderRoomChangeText, renderAmbientEvent, withPathOutput, } from '../presentation.js';
3
- import { formatActivityId } from '../square-application.js';
3
+ import { Square, formatActivityId } from '../square-api.js';
4
4
  import { style } from '../tty-style.js';
5
5
  import { executeExpression } from './express-presentation.js';
6
- import { applicationFor, fail, parseBoundedLimit, parseHardCap, parsePositiveInteger, readStdin, requireSquarePath, requireValue, resolveBody, usage, } from './context.js';
6
+ import { squareInput, withSquare, fail, parseBoundedLimit, parseHardCap, parsePositiveInteger, readStdin, requireSquarePath, requireValue, resolveBody, usage, } from './context.js';
7
7
  const JOIN_DEFAULT_LAST = 10;
8
8
  const JOIN_MAX_LAST = 100;
9
9
  export const buildCommand = {
@@ -42,11 +42,18 @@ export const buildCommand = {
42
42
  const markdown = await readStdin();
43
43
  if (markdown.trim() === '')
44
44
  fail('Missing Markdown body snippet on stdin.');
45
- const { data, projection } = await applicationFor(context).build({ ...intent.options, markdown });
45
+ const square = await Square.build({ ...squareInput(context), ...intent.options, markdown });
46
+ let data;
47
+ try {
48
+ data = await square.status();
49
+ }
50
+ finally {
51
+ await square.close();
52
+ }
46
53
  const cap = data.hardCap === null ? 'unlimited' : formatHardCap(data.hardCap);
47
54
  const throttle = data.throttlePerMinute === undefined ? [] : [` · throttle ${data.throttlePerMinute}/min`];
48
- return withPathOutput(data.location, ['· the square stands ready', ` · cap ${cap}`, ...throttle].join('\n'), {
49
- participantCount: projection.standingCount, held: projection.held,
55
+ return withPathOutput(square.location, ['· the square stands ready', ` · cap ${cap}`, ...throttle].join('\n'), {
56
+ participantCount: 0, held: false,
50
57
  });
51
58
  },
52
59
  present: (result) => process.stdout.write(result),
@@ -68,12 +75,14 @@ export const joinCommand = {
68
75
  return { lastN, kick };
69
76
  },
70
77
  async execute(intent, context) {
71
- const { data, projection: after } = await applicationFor(context).join({ takeover: intent.kick, last: intent.lastN });
72
- const name = data.participant;
73
- const activities = after.recentActivities.map((event) => renderAmbientEvent(event, name, {
78
+ const data = await withSquare(context, (square) => square.join(context.name, { takeover: intent.kick, last: intent.lastN, observe: true }));
79
+ const after = data.observation;
80
+ const entry = after.entry;
81
+ const name = data.participant.name;
82
+ const activities = (entry?.recentActivities ?? []).map((event) => renderAmbientEvent(event, name, {
74
83
  now: after.now, preview: 200,
75
- actNumber: event.kind === 'say' ? after.sayNumbers[event.index] : undefined,
76
- perception: after.perceptions[event.index],
84
+ actNumber: event.kind === 'say' ? entry.sayNumbers[event.index] : undefined,
85
+ perception: entry.perceptions[event.index],
77
86
  })).filter(Boolean).join('\n\n');
78
87
  const fallback = after.automaticDeliveryAvailable ? [] : [
79
88
  '', `${participantCommandPrefix(after.location, name)} catch --idle 30m`,
@@ -83,13 +92,14 @@ export const joinCommand = {
83
92
  : data.kind === 'reconnected' ? '● you are already in the square'
84
93
  : `✓ you banished the original ${participantIdentity(name)} — the name is yours`;
85
94
  const isRejoin = data.kind !== 'joined';
86
- const entryScene = isRejoin ? '' : after.joinedBefore
87
- ? '*the flagstones are where you left them. so is your shadow.*' : after.scene;
95
+ const entryScene = isRejoin || entry === undefined ? '' : entry.joinedBefore
96
+ ? '*the flagstones are where you left them. so is your shadow.*' : entry.scene;
88
97
  return withPathOutput(after.location, [
89
98
  entryLine, '',
90
99
  "· carved into the fountain's edge: every word here lands on a real ear — speak when someone needs it.",
91
100
  ...(entryScene === '' ? [] : ['', entryScene]),
92
- ...(isRejoin || after.joinContext === '' ? [] : ['', style('dim', 'context'), after.joinContext]),
101
+ ...(isRejoin || !entry?.joinContext ? [] : ['', style('dim', 'context'), entry.joinContext]),
102
+ ...(after.enrichmentUnavailable ? [' · joined; recent activity and context are unavailable right now'] : []),
93
103
  ...(isRejoin || activities === '' ? [] : ['', style('dim', 'recent activity'), activities]),
94
104
  ...fallback,
95
105
  ].join('\n'), { participantCount: after.standingCount, held: after.held });
@@ -128,10 +138,6 @@ function parseActivity(argv, context) {
128
138
  bodyArgs.push(argument);
129
139
  }
130
140
  const reach = bell ? 'bell' : undefined;
131
- if (bell && (noMention || mentions.length > 0))
132
- fail('✕ --bell cannot be combined with --mention or --no-mention\nsquare express --help');
133
- if (noMention && mentions.length > 0)
134
- fail('✕ --no-mention cannot be combined with --mention\nsquare express --help');
135
141
  if (bodyArgs.length !== 1) {
136
142
  if (bodyArgs.length === 0 && !process.stdin.isTTY)
137
143
  bodyArgs.push('-');
@@ -144,7 +150,7 @@ export const expressCommand = {
144
150
  parse: parseActivity,
145
151
  async execute(intent, context) {
146
152
  const rawInput = await resolveBody(intent.activity);
147
- await executeExpression(applicationFor(context), requireSquarePath(context), context.name, rawInput, {
153
+ await executeExpression((control) => Square.at(squareInput(context), control), requireSquarePath(context), context.name, rawInput, {
148
154
  force: intent.force, noWait: intent.noWait, noMention: intent.noMention,
149
155
  mentions: intent.mentions, reach: intent.reach, reply: intent.reply,
150
156
  });
@@ -160,7 +166,8 @@ function listenerCommand(verb) {
160
166
  return {
161
167
  parse(argv, context) { return parseListener(argv, context, true); },
162
168
  async execute(intent, context) {
163
- const { data, projection } = await applicationFor(context)[verb](intent.target);
169
+ const data = await withSquare(context, async (square) => (await square.participant(context.name))[verb](intent.target, { observe: true }));
170
+ const projection = data.observation;
164
171
  const actor = participantIdentity(projection.participant);
165
172
  const target = participantIdentity(projection.target);
166
173
  const action = data.activity !== null
@@ -178,7 +185,8 @@ export const ignoreCommand = listenerCommand('ignore');
178
185
  export const listeningCommand = {
179
186
  parse(argv, context) { return parseListener(argv, context, false); },
180
187
  async execute(_intent, context) {
181
- const { data, projection } = await applicationFor(context).listening();
188
+ const data = await withSquare(context, async (square) => (await square.participant(context.name)).listening({ observe: true }));
189
+ const projection = data.observation;
182
190
  const body = data.listening.length === 0
183
191
  ? `○ ${participantIdentity(projection.participant)} is not turned toward anyone`
184
192
  : ['listening', ...data.listening.map((target) => ` · ${participantIdentity(target)}`)].join('\n');
@@ -195,7 +203,8 @@ export const doneCommand = {
195
203
  async execute(intent, context) {
196
204
  const body = intent.body === '-' || ((intent.body === undefined || intent.body === '') && !process.stdin.isTTY)
197
205
  ? await readStdin() : intent.body;
198
- const { data, projection } = await applicationFor(context).done(body);
206
+ const data = await withSquare(context, async (square) => (await square.participant(context.name)).done(body, { observe: true }));
207
+ const projection = data.observation;
199
208
  return withPathOutput(projection.location, `○ you stepped out — your footsteps fade · ${data.activity.id}`, {
200
209
  participantCount: projection.standingCount, held: projection.held,
201
210
  });
@@ -209,7 +218,9 @@ export const holdCommand = {
209
218
  return { body: argv[0] };
210
219
  },
211
220
  async execute(intent, context) {
212
- const { projection } = await applicationFor(context).hold((await resolveBody(intent.body ?? '')).replace(/\r\n/g, '\n').trim());
221
+ const body = (await resolveBody(intent.body ?? '')).replace(/\r\n/g, '\n').trim();
222
+ const result = await withSquare(context, async (square) => (await square.participant(context.name)).hold(body, { observe: true }));
223
+ const projection = result.observation;
213
224
  return withPathOutput(projection.location, `· ${renderRoomChangeText(projection.activity)} · ${formatActivityId(projection.activity.index)} · just now`, { participantCount: projection.standingCount, held: projection.held });
214
225
  },
215
226
  present: (result) => process.stdout.write(result),
@@ -221,7 +232,8 @@ export const resumeCommand = {
221
232
  return {};
222
233
  },
223
234
  async execute(_intent, context) {
224
- const { projection } = await applicationFor(context).resume();
235
+ const result = await withSquare(context, async (square) => (await square.participant(context.name)).resume({ observe: true }));
236
+ const projection = result.observation;
225
237
  return withPathOutput(projection.location, `✓ ${renderRoomChangeText(projection.activity)} · ${formatActivityId(projection.activity.index)} · just now`, { participantCount: projection.standingCount, held: projection.held });
226
238
  },
227
239
  present: (result) => process.stdout.write(result),
@@ -5,7 +5,7 @@ type TimedAct<K extends Act['kind'] = Act['kind']> = Extract<Act, {
5
5
  }> & {
6
6
  at: number;
7
7
  };
8
- import type { ParticipantSummary as ParticipantStatus, UnreadActivitySummary } from './square-facade.js';
8
+ import type { ExpressResult, ParticipantSummary as ParticipantStatus, UnreadActivitySummary } from './square-facade.js';
9
9
  export type { ParticipantSummary as ParticipantStatus, ParticipantPresence as CorePresenceState, UnreadActivitySummary, UnreadActivityPreview } from './square-facade.js';
10
10
  export declare function resolveKnownName(facts: SquareRead, name: string): string;
11
11
  export interface DecideJoinResult {
@@ -20,11 +20,10 @@ export interface DecideImplicitJoinResult {
20
20
  }
21
21
  export declare function decideJoin(facts: SquareRead, name: string, now: number): DecideJoinResult;
22
22
  export declare function decideImplicitJoin(facts: SquareRead, name: string, now: number): DecideImplicitJoinResult;
23
- export type ActDecision = {
23
+ export type ActDecision = ({
24
24
  type: 'sent';
25
25
  act: TimedAct;
26
- replyAuthorNotHere?: string;
27
- } | {
26
+ } & Pick<ExpressResult, 'replyAuthorNotHere' | 'unmatchedMentions'>) | {
28
27
  type: 'blocked';
29
28
  activitySummaries: UnreadActivitySummary[];
30
29
  } | {
@@ -52,7 +51,6 @@ export declare function decideAct(facts: SquareRead, input: {
52
51
  reach?: Reach;
53
52
  reply?: number;
54
53
  noMention?: boolean;
55
- requireExplicitReach?: boolean;
56
54
  }): ActDecision;
57
55
  export declare function validateDoneBody(body: unknown): void;
58
56
  export declare function coreDone(facts: SquareRead, name: string, body: string, now: number): TimedAct<'done'>;
package/dist/decisions.js CHANGED
@@ -2,7 +2,7 @@ import { SquareError, isSquareError, sameName, nameKey, validateName, } from './
2
2
  import { participantIdentity } from './participant-identity.js';
3
3
  import { UNREAD_BLOCK_GRACE_MS, freshWatchLease, readCursor, FACT_PAGE_SIZE, THROTTLE_WINDOW_MS } from './runtime.js';
4
4
  import { formatActivityId, MAX_IDENTITY_SET_SIZE } from './square-core.js';
5
- import { scanMentionCandidates, resolveNameAt } from './mention-parse.js';
5
+ import { scanMentionCandidates, resolveMention } from './mention-parse.js';
6
6
  export function resolveKnownName(facts, name) {
7
7
  validateName(name);
8
8
  const known = facts.participant(name)?.name;
@@ -92,6 +92,8 @@ export function decideAct(facts, input) {
92
92
  throw activityRefusal('invalid_args', 'express body cannot be empty');
93
93
  const roster = facts.roster();
94
94
  const joinedNames = roster.filter((participant) => participant.joined).map((participant) => participant.name);
95
+ if (reach === 'bell' && input.mentions?.length)
96
+ throw activityRefusal('invalid_args', 'A bell cannot be combined with a mention', { reason: 'bell_mention_conflict', targets: [...input.mentions] });
95
97
  const mentionNames = [];
96
98
  const addMention = (resolved) => {
97
99
  if (mentionNames.some((existing) => sameName(existing, resolved)))
@@ -107,16 +109,14 @@ export function decideAct(facts, input) {
107
109
  throw activityRefusal('invalid_args', `${participantIdentity(requested)} is not standing in this square`, { reason: 'not_standing', targets: [requested] });
108
110
  addMention(resolved);
109
111
  }
110
- const bodyMentions = input.noMention === true ? [] : scanMentionCandidates(body);
111
- for (const candidate of bodyMentions) {
112
- const resolved = resolveNameAt(body, candidate, joinedNames);
112
+ const bodyMentions = reach === 'bell' || input.noMention === true ? [] : scanMentionCandidates(body);
113
+ const unmatchedMentions = [];
114
+ for (const token of bodyMentions) {
115
+ const resolved = resolveMention(token, joinedNames);
113
116
  if (resolved !== undefined)
114
117
  addMention(resolved);
115
- }
116
- if (reach === 'bell' && mentionNames.length > 0)
117
- throw activityRefusal('invalid_args', 'A bell cannot be combined with a mention', { reason: 'bell_mention_conflict', targets: mentionNames });
118
- if (input.requireExplicitReach && reach !== 'bell' && mentionNames.length === 0 && bodyMentions.length === 0 && reply === undefined && input.noMention !== true) {
119
- throw activityRefusal('invalid_args', 'Express needs a recipient, reply, bell, or explicit bare reach', { reason: 'explicit_reach_required' });
118
+ else
119
+ unmatchedMentions.push(token);
120
120
  }
121
121
  const header = facts.header();
122
122
  if (reply !== undefined && (!Number.isSafeInteger(reply) || reply < 0 || reply >= header.nextActIndex)) {
@@ -152,6 +152,7 @@ export function decideAct(facts, input) {
152
152
  }
153
153
  return { type: 'sent', act: { kind: 'say', actor: name, at: now, body, mentions: mentionNames,
154
154
  ...(reach === undefined ? {} : { reach }), ...(reply === undefined ? {} : { reply }) },
155
+ ...(unmatchedMentions.length === 0 ? {} : { unmatchedMentions }),
155
156
  ...(replyAuthorNotHere === undefined ? {} : { replyAuthorNotHere }) };
156
157
  }
157
158
  export function validateDoneBody(body) {
@@ -47,7 +47,7 @@ export declare function pendingPages(artifact: SquareArtifactPort, scan: Pending
47
47
  at: number;
48
48
  actor: string;
49
49
  recipient: string;
50
- route: "bell" | "mention" | "reply" | "attention";
50
+ route: "bell" | "reply" | "mention" | "attention";
51
51
  }[], void, unknown>;
52
52
  /** A pass retains only its captured joins; routes and individual receipts are fresh. */
53
53
  export declare function pendingFacts(facts: SquareRead, scan: PendingScan, heads: readonly DirectedHead[]): {
package/dist/help.js CHANGED
@@ -17,9 +17,22 @@ const COMMANDS = [
17
17
  details: ['Options:', ' --last <N> Show the last N public activities (default 10, maximum 100).', ' --kick End the standing participant and reclaim the name for this session.'],
18
18
  },
19
19
  {
20
- names: ['express'], usage: '--as <name> express [-f|--force] [--no-wait] (--mention <name>... | --no-mention | --bell | --reply <activity-id>) <activity | ->', usesSquare: true, group: 'participant',
20
+ names: ['express'], usage: '--as <name> express [-f|--force] [--no-wait] [--mention <name>... | --bell] [--no-mention] [--reply <activity-id>] <activity | ->', usesSquare: true, group: 'participant',
21
21
  summary: 'Speak, gesture, or do both.',
22
- details: ['Reach:', ' --mention <name> Address one participant; repeat for multiple participants. The body stays unchanged.', ' --no-mention Land a bare activity explicitly; @names stay plain text. listen opts someone into future bare delivery.', " --bell Call every participant's attention to this activity.", ' @name An @name in the body also addresses that standing participant; `backticks` keep @text literal.', '', 'Options:', ' -f, --force Express without first catching unread activity or attention etiquette.', ' --no-wait If held or throttled, save a draft and return instead of waiting. A held wait gives up on its own after 10 minutes.', ' --reply <activity-id> Mark this activity as a reply to an earlier activity (for example act/12); it reaches that activity\'s author.'],
22
+ details: [
23
+ 'Reach:',
24
+ ' --mention <name> Address one standing participant; repeat to address more.',
25
+ ' @name Also address a standing participant named in the body; `backticks` keep @text literal.',
26
+ " --bell Call everyone; body @names stay literal. Cannot combine with --mention.",
27
+ ' --no-mention Keep body @names literal; explicit --mention and --reply still apply.',
28
+ ' --reply <activity-id> Also reach the author of an earlier activity (for example act/12).',
29
+ ' Address with --mention, --bell, --reply, or a standing @name in the body. Otherwise the activity lands bare; unmatched @names stay literal, with a notice.',
30
+ ' Bare activities are delivered only to participants listening to the sender.',
31
+ '',
32
+ 'Options:',
33
+ ' -f, --force Express without first catching unread activity or satisfying attention etiquette.',
34
+ ' --no-wait If held or throttled, save a draft and return instead of waiting. A held wait gives up on its own after 10 minutes.',
35
+ ],
23
36
  },
24
37
  { names: ['listen'], usage: '--as <name> listen <participant>', usesSquare: true, group: 'participant', summary: 'Turn an ear toward one participant\'s future bare says.' },
25
38
  { names: ['ignore'], usage: '--as <name> ignore <participant>', usesSquare: true, group: 'participant', summary: 'Ignore one participant\'s future mentions and bare says.' },
package/dist/inbox.js CHANGED
@@ -5,7 +5,7 @@ import { canonicalPath } from './canonical-path.js';
5
5
  import { projectBoundaryEligibility, projectSessionBindings } from './square-projections.js';
6
6
  import { freshWatchLease, WATCH_STALE_MS } from './runtime.js';
7
7
  import { pendingHead, pendingNotifications, leaseOwnsNotification } from './delivery.js';
8
- import { attentionBodyIsClipped, renderAttentionPreview } from './attention-presentation.js';
8
+ import { attentionBodyIsClipped, renderAttentionPreview, renderSquareReminder } from './attention-presentation.js';
9
9
  function bindingCoordinate(binding) {
10
10
  return JSON.stringify([binding.location, nameKey(binding.participant), binding.sessionId, binding.channel, binding.epoch, binding.cancelledThrough ?? -1]);
11
11
  }
@@ -37,8 +37,8 @@ export function nativePendingPreview(facts, input) {
37
37
  const item = facts.activity(input.actIndex);
38
38
  if (item?.kind !== 'say')
39
39
  return undefined;
40
- return { payload: renderAttentionPreview({ squarePath: input.location, recipient: input.participant, actIndex: item.index,
41
- actor: item.actor, route: head.route, body: item.body }), clipped: attentionBodyIsClipped(item.body) };
40
+ return { payload: renderSquareReminder([renderAttentionPreview({ squarePath: input.location, recipient: input.participant, actIndex: item.index,
41
+ actor: item.actor, route: head.route, body: item.body })]), clipped: attentionBodyIsClipped(item.body) };
42
42
  }
43
43
  async function readInboxMembership(binding, signal) {
44
44
  const artifact = createArtifactPort(binding.location, undefined, signal);
package/dist/index.d.ts CHANGED
@@ -1,5 +1,4 @@
1
- export { Square } from './square-application.js';
2
- export { createSquareApplication } from './square-application.js';
1
+ export { Square, squareLocation, squareClock, createSquareResourceOwner, discover, inbox, diagnoseSquare, migrateSquare } from './square-api.js';
3
2
  export { SquareError } from './model.js';
4
3
  export { squareAssignedParticipantName } from './registry.js';
5
4
  export { createDefaultWakeTransport } from './notifications.js';
package/dist/index.js CHANGED
@@ -1,5 +1,4 @@
1
- export { Square } from './square-application.js';
2
- export { createSquareApplication } from './square-application.js';
1
+ export { Square, squareLocation, squareClock, createSquareResourceOwner, discover, inbox, diagnoseSquare, migrateSquare } from './square-api.js';
3
2
  export { SquareError } from './model.js';
4
3
  export { squareAssignedParticipantName } from './registry.js';
5
4
  export { createDefaultWakeTransport } from './notifications.js';
@@ -22,7 +22,7 @@ export interface LedgerMaintenanceReport {
22
22
  /** Canonical locations whose artifact could not be read; their rows were retained. */
23
23
  readonly unavailableArtifacts: readonly string[];
24
24
  }
25
- /** Command/application entry: root selection uses the caller's captured environment. */
25
+ /** Maintenance entry: root selection uses the caller's captured environment. */
26
26
  export declare function gcHostLedger(options?: LedgerMaintenanceOptions & {
27
27
  readonly env?: NodeJS.ProcessEnv;
28
28
  }): Promise<LedgerMaintenanceReport>;
@@ -62,7 +62,7 @@ async function provenClosable(location, group) {
62
62
  await artifact.close();
63
63
  }
64
64
  }
65
- /** Command/application entry: root selection uses the caller's captured environment. */
65
+ /** Maintenance entry: root selection uses the caller's captured environment. */
66
66
  export function gcHostLedger(options = {}) {
67
67
  return maintainHostLedger(hostLedgerForEnv(options.env ?? process.env), options);
68
68
  }
package/dist/list.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import type { DiscoveryApplicationData, DiscoveryApplicationOptions, OpenOptions, OperationControl } from './square-facade.js';
1
+ import type { DiscoveryResult, DiscoveryOptions, OpenOptions, OperationControl } from './square-facade.js';
2
2
  export declare const DEFAULT_LIST_DEPTH = 4;
3
3
  export declare const MAX_LIST_DEPTH = 16;
4
4
  export declare const DEFAULT_LIST_LIMIT = 20;
5
5
  export declare const MAX_LIST_LIMIT = 100;
6
- /** Bounded deterministic discovery. Only the application calls this private operation. */
7
- export declare function discoverSquares(root: string, options?: DiscoveryApplicationOptions, control?: OperationControl, openOptions?: OpenOptions): Promise<DiscoveryApplicationData>;
6
+ /** Bounded deterministic discovery for the public resource API. */
7
+ export declare function discoverSquares(root: string, options?: DiscoveryOptions, control?: OperationControl, openOptions?: OpenOptions): Promise<DiscoveryResult>;
package/dist/list.js CHANGED
@@ -32,7 +32,7 @@ async function readSquareListItem(filePath, root, options, control) {
32
32
  return null;
33
33
  }
34
34
  }
35
- /** Bounded deterministic discovery. Only the application calls this private operation. */
35
+ /** Bounded deterministic discovery for the public resource API. */
36
36
  export async function discoverSquares(root, options = {}, control, openOptions = {}) {
37
37
  const depth = options.depth ?? DEFAULT_LIST_DEPTH;
38
38
  const limit = options.limit ?? DEFAULT_LIST_LIMIT;
package/dist/mcp.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- import type { SquareApplicationContext } from './square-facade.js';
2
+ import type { SquareAtInput } from './square-facade.js';
3
3
  /** The server owns its artifact connection lifetime, so callers cannot lend one. */
4
- export interface SquareMcpOptions extends Omit<SquareApplicationContext, 'resources'> {
4
+ export interface SquareMcpOptions extends Omit<SquareAtInput, 'resources'> {
5
5
  }
6
6
  export type SquareMcpServer = McpServer;
7
7
  export declare function createSquareMcpServer(options: SquareMcpOptions): SquareMcpServer;
package/dist/mcp.js CHANGED
@@ -4,9 +4,9 @@ import { SQUARE_TOOLS, SQUARE_TOOL_INSTRUCTIONS, squareToolFailure } from './squ
4
4
  import { operationEnv } from './automatic-session.js';
5
5
  import { readClaudeMcpCall } from './claude-mcp-context.js';
6
6
  import { SQUARE_IDENTITY } from './identity.js';
7
- import { createSquareApplication, createSquareResourceOwner } from './square-application.js';
7
+ import { Square, createSquareResourceOwner } from './square-api.js';
8
8
  export function createSquareMcpServer(options) {
9
- const baseEnv = { ...options.env };
9
+ const baseEnv = { ...(options.env ?? process.env) };
10
10
  const resources = createSquareResourceOwner();
11
11
  // Calls to the configured square borrow one server-owned connection.
12
12
  const inFlight = new Set();
@@ -33,15 +33,20 @@ export function createSquareMcpServer(options) {
33
33
  : typeof opencodeSessionId === 'string' && opencodeSessionId.trim()
34
34
  ? operationEnv('opencode', opencodeSessionId, baseEnv)
35
35
  : baseEnv;
36
- const app = createSquareApplication({
36
+ const square = await Square.at({
37
37
  ...options, env, cwd: claude?.cwd ?? options.cwd,
38
- squarePath: location ?? options.squarePath,
38
+ path: location ?? options.path,
39
39
  participant: participant ?? options.participant,
40
40
  // The server owner is bound to its configured square; overrides close after each call.
41
41
  resources: location === undefined && claude === undefined ? resources : undefined,
42
- }, { presentation: false });
43
- const outcome = await tool.run(app, args, { signal: extra.signal });
44
- const structuredContent = outcome.data;
42
+ }, { signal: extra.signal });
43
+ let structuredContent;
44
+ try {
45
+ structuredContent = await tool.run(square, args, { signal: extra.signal });
46
+ }
47
+ finally {
48
+ await square.close();
49
+ }
45
50
  return { content: [{ type: 'text', text: JSON.stringify(structuredContent) }], structuredContent };
46
51
  }
47
52
  catch (error) {
@@ -1,8 +1,2 @@
1
- export interface MentionCandidate {
2
- /** The maximal name-grammar token after `@` (roster-free; resolution may shorten it). */
3
- readonly token: string;
4
- /** UTF-16 index of the `@` in the body. */
5
- readonly index: number;
6
- }
7
- export declare function scanMentionCandidates(body: string): MentionCandidate[];
8
- export declare function resolveNameAt(body: string, candidate: MentionCandidate, names: readonly string[]): string | undefined;
1
+ export declare function scanMentionCandidates(body: string): string[];
2
+ export declare function resolveMention(text: string, names: readonly string[]): string | undefined;
@@ -10,8 +10,8 @@ import { NAME_GRAPHEME_SOURCE, sameName } from './model.js';
10
10
  * (`user@host`), URLs (`x.com/@user`), and `snake_@case` stay literal. Only ASCII
11
11
  * word characters, `_`, `-`, `/`, and `@` block a boundary: CJK adjacency like
12
12
  * `告诉@rei` stays mentionable because CJK text has no spaces to lean on.
13
- * - The token must be a complete name (slash-separated segments allowed); resolution
14
- * never backs off to a shorter prefix, so `@rei2` does not mention `rei`.
13
+ * - Slash-separated names are allowed. Resolution may stop at a CJK suffix, but never
14
+ * at an ASCII handle continuation: `@rei这个` can reach `rei`; `@rei2` cannot.
15
15
  */
16
16
  const MENTION_TOKEN = new RegExp(`@((?:${NAME_GRAPHEME_SOURCE})+(?:/(?:${NAME_GRAPHEME_SOURCE})+)*)`, 'vg');
17
17
  const BOUNDARY_BLOCKER = /[A-Za-z0-9_\-/@]/;
@@ -62,29 +62,26 @@ export function scanMentionCandidates(body) {
62
62
  const start = match.index;
63
63
  if (spans.some(([from, to]) => start >= from && start < to))
64
64
  continue;
65
- const unit = start <= 0 ? undefined : body.charCodeAt(start - 1);
66
- const previous = unit === undefined ? undefined
67
- : unit >= 0xdc00 && unit <= 0xdfff && start >= 2 ? String.fromCodePoint(body.codePointAt(start - 2)) : String.fromCodePoint(unit);
65
+ const previous = body[start - 1];
68
66
  if (previous !== undefined && BOUNDARY_BLOCKER.test(previous))
69
67
  continue;
70
68
  const token = match[1];
71
69
  const key = token.toLocaleLowerCase();
72
70
  if (!seen.has(key)) {
73
71
  seen.add(key);
74
- found.push({ token, index: start });
72
+ found.push(token);
75
73
  }
76
74
  }
77
75
  return found;
78
76
  }
79
77
  /**
80
- * Resolve the longest standing name starting at one candidate's position. A partial
78
+ * Resolve the longest standing name at the start of a candidate token. A partial
81
79
  * match is only accepted when the character right after it is not handle-continuation
82
80
  * (ASCII word char, `_`, `-`, `/`): `@rei2` never degrades into mentioning `rei`, while
83
81
  * CJK-fluent text like `告诉@rei这个` still mentions `rei`.
84
82
  */
85
83
  const TRAILING_BLOCKER = /[A-Za-z0-9_\-/]/;
86
- export function resolveNameAt(body, candidate, names) {
87
- const text = body.slice(candidate.index + 1, candidate.index + 1 + candidate.token.length);
84
+ export function resolveMention(text, names) {
88
85
  let best;
89
86
  for (const name of names) {
90
87
  if (name.length > text.length)
package/dist/model.d.ts CHANGED
@@ -21,7 +21,7 @@ export interface SquareErrorFacts {
21
21
  /** Express admission rejected before any activity was committed. */
22
22
  activityUnsent?: true;
23
23
  /** Refusal reason, including absent participants, conflicting reach, and inaccessible activities. */
24
- reason?: 'never_joined' | 'not_standing' | 'bell_mention_conflict' | 'not_addressed' | 'migration_required' | 'explicit_reach_required' | 'invalid_reply' | 'invalid_done_body';
24
+ reason?: 'never_joined' | 'not_standing' | 'bell_mention_conflict' | 'not_addressed' | 'migration_required' | 'invalid_reply' | 'invalid_done_body';
25
25
  /** The activity id a `catch --id` refusal named. */
26
26
  activityId?: string;
27
27
  /** The host ledger requiring offline migration, rather than the selected artifact. */
@@ -38,7 +38,7 @@ export interface SquareErrorFacts {
38
38
  declare class SquareErrorBase<Code extends string> extends Error {
39
39
  code: Code;
40
40
  facts?: SquareErrorFacts | undefined;
41
- operation?: import('./square-facade.js').ApplicationFailureDetail;
41
+ operation?: import('./square-facade.js').OperationFailureDetail;
42
42
  constructor(code: Code, message: string, facts?: SquareErrorFacts | undefined);
43
43
  }
44
44
  export declare class SquareError extends SquareErrorBase<SquareErrorCode> {
@@ -2,7 +2,7 @@ import { hostLedgerRoot } from './host-ledger-root.js';
2
2
  import path from 'node:path';
3
3
  import fs from 'node:fs';
4
4
  import { SquareError } from './model.js';
5
- import { displayAttentionPath } from './attention-presentation.js';
5
+ import { renderAttentionPreview, renderSquareReminder } from './attention-presentation.js';
6
6
  import { openSquare } from './open-square.js';
7
7
  import { closeOpenSquare } from './open-square.js';
8
8
  import { deliverPending, sweepPending } from './delivery-operations.js';
@@ -96,13 +96,29 @@ export function createWakeTransport(adapters, hostLedger, clock, nativePorts = {
96
96
  if (adapter === undefined)
97
97
  return { outcome: 'not-capable', diagnostic: `no adapter for ${request.route.kind}` };
98
98
  try {
99
- const kind = request.route.kind;
100
- const payload = [
101
- `<system-reminder source="square" wake="${kind === 'paseo' ? 'paseo' : kind.startsWith('codex') ? 'codex-queue' : kind}">`,
102
- `square: ${displayAttentionPath(request.location)}`,
103
- `attention: ${request.activity} for ${request.participant} from ${request.actor}`,
104
- '</system-reminder>',
105
- ].join('\n');
99
+ const location = await canonicalPath(request.location);
100
+ const square = await openSquare(location, { hostLedger }).catch(() => undefined);
101
+ if (square === undefined)
102
+ return { outcome: 'not-capable', diagnostic: `Square is unavailable at ${location}` };
103
+ let payload;
104
+ try {
105
+ const index = parseActivityId(request.activity);
106
+ payload = (await square.artifact.read((facts) => {
107
+ const head = index === undefined ? undefined : pendingHead(facts, request.participant, index);
108
+ const item = index === undefined ? undefined : facts.activity(index);
109
+ if (!head || item?.kind !== 'say')
110
+ return undefined;
111
+ return renderSquareReminder([renderAttentionPreview({
112
+ squarePath: location, recipient: request.participant, actIndex: item.index,
113
+ actor: item.actor, route: head.route, body: item.body,
114
+ })]);
115
+ })).value;
116
+ }
117
+ finally {
118
+ await closeOpenSquare(square);
119
+ }
120
+ if (payload === undefined)
121
+ return { outcome: 'not-capable', diagnostic: 'No pending Square activity to preview.' };
106
122
  const result = await adapter.dispatch(request.route.address, payload, finalGate, timeoutMs, request);
107
123
  if (result.outcome !== 'gate-rejected')
108
124
  return result;
@@ -10,6 +10,8 @@ export async function identityRouteDraft(context, participant) {
10
10
  if (context.location === undefined || context.location === 'memory')
11
11
  return undefined;
12
12
  const identity = processIdentity(context.env ?? process.env);
13
+ if (identity.channel === 'unknown')
14
+ return undefined;
13
15
  const provider = identity.channel === 'claude-code' ? 'claude' : identity.channel === 'opencode' ? 'opencode' : identity.channel === 'pi' ? 'pi' : identity.channel === 'paseo' ? 'paseo' : 'codex';
14
16
  const boundary = { location: context.location, participant, sessionId: identity.session, provider };
15
17
  const env = context.env ?? process.env;
@@ -51,6 +53,9 @@ export async function ensureLocalPresence(context, participant, epoch) {
51
53
  if (context.hostLedger === undefined || context.location === undefined || context.location === 'memory')
52
54
  return;
53
55
  const identity = processIdentity(context.env ?? process.env);
56
+ // Sessionless library operations do not establish host presence or ownership.
57
+ if (identity.channel === 'unknown')
58
+ return;
54
59
  const now = Date.now();
55
60
  let existing;
56
61
  try {
@@ -3,7 +3,7 @@ import { hostLedgerForEnv } from '../../registry.js';
3
3
  import { canonicalPath } from '../../canonical-path.js';
4
4
  import { openSquare } from '../../open-square.js';
5
5
  import { closeOpenSquare } from '../../open-square.js';
6
- import { Square } from '../../square-application.js';
6
+ import { Square } from '../../square-api.js';
7
7
  import { connectNative } from '../../packages/agent-delivery/src/opencode-native.js';
8
8
  import { observeOpenCodeContext, publishOpenCodeRoutes, receiveOpenCodePending } from './delivery.js';
9
9
  /** Actual OpenCode 2.0.20 server plugin. Harness identity belongs to ctx, never process.env. */