@astrosheep/square 0.4.6 → 0.4.8

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 (101) hide show
  1. package/dist/card-format.d.ts +2 -0
  2. package/dist/card-format.js +2 -0
  3. package/dist/claude-mcp-context.d.ts +0 -1
  4. package/dist/claude-mcp-context.js +0 -10
  5. package/dist/claude-mod.d.ts +2 -0
  6. package/dist/claude-mod.js +211 -0
  7. package/dist/cli/context.d.ts +2 -2
  8. package/dist/cli/context.js +14 -16
  9. package/dist/cli/express-presentation.js +12 -53
  10. package/dist/cli/json.js +2 -2
  11. package/dist/cli/observation-commands.d.ts +2 -7
  12. package/dist/cli/observation-commands.js +31 -97
  13. package/dist/cli/registry.js +3 -6
  14. package/dist/cli/square-commands.d.ts +2 -3
  15. package/dist/cli/square-commands.js +12 -22
  16. package/dist/decisions.js +5 -4
  17. package/dist/harness-claude.js +3 -3
  18. package/dist/help.js +9 -16
  19. package/dist/index.d.ts +1 -1
  20. package/dist/index.js +1 -1
  21. package/dist/mcp.d.ts +1 -0
  22. package/dist/mcp.js +7 -18
  23. package/dist/model.d.ts +1 -1
  24. package/dist/neutral.d.ts +15 -3
  25. package/dist/notifications.d.ts +1 -3
  26. package/dist/notifications.js +21 -23
  27. package/dist/operation-control.d.ts +0 -1
  28. package/dist/operation-control.js +0 -11
  29. package/dist/operation-presentation.d.ts +28 -21
  30. package/dist/operation-presentation.js +90 -87
  31. package/dist/packages/agent-delivery/src/index.d.ts +4 -9
  32. package/dist/packages/agent-delivery/src/index.js +0 -9
  33. package/dist/packages/agent-delivery/src/local-endpoint.d.ts +2 -12
  34. package/dist/packages/agent-delivery/src/local-endpoint.js +4 -48
  35. package/dist/packages/agent-delivery/src/pi-client.js +0 -2
  36. package/dist/packages/agent-delivery/src/types.d.ts +3 -37
  37. package/dist/participant-identity.d.ts +0 -2
  38. package/dist/participant-identity.js +0 -13
  39. package/dist/plugins/paseo/shared/card-format.d.ts +6 -0
  40. package/dist/plugins/paseo/shared/card-format.js +38 -0
  41. package/dist/plugins/paseo/shared/card-presentation.d.ts +3 -1
  42. package/dist/plugins/paseo/shared/card-presentation.js +3 -1
  43. package/dist/presentation.d.ts +5 -22
  44. package/dist/presentation.js +25 -58
  45. package/dist/runtime.d.ts +6 -6
  46. package/dist/runtime.js +5 -15
  47. package/dist/sqlite-square.d.ts +4 -5
  48. package/dist/sqlite-square.js +29 -32
  49. package/dist/square-api.d.ts +1 -1
  50. package/dist/square-api.js +1 -1
  51. package/dist/square-facade.d.ts +5 -14
  52. package/dist/square-location.d.ts +0 -1
  53. package/dist/square-posting.d.ts +4 -4
  54. package/dist/square-posting.js +14 -16
  55. package/dist/square-tools.d.ts +4 -17
  56. package/dist/square-tools.js +52 -119
  57. package/dist/square-values.d.ts +1 -1
  58. package/dist/square-values.js +1 -1
  59. package/dist/time.d.ts +1 -0
  60. package/dist/time.js +10 -0
  61. package/dist/views.d.ts +1 -1
  62. package/dist/views.js +2 -2
  63. package/dist/wake-transports.d.ts +2 -2
  64. package/dist/wake-transports.js +7 -9
  65. package/dist/wakes.js +1 -1
  66. package/examples/symmetric-domains.mjs +1 -2
  67. package/guides/catch-cold-path-optimization.md +1 -1
  68. package/guides/claude-native.md +37 -7
  69. package/guides/delivery-primitives.md +2 -1
  70. package/guides/mcp.md +3 -3
  71. package/guides/pi-native-delivery.md +10 -2
  72. package/guides/pi-tools.md +1 -3
  73. package/guides/row-storage-design.md +1 -1
  74. package/package.json +1 -1
  75. package/plugins/claude/.claude-plugin/plugin.json +3 -2
  76. package/plugins/claude/README.md +9 -0
  77. package/plugins/claude/hooks/hooks.json +1 -9
  78. package/plugins/claude/hooks/register.js +178 -0
  79. package/plugins/claude/skills/square/SKILL.md +2 -2
  80. package/plugins/codex/.codex-plugin/plugin.json +1 -1
  81. package/plugins/paseo/OVERVIEW.md +1 -1
  82. package/plugins/paseo/README.md +20 -23
  83. package/plugins/paseo/client/markdown.tsx +88 -95
  84. package/plugins/paseo/client/palette.ts +37 -12
  85. package/plugins/paseo/client/square-message.tsx +52 -76
  86. package/plugins/paseo/client/transform.ts +4 -6
  87. package/plugins/paseo/shared/card-format.ts +37 -0
  88. package/plugins/paseo/shared/card-presentation.ts +6 -2
  89. package/plugins/paseo/shared/message.ts +1 -6
  90. package/plugins/pi/square-pi.js +76 -60
  91. package/skills/square/SKILL.md +2 -2
  92. package/dist/claude-boundary-state.d.ts +0 -20
  93. package/dist/claude-boundary-state.js +0 -84
  94. package/dist/claude-hook.d.ts +0 -2
  95. package/dist/claude-hook.js +0 -232
  96. package/dist/packages/agent-delivery/src/claude-auth.d.ts +0 -13
  97. package/dist/packages/agent-delivery/src/claude-auth.js +0 -75
  98. package/dist/packages/agent-delivery/src/claude-native.d.ts +0 -30
  99. package/dist/packages/agent-delivery/src/claude-native.js +0 -75
  100. package/dist/packages/agent-delivery/src/claude.d.ts +0 -5
  101. package/dist/packages/agent-delivery/src/claude.js +0 -66
@@ -1,52 +1,50 @@
1
1
  import { createDefaultDeliveryTransports } from './wake-transports.js';
2
2
  export { createDefaultDeliveryTransports } from './wake-transports.js';
3
- function deliveryWaitMs(env = process.env) {
4
- const configured = Number(env.SQUARE_NOTIFY_DELIVERY_WAIT_MS ?? 5000);
5
- return Number.isSafeInteger(configured) && configured > 0 ? configured : 5000;
6
- }
3
+ const DEFAULT_DELIVERY_WAIT_MS = 5000;
4
+ const NATIVE_RECEIVER_ROUTES = new Set(['codex-queue', 'claude-mod', 'pi-native']);
7
5
  /** Best-effort dispatch for currently eligible cards, including cards restored by release. */
8
6
  export async function dispatchEligibleCards(ledger, owner, options = {}) {
9
- options = { ...options, signal: options.signal ?? AbortSignal.timeout(deliveryWaitMs(options.env)) };
10
- const session = await ledger.session(owner, options.signal);
11
- if (!session || session.state !== 'attached' || options.signal?.aborted)
7
+ const signal = options.signal ?? AbortSignal.timeout(DEFAULT_DELIVERY_WAIT_MS);
8
+ const session = await ledger.session(owner, signal);
9
+ if (!session || session.state !== 'attached' || signal.aborted)
12
10
  return;
13
- const pending = await ledger.pending(owner, { generation: session.generation }, options.signal);
11
+ const pending = await ledger.pending(owner, { generation: session.generation }, signal);
14
12
  for (const card of pending)
15
- await dispatchPostedCard(ledger, owner, card.key, options.track ?? (() => undefined), options.env, options.signal, options.transports);
13
+ await dispatchPostedCard(ledger, owner, card.key, options.track ?? (() => undefined), signal, options.transports);
16
14
  }
17
15
  /** Post, then best-effort dispatch without consulting producer storage. */
18
16
  export async function postAndDispatch(ledger, input, options = {}) {
19
- options = { ...options, signal: options.signal ?? AbortSignal.timeout(deliveryWaitMs(options.env)) };
20
- const result = await ledger.post(input, options.signal);
17
+ const signal = options.signal ?? AbortSignal.timeout(DEFAULT_DELIVERY_WAIT_MS);
18
+ const result = await ledger.post(input, signal);
21
19
  if (result.status === 'posted' || result.status === 'duplicate') {
22
20
  try {
23
- await dispatchEligibleCards(ledger, input.owner, options);
21
+ await dispatchEligibleCards(ledger, input.owner, { ...options, signal });
24
22
  }
25
23
  catch { /* Posting is already committed. */ }
26
24
  }
27
25
  return result;
28
26
  }
29
27
  /** Best-effort dispatch after a card has been posted; never inspect producer storage. */
30
- export async function dispatchPostedCard(ledger, owner, key, track, env = process.env, signal, suppliedTransports) {
28
+ export async function dispatchPostedCard(ledger, owner, key, track, signal, suppliedTransports) {
31
29
  if (signal?.aborted)
32
30
  return;
33
- const timeoutMs = deliveryWaitMs(env);
34
- signal ??= AbortSignal.timeout(timeoutMs);
31
+ signal ??= AbortSignal.timeout(DEFAULT_DELIVERY_WAIT_MS);
35
32
  const session = await ledger.session(owner, signal);
36
33
  if (!session || session.state !== 'attached')
37
34
  return;
38
- const transports = suppliedTransports ?? await createDefaultDeliveryTransports(env);
39
- for (const route of session.routes) {
40
- // Claude and Codex custody belongs to their native receivers, including for
41
- // callers that supply transports explicitly to the producer API.
42
- if (['codex-queue', 'claude-async-hook', 'claude-native'].includes(route.kind))
43
- continue;
44
- if (signal?.aborted)
35
+ // Native receiver custody belongs to the receiver itself, including for
36
+ // callers that supply transports explicitly to the producer API.
37
+ const routes = session.routes.filter(route => !NATIVE_RECEIVER_ROUTES.has(route.kind));
38
+ if (!routes.length)
39
+ return;
40
+ const transports = suppliedTransports ?? await createDefaultDeliveryTransports();
41
+ for (const route of routes) {
42
+ if (signal.aborted)
45
43
  return;
46
44
  const transport = transports.find(candidate => candidate.kind === route.kind);
47
45
  if (!transport)
48
46
  continue;
49
- const result = await ledger.dispatchHandoff(owner, key, transport, { generation: session.generation, timeoutMs, signal });
47
+ const result = await ledger.dispatchHandoff(owner, key, transport, { generation: session.generation, timeoutMs: DEFAULT_DELIVERY_WAIT_MS, signal });
50
48
  track(result);
51
49
  if (result.outcome === 'accepted' || result.outcome === 'presented' || result.outcome === 'unknown')
52
50
  return;
@@ -1,4 +1,3 @@
1
1
  import type { OperationControl, OperationFailureDetail } from './neutral.js';
2
2
  export declare function checkControl(control?: OperationControl): void;
3
- export declare function squareClock(env: NodeJS.ProcessEnv, clock?: () => number): () => number;
4
3
  export declare function failure(error: unknown, detail: OperationFailureDetail): Error;
@@ -1,18 +1,7 @@
1
- import { SquareError } from './neutral.js';
2
1
  export function checkControl(control) {
3
2
  if (control?.signal?.aborted)
4
3
  throw control.signal.reason ?? new Error('Square operation was cancelled');
5
4
  }
6
- export function squareClock(env, clock) {
7
- if (clock !== undefined)
8
- return clock;
9
- if (env.SQUARE_NOW_MS === undefined)
10
- return Date.now;
11
- const value = Number.parseInt(env.SQUARE_NOW_MS, 10);
12
- if (!Number.isFinite(value))
13
- throw new SquareError('invalid_args', 'Invalid SQUARE_NOW_MS: expected integer milliseconds.');
14
- return () => value;
15
- }
16
5
  export function failure(error, detail) {
17
6
  const normalized = error instanceof Error ? error : new Error(String(error), { cause: error });
18
7
  Object.assign(normalized, { operation: detail });
@@ -1,32 +1,34 @@
1
- import type { PerceiveOptions, PerceiveResult, ExpressResult, HistoryQuery, HistoryResult, JoinResult, LifecycleResult, ListenerChangeResult, ListeningResult, OperationFailureDetail, ParticipantsResult, StatusResult, DiscoveryResult } from './square-facade.js';
1
+ import type { PerceiveOptions, PerceiveResult, ExpressResult, HistoryQuery, HistoryResult, JoinResult, LifecycleResult, ListenerChangeResult, OperationFailureDetail, SquareErrorFacts, StatusResult, DiscoveryResult } from './square-facade.js';
2
+ import { type PresentationSurface } from './presentation.js';
2
3
  export declare function renderSquareList(data: DiscoveryResult, projection: {
3
4
  now: number;
4
5
  depth: number;
5
6
  limit: number;
7
+ surface?: PresentationSurface;
6
8
  }): string;
7
- export declare const PARTICIPANTS_DEFAULT_LIMIT = 20;
8
- export declare const PARTICIPANTS_MAX_LIMIT = 100;
9
9
  /** A join receipt whose participant is already reduced to its serializable name. */
10
10
  export type JoinPresentation = Omit<JoinResult, 'participant'> & {
11
11
  readonly participant: {
12
12
  readonly name: string;
13
13
  } | string;
14
14
  };
15
- export declare function renderJoinResult(data: JoinPresentation, owner?: string): string | undefined;
16
- export declare function renderExpression(squarePath: string, data: ExpressResult, owner?: string): string | undefined;
15
+ export declare function renderJoinResult(data: JoinPresentation, owner?: string, surface?: PresentationSurface): string | undefined;
16
+ export declare function renderExpression(squarePath: string, data: ExpressResult, owner?: string, surface?: PresentationSurface): string | undefined;
17
17
  /** Render settled perceive facts; no selection, terminal decisions, receipts or wait machinery. */
18
- export declare function renderPerceiveOutcome(squarePath: string, data: PerceiveResult, options?: PerceiveOptions, owner?: string): {
18
+ export declare function renderPerceiveOutcome(squarePath: string, data: PerceiveResult, options?: PerceiveOptions, owner?: string, surface?: PresentationSurface): {
19
19
  stdout: string;
20
20
  exitCode: number;
21
21
  };
22
+ export declare function historyContinuationCommand(squarePath: string, args: readonly string[]): string;
22
23
  export declare function renderHistoryResult(squarePath: string, data: HistoryResult, options?: {
23
24
  query?: HistoryQuery;
24
- noTruncate?: boolean;
25
+ full?: boolean;
25
26
  continuationArgs?: readonly string[];
27
+ surface?: PresentationSurface;
28
+ }): string | undefined;
29
+ export declare function renderStatusResult(squarePath: string, data: StatusResult, options?: {
30
+ surface?: PresentationSurface;
26
31
  }): string | undefined;
27
- export declare function renderParticipantsResult(squarePath: string, data: ParticipantsResult, limit?: number): string | undefined;
28
- export declare function renderStatusResult(squarePath: string, data: StatusResult): string | undefined;
29
- export declare function renderListeningResult(squarePath: string, data: ListeningResult): string | undefined;
30
32
  export type ListenerVerb = 'listen' | 'ignore' | 'unlisten';
31
33
  export declare function renderListenerChange(squarePath: string, verb: ListenerVerb, data: ListenerChangeResult): string | undefined;
32
34
  export declare function renderLifecycleResult(squarePath: string, data: LifecycleResult): string | undefined;
@@ -34,16 +36,21 @@ export declare function renderLifecycleResult(squarePath: string, data: Lifecycl
34
36
  export interface FailureReport {
35
37
  readonly code: string;
36
38
  readonly message: string;
37
- readonly facts?: {
38
- readonly reason?: 'never_joined' | 'not_standing' | 'bell_mention_conflict' | 'not_addressed' | 'invalid_reply' | 'invalid_done_body' | 'needs_seat';
39
- readonly activityId?: string;
40
- readonly activityExists?: boolean;
41
- };
39
+ readonly facts?: SquareErrorFacts;
42
40
  readonly operation?: OperationFailureDetail;
43
41
  }
44
- /** Refusal lines with the same recovery commands the CLI prints. */
45
- export declare function renderFailureLines(failure: FailureReport, options?: {
46
- squarePath?: string;
47
- name?: string;
48
- owner?: string;
49
- }): string[];
42
+ interface FailureOptions {
43
+ readonly squarePath?: string;
44
+ readonly name?: string;
45
+ readonly owner?: string;
46
+ readonly surface?: PresentationSurface;
47
+ readonly reply?: string;
48
+ /** CLI only: the saved draft and the full command that resends it. */
49
+ readonly draftPath?: string;
50
+ readonly retry?: string;
51
+ }
52
+ /** Express gates are physics, not mistakes: one scene for every surface, then that surface's next action. */
53
+ export declare function isExpressGate(code: string): boolean;
54
+ /** Refusal lines with surface-specific recovery actions. */
55
+ export declare function renderFailureLines(failure: FailureReport, options?: FailureOptions): string[];
56
+ export {};
@@ -1,9 +1,8 @@
1
- // Human-facing text for one settled operation. The CLI and the MCP transport both
2
- // render through these functions, so a person sees the same square either way.
1
+ // Human-facing scenes for settled operations, with surface-specific next actions.
3
2
  import { sameName } from './model.js';
4
3
  import { formatActivityId } from './square-core.js';
5
- import { commandPrefix, joinRecoveryCommand, participantCommandPrefix, participantIdentity, participantsRecoveryCommand, quoteShell, renderAmbientEvent, renderAmbientEventFacts, renderEventCli, renderGrepActivitiesView, renderPresenceAnchor, renderWatchAlreadyActive, renderWatchReplaced, renderWatchStatus, takeoverRecoveryLines, withPathOutput, truncateChars, } from './presentation.js';
6
- import { formatRelativeTime, formatTimestamp } from './time.js';
4
+ import { displayPath, formatToolAction, commandPrefix, joinRecoveryCommand, participantCommandPrefix, participantIdentity, quoteShell, renderAmbientEvent, renderAmbientEventFacts, renderEventCli, renderGrepActivitiesView, renderPresenceAnchor, renderWatchAlreadyActive, renderWatchReplaced, renderWatchStatus, renderUnreadSummary, takeoverRecoveryLines, withPathOutput, truncateChars, } from './presentation.js';
5
+ import { formatDuration, formatRelativeTime, formatTimestamp } from './time.js';
7
6
  import { style } from './tty-style.js';
8
7
  function listDisplay(value) {
9
8
  const truncated = truncateChars(value, 159);
@@ -37,16 +36,14 @@ export function renderSquareList(data, projection) {
37
36
  if (remaining > 0) {
38
37
  lines.push(style('dim', stopped ? ' … more squares' : ` … ${remaining} more ${remaining === 1 ? 'square' : 'squares'}`));
39
38
  if (next !== undefined)
40
- lines.push(`square list --depth ${projection.depth} --limit ${projection.limit} --after ${quoteShell(next)}`);
39
+ lines.push(projection.surface === 'tool' ? formatToolAction('list', { depth: projection.depth, limit: projection.limit, after: next }) : `square list --depth ${projection.depth} --limit ${projection.limit} --after ${quoteShell(next)}`);
41
40
  }
42
41
  if (stopped)
43
42
  lines.push(style('dim', '○ the walk stopped early — deeper squares may still be hiding'));
44
43
  return lines.join('\n') + '\n';
45
44
  }
46
- export const PARTICIPANTS_DEFAULT_LIMIT = 20;
47
- export const PARTICIPANTS_MAX_LIMIT = 100;
48
45
  const STATUS_PARTICIPANT_PREVIEW_LIMIT = 10;
49
- export function renderJoinResult(data, owner) {
46
+ export function renderJoinResult(data, owner, surface = 'cli') {
50
47
  const after = data.observation;
51
48
  if (after === undefined)
52
49
  return undefined;
@@ -56,7 +53,7 @@ export function renderJoinResult(data, owner) {
56
53
  now: after.now, preview: 200, perception: event.perception,
57
54
  })).filter(Boolean).join('\n\n');
58
55
  const fallback = after.deliveryAvailable ? ['', '· when someone calls you, you’ll hear them here'] : [
59
- '', `${participantCommandPrefix(after.location, name, owner)} perceive --idle 30m`,
56
+ '', surface === 'tool' ? formatToolAction('perceive', { location: displayPath(after.location), as: name, idle: '30m' }) : `${participantCommandPrefix(after.location, name, owner)} perceive --idle 30m`,
60
57
  ' · stay within earshot — keep this perceive open for new activity',
61
58
  ];
62
59
  const entryLine = data.kind === 'joined' ? '● you stepped into the square'
@@ -77,7 +74,7 @@ export function renderJoinResult(data, owner) {
77
74
  function isGestureOnly(body) {
78
75
  return /\*[^*]*\*/.test(body) && body.replace(/\*[^*]*\*/g, '').trim() === '';
79
76
  }
80
- export function renderExpression(squarePath, data, owner) {
77
+ export function renderExpression(squarePath, data, owner, surface = 'cli') {
81
78
  const projection = data.observation;
82
79
  if (projection === undefined)
83
80
  return undefined;
@@ -87,7 +84,7 @@ export function renderExpression(squarePath, data, owner) {
87
84
  'Unread context · preview only, not consumed',
88
85
  ...context.activities.map(item => `· ${participantIdentity(item.actor)} · ${item.id}\n ${item.preview}${item.truncated ? '… (truncated)' : ''}`),
89
86
  ...(context.remaining === 0 ? [] : [`· ${context.remaining} more unread activities`]),
90
- `${participantCommandPrefix(projection.location, name, owner)} perceive`,
87
+ surface === 'tool' ? formatToolAction('perceive', { location: displayPath(projection.location), as: name }) : `${participantCommandPrefix(projection.location, name, owner)} perceive`,
91
88
  ].join('\n');
92
89
  const ownCount = projection.ownActivityCount;
93
90
  const capCount = projection.hardCap === null ? '' : ` · ${ownCount}/${projection.hardCap}`;
@@ -104,17 +101,17 @@ export function renderExpression(squarePath, data, owner) {
104
101
  return withPathOutput(projection.location ?? squarePath, confirmation + (data.hint ? `\n${style('dim', `· ${data.hint}`)}` : '') + replyNotice + unmatchedNotice + pending, { participantCount: projection.standingCount });
105
102
  }
106
103
  function continuationCommand(squarePath, name, options, owner) {
107
- const args = ['--now'];
104
+ const args = [];
108
105
  if (options.from?.length)
109
106
  args.push('--from', options.from.join(','));
110
107
  if (options.mention)
111
108
  args.push('--mention');
112
109
  if (options.limit !== undefined)
113
110
  args.push('--limit', String(options.limit));
114
- return `${participantCommandPrefix(squarePath, name, owner)} perceive ${args.join(' ')}`;
111
+ return [participantCommandPrefix(squarePath, name, owner), 'perceive', ...args].join(' ');
115
112
  }
116
113
  /** Render settled perceive facts; no selection, terminal decisions, receipts or wait machinery. */
117
- export function renderPerceiveOutcome(squarePath, data, options = {}, owner) {
114
+ export function renderPerceiveOutcome(squarePath, data, options = {}, owner, surface = 'cli') {
118
115
  const projection = data.observation;
119
116
  if (projection === undefined)
120
117
  return { stdout: '', exitCode: 0 };
@@ -123,7 +120,7 @@ export function renderPerceiveOutcome(squarePath, data, options = {}, owner) {
123
120
  const stop = data.reason === 'drained' && data.activities.length > 0 ? undefined : data.reason;
124
121
  const header = { participantCount: projection.standingCount };
125
122
  const statusOptions = {
126
- squarePath: location, name, owner, presence: projection.presence,
123
+ squarePath: location, name, owner, surface, presence: projection.presence,
127
124
  showPerceiveHint: !projection.deliveryAvailable,
128
125
  ownActivityCount: projection.ownActivityCount, hardCap: projection.hardCap,
129
126
  };
@@ -138,27 +135,27 @@ export function renderPerceiveOutcome(squarePath, data, options = {}, owner) {
138
135
  now: projection.now, perception: activity.perception, mention: options.mention ? name : undefined,
139
136
  })).filter(Boolean).join('\n\n');
140
137
  const remaining = data.remaining === 0 ? ''
141
- : `· ${data.remaining} matching ${data.remaining === 1 ? 'activity remains' : 'activities remain'}\n${continuationCommand(location, name, options, owner)}`;
138
+ : `· ${data.remaining} matching ${data.remaining === 1 ? 'activity remains' : 'activities remain'}\n${surface === 'tool' ? formatToolAction('perceive', { location: displayPath(location), as: name, ...(options.from?.length ? { from: options.from } : {}), ...(options.mention ? { mention: true } : {}), ...(options.limit !== undefined ? { limit: options.limit } : {}) }) : continuationCommand(location, name, options, owner)}`;
142
139
  const terminal = stop === undefined ? '' : renderWatchStatus({
143
140
  ...statusOptions, status: stop,
144
141
  ...(stop === 'idle' && options.idle !== undefined ? { idleMs: options.idle } : {}),
145
142
  });
146
143
  const fallback = stop === undefined && !projection.deliveryAvailable && data.remaining === 0
147
- ? `${participantCommandPrefix(location, name, owner)} perceive --idle 30m\n stay available for new activity` : '';
144
+ ? `${surface === 'tool' ? formatToolAction('perceive', { location: displayPath(location), as: name, idle: '30m' }) : `${participantCommandPrefix(location, name, owner)} perceive --idle 30m`}\n stay available for new activity` : '';
148
145
  const hint = data.hint === undefined ? '' : style('dim', `· ${data.hint}`);
149
146
  return {
150
147
  stdout: withPathOutput(location, [terminal, rendered, hint, remaining, fallback].filter(Boolean).join('\n\n'), header),
151
148
  exitCode: stop === 'capped' ? 1 : 0,
152
149
  };
153
150
  }
154
- function renderHistoryProjection(projection, visible, noTruncate, squarePath) {
151
+ function renderHistoryProjection(projection, visible, full, squarePath, surface) {
155
152
  const shown = visible;
156
- const preview = noTruncate || shown.length <= 1 ? undefined : 200;
153
+ const preview = full || shown.length <= 1 ? undefined : 200;
157
154
  const chunks = [];
158
155
  // Presence anchors collect into one closing line instead of interrupting the page.
159
156
  const anchored = [];
160
157
  for (const activity of shown) {
161
- const rendered = renderEventCli(activity, { preview });
158
+ const rendered = renderEventCli(activity, { now: projection.now, preview });
162
159
  if (rendered !== '')
163
160
  chunks.push(rendered);
164
161
  for (const participant of projection.presenceAnchors[activity.index] ?? []) {
@@ -169,13 +166,14 @@ function renderHistoryProjection(projection, visible, noTruncate, squarePath) {
169
166
  if (chunks.length === 0)
170
167
  return '○ no activity matches this history query';
171
168
  if (preview !== undefined && shown.some((activity) => activity.kind === 'say' && (activity.body?.length ?? 0) > preview)) {
172
- chunks.push(`${commandPrefix(squarePath)} history --at ${shown.map((activity) => formatActivityId(activity.index)).join(',')} --no-truncate`);
169
+ const ids = shown.map((activity) => formatActivityId(activity.index));
170
+ chunks.push(surface === 'tool' ? formatToolAction('history', { location: displayPath(squarePath), at: ids, full: true }) : `${commandPrefix(squarePath)} history --at ${ids.join(',')} --full`);
173
171
  }
174
172
  if (anchored.length > 0)
175
173
  chunks.push(renderPresenceAnchor(anchored));
176
174
  return chunks.join('\n\n');
177
175
  }
178
- function historyContinuationCommand(squarePath, args) {
176
+ export function historyContinuationCommand(squarePath, args) {
179
177
  return `${commandPrefix(squarePath)} history ${args.map((arg) => arg.startsWith('-') || /^act\/\d+$/.test(arg) || /^\d+$/.test(arg) ? arg : quoteShell(arg)).join(' ')}`;
180
178
  }
181
179
  /** The same query spelled as CLI arguments, for a continuation someone can actually run. */
@@ -187,14 +185,15 @@ function historyQueryArgs(query) {
187
185
  args.push('--since', formatTimestamp(query.since));
188
186
  if (query.at?.length)
189
187
  args.push('--at', query.at.join(','));
190
- if (query.contextBefore !== undefined)
191
- args.push('-B', String(query.contextBefore));
192
- if (query.contextAfter !== undefined)
193
- args.push('-A', String(query.contextAfter));
188
+ if (query.context !== undefined)
189
+ args.push('-C', String(query.context));
194
190
  if (query.mention !== undefined)
195
191
  args.push('--mention', query.mention);
196
- if (query.search !== undefined)
197
- args.push(query.search.mode === 'regex' ? '--grep' : '--fixed', query.search.text);
192
+ if (query.search !== undefined) {
193
+ args.push('--search', query.search.text);
194
+ if (query.search.mode === 'regex')
195
+ args.push('--regex');
196
+ }
198
197
  if (query.order !== undefined)
199
198
  args.push('--order', query.order);
200
199
  return args;
@@ -204,44 +203,17 @@ export function renderHistoryResult(squarePath, data, options = {}) {
204
203
  if (projection === undefined)
205
204
  return undefined;
206
205
  const query = options.query ?? {};
207
- const noTruncate = options.noTruncate === true;
206
+ const full = options.full === true;
208
207
  const search = query.search;
209
208
  const output = search === undefined
210
- ? renderHistoryProjection(projection, projection.visibleActivities, noTruncate, squarePath)
211
- : renderGrepActivitiesView(projection.visibleActivities, projection.totalMatches, noTruncate, squarePath, search.text, search.mode === 'literal', () => 'full');
209
+ ? renderHistoryProjection(projection, projection.visibleActivities, full, squarePath, options.surface ?? 'cli')
210
+ : renderGrepActivitiesView(projection.visibleActivities, projection.totalMatches, full, squarePath, search.text, search.mode === 'literal', () => 'full', options.surface);
212
211
  const next = projection.continuation;
213
- const base = options.continuationArgs ?? historyQueryArgs(query);
214
- const continuation = next === undefined ? '' : `\n\n${historyContinuationCommand(squarePath, [...base, `--${next.direction}`, next.cursor, '--limit', String(next.limit)])}`;
212
+ const base = options.continuationArgs ?? [...historyQueryArgs(query), ...(full ? ['--full'] : [])];
213
+ const continuation = next === undefined ? '' : `\n\n${options.surface === 'tool' ? formatToolAction('history', { location: displayPath(squarePath), ...(query.from?.length ? { from: query.from } : {}), ...(query.since !== undefined ? { since: formatTimestamp(query.since) } : {}), ...(query.at?.length ? { at: query.at } : {}), ...(query.context !== undefined ? { context: query.context } : {}), ...(query.mention !== undefined ? { mention: query.mention } : {}), ...(query.search !== undefined ? { search: query.search.text, ...(query.search.mode === 'regex' ? { regex: true } : {}) } : {}), ...(query.order !== undefined ? { order: query.order } : {}), ...(full ? { full: true } : {}), [next.direction]: next.cursor, limit: next.limit }) : historyContinuationCommand(squarePath, [...base, `--${next.direction}`, next.cursor, '--limit', String(next.limit)])}`;
215
214
  return withPathOutput(squarePath, output + continuation, { participantCount: projection.standingCount });
216
215
  }
217
- function participantsLimitCommand(squarePath, limit) {
218
- return `${commandPrefix(squarePath)} participants --limit ${limit}`;
219
- }
220
- export function renderParticipantsResult(squarePath, data, limit = PARTICIPANTS_DEFAULT_LIMIT) {
221
- const projection = data.observation;
222
- if (projection === undefined)
223
- return undefined;
224
- const now = projection.now;
225
- const participants = data.participants;
226
- const lines = participants.slice(0, limit).map((participant) => {
227
- const glyph = participant.presence === 'done' ? '○' : participant.presence === 'watching' ? '◎' : participant.activityCount > 0 && projection.classificationByName[participant.name] === 'here' ? '●' : '○';
228
- const state = participant.presence === 'watching' ? 'perceiving' : participant.presence === 'never-joined' ? 'not joined' : participant.presence;
229
- const last = participant.lastActiveAt === undefined ? '—' : formatRelativeTime(participant.lastActiveAt, now);
230
- return ` ${glyph} ${participant.name}${style('dim', ` · ${state} · ${participant.activityCount} ${participant.activityCount === 1 ? 'activity' : 'activities'} · ${last}`)}`;
231
- });
232
- const tail = participants.length <= limit
233
- ? []
234
- : [
235
- style('dim', ` · ${lines.length} of ${participants.length} participants shown`),
236
- ...(participants.length <= PARTICIPANTS_MAX_LIMIT ? [
237
- `${participantsLimitCommand(squarePath, participants.length)}`,
238
- ] : []),
239
- ];
240
- return withPathOutput(squarePath, ['participants', ...lines, ...tail].join('\n'), {
241
- participantCount: projection.standingCount,
242
- });
243
- }
244
- export function renderStatusResult(squarePath, data) {
216
+ export function renderStatusResult(squarePath, data, options = {}) {
245
217
  const presentation = data.observation;
246
218
  if (presentation === undefined)
247
219
  return undefined;
@@ -278,18 +250,11 @@ export function renderStatusResult(squarePath, data) {
278
250
  people.push(...away.slice(0, STATUS_PARTICIPANT_PREVIEW_LIMIT).map(participant => ` ○ ${participantIdentity(participant.name)}${style('dim', ' · away')}`));
279
251
  if (away.length > STATUS_PARTICIPANT_PREVIEW_LIMIT)
280
252
  people.push(` ○ … ${away.length - STATUS_PARTICIPANT_PREVIEW_LIMIT} more away`);
281
- const unshown = Math.max(0, orderedHere.length - STATUS_PARTICIPANT_PREVIEW_LIMIT)
282
- + Math.max(0, away.length - STATUS_PARTICIPANT_PREVIEW_LIMIT) + lingering;
283
253
  if (orderedHere.length > STATUS_PARTICIPANT_PREVIEW_LIMIT) {
284
254
  people.push(` ○ … ${orderedHere.length - STATUS_PARTICIPANT_PREVIEW_LIMIT} more here`);
285
255
  }
286
256
  if (lingering > 0)
287
257
  people.push(` ○ … ${lingering} lingering`);
288
- if (unshown > 0) {
289
- people.push(`${presentation.knownParticipantCount <= PARTICIPANTS_MAX_LIMIT
290
- ? participantsLimitCommand(squarePath, presentation.knownParticipantCount)
291
- : `${commandPrefix(squarePath)} participants`}`);
292
- }
293
258
  }
294
259
  const cap = data.hardCap === null ? 'unlimited' : `${data.hardCap} each`;
295
260
  const visible = data.latestActivity === undefined
@@ -312,7 +277,7 @@ export function renderStatusResult(squarePath, data) {
312
277
  if (presentation.joinCount > 3)
313
278
  latest.push(style('dim', ` → … ${presentation.joinCount - 3} more stepped in`));
314
279
  if (visible.bodyClipped && data.latestActivity !== undefined) {
315
- latest.push(`${commandPrefix(squarePath)} history --at ${data.latestActivity.id} -C 2 --no-truncate`);
280
+ latest.push(options.surface === 'tool' ? formatToolAction('history', { location: displayPath(squarePath), at: [data.latestActivity.id], context: 2, full: true }) : `${commandPrefix(squarePath)} history --at ${data.latestActivity.id} -C 2 --full`);
316
281
  }
317
282
  const counts = [
318
283
  `${orderedHere.length} here`,
@@ -332,15 +297,6 @@ export function renderStatusResult(squarePath, data) {
332
297
  ].join('\n');
333
298
  return withPathOutput(squarePath, output, { participantCount: presentation.standingCount });
334
299
  }
335
- export function renderListeningResult(squarePath, data) {
336
- const projection = data.observation;
337
- if (projection?.participant === undefined)
338
- return undefined;
339
- const body = data.listening.length === 0
340
- ? `○ ${participantIdentity(projection.participant)} is not turned toward anyone`
341
- : ['listening', ...data.listening.map((target) => ` · ${participantIdentity(target)}`)].join('\n');
342
- return withPathOutput(squarePath, body, { participantCount: projection.standingCount });
343
- }
344
300
  export function renderListenerChange(squarePath, verb, data) {
345
301
  const projection = data.observation;
346
302
  if (projection?.participant === undefined)
@@ -366,34 +322,81 @@ export function renderLifecycleResult(squarePath, data) {
366
322
  participantCount: projection.standingCount,
367
323
  });
368
324
  }
369
- /** Refusal lines with the same recovery commands the CLI prints. */
325
+ /** Express gates are physics, not mistakes: one scene for every surface, then that surface's next action. */
326
+ export function isExpressGate(code) {
327
+ return code === 'behind' || code === 'capped' || code === 'throttled' || code === 'bell_quota';
328
+ }
329
+ function expressGateLines(failure, options) {
330
+ if (!isExpressGate(failure.code))
331
+ return undefined;
332
+ const { squarePath, name, owner } = options;
333
+ const facts = failure.facts;
334
+ const tool = options.surface === 'tool';
335
+ const scene = failure.code === 'behind'
336
+ ? ['✕ your words stay unsent — someone spoke while you were away', ...renderUnreadSummary(facts?.activitySummaries ?? [])]
337
+ : failure.code === 'capped'
338
+ ? [`✕ you’ve reached your speaking cap${facts?.spoken !== undefined && facts.hardCap !== undefined ? ` — ${facts.spoken}/${facts.hardCap} spoken` : ''}`]
339
+ : failure.code === 'throttled'
340
+ ? ['✕ the square is packed — shoulder to shoulder', ` · a lull opens in ${formatDuration(facts?.retryAfterMs ?? 1)}`]
341
+ : ['✕ the bell stays quiet for now', ` · you can ring it again in ${formatDuration(facts?.retryAfterMs ?? 1)}`];
342
+ const lines = [...scene, ...(options.draftPath === undefined ? [] : [style('dim', `· draft kept: ${options.draftPath}`)])];
343
+ if (squarePath === undefined || name === undefined)
344
+ return lines;
345
+ const action = (verb) => tool
346
+ ? formatToolAction(verb, { location: displayPath(squarePath), as: name })
347
+ : `${participantCommandPrefix(squarePath, name, owner)} ${verb}`;
348
+ if (failure.code === 'behind')
349
+ lines.push(...(tool ? ['Take it in, then express again:', action('perceive')] : [action('perceive'), ' · take it in, then retry:']));
350
+ if (failure.code === 'capped')
351
+ lines.push(' · your words stay unsent; done only steps out', action('done'));
352
+ if (failure.code === 'bell_quota' && !tool)
353
+ lines.push(' · retry when the bell is ready:');
354
+ if (failure.code !== 'capped' && options.retry !== undefined)
355
+ lines.push(options.retry);
356
+ return lines;
357
+ }
358
+ /** Refusal lines with surface-specific recovery actions. */
370
359
  export function renderFailureLines(failure, options = {}) {
371
360
  const { squarePath, name, owner } = options;
372
361
  const facts = failure.facts;
373
362
  const reason = facts?.reason;
363
+ const gate = expressGateLines(failure, options);
364
+ if (gate !== undefined)
365
+ return gate;
374
366
  const marked = reason === 'never_joined' || reason === 'not_standing';
375
367
  const lines = marked ? failure.message.split('\n').map((line) => `✕ ${line}`) : [failure.message];
376
- if (reason === 'needs_seat' && squarePath !== undefined && name !== undefined) {
368
+ if (reason === 'bell_mention_conflict' || reason === 'invalid_reply') {
369
+ lines[0] = reason === 'bell_mention_conflict' ? '✕ a bell cannot be combined with a mention'
370
+ : `Invalid ${options.surface === 'tool' ? 'reply' : '--reply'}: ${facts?.activityExists === false ? `${facts.activityId ?? options.reply ?? 'that activity'} is not in this square's history` : 'expected an activity id like act/12'}`;
371
+ if (squarePath !== undefined)
372
+ lines.push(options.surface === 'tool'
373
+ ? formatToolAction(reason === 'invalid_reply' ? 'history' : 'status', { location: displayPath(squarePath), ...(reason === 'invalid_reply' ? { limit: 10 } : {}) })
374
+ : reason === 'invalid_reply' ? `${commandPrefix(squarePath)} history --limit 10` : `${commandPrefix(squarePath)} status`);
375
+ }
376
+ else if (reason === 'needs_seat' && squarePath !== undefined && name !== undefined) {
377
377
  lines.splice(0, 1, `✕ ${failure.message}`);
378
- lines.push(joinRecoveryCommand(squarePath, name, owner));
378
+ lines.push(options.surface === 'tool' ? formatToolAction('join', { location: displayPath(squarePath), as: name }) : joinRecoveryCommand(squarePath, name, owner));
379
379
  }
380
380
  else if (squarePath !== undefined && name !== undefined && failure.code === 'already_joined') {
381
381
  lines.splice(0, 1, `✕ ${participantIdentity(name)} shoos you out of the square`, ` · ${participantIdentity(name)} already stands here — another session holds the name`);
382
- lines.push(...takeoverRecoveryLines(squarePath, name, owner));
382
+ lines.push(...(options.surface === 'tool' ? ['If this is your name to reclaim, takeover banishes the one standing here so you can step in:', formatToolAction('join', { location: displayPath(squarePath), as: name, takeover: true })] : takeoverRecoveryLines(squarePath, name, owner)));
383
+ }
384
+ else if (squarePath !== undefined && name !== undefined && (failure.code === 'unknown_participant' || failure.code === 'not_joined') && reason !== 'not_standing') {
385
+ lines.push(options.surface === 'tool' ? formatToolAction('join', { location: displayPath(squarePath), as: name }) : joinRecoveryCommand(squarePath, name, owner));
383
386
  }
384
387
  else if (squarePath !== undefined && reason === 'never_joined') {
385
- lines.push(failure.operation?.subject !== 'filter' && name !== undefined
386
- ? joinRecoveryCommand(squarePath, name, owner) : participantsRecoveryCommand(squarePath));
388
+ lines.push(options.surface === 'tool' ? (failure.operation?.subject !== 'filter' && name !== undefined ? formatToolAction('join', { location: displayPath(squarePath), as: name }) : formatToolAction('status', { location: displayPath(squarePath) })) : failure.operation?.subject !== 'filter' && name !== undefined
389
+ ? joinRecoveryCommand(squarePath, name, owner) : `${commandPrefix(squarePath)} status`);
387
390
  }
388
391
  else if (squarePath !== undefined && reason === 'not_standing') {
389
- lines.push(participantsRecoveryCommand(squarePath));
392
+ lines.push(options.surface === 'tool' ? formatToolAction('status', { location: displayPath(squarePath) }) : `${commandPrefix(squarePath)} status`);
390
393
  }
391
394
  else if (squarePath !== undefined && reason === 'not_addressed' && facts?.activityId !== undefined) {
392
- lines.push(facts.activityExists === true
395
+ lines.push(options.surface === 'tool' ? (facts.activityExists === true ? formatToolAction('history', { location: displayPath(squarePath), at: [facts.activityId], context: 2 }) : formatToolAction('history', { location: displayPath(squarePath), limit: 10 })) : facts.activityExists === true
393
396
  ? `${commandPrefix(squarePath)} history --at ${facts.activityId} -C 2`
394
397
  : `${commandPrefix(squarePath)} history --limit 10`);
395
398
  }
396
- if (reason === 'invalid_done_body')
399
+ if (reason === 'invalid_done_body' && options.surface !== 'tool')
397
400
  lines.push('square done --help');
398
401
  return lines;
399
402
  }
@@ -1,6 +1,6 @@
1
1
  import { DeliveryError } from './errors.js';
2
- import type { OpenCodeConnectOptions, ClaudeConnectOptions, PiConnectOptions, PaseoConnectOptions, CodexConnectOptions, CodexAppServerConnectOptions, CodexCliConnectOptions } from './types.js';
3
- export type { OpenCodeConnectOptions, ClaudeConnectOptions, PiConnectOptions, PaseoConnectOptions, CodexConnectOptions, CodexAppServerConnectOptions, CodexCliConnectOptions } from './types.js';
2
+ import type { OpenCodeConnectOptions, PiConnectOptions, PaseoConnectOptions, CodexConnectOptions, CodexAppServerConnectOptions, CodexCliConnectOptions } from './types.js';
3
+ export type { OpenCodeConnectOptions, PiConnectOptions, PaseoConnectOptions, CodexConnectOptions, CodexAppServerConnectOptions, CodexCliConnectOptions } from './types.js';
4
4
  export interface SendOptions {
5
5
  readonly timeoutMs?: number;
6
6
  readonly signal?: AbortSignal;
@@ -10,7 +10,7 @@ export interface KeyedSendOptions extends SendOptions {
10
10
  }
11
11
  export interface Receipt {
12
12
  readonly id: string;
13
- readonly proof: 'written' | 'admitted' | 'observed';
13
+ readonly proof: 'admitted' | 'observed';
14
14
  }
15
15
  export interface Steerable<O = SendOptions> {
16
16
  steer(text: string, options?: O): Promise<Receipt>;
@@ -22,10 +22,6 @@ export type OpenCodeAgent = Steerable<KeyedSendOptions> & Queueable<KeyedSendOpt
22
22
  readonly harness: 'opencode';
23
23
  readonly sessionId: string;
24
24
  };
25
- export type ClaudeAgent = Steerable & {
26
- readonly harness: 'claude';
27
- readonly sessionId: string;
28
- };
29
25
  export type PiAgent = Steerable & Queueable & {
30
26
  readonly harness: 'pi';
31
27
  readonly sessionId: string;
@@ -47,9 +43,8 @@ export type CodexCliAgent = Queueable & {
47
43
  readonly sessionId: string;
48
44
  };
49
45
  export type CodexAgent = CodexAppServerAgent | CodexCliAgent;
50
- export type Agent = OpenCodeAgent | ClaudeAgent | PiAgent | PaseoAgent | CodexAgent;
46
+ export type Agent = OpenCodeAgent | PiAgent | PaseoAgent | CodexAgent;
51
47
  export declare function connect(options: OpenCodeConnectOptions): Promise<OpenCodeAgent>;
52
- export declare function connect(options: ClaudeConnectOptions): Promise<ClaudeAgent>;
53
48
  export declare function connect(options: PiConnectOptions): Promise<PiAgent>;
54
49
  export declare function connect(options: PaseoConnectOptions): Promise<PaseoAgent>;
55
50
  export declare function connect(options: CodexCliConnectOptions): Promise<CodexCliAgent>;
@@ -58,15 +58,6 @@ const harnesses = {
58
58
  return { coordinate: target.sessionId, send: (text, value) => sendText(target, text, value) };
59
59
  },
60
60
  },
61
- claude: {
62
- harness: 'claude', proof: 'written', accepted: 'written', coordinate: 'sessionId', keyed: false, queue: false,
63
- id: (result) => result.messageId,
64
- open: async (options) => {
65
- const { connectExisting, sendText } = await import('./claude.js');
66
- const target = await connectExisting(options);
67
- return { coordinate: target.sessionId, send: (text, value) => sendText(target, text, value) };
68
- },
69
- },
70
61
  pi: {
71
62
  harness: 'pi', proof: 'observed', accepted: 'observed', coordinate: 'sessionId', keyed: false, queue: true,
72
63
  id: (result) => result.inputId,
@@ -1,20 +1,12 @@
1
1
  import net from 'node:net';
2
2
  export interface LocalEndpoint {
3
- readonly kind: 'unix' | 'pipe';
4
- /** What the socket connects to. */
5
3
  readonly path: string;
6
- /** What the native receiver fingerprints; also the auth key file digest input. */
7
- readonly canonical: string;
8
4
  }
9
- /**
10
- * A local endpoint is either a Windows named pipe or an absolute Unix socket
11
- * path. There is no `stat().isSocket()` check: connecting is the existence check.
12
- */
5
+ /** An absolute Unix socket path; connecting is the existence check. */
13
6
  export declare function parseLocalEndpoint(raw: unknown): LocalEndpoint;
14
7
  export declare class LocalEndpointError extends Error {
15
8
  readonly code: 'unavailable' | 'aborted' | 'timeout' | 'transport';
16
- readonly maybeDelivered: boolean;
17
- constructor(code: LocalEndpointError['code'], maybeDelivered?: boolean);
9
+ constructor(code: LocalEndpointError['code']);
18
10
  }
19
11
  export interface LocalConnectScope {
20
12
  readonly signal?: AbortSignal;
@@ -22,5 +14,3 @@ export interface LocalConnectScope {
22
14
  }
23
15
  /** Connect with bounded waiting. A connect failure is `unavailable`: nothing was written. */
24
16
  export declare function connectLocal(endpoint: LocalEndpoint, scope?: LocalConnectScope): Promise<net.Socket>;
25
- /** LF-delimited JSON frames. Any failure here is past the uncertainty boundary. */
26
- export declare function writeFrames(socket: net.Socket, frames: object[]): Promise<void>;