@astrosheep/square 0.3.58 → 0.3.62

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 (91) hide show
  1. package/claude-plugin/.claude-plugin/plugin.json +1 -1
  2. package/claude-plugin/skills/square/SKILL.md +5 -7
  3. package/codex-plugin/.codex-plugin/plugin.json +1 -1
  4. package/dist/activity.d.ts +6 -2
  5. package/dist/activity.js +170 -133
  6. package/dist/artifact.d.ts +4 -0
  7. package/dist/artifact.js +43 -14
  8. package/dist/attention-presentation.d.ts +1 -0
  9. package/dist/attention-presentation.js +21 -19
  10. package/dist/automatic-session.js +16 -26
  11. package/dist/boundary-presentation.d.ts +1 -2
  12. package/dist/boundary-presentation.js +4 -19
  13. package/dist/catch-decisions.js +1 -1
  14. package/dist/cli/context.d.ts +2 -0
  15. package/dist/cli/context.js +17 -33
  16. package/dist/cli/harness-command.js +1 -1
  17. package/dist/cli/mcp-server-command.d.ts +2 -0
  18. package/dist/cli/mcp-server-command.js +13 -0
  19. package/dist/cli/observation-commands.js +132 -64
  20. package/dist/cli/program.js +36 -9
  21. package/dist/cli/registry.js +2 -0
  22. package/dist/cli/square-commands.d.ts +1 -1
  23. package/dist/cli/square-commands.js +103 -133
  24. package/dist/decisions.d.ts +4 -0
  25. package/dist/decisions.js +82 -34
  26. package/dist/delivery-operations.d.ts +1 -2
  27. package/dist/delivery-operations.js +1 -4
  28. package/dist/file-changes.d.ts +12 -0
  29. package/dist/file-changes.js +235 -0
  30. package/dist/help.js +4 -8
  31. package/dist/host-ledger-file-adapter.d.ts +15 -13
  32. package/dist/host-ledger-file-adapter.js +131 -79
  33. package/dist/host-ledger-root.d.ts +2 -0
  34. package/dist/host-ledger-root.js +9 -0
  35. package/dist/host-ledger.d.ts +4 -24
  36. package/dist/inbox.d.ts +10 -5
  37. package/dist/inbox.js +152 -67
  38. package/dist/index.d.ts +3 -1
  39. package/dist/index.js +1 -0
  40. package/dist/list.js +12 -9
  41. package/dist/mcp.d.ts +7 -0
  42. package/dist/mcp.js +122 -0
  43. package/dist/mention-parse.d.ts +8 -0
  44. package/dist/mention-parse.js +107 -0
  45. package/dist/model.d.ts +5 -0
  46. package/dist/model.js +4 -1
  47. package/dist/notifications.d.ts +1 -0
  48. package/dist/notifications.js +25 -20
  49. package/dist/participant-host.js +2 -2
  50. package/dist/ports.d.ts +2 -9
  51. package/dist/presentation.d.ts +19 -1
  52. package/dist/presentation.js +111 -73
  53. package/dist/presented.js +2 -1
  54. package/dist/registry.d.ts +8 -4
  55. package/dist/registry.js +43 -76
  56. package/dist/routes.d.ts +19 -2
  57. package/dist/routes.js +48 -5
  58. package/dist/runtime.d.ts +10 -0
  59. package/dist/runtime.js +39 -0
  60. package/dist/square-actions.d.ts +26 -13
  61. package/dist/square-actions.js +203 -87
  62. package/dist/square-application.d.ts +58 -0
  63. package/dist/square-application.js +183 -0
  64. package/dist/square-facade.d.ts +24 -9
  65. package/dist/square-file-adapter.d.ts +5 -12
  66. package/dist/square-file-adapter.js +12 -64
  67. package/dist/square-projections.d.ts +3 -2
  68. package/dist/square-projections.js +12 -3
  69. package/dist/square-storage.js +27 -34
  70. package/dist/square-wiring.d.ts +12 -7
  71. package/dist/square-wiring.js +36 -32
  72. package/dist/state-cell.d.ts +1 -1
  73. package/dist/stream.js +31 -27
  74. package/dist/tty-style.d.ts +7 -0
  75. package/dist/tty-style.js +13 -0
  76. package/dist/views.d.ts +3 -3
  77. package/dist/views.js +10 -9
  78. package/dist/wake-attempts.js +2 -1
  79. package/dist/wake-evidence.js +2 -8
  80. package/dist/wakes.d.ts +1 -0
  81. package/dist/wakes.js +5 -5
  82. package/dist/watch.d.ts +6 -1
  83. package/dist/watch.js +43 -28
  84. package/extensions/square-pi.js +107 -109
  85. package/guides/application-boundary.md +32 -0
  86. package/guides/change-waits.md +28 -0
  87. package/guides/host-ledger.md +9 -0
  88. package/guides/mcp.md +9 -0
  89. package/guides/operation-cancellation.md +17 -0
  90. package/package.json +10 -4
  91. package/skills/square/SKILL.md +5 -7
@@ -0,0 +1,107 @@
1
+ import { NAME_GRAPHEME_SOURCE, sameName } from './model.js';
2
+ /**
3
+ * Roster-free scan for `@name` candidates in an activity body. Resolution against the
4
+ * standing roster happens at landing time in decideAct; this scanner only knows the
5
+ * name grammar and Markdown code spans.
6
+ *
7
+ * Guard rails:
8
+ * - Backtick code spans never yield candidates (also the escape hatch: `` `@name` `` stays literal).
9
+ * - A `@` glued to a handle-like prefix does not start a candidate, so emails
10
+ * (`user@host`), URLs (`x.com/@user`), and `snake_@case` stay literal. Only ASCII
11
+ * word characters, `_`, `-`, `/`, and `@` block a boundary: CJK adjacency like
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`.
15
+ */
16
+ const MENTION_TOKEN = new RegExp(`@((?:${NAME_GRAPHEME_SOURCE})+(?:/(?:${NAME_GRAPHEME_SOURCE})+)*)`, 'vg');
17
+ const BOUNDARY_BLOCKER = /[A-Za-z0-9_\-/@]/;
18
+ /** Ranges [from, to) covered by Markdown code spans (backtick runs close at a run of equal length). */
19
+ function codeSpanRanges(body) {
20
+ const ranges = [];
21
+ let index = 0;
22
+ while (index < body.length) {
23
+ if (body[index] !== '`') {
24
+ index += 1;
25
+ continue;
26
+ }
27
+ let runEnd = index;
28
+ while (runEnd < body.length && body[runEnd] === '`')
29
+ runEnd += 1;
30
+ const runLength = runEnd - index;
31
+ let scan = runEnd;
32
+ let closedEnd = -1;
33
+ while (scan < body.length) {
34
+ if (body[scan] !== '`') {
35
+ scan += 1;
36
+ continue;
37
+ }
38
+ let closeEnd = scan;
39
+ while (closeEnd < body.length && body[closeEnd] === '`')
40
+ closeEnd += 1;
41
+ if (closeEnd - scan === runLength) {
42
+ closedEnd = closeEnd;
43
+ break;
44
+ }
45
+ scan = closeEnd;
46
+ }
47
+ if (closedEnd === -1) {
48
+ // Unmatched backticks are literal text; scanning continues after the run.
49
+ index = runEnd;
50
+ continue;
51
+ }
52
+ ranges.push([index, closedEnd]);
53
+ index = closedEnd;
54
+ }
55
+ return ranges;
56
+ }
57
+ function codePointBefore(body, index) {
58
+ if (index <= 0)
59
+ return undefined;
60
+ const unit = body.charCodeAt(index - 1);
61
+ if (unit >= 0xdc00 && unit <= 0xdfff && index >= 2)
62
+ return String.fromCodePoint(body.codePointAt(index - 2));
63
+ return String.fromCodePoint(unit);
64
+ }
65
+ export function scanMentionCandidates(body) {
66
+ const spans = codeSpanRanges(body);
67
+ const found = [];
68
+ const seen = new Set();
69
+ for (const match of body.matchAll(MENTION_TOKEN)) {
70
+ const start = match.index;
71
+ if (spans.some(([from, to]) => start >= from && start < to))
72
+ continue;
73
+ const previous = codePointBefore(body, start);
74
+ if (previous !== undefined && BOUNDARY_BLOCKER.test(previous))
75
+ continue;
76
+ const token = match[1];
77
+ const key = token.toLocaleLowerCase();
78
+ if (!seen.has(key)) {
79
+ seen.add(key);
80
+ found.push({ token, index: start });
81
+ }
82
+ }
83
+ return found;
84
+ }
85
+ /**
86
+ * Resolve the longest standing name starting at one candidate's position. A partial
87
+ * match is only accepted when the character right after it is not handle-continuation
88
+ * (ASCII word char, `_`, `-`, `/`): `@rei2` never degrades into mentioning `rei`, while
89
+ * CJK-fluent text like `告诉@rei这个` still mentions `rei`.
90
+ */
91
+ const TRAILING_BLOCKER = /[A-Za-z0-9_\-/]/;
92
+ export function resolveNameAt(body, candidate, names) {
93
+ const text = body.slice(candidate.index + 1, candidate.index + 1 + candidate.token.length);
94
+ let best;
95
+ for (const name of names) {
96
+ if (name.length > text.length)
97
+ continue;
98
+ if (!sameName(text.slice(0, name.length), name))
99
+ continue;
100
+ const after = text[name.length];
101
+ if (after !== undefined && TRAILING_BLOCKER.test(after))
102
+ continue;
103
+ if (best === undefined || name.length > best.length)
104
+ best = name;
105
+ }
106
+ return best;
107
+ }
package/dist/model.d.ts CHANGED
@@ -18,8 +18,11 @@ export interface ReceiverRoute extends WakeRoute {
18
18
  export type SquareErrorCode = 'invalid_name' | 'invalid_args' | 'unknown_participant' | 'not_joined' | 'already_joined' | 'already_done' | 'held' | 'capped' | 'throttled' | 'bell_quota' | 'behind' | 'io' | 'unavailable';
19
19
  export type InternalSquareErrorCode = SquareErrorCode | 'not_found' | 'cap_reached' | 'conflict' | 'pending_peer';
20
20
  export interface SquareErrorFacts {
21
+ /** Express admission rejected before any activity was committed. */
22
+ activityUnsent?: true;
21
23
  pending?: number;
22
24
  holder?: string;
25
+ holdReason?: string;
23
26
  retryAfterMs?: number;
24
27
  }
25
28
  declare class SquareErrorBase<Code extends string> extends Error {
@@ -132,4 +135,6 @@ export declare function parseParticipantList(value: string): string[];
132
135
  export declare function nameKey(name: string): string;
133
136
  export declare function sameName(a: string, b: string): boolean;
134
137
  export declare function findParticipantName(participants: string[], name: string): string | undefined;
138
+ /** One name grapheme: Unicode letters, digits, marks, hyphens, or underscores (no isolated variation selectors), or a complete RGI emoji. Shared by name validation and body @mention scanning. */
139
+ export declare const NAME_GRAPHEME_SOURCE: string;
135
140
  export declare function validateName(name: string): void;
package/dist/model.js CHANGED
@@ -39,8 +39,11 @@ export function sameName(a, b) {
39
39
  export function findParticipantName(participants, name) {
40
40
  return participants.find((participant) => sameName(participant, name));
41
41
  }
42
+ /** One name grapheme: Unicode letters, digits, marks, hyphens, or underscores (no isolated variation selectors), or a complete RGI emoji. Shared by name validation and body @mention scanning. */
43
+ export const NAME_GRAPHEME_SOURCE = String.raw `[[\p{L}\p{N}\p{M}_\x2D]--[\p{Variation_Selector}]]|\p{RGI_Emoji}`;
44
+ const NAME_PATTERN = new RegExp(`^(?:${NAME_GRAPHEME_SOURCE})+(?:/(?:${NAME_GRAPHEME_SOURCE})+)*$`, 'v');
42
45
  export function validateName(name) {
43
- if (!name || !/^(?:[[\p{L}\p{N}\p{M}_\x2D]--[\p{Variation_Selector}]]|\p{RGI_Emoji})+(?:\/(?:[[\p{L}\p{N}\p{M}_\x2D]--[\p{Variation_Selector}]]|\p{RGI_Emoji})+)*$/v.test(name)) {
46
+ if (!name || !NAME_PATTERN.test(name)) {
44
47
  throw new SquareError('invalid_name', 'Invalid name: names must contain non-empty slash-separated segments using Unicode letters, digits, marks, hyphens, underscores, or complete RGI emoji graphemes.');
45
48
  }
46
49
  }
@@ -12,6 +12,7 @@ export declare function hasDeliveredNotification(squarePath: string, name: strin
12
12
  export declare function hasAttentionNotification(squarePath: string, name: string, ref: number | ActivityId, env?: NodeJS.ProcessEnv): Promise<boolean>;
13
13
  export declare function waitForDeliveredNotification(squarePath: string, name: string, ref: number | ActivityId, opts?: {
14
14
  timeoutMs?: number;
15
+ signal?: AbortSignal;
15
16
  }): Promise<boolean>;
16
17
  interface ProcessNotificationOptions {
17
18
  adapters?: WakeAdapter[];
@@ -1,12 +1,12 @@
1
- import { setTimeout as sleep } from 'node:timers/promises';
1
+ import { hostLedgerRoot } from './host-ledger-root.js';
2
2
  import path from 'node:path';
3
3
  import fs from 'node:fs';
4
4
  import { planActNotifications, } from './delivery.js';
5
5
  import { SquareError } from './model.js';
6
- import { SLEEP_MS, matchesMentionTarget } from './runtime.js';
6
+ import { matchesMentionTarget } from './runtime.js';
7
7
  import { formatActivityId, parseActivityId } from './square-core.js';
8
8
  import { displayAttentionPath } from './attention-presentation.js';
9
- import { openSquare } from './square-file-adapter.js';
9
+ import { openSquare, observeSquareChanges } from './square-file-adapter.js';
10
10
  import { closeOpenSquare } from './open-square.js';
11
11
  import { notificationDelivered, resolveParticipant } from './views.js';
12
12
  import { deliverPending, observeSquare, sweepPending, sweepPendingFromState } from './delivery-operations.js';
@@ -63,8 +63,8 @@ export async function hasAttentionNotification(squarePath, name, ref, env = proc
63
63
  const index = notificationIndex(ref);
64
64
  if (await notificationDelivered(square, recipient, index))
65
65
  return true;
66
- const root = env.SQUARE_REGISTRY === undefined ? undefined : path.dirname(env.SQUARE_REGISTRY);
67
- const hostLedger = createHostLedgerPort({ userPath: env.SQUARE_HOST_LEDGER_USER ?? root, localPath: env.SQUARE_HOST_LEDGER_LOCAL ?? root, readableScopes: ['user'], writableScope: 'user' });
66
+ const root = hostLedgerRoot(env);
67
+ const hostLedger = createHostLedgerPort({ rootPath: root });
68
68
  return (await projectPresentationEvidence({ hostLedger, location: squarePath, participant: recipient, activity: formatActivityId(index), now: Date.now() })).some((row) => row.outcome === 'presented');
69
69
  }
70
70
  finally {
@@ -73,12 +73,20 @@ export async function hasAttentionNotification(squarePath, name, ref, env = proc
73
73
  }
74
74
  export async function waitForDeliveredNotification(squarePath, name, ref, opts = {}) {
75
75
  const deadline = Date.now() + (opts.timeoutMs ?? 30000);
76
- while (Date.now() <= deadline) {
77
- if (await hasDeliveredNotification(squarePath, name, ref))
78
- return true;
79
- await sleep(Math.min(SLEEP_MS, Math.max(1, deadline - Date.now())));
76
+ const observer = await observeSquareChanges(squarePath);
77
+ try {
78
+ while (Date.now() <= deadline) {
79
+ const baseline = await observer.read(opts.signal);
80
+ if (await hasDeliveredNotification(squarePath, name, ref))
81
+ return true;
82
+ if (!await observer.changed(baseline, Math.max(0, deadline - Date.now()), opts.signal))
83
+ return false;
84
+ }
85
+ return false;
86
+ }
87
+ finally {
88
+ observer.close();
80
89
  }
81
- return false;
82
90
  }
83
91
  export async function defaultWakeAdapters() {
84
92
  const adapters = [];
@@ -188,10 +196,9 @@ export function createWakeTransport(adapters, hostLedger, clock) {
188
196
  export async function processActNotificationsOnce(squarePath, actIndex, opts = {}) {
189
197
  const env = opts.env ?? process.env;
190
198
  const now = opts.now ?? Date.now;
191
- const ledgerRoot = env.SQUARE_REGISTRY === undefined ? undefined : path.dirname(env.SQUARE_REGISTRY);
199
+ const ledgerRoot = hostLedgerRoot(env);
192
200
  const hostLedger = createHostLedgerPort({
193
- userPath: env.SQUARE_HOST_LEDGER_USER ?? ledgerRoot,
194
- localPath: env.SQUARE_HOST_LEDGER_LOCAL ?? ledgerRoot,
201
+ rootPath: ledgerRoot,
195
202
  });
196
203
  const square = await openSquare(squarePath, { clock: now, hostLedger, env });
197
204
  try {
@@ -213,11 +220,11 @@ export async function sweepPrivilegedPending(cwd, env = process.env, suppliedAda
213
220
  const remainingMs = () => Math.max(0, deadline - Date.now());
214
221
  if (remainingMs() === 0 || signal?.aborted)
215
222
  return;
216
- const root = env.SQUARE_REGISTRY === undefined ? undefined : path.dirname(env.SQUARE_REGISTRY);
217
- const hostLedger = createHostLedgerPort({ userPath: env.SQUARE_HOST_LEDGER_USER ?? root, localPath: env.SQUARE_HOST_LEDGER_LOCAL ?? root, readableScopes: ['user'], writableScope: 'user' });
223
+ const root = hostLedgerRoot(env);
224
+ const hostLedger = createHostLedgerPort({ rootPath: root });
218
225
  let indexed = [];
219
226
  try {
220
- indexed = await hostLedger.listPresence({ scopes: ['user'], now: Date.now() });
227
+ indexed = await hostLedger.listPresence({ now: Date.now() });
221
228
  }
222
229
  catch { /* capability is best effort */ }
223
230
  const paths = new Set(indexed.map((binding) => binding.location));
@@ -237,7 +244,6 @@ export async function sweepPrivilegedPending(cwd, env = process.env, suppliedAda
237
244
  try {
238
245
  const square = await openSquare(squarePath, { hostLedger, env, signal });
239
246
  try {
240
- await hostLedger.reconcileBinding({ artifact: square.artifact, scopes: ['user'], now: Date.now() }).catch(() => undefined);
241
247
  if (remainingMs() === 0 || signal?.aborted)
242
248
  break;
243
249
  const limit = Number.parseInt(env.SQUARE_NOTIFY_SWEEP_LIMIT ?? '8', 10);
@@ -262,7 +268,7 @@ export async function sweepPrivilegedPending(cwd, env = process.env, suppliedAda
262
268
  }
263
269
  /** Select sweep candidates from one frozen snapshot and one delivery replay. */
264
270
  export async function pendingNotificationSweepFromState(squarePath, state, now, env, limit, deriveDelivery) {
265
- const ledger = createHostLedgerPort({ userPath: env.SQUARE_HOST_LEDGER_USER, writableScope: 'user', readableScopes: ['user'] });
271
+ const ledger = createHostLedgerPort({ rootPath: hostLedgerRoot(env) });
266
272
  return sweepPendingFromState({ state, hostLedger: ledger, location: squarePath, now, graceMs: wakeGraceMs(env), limit, deriveDelivery });
267
273
  }
268
274
  /** Select old pending attention at a bounded action boundary for an explicit executor. */
@@ -272,8 +278,7 @@ export async function sweepPendingNotifications(squarePath, opts = {}) {
272
278
  return [];
273
279
  const now = opts.now ?? Date.now();
274
280
  const limit = opts.limit ?? 8;
275
- const ledgerRoot = env.SQUARE_REGISTRY === undefined ? undefined : path.dirname(env.SQUARE_REGISTRY);
276
- const hostLedger = createHostLedgerPort({ userPath: env.SQUARE_HOST_LEDGER_USER ?? ledgerRoot, localPath: env.SQUARE_HOST_LEDGER_LOCAL ?? ledgerRoot });
281
+ const hostLedger = createHostLedgerPort({ rootPath: hostLedgerRoot(env) });
277
282
  const square = await openSquare(squarePath, { clock: () => now, hostLedger, env });
278
283
  let selected;
279
284
  try {
@@ -53,7 +53,7 @@ export async function ensureLocalPresence(context, participant, epoch) {
53
53
  const now = context.clock();
54
54
  // Semantic publish: an unexpired presence row already stands when it carries the same owner epoch.
55
55
  try {
56
- const existing = await context.hostLedger.listPresence({ location: context.location, participant, session: identity.session, scopes: ['local'], now });
56
+ const existing = await context.hostLedger.listPresence({ location: context.location, participant, session: identity.session, now });
57
57
  if (existing.some((row) => row.channel === identity.channel
58
58
  && now - (row.updatedAt ?? 0) < ROUTE_FRESH_MS
59
59
  && (ownerEpoch === undefined || row.epoch === ownerEpoch)))
@@ -68,7 +68,7 @@ export async function ensureLocalPresence(context, participant, epoch) {
68
68
  // Presence rows are host-ledger wall-time evidence; the square clock belongs to artifact activities.
69
69
  updatedAt: Date.now(),
70
70
  ...(ownerEpoch === undefined || ownerEpoch <= 0 ? {} : { epoch: ownerEpoch }),
71
- }, 'local').catch((error) => ({
71
+ }).catch((error) => ({
72
72
  status: 'degraded',
73
73
  record: { location: context.location, participant, session: identity.session, channel: identity.channel },
74
74
  error,
package/dist/ports.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { InboxNotification, SquareState, StoredAct, WakeRoute, WatchLease } from './model.js';
2
2
  import type { PlannedNotification } from './delivery.js';
3
- export type { ClaimResult, EvidenceClaim, EvidenceRelease, EvidenceGc, EvidenceLookup, EvidenceRecord, HostLedgerPort, HostLedgerScope, PresenceKey, PresenceLookup, PresenceRecord, PresenceClaimResult, PresenceChannel, PresenceResult, ReconcileBindingInput as HostLedgerReconcileBindingInput, ReconcileBindingResult, } from './host-ledger.js';
3
+ export type { ClaimResult, EvidenceClaim, EvidenceRelease, EvidenceGc, EvidenceLookup, EvidenceRecord, HostLedgerPort, PresenceKey, PresenceLookup, PresenceRecord, PresenceClaimResult, PresenceChannel, PresenceResult, } from './host-ledger.js';
4
4
  import type { HostLedgerPort } from './host-ledger.js';
5
5
  /** Atomic artifact access. Never exposes framing, compression, locks, or storage schema. */
6
6
  export interface SquareArtifactPort {
@@ -12,7 +12,7 @@ export interface SquareArtifactPort {
12
12
  state?: SquareState;
13
13
  result: R;
14
14
  }, signal?: AbortSignal): Promise<R>;
15
- changed(sinceVersion: number, timeoutMs: number): Promise<boolean>;
15
+ changed(sinceVersion: number, timeoutMs: number, signal?: AbortSignal): Promise<boolean>;
16
16
  close(): Promise<void>;
17
17
  }
18
18
  /** Capability-neutral wake transport. Unused by this Contract's activity operations. */
@@ -101,13 +101,6 @@ export interface ObserveSquareInput {
101
101
  readonly location?: string;
102
102
  readonly now?: number;
103
103
  }
104
- export interface ReconcileBindingInput {
105
- readonly artifact: SquareArtifactPort;
106
- readonly hostLedger: HostLedgerPort;
107
- readonly location?: string;
108
- readonly scopes?: readonly import('./host-ledger.js').HostLedgerScope[];
109
- readonly now?: number;
110
- }
111
104
  export interface DeliveryResult {
112
105
  readonly attempted: number;
113
106
  readonly accepted: number;
@@ -6,6 +6,8 @@ export type WatchStatus = 'stale' | 'empty-now' | 'quorum' | 'capped';
6
6
  interface HeaderOptions {
7
7
  participantCount?: number;
8
8
  held?: boolean;
9
+ /** Refusal bodies go to stderr, which stays literal even when it is a TTY. */
10
+ plain?: boolean;
9
11
  }
10
12
  interface ParticipantOutputOptions {
11
13
  squarePath: string;
@@ -19,6 +21,7 @@ interface ActivityLimitOptions extends ParticipantOutputOptions {
19
21
  draftPath?: string;
20
22
  }
21
23
  interface ActivityBlockedOptions extends ParticipantOutputOptions {
24
+ retryCommand: string;
22
25
  forceCommand: string;
23
26
  activitySummaries: UnreadActivitySummary[];
24
27
  unreadRoomChanges: RoomChangeAct[];
@@ -27,10 +30,12 @@ interface ActivityBlockedOptions extends ParticipantOutputOptions {
27
30
  interface ExpressWaitingOptions {
28
31
  reason: 'throttled' | 'held';
29
32
  delayMs?: number;
33
+ holder?: string;
34
+ holdReason?: string;
30
35
  }
31
36
  interface ExpressNoWaitOptions extends ParticipantOutputOptions {
32
37
  reason: 'throttled' | 'held';
33
- forceCommand: string;
38
+ retryCommand: string;
34
39
  delayMs?: number;
35
40
  holdReason?: string;
36
41
  draftPath?: string;
@@ -46,11 +51,18 @@ interface WatchStatusOptions extends ParticipantOutputOptions {
46
51
  }
47
52
  export declare function displayPath(squarePath: string, cwd?: string): string;
48
53
  export declare function withPathOutput(squarePath: string, body?: string, opts?: HeaderOptions): string;
54
+ /** The same header/blank/body layout as withPathOutput, for refusal bodies written to stderr. */
55
+ export declare function formatRefusal(squarePath: string, bodyLines: string[], opts?: {
56
+ participantCount?: number;
57
+ held?: boolean;
58
+ }): string;
49
59
  export declare function quoteShell(value: string): string;
50
60
  export declare function commandPrefix(squarePath: string): string;
51
61
  export declare function participantCommandPrefix(squarePath: string, name: string): string;
52
62
  /** Blocked actions end with a full copy-pasteable recovery command. */
53
63
  export declare function joinRecoveryCommand(squarePath: string, name: string): string;
64
+ /** Taking a name is an explicit recovery choice, never an automatic retry. */
65
+ export declare function takeoverRecoveryLines(squarePath: string, name: string): string[];
54
66
  export declare function participantsRecoveryCommand(squarePath: string): string;
55
67
  export declare function renderPresenceLines(participants: ParticipantStatus[], now: number, limit?: number): string[];
56
68
  export declare function expressHintLine(ownActivityCount: number): string | undefined;
@@ -78,6 +90,12 @@ export declare function renderAmbientEvent(event: StoredAct, viewer: string, opt
78
90
  }): string;
79
91
  export declare function renderPendingFeed(history: StoredAct[], publicItems: PublicAct[], roomChanges: RoomChangeAct[], viewer: string | undefined, squareState: SquareState): string;
80
92
  export declare function renderActivityBlocked(opts: ActivityBlockedOptions): string;
93
+ /** An indeterminate or already-committed send only points to inspection, never resend. */
94
+ export declare function renderActivityUncertain(opts: ParticipantOutputOptions & {
95
+ draftPath: string;
96
+ landedId?: string;
97
+ detail?: string;
98
+ }): string;
81
99
  export declare function renderExpressWaiting(opts: ExpressWaitingOptions): string;
82
100
  export declare function renderExpressNoWait(opts: ExpressNoWaitOptions): string;
83
101
  export declare function renderPublicTail(squareState: SquareState, events: StoredAct[], lastN: number | null | undefined, now?: number, viewer?: string): string;