@astrosheep/square 0.3.85 → 0.3.87

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 (148) hide show
  1. package/dist/artifact.d.ts +6 -1
  2. package/dist/artifact.js +79 -10
  3. package/dist/boundary-presentation.d.ts +5 -4
  4. package/dist/boundary-presentation.js +21 -25
  5. package/dist/catch-decisions.js +2 -1
  6. package/dist/claude-delivery-status.d.ts +8 -2
  7. package/dist/claude-delivery-status.js +26 -10
  8. package/dist/claude-delivery.d.ts +8 -4
  9. package/dist/claude-delivery.js +114 -104
  10. package/dist/claude-mcp-context.js +2 -2
  11. package/dist/claude-mod.js +119 -105
  12. package/dist/cli/context.d.ts +3 -1
  13. package/dist/cli/context.js +25 -1
  14. package/dist/cli/express-presentation.d.ts +3 -1
  15. package/dist/cli/express-presentation.js +15 -12
  16. package/dist/cli/mcp-server-command.js +2 -2
  17. package/dist/cli/observation-commands.js +2 -1
  18. package/dist/cli/registry.js +0 -2
  19. package/dist/cli/square-commands.d.ts +4 -2
  20. package/dist/cli/square-commands.js +21 -6
  21. package/dist/codex-hook.d.ts +3 -3
  22. package/dist/codex-hook.js +4 -4
  23. package/dist/codex-queue.d.ts +5 -6
  24. package/dist/codex-queue.js +9 -5
  25. package/dist/decisions.d.ts +3 -2
  26. package/dist/decisions.js +18 -6
  27. package/dist/delivery-health.d.ts +32 -2
  28. package/dist/delivery-health.js +108 -45
  29. package/dist/delivery-operations.d.ts +1 -2
  30. package/dist/delivery-operations.js +155 -196
  31. package/dist/delivery.d.ts +14 -23
  32. package/dist/delivery.js +48 -18
  33. package/dist/file-lock.d.ts +8 -1
  34. package/dist/file-lock.js +2 -0
  35. package/dist/help.js +12 -14
  36. package/dist/hints.js +2 -3
  37. package/dist/inbox.d.ts +35 -9
  38. package/dist/inbox.js +228 -144
  39. package/dist/index.d.ts +4 -4
  40. package/dist/index.js +1 -3
  41. package/dist/list.js +9 -4
  42. package/dist/mcp.js +12 -1
  43. package/dist/model.d.ts +39 -5
  44. package/dist/model.js +84 -0
  45. package/dist/notifications.d.ts +8 -6
  46. package/dist/notifications.js +56 -143
  47. package/dist/operation-presentation.js +12 -14
  48. package/dist/participant-identity.d.ts +0 -3
  49. package/dist/participant-identity.js +0 -31
  50. package/dist/paseo-delivery.d.ts +5 -6
  51. package/dist/paseo-delivery.js +13 -8
  52. package/dist/paseo.d.ts +1 -1
  53. package/dist/paseo.js +1 -1
  54. package/dist/plugins/opencode/delivery.d.ts +8 -3
  55. package/dist/plugins/opencode/delivery.js +187 -132
  56. package/dist/plugins/opencode/index.js +12 -38
  57. package/dist/ports.d.ts +112 -40
  58. package/dist/presentation-operations.d.ts +1 -1
  59. package/dist/presentation-operations.js +51 -35
  60. package/dist/presentation.d.ts +0 -1
  61. package/dist/presentation.js +18 -13
  62. package/dist/seats.d.ts +7 -0
  63. package/dist/seats.js +27 -0
  64. package/dist/session-lifecycle.d.ts +2 -2
  65. package/dist/session-lifecycle.js +10 -12
  66. package/dist/sqlite-square.d.ts +53 -0
  67. package/dist/sqlite-square.js +602 -0
  68. package/dist/square-api.d.ts +12 -41
  69. package/dist/square-api.js +26 -474
  70. package/dist/square-backend.d.ts +13 -0
  71. package/dist/square-core.d.ts +3 -2
  72. package/dist/square-domain.d.ts +12 -5
  73. package/dist/square-domain.js +163 -7
  74. package/dist/square-facade.d.ts +16 -10
  75. package/dist/square-index.d.ts +24 -0
  76. package/dist/square-index.js +131 -0
  77. package/dist/square-legacy-framing.d.ts +9 -0
  78. package/dist/square-legacy-framing.js +56 -0
  79. package/dist/square-location.d.ts +15 -0
  80. package/dist/square-location.js +76 -0
  81. package/dist/square-migration-host-ledger.d.ts +26 -0
  82. package/dist/square-migration-host-ledger.js +168 -0
  83. package/dist/square-migration.d.ts +5 -5
  84. package/dist/square-migration.js +373 -71
  85. package/dist/square-sqlite.d.ts +9 -9
  86. package/dist/square-sqlite.js +329 -79
  87. package/dist/square-stdio.d.ts +22 -0
  88. package/dist/square-stdio.js +222 -0
  89. package/dist/square-tools.js +30 -3
  90. package/dist/stdio-server.d.ts +3 -0
  91. package/dist/stdio-server.js +192 -0
  92. package/dist/stream.d.ts +3 -2
  93. package/dist/stream.js +9 -10
  94. package/dist/views.d.ts +9 -14
  95. package/dist/views.js +50 -70
  96. package/dist/wakes.d.ts +6 -5
  97. package/dist/wakes.js +20 -17
  98. package/guides/application-facade-delivery-evidence.md +1 -1
  99. package/guides/backends.md +180 -0
  100. package/guides/claude-native.md +9 -9
  101. package/guides/mcp.md +1 -1
  102. package/guides/migration.md +107 -67
  103. package/guides/row-storage-design.md +1 -1
  104. package/guides/square-api.md +69 -30
  105. package/package.json +1 -1
  106. package/plugins/claude/.claude-plugin/plugin.json +1 -1
  107. package/plugins/claude/hooks/register.js +1 -1
  108. package/plugins/claude/skills/square/SKILL.md +7 -7
  109. package/plugins/claude/types.d.ts +1 -1
  110. package/plugins/codex/.codex-plugin/plugin.json +1 -1
  111. package/plugins/opencode/delivery.ts +170 -118
  112. package/plugins/opencode/index.ts +12 -30
  113. package/plugins/pi/square-pi.js +58 -20
  114. package/skills/square/SKILL.md +7 -7
  115. package/dist/cli/ledger-command.d.ts +0 -12
  116. package/dist/cli/ledger-command.js +0 -72
  117. package/dist/host-ledger-file-adapter.d.ts +0 -47
  118. package/dist/host-ledger-file-adapter.js +0 -384
  119. package/dist/host-ledger-migration.d.ts +0 -27
  120. package/dist/host-ledger-migration.js +0 -264
  121. package/dist/host-ledger-root.d.ts +0 -2
  122. package/dist/host-ledger-root.js +0 -9
  123. package/dist/host-ledger-sqlite.d.ts +0 -65
  124. package/dist/host-ledger-sqlite.js +0 -368
  125. package/dist/host-ledger.d.ts +0 -195
  126. package/dist/ledger-maintenance.d.ts +0 -30
  127. package/dist/ledger-maintenance.js +0 -110
  128. package/dist/open-square.d.ts +0 -36
  129. package/dist/open-square.js +0 -81
  130. package/dist/participant-host.d.ts +0 -22
  131. package/dist/participant-host.js +0 -92
  132. package/dist/registry.d.ts +0 -55
  133. package/dist/registry.js +0 -123
  134. package/dist/routes.d.ts +0 -39
  135. package/dist/routes.js +0 -99
  136. package/dist/square-actions.d.ts +0 -56
  137. package/dist/square-actions.js +0 -353
  138. package/dist/square-projections.d.ts +0 -31
  139. package/dist/square-projections.js +0 -64
  140. package/dist/wake-attempts.d.ts +0 -10
  141. package/dist/wake-attempts.js +0 -39
  142. package/dist/wake-eligibility.d.ts +0 -53
  143. package/dist/wake-eligibility.js +0 -123
  144. package/dist/wake-evidence.d.ts +0 -33
  145. package/dist/wake-evidence.js +0 -32
  146. package/guides/host-ledger.md +0 -58
  147. package/guides/indexed-host-ledger-performance.md +0 -39
  148. /package/dist/{host-ledger.js → square-backend.js} +0 -0
package/dist/model.js CHANGED
@@ -1,6 +1,90 @@
1
1
  // Shared model and constants for Square.
2
2
  export { formatActivityId, parseActivityId } from './square-core.js';
3
3
  export const WAKE_ROUTE_KINDS = ['opencode-server', 'codex-queue', 'claude-native', 'paseo'];
4
+ export const ACTIVITY_METADATA_MAX_BYTES = 16 * 1024;
5
+ export const ACTIVITY_METADATA_MAX_DEPTH = 256;
6
+ /** Validate the bounded, lossless JSON object carried by a say. */
7
+ export function validateActivityMetadata(value) {
8
+ if (value === undefined)
9
+ return undefined;
10
+ if (value === null || typeof value !== 'object' || Array.isArray(value))
11
+ throw new SquareError('invalid_args', 'Activity metadata must be a non-null JSON object.');
12
+ const visiting = new Set();
13
+ const stack = [{ value, depth: 0 }];
14
+ while (stack.length > 0) {
15
+ const frame = stack.pop();
16
+ if (frame.exit !== undefined) {
17
+ visiting.delete(frame.exit);
18
+ continue;
19
+ }
20
+ const item = frame.value;
21
+ if (item === null || typeof item === 'boolean' || typeof item === 'string')
22
+ continue;
23
+ if (typeof item === 'number') {
24
+ if (!Number.isFinite(item))
25
+ throw new SquareError('invalid_args', 'Activity metadata must contain only finite JSON values.');
26
+ continue;
27
+ }
28
+ if (typeof item !== 'object')
29
+ throw new SquareError('invalid_args', 'Activity metadata must contain only JSON values.');
30
+ if (frame.depth > ACTIVITY_METADATA_MAX_DEPTH)
31
+ throw new SquareError('invalid_args', `Activity metadata nesting cannot exceed ${ACTIVITY_METADATA_MAX_DEPTH} levels.`);
32
+ if (visiting.has(item))
33
+ throw new SquareError('invalid_args', 'Activity metadata cannot contain a cycle.');
34
+ visiting.add(item);
35
+ stack.push({ value: item, depth: frame.depth, exit: item });
36
+ if (Array.isArray(item)) {
37
+ if (Object.getOwnPropertySymbols(item).length > 0 || Object.keys(item).length !== item.length)
38
+ throw new SquareError('invalid_args', 'Activity metadata arrays must be dense JSON arrays.');
39
+ const names = Object.getOwnPropertyNames(item);
40
+ if (names.some(name => name !== 'length' && (!/^(0|[1-9]\d*)$/.test(name) || Number(name) >= item.length)))
41
+ throw new SquareError('invalid_args', 'Activity metadata arrays must contain only indexed values.');
42
+ for (const key of Object.keys(item).reverse()) {
43
+ const descriptor = Object.getOwnPropertyDescriptor(item, key);
44
+ if (descriptor === undefined || !('value' in descriptor))
45
+ throw new SquareError('invalid_args', 'Activity metadata cannot contain accessors.');
46
+ stack.push({ value: descriptor.value, depth: frame.depth + 1 });
47
+ }
48
+ continue;
49
+ }
50
+ const prototype = Object.getPrototypeOf(item);
51
+ if (prototype !== Object.prototype && prototype !== null)
52
+ throw new SquareError('invalid_args', 'Activity metadata must contain only plain JSON objects.');
53
+ if (Object.getOwnPropertySymbols(item).length > 0)
54
+ throw new SquareError('invalid_args', 'Activity metadata cannot contain symbol keys.');
55
+ const names = Object.getOwnPropertyNames(item);
56
+ if (names.some(name => !Object.prototype.propertyIsEnumerable.call(item, name)))
57
+ throw new SquareError('invalid_args', 'Activity metadata cannot contain non-enumerable properties.');
58
+ for (const name of names.reverse()) {
59
+ const descriptor = Object.getOwnPropertyDescriptor(item, name);
60
+ if (!('value' in descriptor))
61
+ throw new SquareError('invalid_args', 'Activity metadata cannot contain accessors.');
62
+ stack.push({ value: descriptor.value, depth: frame.depth + 1 });
63
+ }
64
+ }
65
+ let serialized;
66
+ try {
67
+ serialized = JSON.stringify(value);
68
+ }
69
+ catch {
70
+ throw new SquareError('invalid_args', 'Activity metadata must contain only lossless JSON values.');
71
+ }
72
+ if (serialized === undefined || Buffer.byteLength(serialized, 'utf8') > ACTIVITY_METADATA_MAX_BYTES)
73
+ throw new SquareError('invalid_args', `Activity metadata cannot exceed ${ACTIVITY_METADATA_MAX_BYTES} UTF-8 bytes.`);
74
+ return value;
75
+ }
76
+ export function activityMetadataHasContent(metadata) {
77
+ return metadata !== undefined && Object.keys(metadata).length > 0;
78
+ }
79
+ export function sameBody(left, right) {
80
+ return left.channel === right.channel && left.session === right.session && left.agent === right.agent;
81
+ }
82
+ export function validateBody(body) {
83
+ if (!body || typeof body.channel !== 'string' || !body.channel || typeof body.session !== 'string' || !body.session
84
+ || (body.agent !== undefined && (typeof body.agent !== 'string' || !body.agent))) {
85
+ throw new SquareError('invalid_args', 'A participant write requires an explicit channel and session body.');
86
+ }
87
+ }
4
88
  export function isWakeRouteKind(value) {
5
89
  return typeof value === 'string' && WAKE_ROUTE_KINDS.includes(value);
6
90
  }
@@ -1,19 +1,21 @@
1
- import { type WakeAdapter } from './delivery.js';
1
+ import { type WakeRouteKind } from './model.js';
2
2
  import type { WakeTransportPort } from './ports.js';
3
3
  export type { PlannedNotification } from './delivery.js';
4
4
  export declare const PRIVILEGED_HOOK_BUDGET_MS = 3000;
5
5
  export declare function wakeGraceMs(env?: NodeJS.ProcessEnv): number;
6
+ export type WakeTransports = Readonly<Partial<Record<WakeRouteKind, WakeTransportPort>>>;
6
7
  interface ProcessNotificationOptions {
7
- adapters?: WakeAdapter[];
8
+ transports?: WakeTransports;
8
9
  env?: NodeJS.ProcessEnv;
9
10
  now?: () => number;
10
11
  }
11
- export declare function defaultWakeAdapters(env?: NodeJS.ProcessEnv): Promise<WakeAdapter[]>;
12
- export declare function createDefaultWakeTransport(hostLedger: import('./host-ledger.js').HostLedgerPort, clock: () => number, env?: NodeJS.ProcessEnv, suppliedAdapters?: readonly WakeAdapter[]): Promise<WakeTransportPort>;
13
- export declare function createWakeTransport(adapters: readonly WakeAdapter[], hostLedger: import('./host-ledger.js').HostLedgerPort, clock: () => number, nativePorts?: Readonly<Partial<Record<import('./model.js').WakeRouteKind, WakeTransportPort>>>): WakeTransportPort;
12
+ /** Concrete transports use wall time; semantic state time belongs to Square operations. */
13
+ export declare function createDefaultWakeTransport(env?: NodeJS.ProcessEnv, suppliedTransports?: WakeTransports): Promise<WakeTransportPort>;
14
+ /** Route selection only: all implementations receive the same request and final gate. */
15
+ export declare function createWakeTransport(transports: WakeTransports): WakeTransportPort;
14
16
  export declare function processActNotificationsOnce(squarePath: string, actIndex: number, opts?: ProcessNotificationOptions): Promise<import("./ports.js").DeliveryResult>;
15
17
  /** Privileged hook fallback: sweep indexed squares plus the current cwd's local squares within one boundary budget. */
16
- export declare function sweepPrivilegedPending(cwd: string, env?: NodeJS.ProcessEnv, suppliedAdapters?: WakeAdapter[], deadline?: number, signal?: AbortSignal): Promise<void>;
18
+ export declare function sweepPrivilegedPending(cwd: string, env?: NodeJS.ProcessEnv, suppliedTransports?: WakeTransports, deadline?: number, signal?: AbortSignal): Promise<void>;
17
19
  export interface SweepPendingNotificationsOptions {
18
20
  env?: NodeJS.ProcessEnv;
19
21
  now?: number;
@@ -1,16 +1,9 @@
1
- import { hostLedgerRoot } from './host-ledger-root.js';
2
1
  import path from 'node:path';
3
2
  import fs from 'node:fs';
4
- import { SquareError } from './model.js';
5
- import { renderAttentionPreview, renderSquareReminder } from './attention-presentation.js';
6
- import { openSquare } from './open-square.js';
7
- import { closeOpenSquare } from './open-square.js';
3
+ import { indexedSquareEntries } from './square-index.js';
4
+ import { InternalSquareError, SquareError } from './model.js';
5
+ import { createArtifactPort } from './artifact.js';
8
6
  import { deliverPending, sweepPending } from './delivery-operations.js';
9
- import { projectWakeEvidence } from './wake-eligibility.js';
10
- import { pendingHead } from './delivery.js';
11
- import { parseActivityId } from './square-core.js';
12
- import { canonicalPath } from './canonical-path.js';
13
- import { createHostLedgerPort } from './host-ledger-file-adapter.js';
14
7
  export const PRIVILEGED_HOOK_BUDGET_MS = 3000;
15
8
  export function wakeGraceMs(env = process.env) {
16
9
  const value = Number.parseInt(env.SQUARE_NOTIFY_DELIVERY_WAIT_MS ?? '5000', 10);
@@ -19,161 +12,75 @@ export function wakeGraceMs(env = process.env) {
19
12
  }
20
13
  return value;
21
14
  }
22
- export async function defaultWakeAdapters(env = process.env) {
23
- const adapters = [];
15
+ /** Concrete transports use wall time; semantic state time belongs to Square operations. */
16
+ export async function createDefaultWakeTransport(env = process.env, suppliedTransports) {
17
+ if (suppliedTransports !== undefined)
18
+ return createWakeTransport(suppliedTransports);
19
+ const transports = {};
24
20
  try {
25
- const { CodexQueueAdapter } = await import('./codex-queue.js');
26
- adapters.push(new CodexQueueAdapter({ env }));
21
+ const { CodexQueueTransport } = await import('./codex-queue.js');
22
+ transports['codex-queue'] = new CodexQueueTransport({ env });
27
23
  }
28
- catch {
29
- // Codex is unavailable only when this build omits its local adapter.
30
- }
31
- try {
32
- const { PaseoAdapter } = await import('./paseo-delivery.js');
33
- adapters.push(new PaseoAdapter());
34
- }
35
- catch {
36
- // Paseo is an optional integration; a core-only install simply has no Paseo adapter.
24
+ catch { /* core-only builds may omit Codex */ }
25
+ if (env.SQUARE_DISABLE_PASEO_WAKE !== '1') {
26
+ try {
27
+ const { PaseoWakeTransport } = await import('./paseo-delivery.js');
28
+ transports.paseo = new PaseoWakeTransport();
29
+ }
30
+ catch { /* Paseo is optional */ }
37
31
  }
38
- return adapters;
39
- }
40
- export async function createDefaultWakeTransport(hostLedger, clock, env = process.env, suppliedAdapters) {
41
- const adapters = suppliedAdapters ?? await defaultWakeAdapters(env);
42
32
  const { createClaudeWakeTransport } = await import('./claude-delivery.js');
43
- return createWakeTransport(suppliedAdapters === undefined && env.SQUARE_DISABLE_PASEO_WAKE === '1' ? adapters.filter((adapter) => adapter.kind !== 'paseo') : adapters, hostLedger, clock, { 'claude-native': createClaudeWakeTransport(hostLedger, env) });
44
- }
45
- async function observeWakeRequest(request, hostLedger, now) {
46
- let square;
47
- try {
48
- const location = await canonicalPath(request.location);
49
- square = await openSquare(location, { hostLedger });
50
- const { value: facts } = await square.artifact.read((facts) => {
51
- const index = parseActivityId(request.activity);
52
- const head = index === undefined ? undefined : pendingHead(facts, request.participant, index);
53
- return { routes: facts.routes(), pending: head === undefined ? [] : [head] };
54
- });
55
- const eligibility = await projectWakeEvidence({ ...facts, hostLedger, location, now });
56
- return { ...eligibility.currentness({ ...request, location }), observationAvailable: true };
57
- }
58
- catch {
59
- return { current: false, activityPending: false, sessionBound: false, routePublished: false,
60
- selectedOwner: false, cancelled: false, presented: false, observationAvailable: false };
61
- }
62
- finally {
63
- if (square !== undefined)
64
- await closeOpenSquare(square);
65
- }
33
+ transports['claude-native'] = createClaudeWakeTransport(env);
34
+ return createWakeTransport(transports);
66
35
  }
67
- export function createWakeTransport(adapters, hostLedger, clock, nativePorts = {}) {
36
+ /** Route selection only: all implementations receive the same request and final gate. */
37
+ export function createWakeTransport(transports) {
38
+ const missing = (kind) => ({ outcome: 'not-capable', diagnostic: `no transport for ${kind}` });
68
39
  return {
69
- probe: async (route) => {
70
- const native = nativePorts[route.kind];
71
- if (native !== undefined)
72
- return native.probe?.(route) ?? true;
73
- const adapter = adapters.find((candidate) => candidate.kind === route.kind);
74
- if (adapter === undefined)
75
- return { outcome: 'not-capable', diagnostic: `no adapter for ${route.kind}` };
76
- const probe = adapter.probe;
77
- if (probe === undefined)
78
- return true;
40
+ async probe(route) {
41
+ const transport = transports[route.kind];
42
+ if (transport === undefined)
43
+ return missing(route.kind);
79
44
  try {
80
- return await probe.call(adapter, route.address);
45
+ return await transport.probe?.(route) ?? true;
81
46
  }
82
47
  catch (error) {
83
48
  return { outcome: 'not-capable', diagnostic: error instanceof Error ? error.message : String(error) };
84
49
  }
85
50
  },
86
- attempt: async (request, timeoutMs, beforeSend) => {
87
- let revalidation;
88
- const finalGate = beforeSend ?? (async () => {
89
- revalidation = await observeWakeRequest(request, hostLedger, clock());
90
- return revalidation.current;
91
- });
92
- const native = nativePorts[request.route.kind];
93
- if (native !== undefined)
94
- return native.attempt(request, timeoutMs, finalGate);
95
- const adapter = adapters.find((candidate) => candidate.kind === request.route.kind);
96
- if (adapter === undefined)
97
- return { outcome: 'not-capable', diagnostic: `no adapter for ${request.route.kind}` };
98
- try {
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.' };
122
- const result = await adapter.dispatch(request.route.address, payload, finalGate, timeoutMs, request);
123
- if (result.outcome !== 'gate-rejected')
124
- return result;
125
- if (revalidation !== undefined && !revalidation.current) {
126
- return {
127
- outcome: 'failed',
128
- signature: 'pre_send_revalidation_failed',
129
- message: revalidation.observationAvailable
130
- ? 'Wake was not sent because current attention, session binding, or route no longer matches.'
131
- : 'Wake was not sent because current Square delivery state could not be verified.',
132
- diagnostic: revalidation,
133
- unavailable: true,
134
- };
135
- }
136
- return { outcome: 'unknown', diagnostic: 'wake dispatch cancelled' };
137
- }
138
- catch (error) {
139
- return { outcome: 'unknown', diagnostic: error instanceof Error ? error.message : String(error) };
140
- }
141
- },
51
+ attempt: (request, timeoutMs, beforeSend) => transports[request.route.kind]?.attempt(request, timeoutMs, beforeSend)
52
+ ?? Promise.resolve(missing(request.route.kind)),
142
53
  };
143
54
  }
144
55
  export async function processActNotificationsOnce(squarePath, actIndex, opts = {}) {
145
56
  const env = opts.env ?? process.env;
146
57
  const now = opts.now ?? Date.now;
147
- const ledgerRoot = hostLedgerRoot(env);
148
- const hostLedger = createHostLedgerPort({
149
- rootPath: ledgerRoot,
150
- });
151
- const square = await openSquare(squarePath, { clock: now, hostLedger, env });
58
+ const artifact = createArtifactPort(squarePath);
152
59
  try {
153
- const transport = await createDefaultWakeTransport(hostLedger, now, env, opts.adapters);
60
+ await artifact.read(facts => facts.header());
61
+ const transport = await createDefaultWakeTransport(env, opts.transports);
154
62
  try {
155
- return await deliverPending({ artifact: square.artifact, hostLedger, transport, location: squarePath, activity: actIndex, timeoutMs: Number(env.SQUARE_NOTIFY_DELIVERY_WAIT_MS ?? 5000), now: now() });
63
+ return await deliverPending({ artifact, transport, location: squarePath, activity: actIndex, timeoutMs: Number(env.SQUARE_NOTIFY_DELIVERY_WAIT_MS ?? 5000), now: now() });
156
64
  }
157
65
  catch {
158
66
  return { attempted: 0, accepted: 0, failed: 0, unknown: 0, notCapable: 1 };
159
67
  }
160
68
  }
69
+ catch (error) {
70
+ if (error instanceof InternalSquareError && error.code === 'not_found')
71
+ throw new SquareError('unavailable', `Square is unavailable at ${squarePath}`);
72
+ throw error;
73
+ }
161
74
  finally {
162
- await closeOpenSquare(square);
75
+ await artifact.close();
163
76
  }
164
77
  }
165
78
  /** Privileged hook fallback: sweep indexed squares plus the current cwd's local squares within one boundary budget. */
166
- export async function sweepPrivilegedPending(cwd, env = process.env, suppliedAdapters, deadline = Date.now() + PRIVILEGED_HOOK_BUDGET_MS, signal) {
79
+ export async function sweepPrivilegedPending(cwd, env = process.env, suppliedTransports, deadline = Date.now() + PRIVILEGED_HOOK_BUDGET_MS, signal) {
167
80
  const remainingMs = () => Math.max(0, deadline - Date.now());
168
81
  if (remainingMs() === 0 || signal?.aborted)
169
82
  return;
170
- const root = hostLedgerRoot(env);
171
- const hostLedger = createHostLedgerPort({ rootPath: root });
172
- let indexed = [];
173
- try {
174
- indexed = await hostLedger.listPresence({ now: Date.now() });
175
- }
176
- catch { /* capability is best effort */ }
83
+ const indexed = await indexedSquareEntries({}, env).catch(() => []);
177
84
  const paths = new Set(indexed.map((binding) => binding.location));
178
85
  try {
179
86
  for (const entry of await fs.promises.readdir(path.join(cwd, '.square'))) {
@@ -184,29 +91,30 @@ export async function sweepPrivilegedPending(cwd, env = process.env, suppliedAda
184
91
  catch { /* no local square directory */ }
185
92
  if (remainingMs() === 0 || signal?.aborted)
186
93
  return;
187
- const transport = await createDefaultWakeTransport(hostLedger, Date.now, env, suppliedAdapters);
94
+ const transport = await createDefaultWakeTransport(env, suppliedTransports);
188
95
  for (const squarePath of paths) {
189
96
  if (remainingMs() === 0 || signal?.aborted)
190
97
  break;
191
98
  try {
192
- const square = await openSquare(squarePath, { hostLedger, env, signal });
99
+ const artifact = createArtifactPort(squarePath, undefined, signal);
193
100
  try {
101
+ await artifact.read(facts => facts.header(), signal);
194
102
  if (remainingMs() === 0 || signal?.aborted)
195
103
  break;
196
104
  const limit = Number.parseInt(env.SQUARE_NOTIFY_SWEEP_LIMIT ?? '8', 10);
197
105
  const graceMs = 0;
198
- const selected = await sweepPending({ artifact: square.artifact, hostLedger, location: squarePath, now: Date.now(), graceMs, limit: Number.isFinite(limit) && limit > 0 ? limit : 8 }).catch(() => []);
106
+ const selected = await sweepPending({ artifact, location: squarePath, now: Date.now(), graceMs, limit: Number.isFinite(limit) && limit > 0 ? limit : 8 }).catch(() => []);
199
107
  for (const actIndex of selected) {
200
108
  const remaining = remainingMs();
201
109
  if (remaining === 0 || signal?.aborted)
202
110
  break;
203
111
  const configured = Number(env.SQUARE_NOTIFY_DELIVERY_WAIT_MS ?? 5000);
204
112
  const timeoutMs = Math.max(1, Math.min(Number.isFinite(configured) && configured > 0 ? configured : 5000, remaining));
205
- await deliverPending({ artifact: square.artifact, hostLedger, transport, location: squarePath, activity: actIndex, timeoutMs, now: Date.now() }).catch(() => undefined);
113
+ await deliverPending({ artifact, transport, location: squarePath, activity: actIndex, timeoutMs, now: Date.now() }).catch(() => undefined);
206
114
  }
207
115
  }
208
116
  finally {
209
- await closeOpenSquare(square);
117
+ await artifact.close();
210
118
  }
211
119
  }
212
120
  catch { /* stale index entries are ignored by the hook */ }
@@ -219,14 +127,19 @@ export async function sweepPendingNotifications(squarePath, opts = {}) {
219
127
  return [];
220
128
  const now = opts.now ?? Date.now();
221
129
  const limit = opts.limit ?? 8;
222
- const hostLedger = createHostLedgerPort({ rootPath: hostLedgerRoot(env) });
223
- const square = await openSquare(squarePath, { clock: () => now, hostLedger, env });
130
+ const artifact = createArtifactPort(squarePath);
224
131
  let selected;
225
132
  try {
226
- selected = await sweepPending({ artifact: square.artifact, hostLedger, location: squarePath, now, graceMs: wakeGraceMs(env), limit });
133
+ await artifact.read(facts => facts.header());
134
+ selected = await sweepPending({ artifact, location: squarePath, now, graceMs: wakeGraceMs(env), limit });
135
+ }
136
+ catch (error) {
137
+ if (error instanceof InternalSquareError && error.code === 'not_found')
138
+ throw new SquareError('unavailable', `Square is unavailable at ${squarePath}`);
139
+ throw error;
227
140
  }
228
141
  finally {
229
- await closeOpenSquare(square);
142
+ await artifact.close();
230
143
  }
231
144
  if (opts.dispatchCandidate !== undefined) {
232
145
  for (const actIndex of selected)
@@ -19,9 +19,9 @@ export function renderJoinResult(data) {
19
19
  actNumber: event.kind === 'say' ? entry.sayNumbers[event.index] : undefined,
20
20
  perception: entry.perceptions[event.index],
21
21
  })).filter(Boolean).join('\n\n');
22
- const fallback = after.automaticDeliveryAvailable ? [] : [
22
+ const fallback = after.automaticDeliveryAvailable ? ['', '· when someone calls you, you’ll hear them here'] : [
23
23
  '', `${participantCommandPrefix(after.location, name)} catch --idle 30m`,
24
- ' the square has no way to call you — keep this catch open and stay within earshot',
24
+ ' · stay within earshot — keep this catch open for new activity',
25
25
  ];
26
26
  const entryLine = data.kind === 'joined' ? '● you stepped into the square'
27
27
  : data.kind === 'reconnected' ? '● you are already in the square'
@@ -31,7 +31,6 @@ export function renderJoinResult(data) {
31
31
  ? '*the flagstones are where you left them. so is your shadow.*' : entry.scene;
32
32
  return withPathOutput(after.location, [
33
33
  entryLine, '',
34
- "· carved into the fountain's edge: every word here lands on a real ear — speak when someone needs it.",
35
34
  ...(entryScene === '' ? [] : ['', entryScene]),
36
35
  ...(isRejoin || !entry?.joinContext ? [] : ['', style('dim', 'context'), entry.joinContext]),
37
36
  ...(after.enrichmentUnavailable ? [' · joined; recent activity and context are unavailable right now'] : []),
@@ -59,13 +58,12 @@ export function renderExpression(squarePath, data) {
59
58
  ? `● you ring the bell — everyone turns · ${activity.id}${style('dim', capCount)}`
60
59
  : `● ${activity.mentions.length > 0
61
60
  ? `you walk over to ${activity.mentions.map((target) => participantIdentity(target)).join(', ')}`
62
- : isGestureOnly(activity.body ?? '') ? 'your gesture lands' : 'your words land'} — ${activity.id}${style('dim', capCount)}`;
61
+ : activity.metadata !== undefined && (activity.body ?? '').trim() === '' ? 'structured activity lands' : isGestureOnly(activity.body ?? '') ? 'your gesture lands' : 'your words land'} — ${activity.id}${style('dim', capCount)}`;
63
62
  const replyNotice = data.replyAuthorNotHere === undefined ? ''
64
63
  : `\n○ ${participantIdentity(data.replyAuthorNotHere)} isn't in the square right now — the reply landed without reaching them`;
65
64
  const unmatchedNotice = data.unmatchedMentions?.length
66
65
  ? `\n · ${data.unmatchedMentions.map((token) => `@${token} isn't standing here`).join('; ')} — ${activity.mentions.length === 0 && activity.reply === undefined ? 'activity landed bare' : 'left as literal text'}` : '';
67
- return withPathOutput(projection.location ?? squarePath, (projection.reentry === undefined ? '' : '● you stepped back into the square\n')
68
- + confirmation + (data.hint ? `\n${style('dim', `· ${data.hint}`)}` : '') + replyNotice + unmatchedNotice + pending, { participantCount: projection.standingCount, held: projection.held });
66
+ return withPathOutput(projection.location ?? squarePath, confirmation + (data.hint ? `\n${style('dim', `· ${data.hint}`)}` : '') + replyNotice + unmatchedNotice + pending, { participantCount: projection.standingCount, held: projection.held });
69
67
  }
70
68
  function continuationCommand(squarePath, name, options) {
71
69
  const args = ['--now'];
@@ -103,7 +101,7 @@ export function renderCatchOutcome(squarePath, data, options = {}) {
103
101
  perception: activity.perception, mention: options.mention ? name : undefined,
104
102
  })).filter(Boolean).join('\n\n');
105
103
  const remaining = data.remaining === 0 ? ''
106
- : `○ ${data.remaining} matching ${data.remaining === 1 ? 'activity remains' : 'activities remain'}\n${continuationCommand(location, name, options)}`;
104
+ : `· ${data.remaining} matching ${data.remaining === 1 ? 'activity remains' : 'activities remain'}\n${continuationCommand(location, name, options)}`;
107
105
  const terminal = stop === undefined ? '' : renderWatchStatus({
108
106
  ...statusOptions, status: stop,
109
107
  ...(stop === 'stale' && options.idle !== undefined ? { idleMs: options.idle } : {}),
@@ -136,9 +134,9 @@ function renderHistoryProjection(projection, visible, noTruncate, squarePath) {
136
134
  }
137
135
  }
138
136
  if (chunks.length === 0)
139
- return 'latest\n ○ no public activity in this view';
140
- if (preview !== undefined && shown.some((activity) => activity.kind === 'say' && activity.body.length > preview)) {
141
- chunks.push(`${commandPrefix(squarePath)} history --no-truncate`);
137
+ return '○ no activity matches this history query';
138
+ if (preview !== undefined && shown.some((activity) => activity.kind === 'say' && (activity.body?.length ?? 0) > preview)) {
139
+ chunks.push(`${commandPrefix(squarePath)} history --at ${shown.map((activity) => formatActivityId(activity.index)).join(',')} --no-truncate`);
142
140
  }
143
141
  if (anchored.length > 0)
144
142
  chunks.push(renderPresenceAnchor(anchored));
@@ -203,7 +201,7 @@ export function renderParticipantsResult(squarePath, data, limit = PARTICIPANTS_
203
201
  const tail = participants.length <= limit
204
202
  ? []
205
203
  : [
206
- style('dim', ` ○ ${lines.length} of ${participants.length} participants shown`),
204
+ style('dim', ` · ${lines.length} of ${participants.length} participants shown`),
207
205
  ...(participants.length <= PARTICIPANTS_MAX_LIMIT ? [
208
206
  `${participantsLimitCommand(squarePath, participants.length)}`,
209
207
  ] : []),
@@ -315,10 +313,10 @@ export function renderListenerChange(squarePath, verb, data) {
315
313
  const target = participantIdentity(projection.target);
316
314
  const action = data.activity !== null
317
315
  ? verb === 'listen' ? `${actor} turns an ear toward ${target}`
318
- : verb === 'ignore' ? `${actor} turns away from ${target}`
319
- : `${actor} stops listening to ${target}`
316
+ : verb === 'ignore' ? `${actor} turns away from ${target} — only bells will reach you from them`
317
+ : `${actor} stops listening to ${target} — direct mentions and replies still reach you unless ignored`
320
318
  : verb === 'listen' ? `${actor} already turns an ear toward ${target}`
321
- : verb === 'ignore' ? `${actor} is not turned toward ${target}`
319
+ : verb === 'ignore' ? `${actor} already ignores ${target} — only bells will reach you from them`
322
320
  : `${actor} is not listening to ${target}`;
323
321
  return withPathOutput(squarePath, `${data.activity === null ? '○' : '·'} ${action}`, {
324
322
  participantCount: projection.standingCount, held: projection.held,
@@ -23,6 +23,3 @@ export declare function firstHarnessSession(env?: NodeJS.ProcessEnv): {
23
23
  readonly sessionId: string;
24
24
  readonly channel: HarnessSessionChannel;
25
25
  } | undefined;
26
- export declare function automaticParticipant(provider: AutomaticProvider, sessionId: string, env: NodeJS.ProcessEnv): string;
27
- /** Compute the current harness participant without reading a Square or registry artifact. */
28
- export declare function squareAssignedParticipantName(env?: NodeJS.ProcessEnv): string | undefined;
@@ -1,5 +1,3 @@
1
- import { createHash } from 'node:crypto';
2
- import { validateName } from './model.js';
3
1
  export function participantIdentity(name) {
4
2
  return `@${name}`;
5
3
  }
@@ -38,32 +36,3 @@ export function firstHarnessSession(env = process.env) {
38
36
  }
39
37
  return undefined;
40
38
  }
41
- export function automaticParticipant(provider, sessionId, env) {
42
- const configured = env.SQUARE_PARTICIPANT_NAME?.trim();
43
- if (configured) {
44
- validateName(configured);
45
- return configured;
46
- }
47
- const digest = createHash('sha256').update(sessionId, 'utf8').digest('hex').slice(0, 12);
48
- return `${provider}-${digest}`;
49
- }
50
- /** Compute the current harness participant without reading a Square or registry artifact. */
51
- export function squareAssignedParticipantName(env = process.env) {
52
- const configured = env.SQUARE_PARTICIPANT_NAME?.trim();
53
- if (configured) {
54
- validateName(configured);
55
- return configured;
56
- }
57
- // Every native provider contributes its own name without deduping session ids first: equal ids
58
- // in different providers are different coordinates and must stay ambiguous.
59
- const names = new Set();
60
- for (const source of harnessSessionSources) {
61
- if (source.provider === undefined)
62
- continue;
63
- const sessionId = env[source.variable]?.trim();
64
- if (!sessionId)
65
- continue;
66
- names.add(automaticParticipant(source.provider, sessionId, env));
67
- }
68
- return names.size === 1 ? [...names][0] : undefined;
69
- }
@@ -1,14 +1,13 @@
1
- import { type WakeAdapter, type WakeAdapterResult, type WakeDispatchContext } from './delivery.js';
1
+ import type { WakeRequest, WakeOutcome, WakeTransportPort } from './ports.js';
2
2
  import { discoverPaseoAgents, waitForPaseoWakeBoundary } from './paseo-state.js';
3
3
  import { sendPaseoWake } from './wake-sink.js';
4
- export interface PaseoAdapterOptions {
4
+ export interface PaseoWakeTransportOptions {
5
5
  discover?: typeof discoverPaseoAgents;
6
6
  waitForBoundary?: typeof waitForPaseoWakeBoundary;
7
7
  sendWake?: typeof sendPaseoWake;
8
8
  }
9
- export declare class PaseoAdapter implements WakeAdapter {
9
+ export declare class PaseoWakeTransport implements WakeTransportPort {
10
10
  private readonly opts;
11
- readonly kind: "paseo";
12
- constructor(opts?: PaseoAdapterOptions);
13
- dispatch(address: Readonly<Record<string, string>>, payload: string, beforeSend: () => Promise<boolean>, timeoutMs?: number, context?: WakeDispatchContext): Promise<WakeAdapterResult>;
11
+ constructor(opts?: PaseoWakeTransportOptions);
12
+ attempt(request: WakeRequest, timeoutMs: number, beforeSend: () => Promise<boolean>): Promise<WakeOutcome>;
14
13
  }
@@ -1,3 +1,4 @@
1
+ import { wakePreview } from './delivery.js';
1
2
  import { DeliveryError } from './packages/agent-delivery/src/index.js';
2
3
  import { resolvePaseoDaemon } from './packages/agent-delivery/src/paseo-native.js';
3
4
  import { discoverPaseoAgents, waitForPaseoWakeBoundary } from './paseo-state.js';
@@ -14,13 +15,13 @@ function diagnostic(phase, address, code) {
14
15
  passwordPresent: Boolean(process.env.PASEO_PASSWORD),
15
16
  };
16
17
  }
17
- export class PaseoAdapter {
18
+ export class PaseoWakeTransport {
18
19
  opts;
19
- kind = 'paseo';
20
20
  constructor(opts = {}) {
21
21
  this.opts = opts;
22
22
  }
23
- async dispatch(address, payload, beforeSend, timeoutMs = 5000, context) {
23
+ async attempt(request, timeoutMs, beforeSend) {
24
+ const address = request.route.address;
24
25
  const deadline = Date.now() + timeoutMs;
25
26
  const remainingMs = () => Math.max(0, deadline - Date.now());
26
27
  const budgetUnavailable = () => ({
@@ -41,6 +42,10 @@ export class PaseoAdapter {
41
42
  diagnostic: diagnostic('selection', address, 'invalid_address'),
42
43
  };
43
44
  }
45
+ const payload = request.artifact === undefined ? undefined
46
+ : (await request.artifact.read((facts) => wakePreview(facts, request))).value;
47
+ if (payload === undefined)
48
+ return { outcome: 'not-capable', diagnostic: 'No pending Square activity to preview.' };
44
49
  let remaining = remainingMs();
45
50
  if (remaining === 0)
46
51
  return budgetUnavailable();
@@ -84,7 +89,7 @@ export class PaseoAdapter {
84
89
  };
85
90
  }
86
91
  if (!(await beforeSend()))
87
- return { outcome: 'gate-rejected' };
92
+ return { outcome: 'not-capable' };
88
93
  remaining = remainingMs();
89
94
  if (remaining === 0)
90
95
  return budgetUnavailable();
@@ -92,10 +97,10 @@ export class PaseoAdapter {
92
97
  await (this.opts.sendWake ?? sendPaseoWake)({
93
98
  agentId,
94
99
  prompt: payload,
95
- location: context?.location ?? '',
96
- participant: context?.participant ?? '',
97
- activity: context?.activity ?? '',
98
- ...(context?.attemptN === undefined ? {} : { attemptN: context.attemptN }),
100
+ location: request.location,
101
+ participant: request.participant,
102
+ activity: request.activity,
103
+ ...(request.attemptN === undefined ? {} : { attemptN: request.attemptN }),
99
104
  }, { timeoutMs: remaining });
100
105
  return { outcome: 'accepted' };
101
106
  }
package/dist/paseo.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /** Optional Paseo integration. Importing this entry requires the Paseo peer dependencies. */
2
- export { PaseoAdapter } from './paseo-delivery.js';
2
+ export { PaseoWakeTransport } from './paseo-delivery.js';
3
3
  export { paseoWakeMessageId, sendPaseoWake } from './wake-sink.js';
4
4
  export type { PaseoWakeRequest } from './wake-sink.js';
5
5
  export { discoverPaseoAgents, waitForPaseoWakeBoundary } from './paseo-state.js';
package/dist/paseo.js CHANGED
@@ -1,4 +1,4 @@
1
1
  /** Optional Paseo integration. Importing this entry requires the Paseo peer dependencies. */
2
- export { PaseoAdapter } from './paseo-delivery.js';
2
+ export { PaseoWakeTransport } from './paseo-delivery.js';
3
3
  export { paseoWakeMessageId, sendPaseoWake } from './wake-sink.js';
4
4
  export { discoverPaseoAgents, waitForPaseoWakeBoundary } from './paseo-state.js';