@astrosheep/square 0.3.86 → 0.3.88

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 (143) 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 +14 -10
  16. package/dist/cli/mcp-server-command.js +2 -2
  17. package/dist/cli/observation-commands.js +1 -0
  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 +8 -11
  36. package/dist/inbox.d.ts +35 -9
  37. package/dist/inbox.js +228 -144
  38. package/dist/index.d.ts +3 -4
  39. package/dist/index.js +1 -3
  40. package/dist/list.js +9 -4
  41. package/dist/mcp.js +12 -1
  42. package/dist/model.d.ts +39 -5
  43. package/dist/model.js +84 -0
  44. package/dist/notifications.d.ts +8 -6
  45. package/dist/notifications.js +56 -143
  46. package/dist/operation-presentation.js +7 -4
  47. package/dist/participant-identity.d.ts +0 -3
  48. package/dist/participant-identity.js +0 -31
  49. package/dist/paseo-delivery.d.ts +5 -6
  50. package/dist/paseo-delivery.js +13 -8
  51. package/dist/paseo.d.ts +1 -1
  52. package/dist/paseo.js +1 -1
  53. package/dist/plugins/opencode/delivery.d.ts +8 -3
  54. package/dist/plugins/opencode/delivery.js +187 -132
  55. package/dist/plugins/opencode/index.js +12 -38
  56. package/dist/ports.d.ts +112 -40
  57. package/dist/presentation-operations.d.ts +1 -1
  58. package/dist/presentation-operations.js +51 -35
  59. package/dist/presentation.js +8 -3
  60. package/dist/seats.d.ts +7 -0
  61. package/dist/seats.js +27 -0
  62. package/dist/session-lifecycle.d.ts +2 -2
  63. package/dist/session-lifecycle.js +10 -12
  64. package/dist/sqlite-square.d.ts +20 -9
  65. package/dist/sqlite-square.js +476 -341
  66. package/dist/square-api.d.ts +1 -0
  67. package/dist/square-api.js +3 -4
  68. package/dist/square-core.d.ts +3 -2
  69. package/dist/square-domain.d.ts +12 -5
  70. package/dist/square-domain.js +163 -7
  71. package/dist/square-facade.d.ts +16 -10
  72. package/dist/square-index.d.ts +24 -0
  73. package/dist/square-index.js +131 -0
  74. package/dist/square-legacy-framing.d.ts +9 -0
  75. package/dist/square-legacy-framing.js +56 -0
  76. package/dist/square-migration-host-ledger.d.ts +26 -0
  77. package/dist/square-migration-host-ledger.js +168 -0
  78. package/dist/square-migration.d.ts +5 -5
  79. package/dist/square-migration.js +373 -71
  80. package/dist/square-sqlite.d.ts +9 -9
  81. package/dist/square-sqlite.js +329 -79
  82. package/dist/square-stdio.d.ts +1 -1
  83. package/dist/square-stdio.js +12 -7
  84. package/dist/square-tools.d.ts +1 -1
  85. package/dist/square-tools.js +80 -27
  86. package/dist/stdio-server.js +4 -2
  87. package/dist/stream.d.ts +3 -2
  88. package/dist/stream.js +9 -10
  89. package/dist/views.d.ts +9 -14
  90. package/dist/views.js +52 -70
  91. package/dist/wakes.d.ts +6 -5
  92. package/dist/wakes.js +20 -17
  93. package/guides/application-facade-delivery-evidence.md +1 -1
  94. package/guides/claude-native.md +9 -9
  95. package/guides/mcp.md +2 -2
  96. package/guides/migration.md +107 -67
  97. package/guides/pi-tools.md +1 -1
  98. package/guides/row-storage-design.md +1 -1
  99. package/guides/square-api.md +61 -28
  100. package/package.json +1 -1
  101. package/plugins/claude/.claude-plugin/plugin.json +1 -1
  102. package/plugins/claude/hooks/register.js +1 -1
  103. package/plugins/claude/skills/square/SKILL.md +4 -4
  104. package/plugins/claude/types.d.ts +1 -1
  105. package/plugins/codex/.codex-plugin/plugin.json +1 -1
  106. package/plugins/opencode/delivery.ts +170 -118
  107. package/plugins/opencode/index.ts +12 -30
  108. package/plugins/pi/square-pi.js +56 -18
  109. package/skills/square/SKILL.md +4 -4
  110. package/dist/cli/ledger-command.d.ts +0 -12
  111. package/dist/cli/ledger-command.js +0 -72
  112. package/dist/host-ledger-file-adapter.d.ts +0 -47
  113. package/dist/host-ledger-file-adapter.js +0 -384
  114. package/dist/host-ledger-migration.d.ts +0 -27
  115. package/dist/host-ledger-migration.js +0 -264
  116. package/dist/host-ledger-root.d.ts +0 -2
  117. package/dist/host-ledger-root.js +0 -9
  118. package/dist/host-ledger-sqlite.d.ts +0 -65
  119. package/dist/host-ledger-sqlite.js +0 -368
  120. package/dist/host-ledger.d.ts +0 -195
  121. package/dist/host-ledger.js +0 -1
  122. package/dist/ledger-maintenance.d.ts +0 -30
  123. package/dist/ledger-maintenance.js +0 -110
  124. package/dist/open-square.d.ts +0 -38
  125. package/dist/open-square.js +0 -81
  126. package/dist/participant-host.d.ts +0 -23
  127. package/dist/participant-host.js +0 -92
  128. package/dist/registry.d.ts +0 -55
  129. package/dist/registry.js +0 -123
  130. package/dist/routes.d.ts +0 -39
  131. package/dist/routes.js +0 -99
  132. package/dist/square-actions.d.ts +0 -56
  133. package/dist/square-actions.js +0 -353
  134. package/dist/square-projections.d.ts +0 -31
  135. package/dist/square-projections.js +0 -64
  136. package/dist/wake-attempts.d.ts +0 -10
  137. package/dist/wake-attempts.js +0 -39
  138. package/dist/wake-eligibility.d.ts +0 -53
  139. package/dist/wake-eligibility.js +0 -123
  140. package/dist/wake-evidence.d.ts +0 -33
  141. package/dist/wake-evidence.js +0 -32
  142. package/guides/host-ledger.md +0 -58
  143. package/guides/indexed-host-ledger-performance.md +0 -39
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)
@@ -58,13 +58,12 @@ export function renderExpression(squarePath, data) {
58
58
  ? `● you ring the bell — everyone turns · ${activity.id}${style('dim', capCount)}`
59
59
  : `● ${activity.mentions.length > 0
60
60
  ? `you walk over to ${activity.mentions.map((target) => participantIdentity(target)).join(', ')}`
61
- : 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)}`;
62
62
  const replyNotice = data.replyAuthorNotHere === undefined ? ''
63
63
  : `\n○ ${participantIdentity(data.replyAuthorNotHere)} isn't in the square right now — the reply landed without reaching them`;
64
64
  const unmatchedNotice = data.unmatchedMentions?.length
65
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'}` : '';
66
- return withPathOutput(projection.location ?? squarePath, (projection.reentry === undefined ? '' : '● you stepped back into the square\n')
67
- + 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 });
68
67
  }
69
68
  function continuationCommand(squarePath, name, options) {
70
69
  const args = ['--now'];
@@ -136,7 +135,7 @@ function renderHistoryProjection(projection, visible, noTruncate, squarePath) {
136
135
  }
137
136
  if (chunks.length === 0)
138
137
  return '○ no activity matches this history query';
139
- if (preview !== undefined && shown.some((activity) => activity.kind === 'say' && activity.body.length > preview)) {
138
+ if (preview !== undefined && shown.some((activity) => activity.kind === 'say' && (activity.body?.length ?? 0) > preview)) {
140
139
  chunks.push(`${commandPrefix(squarePath)} history --at ${shown.map((activity) => formatActivityId(activity.index)).join(',')} --no-truncate`);
141
140
  }
142
141
  if (anchored.length > 0)
@@ -294,6 +293,10 @@ export function renderStatusResult(squarePath, data) {
294
293
  const output = [
295
294
  style('dim', counts),
296
295
  ...(hold === undefined ? [] : ['', hold]), '', style('dim', 'around the square'), ...people, '', style('dim', 'latest'), ...latest,
296
+ ...(data.listening === undefined ? [] : [
297
+ '', style('dim', 'you listen to'),
298
+ ...(data.listening.length === 0 ? [' ○ not turned toward anyone'] : data.listening.map((name) => ` · ${participantIdentity(name)}`)),
299
+ ]),
297
300
  ].join('\n');
298
301
  return withPathOutput(squarePath, output, { participantCount: presentation.standingCount, held: presentation.held });
299
302
  }
@@ -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';
@@ -7,8 +7,13 @@ export interface OpenCodeContextMessage {
7
7
  readonly text?: string;
8
8
  }[];
9
9
  }
10
- /** Only the matching prepared primary context authorizes presentation, never admission. */
10
+ export declare function indexedOpenCodeSessions(env: NodeJS.ProcessEnv): Promise<readonly string[]>;
11
+ export declare function discoverOpenCodeSeats(sessionId: string, env: NodeJS.ProcessEnv, signal?: AbortSignal): Promise<import("../../model.js").LocatedSeat[]>;
12
+ /** The exact prepared native message can authorize artifact-backed presentation once. */
11
13
  export declare function observeOpenCodeContext(sessionId: string, messages: readonly OpenCodeContextMessage[], env: NodeJS.ProcessEnv, signal: AbortSignal): Promise<void>;
12
- /** One existing observer and the existing dispatch owner; no second queue or evidence store. */
14
+ /** The native receiver owns no claims outside the artifact; HOME is used only for discovery. */
13
15
  export declare function receiveOpenCodePending(target: OpenCodeNativeSession, env: NodeJS.ProcessEnv, signal: AbortSignal): Promise<void>;
14
- export declare function publishOpenCodeRoutes(sessionId: string, env: NodeJS.ProcessEnv, signal: AbortSignal): Promise<boolean>;
16
+ /** Publish the native address into current artifact seats; the legacy route projection is not read. */
17
+ export declare function publishOpenCodeAddress(sessionId: string, env: NodeJS.ProcessEnv, signal: AbortSignal): Promise<boolean>;
18
+ export declare function cancelOpenCodeDeliveries(sessionId: string, env: NodeJS.ProcessEnv, signal: AbortSignal): Promise<void>;
19
+ export declare function endOpenCodeSession(sessionId: string, env: NodeJS.ProcessEnv, signal: AbortSignal): Promise<void>;