@astrosheep/square 0.3.69 → 0.3.71

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 (132) hide show
  1. package/dist/activity-feed.d.ts +4 -5
  2. package/dist/activity-feed.js +14 -10
  3. package/dist/artifact.d.ts +26 -33
  4. package/dist/artifact.js +346 -317
  5. package/dist/attention-presentation.js +2 -9
  6. package/dist/automatic-session.js +6 -6
  7. package/dist/boundary-presentation.js +3 -3
  8. package/dist/catch-decisions.d.ts +19 -12
  9. package/dist/catch-decisions.js +32 -68
  10. package/dist/claude-delivery.js +7 -7
  11. package/dist/claude-mod.js +18 -7
  12. package/dist/cli/catch-presentation.d.ts +7 -0
  13. package/dist/cli/catch-presentation.js +55 -0
  14. package/dist/cli/context.d.ts +1 -1
  15. package/dist/cli/context.js +5 -23
  16. package/dist/cli/express-presentation.d.ts +10 -0
  17. package/dist/cli/express-presentation.js +196 -0
  18. package/dist/cli/maintenance-commands.d.ts +4 -0
  19. package/dist/cli/maintenance-commands.js +29 -6
  20. package/dist/cli/observation-commands.d.ts +7 -5
  21. package/dist/cli/observation-commands.js +275 -262
  22. package/dist/cli/program.d.ts +1 -1
  23. package/dist/cli/program.js +43 -56
  24. package/dist/cli/registry.js +2 -1
  25. package/dist/cli/square-commands.d.ts +3 -9
  26. package/dist/cli/square-commands.js +134 -255
  27. package/dist/codex-queue.js +4 -1
  28. package/dist/decisions.d.ts +26 -74
  29. package/dist/decisions.js +190 -335
  30. package/dist/delivery-health.js +13 -22
  31. package/dist/delivery-operations.d.ts +0 -11
  32. package/dist/delivery-operations.js +44 -33
  33. package/dist/delivery.d.ts +33 -32
  34. package/dist/delivery.js +70 -81
  35. package/dist/harness-claude.js +1 -1
  36. package/dist/harness-codex.js +1 -1
  37. package/dist/harness-pi.js +1 -1
  38. package/dist/help.js +6 -1
  39. package/dist/inbox.d.ts +27 -0
  40. package/dist/inbox.js +55 -18
  41. package/dist/index.d.ts +2 -4
  42. package/dist/index.js +1 -1
  43. package/dist/list.d.ts +7 -1
  44. package/dist/list.js +49 -122
  45. package/dist/mcp.d.ts +1 -1
  46. package/dist/mcp.js +7 -37
  47. package/dist/model.d.ts +4 -44
  48. package/dist/model.js +1 -3
  49. package/dist/notifications.d.ts +1 -1
  50. package/dist/notifications.js +13 -7
  51. package/dist/open-square.d.ts +16 -2
  52. package/dist/open-square.js +40 -1
  53. package/dist/perception-projection.d.ts +6 -15
  54. package/dist/perception-projection.js +6 -26
  55. package/dist/{opencode-delivery.d.ts → plugins/opencode/delivery.d.ts} +1 -1
  56. package/dist/{opencode-delivery.js → plugins/opencode/delivery.js} +18 -18
  57. package/dist/{opencode.d.ts → plugins/opencode/index.d.ts} +2 -2
  58. package/dist/{opencode.js → plugins/opencode/index.js} +9 -9
  59. package/dist/ports.d.ts +156 -11
  60. package/dist/presentation-operations.d.ts +1 -1
  61. package/dist/presentation-operations.js +35 -44
  62. package/dist/presentation.d.ts +12 -19
  63. package/dist/presentation.js +33 -54
  64. package/dist/routes.d.ts +7 -14
  65. package/dist/routes.js +33 -62
  66. package/dist/runtime.d.ts +12 -39
  67. package/dist/runtime.js +33 -164
  68. package/dist/square-actions.d.ts +11 -21
  69. package/dist/square-actions.js +171 -271
  70. package/dist/square-application.d.ts +31 -55
  71. package/dist/square-application.js +637 -141
  72. package/dist/square-core.d.ts +3 -94
  73. package/dist/square-core.js +8 -353
  74. package/dist/square-domain.d.ts +24 -0
  75. package/dist/square-domain.js +77 -0
  76. package/dist/square-facade.d.ts +360 -9
  77. package/dist/square-migration.d.ts +24 -0
  78. package/dist/square-migration.js +211 -0
  79. package/dist/square-projections.d.ts +2 -42
  80. package/dist/square-projections.js +1 -37
  81. package/dist/square-sqlite.d.ts +59 -0
  82. package/dist/square-sqlite.js +702 -0
  83. package/dist/stream.d.ts +3 -10
  84. package/dist/stream.js +81 -52
  85. package/dist/views.d.ts +27 -95
  86. package/dist/views.js +217 -87
  87. package/dist/wake-eligibility.d.ts +15 -5
  88. package/dist/wake-eligibility.js +32 -16
  89. package/dist/wakes.d.ts +3 -3
  90. package/dist/wakes.js +31 -27
  91. package/guides/application-facade-delivery-evidence.md +200 -0
  92. package/guides/application-facade-design.md +95 -0
  93. package/guides/change-waits.md +4 -4
  94. package/guides/migration.md +77 -0
  95. package/guides/pi-native-delivery.md +1 -1
  96. package/guides/primitive-catch-consolidation.md +85 -0
  97. package/guides/primitive-history-consolidation.md +76 -0
  98. package/guides/primitive-status-consolidation.md +87 -0
  99. package/guides/primitive-storage-consolidation.md +128 -0
  100. package/guides/row-runtime-delivery-evidence.md +160 -0
  101. package/guides/row-storage-design.md +567 -0
  102. package/package.json +5 -7
  103. package/{claude-plugin → plugins/claude}/.claude-plugin/plugin.json +1 -1
  104. package/{codex-plugin → plugins/codex}/.codex-plugin/plugin.json +1 -1
  105. package/plugins/opencode/delivery.ts +149 -0
  106. package/plugins/opencode/index.ts +136 -0
  107. package/plugins/paseo/OVERVIEW.md +14 -0
  108. package/plugins/paseo/README.md +54 -0
  109. package/plugins/paseo/client/square-message.tsx +106 -0
  110. package/plugins/paseo/client/transform.ts +24 -0
  111. package/plugins/paseo/index.client.tsx +19 -0
  112. package/plugins/paseo/package.json +19 -0
  113. package/plugins/paseo/paseo-plugin.json +7 -0
  114. package/plugins/paseo/shared/message.ts +29 -0
  115. package/{extensions → plugins/pi}/square-pi.js +8 -5
  116. package/dist/activity.d.ts +0 -14
  117. package/dist/activity.js +0 -248
  118. package/dist/square-file-adapter.d.ts +0 -26
  119. package/dist/square-file-adapter.js +0 -85
  120. package/dist/square-state.d.ts +0 -6
  121. package/dist/square-state.js +0 -138
  122. package/dist/square-storage.d.ts +0 -14
  123. package/dist/square-storage.js +0 -188
  124. package/dist/square-wiring.d.ts +0 -33
  125. package/dist/square-wiring.js +0 -109
  126. package/dist/watch.d.ts +0 -8
  127. package/dist/watch.js +0 -252
  128. /package/{claude-plugin → plugins/claude}/hooks/hooks.json +0 -0
  129. /package/{claude-plugin → plugins/claude}/hooks/register.js +0 -0
  130. /package/{claude-plugin → plugins/claude}/skills/square/SKILL.md +0 -0
  131. /package/{claude-plugin → plugins/claude}/types.d.ts +0 -0
  132. /package/{codex-plugin → plugins/codex}/hooks/hooks.json +0 -0
@@ -1,5 +1,6 @@
1
1
  import type { HostLedgerPort, SquareArtifactPort, WakeTransportPort } from './ports.js';
2
- /** Private binding assembled by storage and consumed by the four concerns. */
2
+ import type { OperationControl } from './square-facade.js';
3
+ /** Host-bound resource: artifact, captured environment, clock, ledger and transport. */
3
4
  export interface OpenSquare {
4
5
  readonly artifact: SquareArtifactPort;
5
6
  readonly clock: () => number;
@@ -8,5 +9,18 @@ export interface OpenSquare {
8
9
  readonly wakeTransport?: WakeTransportPort;
9
10
  readonly env?: NodeJS.ProcessEnv;
10
11
  }
11
- /** Package-private lifecycle boundary for bound squares. */
12
+ export interface SquareBuildOptions {
13
+ markdown: string;
14
+ hardCap?: number | null;
15
+ throttlePerMinute?: number;
16
+ clock?: () => number;
17
+ hostLedger?: HostLedgerPort;
18
+ wakeTransport?: WakeTransportPort;
19
+ env?: NodeJS.ProcessEnv;
20
+ /** One caller deadline propagated into artifact busy retries. */
21
+ signal?: AbortSignal;
22
+ }
23
+ export declare function openSquare(squarePath: string, options?: Pick<SquareBuildOptions, 'clock' | 'hostLedger' | 'wakeTransport' | 'env' | 'signal'>, control?: OperationControl): Promise<OpenSquare>;
24
+ export declare function buildSquare(squarePath: string, options: SquareBuildOptions): Promise<OpenSquare>;
25
+ export declare function buildMemorySquare(options: SquareBuildOptions): OpenSquare;
12
26
  export declare function closeOpenSquare(square: OpenSquare): Promise<void>;
@@ -1,4 +1,43 @@
1
- /** Package-private lifecycle boundary for bound squares. */
1
+ import { createArtifactPort, createSquare, prepareSquareBuild } from './artifact.js';
2
+ import { InternalSquareError, SquareError } from './model.js';
3
+ import { hostLedgerRoot } from './host-ledger-root.js';
4
+ import { createHostLedgerPort } from './host-ledger-file-adapter.js';
5
+ export async function openSquare(squarePath, options = {}, control) {
6
+ const env = { ...(options.env ?? process.env) };
7
+ const artifact = createArtifactPort(squarePath, undefined, options.signal);
8
+ try {
9
+ await artifact.read(facts => facts.header(), control?.signal);
10
+ return {
11
+ artifact, clock: options.clock ?? Date.now, location: squarePath, env,
12
+ hostLedger: options.hostLedger ?? createHostLedgerPort({ rootPath: hostLedgerRoot(env) }),
13
+ wakeTransport: options.wakeTransport,
14
+ };
15
+ }
16
+ catch (error) {
17
+ await artifact.close();
18
+ if (error instanceof InternalSquareError && error.code === 'not_found')
19
+ throw new SquareError('unavailable', `Square is unavailable at ${squarePath}`);
20
+ throw error;
21
+ }
22
+ }
23
+ export async function buildSquare(squarePath, options) {
24
+ try {
25
+ await createSquare(squarePath, { ...options, force: false }, options.markdown, options.signal);
26
+ }
27
+ catch (error) {
28
+ if (error instanceof InternalSquareError && error.code === 'conflict')
29
+ throw new SquareError('io', `Cannot build over an existing square at ${squarePath}`);
30
+ throw error;
31
+ }
32
+ return openSquare(squarePath, { ...options, signal: undefined });
33
+ }
34
+ export function buildMemorySquare(options) {
35
+ return {
36
+ artifact: createArtifactPort(':memory:', prepareSquareBuild(options, options.markdown)),
37
+ clock: options.clock ?? Date.now, location: 'memory',
38
+ hostLedger: options.hostLedger, wakeTransport: options.wakeTransport,
39
+ };
40
+ }
2
41
  export function closeOpenSquare(square) {
3
42
  return square.artifact.close();
4
43
  }
@@ -1,15 +1,6 @@
1
- import { type LandedAudienceReplay, type Perception } from './square-core.js';
2
- import { type SquareState, type StoredAct } from './model.js';
3
- /**
4
- * The small, state-only perception surface shared by catch and delivery.
5
- * Notification planning deliberately remains outside this projection.
6
- */
7
- export interface PerceptionProjection {
8
- directedTo(activity: StoredAct, name: string): boolean;
9
- perceive(activity: StoredAct, name: string): Perception;
10
- cursorFor(name: string): number;
11
- isSeen(name: string, index: number): boolean;
12
- readonly replayedActivityCount: number;
13
- }
14
- /** Build perception over one audience replay of a SquareState snapshot. */
15
- export declare function derivePerceptionProjection(state: SquareState, landed?: LandedAudienceReplay): PerceptionProjection;
1
+ import type { Perception } from './square-core.js';
2
+ import { type StoredAct } from './model.js';
3
+ import type { SquareRead } from './ports.js';
4
+ export declare function directedTo(facts: SquareRead, activity: StoredAct, name: string): boolean;
5
+ /** Audience is settled at landing, never reconstructed from a partial history. */
6
+ export declare function perceiveActivity(facts: SquareRead, activity: StoredAct, name: string): Perception;
@@ -1,28 +1,8 @@
1
- import { formatActivityId, replayLandedAudiences } from './square-core.js';
2
1
  import { sameName } from './model.js';
3
- import { readCursor } from './runtime.js';
4
- /** Build perception over one audience replay of a SquareState snapshot. */
5
- export function derivePerceptionProjection(state, landed = replayLandedAudiences(state.acts)) {
6
- function canonicalName(name) {
7
- return landed.resolveParticipant(name) ?? name;
8
- }
9
- function directedTo(activity, name) {
10
- return activity.kind === 'say' && landed.includes(activity, name);
11
- }
12
- function perceive(activity, name) {
13
- if (activity.kind !== 'say' || sameName(activity.actor, name))
14
- return 'full';
15
- return directedTo(activity, name) ? 'full' : 'presence';
16
- }
17
- function isSeen(name, index) {
18
- const recipient = canonicalName(name);
19
- return state.runtime.observations?.[recipient]?.[formatActivityId(index)]?.state === 'seen';
20
- }
21
- return {
22
- directedTo,
23
- perceive,
24
- cursorFor: (name) => readCursor(state, name, landed),
25
- isSeen,
26
- replayedActivityCount: landed.replayedActivityCount,
27
- };
2
+ export function directedTo(facts, activity, name) {
3
+ return activity.kind === 'say' && facts.directedAt(name, activity.index) !== undefined;
4
+ }
5
+ /** Audience is settled at landing, never reconstructed from a partial history. */
6
+ export function perceiveActivity(facts, activity, name) {
7
+ return activity.kind !== 'say' || sameName(activity.actor, name) || directedTo(facts, activity, name) ? 'full' : 'presence';
28
8
  }
@@ -1,4 +1,4 @@
1
- import { type OpenCodeNativeSession } from './packages/agent-delivery/src/opencode-native.js';
1
+ import { type OpenCodeNativeSession } from '../../packages/agent-delivery/src/opencode-native.js';
2
2
  export interface OpenCodeContextMessage {
3
3
  readonly id?: string;
4
4
  readonly role: string;
@@ -1,20 +1,20 @@
1
- import { nameKey } from './model.js';
2
- import { observeSessionPending, sessionInbox } from './inbox.js';
3
- import { hostLedgerForEnv } from './registry.js';
4
- import { openSquare } from './square-file-adapter.js';
5
- import { closeOpenSquare } from './open-square.js';
6
- import { nativePendingPreview, presentationSuppressesWake, projectPresentationEvidence } from './square-projections.js';
7
- import { presentPending } from './presentation-operations.js';
8
- import { deliverPending } from './delivery-operations.js';
9
- import { createWakeTransport } from './notifications.js';
10
- import { publishWakeRoute } from './routes.js';
11
- import { formatActivityId, parseActivityId } from './square-core.js';
12
- import { createNativeInputId, sendNativeText } from './packages/agent-delivery/src/opencode-native.js';
1
+ import { nameKey } from '../../model.js';
2
+ import { nativePendingPreview, observeSessionPending, sessionInbox } from '../../inbox.js';
3
+ import { hostLedgerForEnv } from '../../registry.js';
4
+ import { openSquare } from '../../open-square.js';
5
+ import { closeOpenSquare } from '../../open-square.js';
6
+ import { presentationSuppressesWake, projectPresentationEvidence } from '../../square-projections.js';
7
+ import { presentPending } from '../../presentation-operations.js';
8
+ import { deliverPending } from '../../delivery-operations.js';
9
+ import { createWakeTransport } from '../../notifications.js';
10
+ import { publishWakeRoute } from '../../routes.js';
11
+ import { formatActivityId, parseActivityId } from '../../square-core.js';
12
+ import { createNativeInputId, sendNativeText } from '../../packages/agent-delivery/src/opencode-native.js';
13
13
  const routeKind = 'opencode-server';
14
14
  const key = (location, participant, index) => `${location}\0${nameKey(participant)}\0${index}`;
15
15
  /** The one native pending preview: the audience/catch/epoch fence before send and inside presentation. */
16
- function preview(state, owner, index) {
17
- return nativePendingPreview(state, { location: owner.location, routeKind, sessionId: owner.session, participant: owner.participant, actIndex: index, epoch: owner.epoch, address: { sessionId: owner.session }, cancelledThrough: owner.cancelledThrough, now: Date.now() });
16
+ function preview(facts, owner, index) {
17
+ return nativePendingPreview(facts, { location: owner.location, routeKind, sessionId: owner.session, participant: owner.participant, actIndex: index, epoch: owner.epoch, address: { sessionId: owner.session }, cancelledThrough: owner.cancelledThrough, now: Date.now() });
18
18
  }
19
19
  function createOpenCodeWakeTransport(target, env, lifetime) {
20
20
  const ledger = hostLedgerForEnv(env);
@@ -36,14 +36,14 @@ function createOpenCodeWakeTransport(target, env, lifetime) {
36
36
  return undefined;
37
37
  const square = await openSquare(owner.location, { hostLedger: ledger, env, signal });
38
38
  try {
39
- const prepared = preview((await square.artifact.read(signal)).state, owner, index);
39
+ const { value: prepared } = await square.artifact.read((facts) => preview(facts, owner, index), signal);
40
40
  if (!prepared)
41
41
  return undefined;
42
42
  const row = { location: owner.location, participant: owner.participant, session: target.sessionId,
43
43
  activity: request.activity, kind: 'wake', outcome: 'dispatching', routeKind, attemptN: request.attemptN, claimToken: request.claimToken,
44
44
  nativeDelivery: { harness: 'opencode', epoch: owner.epoch ?? 0, inputId: createNativeInputId(), payload: prepared.payload } };
45
45
  if (!await ledger.prepareNativeWake(row) || !await (beforeSend?.() ?? Promise.resolve(true)) || signal.aborted
46
- || preview((await square.artifact.read(signal)).state, owner, index)?.payload !== prepared.payload)
46
+ || (await square.artifact.read((facts) => preview(facts, owner, index)?.payload, signal)).value !== prepared.payload)
47
47
  return undefined;
48
48
  return { sending: sendNativeText(target, prepared.payload, { inputId: row.nativeDelivery.inputId, delivery: 'steer',
49
49
  timeoutMs: Math.max(1, deadline - Date.now()), signal }) };
@@ -82,7 +82,7 @@ export async function observeOpenCodeContext(sessionId, messages, env, signal) {
82
82
  continue;
83
83
  const square = await openSquare(row.location, { hostLedger: ledger, env, signal });
84
84
  try {
85
- const prepared = preview((await square.artifact.read(signal)).state, owner, index);
85
+ const { value: prepared } = await square.artifact.read((facts) => preview(facts, owner, index), signal);
86
86
  if (!prepared || prepared.payload !== row.nativeDelivery.payload)
87
87
  continue;
88
88
  const prior = await projectPresentationEvidence({ hostLedger: ledger, location: row.location, participant: row.participant, sessionId, activity: row.activity });
@@ -90,7 +90,7 @@ export async function observeOpenCodeContext(sessionId, messages, env, signal) {
90
90
  continue;
91
91
  await presentPending({ artifact: square.artifact, hostLedger: ledger, location: row.location, participant: row.participant, session: sessionId,
92
92
  activity: row.activity, sink: { present() { } }, markSeen: !prepared.clipped, signal,
93
- current: (state) => !signal.aborted && preview(state, owner, index)?.payload === row.nativeDelivery.payload });
93
+ current: (facts) => !signal.aborted && preview(facts, owner, index)?.payload === row.nativeDelivery.payload });
94
94
  }
95
95
  finally {
96
96
  await closeOpenSquare(square);
@@ -1,5 +1,5 @@
1
- import { type OpenCodeSessionCapability } from './packages/agent-delivery/src/opencode-native.js';
2
- import { type OpenCodeContextMessage } from './opencode-delivery.js';
1
+ import { type OpenCodeSessionCapability } from '../../packages/agent-delivery/src/opencode-native.js';
2
+ import { type OpenCodeContextMessage } from './delivery.js';
3
3
  interface OpenCodePluginContext {
4
4
  readonly app: {
5
5
  readonly version: string;
@@ -1,11 +1,11 @@
1
- import { automaticSessionStart, operationEnv } from './automatic-session.js';
2
- import { hostLedgerForEnv } from './registry.js';
3
- import { canonicalPath } from './canonical-path.js';
4
- import { openSquare } from './square-file-adapter.js';
5
- import { closeOpenSquare } from './open-square.js';
6
- import { Square } from './square-wiring.js';
7
- import { connectNative } from './packages/agent-delivery/src/opencode-native.js';
8
- import { observeOpenCodeContext, publishOpenCodeRoutes, receiveOpenCodePending } from './opencode-delivery.js';
1
+ import { automaticSessionStart, operationEnv } from '../../automatic-session.js';
2
+ import { hostLedgerForEnv } from '../../registry.js';
3
+ import { canonicalPath } from '../../canonical-path.js';
4
+ import { openSquare } from '../../open-square.js';
5
+ import { closeOpenSquare } from '../../open-square.js';
6
+ import { Square } from '../../square-application.js';
7
+ import { connectNative } from '../../packages/agent-delivery/src/opencode-native.js';
8
+ import { observeOpenCodeContext, publishOpenCodeRoutes, receiveOpenCodePending } from './delivery.js';
9
9
  /** Actual OpenCode 2.0.20 server plugin. Harness identity belongs to ctx, never process.env. */
10
10
  export default {
11
11
  id: 'square',
@@ -81,7 +81,7 @@ export default {
81
81
  for (const owner of (await ledger.listPresence({ session: sessionId })).filter((binding) => binding.channel === 'opencode')) {
82
82
  const square = await openSquare(owner.location, { hostLedger: ledger, env, signal: lifetime.signal });
83
83
  try {
84
- await ledger.suppressPresence(owner, (await square.artifact.read(lifetime.signal)).state.runtime.nextActIndex - 1);
84
+ await ledger.suppressPresence(owner, (await square.artifact.read((facts) => facts.header().nextActIndex - 1, lifetime.signal)).value);
85
85
  }
86
86
  finally {
87
87
  await closeOpenSquare(square);
package/dist/ports.d.ts CHANGED
@@ -1,16 +1,161 @@
1
- import type { InboxNotification, SquareState, StoredAct, WakeRoute, WatchLease } from './model.js';
1
+ import type { Act, HardCap, InboxNotification, StoredAct, WakeRoute, WakeRouteKind, WatchLease } from './model.js';
2
2
  export type { ClaimResult, EvidenceClaim, EvidenceRelease, EvidenceGc, EvidenceLookup, EvidenceRecord, HostLedgerPort, PresenceKey, PresenceLookup, PresenceRecord, PresenceClaimResult, PresenceChannel, PresenceResult, } from './host-ledger.js';
3
3
  import type { HostLedgerPort } from './host-ledger.js';
4
- /** Atomic artifact access. Never exposes framing, compression, locks, or storage schema. */
4
+ export type ActivityIndex = number;
5
+ export interface SquareHeader {
6
+ readonly revision: number;
7
+ readonly nextActIndex: number;
8
+ readonly lastActIndex: number;
9
+ readonly hardCap: HardCap;
10
+ readonly throttlePerMinute?: number;
11
+ readonly preamble: readonly string[];
12
+ readonly warmup: readonly string[];
13
+ }
14
+ export interface ParticipantFact {
15
+ readonly name: string;
16
+ readonly firstIndex: number;
17
+ readonly joined: boolean;
18
+ readonly done: boolean;
19
+ readonly sayCount: number;
20
+ readonly lastActiveAt?: number;
21
+ readonly lastJoinIndex?: number;
22
+ }
23
+ export interface AttentionFact {
24
+ readonly listener: string;
25
+ readonly target: string;
26
+ readonly kind: 'listen' | 'ignore';
27
+ readonly sinceIndex: number;
28
+ }
29
+ export interface ActivityHead {
30
+ readonly index: ActivityIndex;
31
+ readonly at: number;
32
+ readonly kind: StoredAct['kind'];
33
+ readonly actor?: string;
34
+ readonly reach?: 'bell';
35
+ readonly reply?: ActivityIndex;
36
+ }
37
+ export interface ActivityFilter {
38
+ readonly afterIndex?: number;
39
+ readonly beforeIndex?: number;
40
+ readonly throughIndex?: number;
41
+ readonly indexes?: readonly ActivityIndex[];
42
+ readonly kinds?: readonly StoredAct['kind'][];
43
+ readonly actors?: readonly string[];
44
+ readonly actorMatch?: 'identity' | 'exact';
45
+ readonly actorRequired?: true;
46
+ readonly afterAt?: number;
47
+ }
48
+ export interface IndexPage {
49
+ readonly edge: 'oldest' | 'newest';
50
+ readonly limit: number;
51
+ }
52
+ export interface FactPage<T> {
53
+ readonly items: readonly T[];
54
+ readonly hasMore: boolean;
55
+ }
56
+ export interface DirectedFilter {
57
+ readonly afterIndex?: number;
58
+ readonly beforeIndex?: number;
59
+ readonly throughIndex?: number;
60
+ readonly unseenOnly?: true;
61
+ readonly senders?: readonly string[];
62
+ readonly attentionOnly?: true;
63
+ }
64
+ export interface DirectedHead {
65
+ readonly index: ActivityIndex;
66
+ readonly at: number;
67
+ readonly actor: string;
68
+ readonly recipient: string;
69
+ readonly route: 'mention' | 'reply' | 'attention' | 'bell';
70
+ }
71
+ export interface LandedRecipient {
72
+ readonly recipient: string;
73
+ readonly route: DirectedHead['route'];
74
+ }
75
+ export interface ReceiptFacts {
76
+ readonly latestAt?: number;
77
+ readonly latestActorActivityIndex?: number;
78
+ }
79
+ export interface ReceiptAssignment {
80
+ readonly owner: string;
81
+ readonly index: ActivityIndex;
82
+ }
83
+ export type JsonValue = null | boolean | number | string | readonly JsonValue[] | {
84
+ readonly [key: string]: JsonValue;
85
+ };
86
+ export interface RouteFact {
87
+ readonly location: string;
88
+ readonly participant: string;
89
+ readonly sessionId: string;
90
+ readonly channel: string;
91
+ readonly kind: WakeRouteKind;
92
+ readonly address: Readonly<Record<string, string>>;
93
+ readonly updatedAt: number;
94
+ readonly epoch?: JsonValue;
95
+ readonly extra: Readonly<Record<string, JsonValue>>;
96
+ }
97
+ /** Domain facts exist only during a synchronous artifact callback. Values are detached. */
98
+ export interface SquareRead {
99
+ header(): SquareHeader;
100
+ participant(requestedName: string): ParticipantFact | undefined;
101
+ roster(): readonly ParticipantFact[];
102
+ attention(listener: string): readonly AttentionFact[];
103
+ incomingAttention(sender: string): readonly AttentionFact[];
104
+ hold(): {
105
+ readonly active: boolean;
106
+ readonly at?: number;
107
+ readonly reason?: string;
108
+ readonly actor?: string;
109
+ };
110
+ activity(index: ActivityIndex): StoredAct | undefined;
111
+ activityHeads(filter: ActivityFilter, page: IndexPage): FactPage<ActivityHead>;
112
+ activities(indexes: readonly ActivityIndex[]): readonly StoredAct[];
113
+ activityCount(filter: ActivityFilter): number;
114
+ contextIndexes(centers: readonly ActivityIndex[], before: number, after: number): readonly ActivityIndex[];
115
+ sayOrdinals(indexes: readonly ActivityIndex[]): Readonly<Record<number, number>>;
116
+ recentSayTimes(afterExclusive: number, takeLatest: number, actor?: string, bellOnly?: true): readonly number[];
117
+ audience(index: ActivityIndex): readonly LandedRecipient[];
118
+ directed(recipient: string, filter: DirectedFilter, page: IndexPage): FactPage<DirectedHead>;
119
+ directedAt(recipient: string, index: ActivityIndex): DirectedHead | undefined;
120
+ directedCount(recipient: string, filter: DirectedFilter): number;
121
+ pendingAudience(afterExclusive: number, throughInclusive: number, activityLimit: number): {
122
+ readonly groups: readonly {
123
+ readonly index: ActivityIndex;
124
+ readonly at: number;
125
+ readonly actor: string;
126
+ readonly recipients: readonly LandedRecipient[];
127
+ }[];
128
+ readonly hasMore: boolean;
129
+ };
130
+ receipt(requestedName: string, index: ActivityIndex): {
131
+ readonly at: number;
132
+ } | undefined;
133
+ receiptFacts(requestedName: string, afterIndex: number): ReceiptFacts;
134
+ receiptAssignments(): readonly ReceiptAssignment[];
135
+ lease(requestedName: string): WatchLease | undefined;
136
+ routes(): readonly RouteFact[];
137
+ }
138
+ export interface SquareWrite extends SquareRead {
139
+ land(activity: Act & {
140
+ readonly at: number;
141
+ }): StoredAct;
142
+ markSeen(requestedName: string, indexes: readonly ActivityIndex[], at: number): number;
143
+ putLease(requestedName: string, lease: WatchLease): boolean;
144
+ removeLease(requestedName: string, expectedLeaseId: string): boolean;
145
+ replaceRoutes(routes: readonly RouteFact[]): boolean;
146
+ }
147
+ export interface SquareReadResult<R> {
148
+ readonly value: R;
149
+ readonly version: number;
150
+ }
151
+ export interface SquareCommitResult<R> extends SquareReadResult<R> {
152
+ readonly changed: boolean;
153
+ }
154
+ /** Atomic artifact access; no framing, SQL or lazy transaction capability escapes. */
5
155
  export interface SquareArtifactPort {
6
- read(signal?: AbortSignal): Promise<{
7
- state: SquareState;
8
- version: number;
9
- }>;
10
- transact<R>(fn: (state: SquareState, version: number) => {
11
- state?: SquareState;
12
- result: R;
13
- }, signal?: AbortSignal): Promise<R>;
156
+ read<R>(select: (facts: SquareRead) => R, signal?: AbortSignal): Promise<SquareReadResult<R>>;
157
+ transact<R>(change: (facts: SquareWrite) => R, signal?: AbortSignal): Promise<SquareCommitResult<R>>;
158
+ revision(signal?: AbortSignal): Promise<number>;
14
159
  changed(sinceVersion: number, timeoutMs: number, signal?: AbortSignal): Promise<boolean>;
15
160
  close(): Promise<void>;
16
161
  }
@@ -121,5 +266,5 @@ export interface PresentPendingInput {
121
266
  /** Boundary previews that were clipped remain pending for a later full presentation. */
122
267
  readonly markSeen?: boolean;
123
268
  /** Receiver-specific fences checked inside the artifact observation transaction. */
124
- readonly current?: (state: SquareState) => boolean;
269
+ readonly current?: (facts: SquareRead) => boolean;
125
270
  }
@@ -1,3 +1,3 @@
1
1
  import type { PresentationResult, PresentPendingInput } from './ports.js';
2
- /** Present one pending activity; artifact seen remains the authoritative receipt. */
2
+ /** Presentation evidence is not a receipt; only the fenced receipt transaction marks seen. */
3
3
  export declare function presentPending(input: PresentPendingInput): Promise<PresentationResult>;
@@ -1,65 +1,56 @@
1
1
  import { formatActivityId, parseActivityId } from './square-core.js';
2
- import { deriveDeliveryModel } from './delivery.js';
3
- import { recordObservation } from './runtime.js';
4
- /** Present one pending activity; artifact seen remains the authoritative receipt. */
2
+ import { pendingHead, sameJoin } from './delivery.js';
3
+ /** Presentation evidence is not a receipt; only the fenced receipt transaction marks seen. */
5
4
  export async function presentPending(input) {
6
5
  const index = typeof input.activity === 'number' ? input.activity : parseActivityId(input.activity);
7
6
  if (index === undefined)
8
7
  return { presented: false };
9
- let before;
10
- try {
11
- before = await input.artifact.read(input.signal);
12
- }
13
- catch {
14
- return { presented: false };
15
- }
16
- const delivery = deriveDeliveryModel(before.state);
17
- const item = before.state.acts.find((activity) => activity.index === index);
18
- if (input.current?.(before.state) === false || item === undefined || delivery.isSeen(input.participant, index) || !delivery.pendingFor(input.participant).some((notification) => notification.item.index === index))
8
+ const before = await input.artifact.read((facts) => {
9
+ if (input.current?.(facts) === false || !pendingHead(facts, input.participant, index))
10
+ return undefined;
11
+ return { item: facts.activity(index), participant: facts.participant(input.participant) };
12
+ }, input.signal).catch(() => undefined);
13
+ if (before?.value === undefined)
19
14
  return { presented: false };
15
+ const { item, participant } = before.value;
16
+ const current = (facts) => sameJoin(facts, participant) && input.current?.(facts) !== false
17
+ && pendingHead(facts, participant.name, index) !== undefined;
18
+ const coordinate = { location: input.location, participant: input.participant, session: input.session, activity: formatActivityId(index), kind: 'presentation' };
20
19
  let claimToken;
20
+ const release = async () => {
21
+ if (claimToken !== undefined)
22
+ await input.hostLedger.releaseEvidence({ ...coordinate, claimToken });
23
+ };
21
24
  if (input.hostLedger !== undefined && input.session !== undefined) {
22
- const claim = await input.hostLedger.claimEvidence({ location: input.location, participant: input.participant, session: input.session, activity: formatActivityId(index), kind: 'presentation', leaseMs: input.timeoutMs ?? 5000 });
25
+ const claim = await input.hostLedger.claimEvidence({ ...coordinate, leaseMs: input.timeoutMs ?? 5000 });
23
26
  if (claim.status !== 'acquired')
24
27
  return { presented: false };
25
28
  claimToken = claim.claimToken;
26
- let current;
27
- try {
28
- current = await input.artifact.read(input.signal);
29
- }
30
- catch {
31
- await input.hostLedger.releaseEvidence({ location: input.location, participant: input.participant, session: input.session, activity: formatActivityId(index), kind: 'presentation', claimToken }).catch(() => undefined);
32
- return { presented: false };
33
- }
34
- if (input.current?.(current.state) === false || deriveDeliveryModel(current.state).isSeen(input.participant, index)) {
35
- await input.hostLedger.releaseEvidence({ location: input.location, participant: input.participant, session: input.session, activity: formatActivityId(index), kind: 'presentation', claimToken }).catch(() => undefined);
29
+ const fresh = await input.artifact.read(current, input.signal).catch(() => undefined);
30
+ if (!fresh?.value) {
31
+ await release().catch(() => undefined);
36
32
  return { presented: false };
37
33
  }
38
- try {
39
- await input.sink.present(item);
40
- }
41
- catch (error) {
42
- await input.hostLedger.appendEvidence({ location: input.location, participant: input.participant, session: input.session, activity: formatActivityId(index), kind: 'presentation', outcome: 'failed', message: error instanceof Error ? error.message : String(error), claimToken });
43
- throw error;
44
- }
45
34
  }
46
- else
35
+ try {
47
36
  await input.sink.present(item);
48
- const committed = await input.artifact.transact((state) => {
49
- const current = deriveDeliveryModel(state);
50
- if (input.current?.(state) === false || current.isSeen(input.participant, index) || !current.pendingFor(input.participant).some((notification) => notification.item.index === index))
51
- return { result: false };
52
- if (input.markSeen === false)
53
- return { result: true };
54
- const changed = recordObservation(state, input.participant, index, 'seen', input.now ?? Date.now());
55
- return changed ? { state, result: true } : { result: false };
37
+ }
38
+ catch (error) {
39
+ if (claimToken !== undefined)
40
+ await input.hostLedger.appendEvidence({ ...coordinate, outcome: 'failed', message: error instanceof Error ? error.message : String(error), claimToken });
41
+ throw error;
42
+ }
43
+ const { value: committed } = await input.artifact.transact((facts) => {
44
+ if (!current(facts))
45
+ return false;
46
+ return input.markSeen === false || facts.markSeen(participant.name, [index], input.now ?? Date.now()) > 0;
56
47
  }, input.signal);
57
48
  if (!committed) {
58
- if (input.hostLedger !== undefined && input.session !== undefined && claimToken !== undefined)
59
- await input.hostLedger.releaseEvidence({ location: input.location, participant: input.participant, session: input.session, activity: formatActivityId(index), kind: 'presentation', claimToken });
49
+ await release();
60
50
  return { presented: false };
61
51
  }
62
- if (input.hostLedger !== undefined && input.session !== undefined && claimToken !== undefined)
63
- await input.hostLedger.appendEvidence({ location: input.location, participant: input.participant, session: input.session, activity: formatActivityId(index), kind: 'presentation', outcome: input.markSeen === false ? 'clipped' : 'presented', ...(input.markSeen === false ? { message: 'presentation clipped' } : {}), claimToken });
52
+ if (claimToken !== undefined)
53
+ await input.hostLedger.appendEvidence({ ...coordinate, outcome: input.markSeen === false ? 'clipped' : 'presented',
54
+ ...(input.markSeen === false ? { message: 'presentation clipped' } : {}), claimToken });
64
55
  return { presented: true, activity: item };
65
56
  }
@@ -1,7 +1,7 @@
1
- import { type StoredAct, type SquareState, type PublicAct, type RoomChangeAct } from './model.js';
1
+ import { type StoredAct, type RoomChangeAct } from './model.js';
2
2
  export { participantIdentity } from './participant-identity.js';
3
3
  import { type Perception } from './square-core.js';
4
- import type { UnreadActivitySummary, ParticipantStatus } from './decisions.js';
4
+ import type { Activity, UnreadActivitySummary, ParticipantSummary } from './square-facade.js';
5
5
  export type WatchStatus = 'stale' | 'empty-now' | 'quorum' | 'capped';
6
6
  interface HeaderOptions {
7
7
  participantCount?: number;
@@ -23,7 +23,7 @@ interface ActivityLimitOptions extends ParticipantOutputOptions {
23
23
  interface ActivityBlockedOptions extends ParticipantOutputOptions {
24
24
  retryCommand: string;
25
25
  forceCommand: string;
26
- activitySummaries: UnreadActivitySummary[];
26
+ activitySummaries: readonly UnreadActivitySummary[];
27
27
  draftPath?: string;
28
28
  }
29
29
  interface ExpressWaitingOptions {
@@ -44,7 +44,7 @@ interface WatchStatusOptions extends ParticipantOutputOptions {
44
44
  status: WatchStatus;
45
45
  idleMs?: number;
46
46
  presence?: {
47
- participants: ParticipantStatus[];
47
+ participants: readonly ParticipantSummary[];
48
48
  now: number;
49
49
  };
50
50
  showCatchHint?: boolean;
@@ -58,6 +58,7 @@ export declare function formatRefusal(squarePath: string, bodyLines: string[], o
58
58
  participantCount?: number;
59
59
  held?: boolean;
60
60
  }): string;
61
+ export declare function refusalText(error: unknown): string;
61
62
  export declare function quoteShell(value: string): string;
62
63
  export declare function commandPrefix(squarePath: string): string;
63
64
  export declare function participantCommandPrefix(squarePath: string, name: string): string;
@@ -66,44 +67,42 @@ export declare function joinRecoveryCommand(squarePath: string, name: string): s
66
67
  /** Taking a name is an explicit recovery choice, never an automatic retry. */
67
68
  export declare function takeoverRecoveryLines(squarePath: string, name: string): string[];
68
69
  export declare function participantsRecoveryCommand(squarePath: string): string;
69
- export declare function renderPresenceLines(participants: ParticipantStatus[], now: number, limit?: number): string[];
70
+ export declare function renderPresenceLines(recent: readonly ParticipantSummary[], now: number, limit?: number): string[];
70
71
  export declare function expressHintLine(ownActivityCount: number): string | undefined;
71
72
  export declare function truncateChars(body: string, maxChars: number): {
72
73
  text: string;
73
74
  remaining: number;
74
75
  };
75
76
  export declare function truncateExternalDiagnostic(diagnostic: string): string;
76
- export declare function renderRoomChangeText(event: RoomChangeAct): string;
77
+ export declare function renderRoomChangeText(event: RoomChangeAct | Activity): string;
77
78
  interface RenderedEvent {
78
79
  text: string;
79
80
  /** The rendered body was cut short by `preview`. */
80
81
  bodyClipped: boolean;
81
82
  }
82
- export declare function renderEventCli(event: StoredAct, opts?: {
83
+ export declare function renderEventCli(event: StoredAct | Activity, opts?: {
83
84
  now?: number;
84
85
  preview?: number;
85
86
  actNumber?: number;
86
87
  mention?: string;
87
88
  }): string;
88
- export declare function renderAmbientEventFacts(event: StoredAct, viewer: string, opts?: {
89
+ export declare function renderAmbientEventFacts(event: StoredAct | Activity, viewer: string, opts?: {
89
90
  now?: number;
90
91
  preview?: number;
91
92
  presencePreview?: number;
92
93
  actNumber?: number;
93
94
  mention?: string;
94
- squareState?: SquareState;
95
95
  perception?: Perception;
96
96
  }): RenderedEvent;
97
- export declare function renderAmbientEvent(event: StoredAct, viewer: string, opts?: {
97
+ export declare function renderAmbientEvent(event: StoredAct | Activity, viewer: string, opts?: {
98
98
  now?: number;
99
99
  preview?: number;
100
100
  presencePreview?: number;
101
101
  actNumber?: number;
102
102
  mention?: string;
103
- squareState?: SquareState;
104
103
  perception?: Perception;
105
104
  }): string;
106
- export declare function renderPendingFeed(history: StoredAct[], publicItems: PublicAct[], viewer: string | undefined, squareState: SquareState): string;
105
+ export declare function renderPendingFeed(publicItems: readonly StoredAct[], sayNumbers: Readonly<Record<number, number>>, perceptions: Readonly<Record<number, Perception>>, viewer: string): string;
107
106
  export declare function renderActivityBlocked(opts: ActivityBlockedOptions): string;
108
107
  /** An indeterminate or already-committed send only points to inspection, never resend. */
109
108
  export declare function renderActivityUncertain(opts: ParticipantOutputOptions & {
@@ -114,7 +113,7 @@ export declare function renderActivityUncertain(opts: ParticipantOutputOptions &
114
113
  export declare function renderExpressWaiting(opts: ExpressWaitingOptions): string;
115
114
  export declare function renderExpressNoWait(opts: ExpressNoWaitOptions): string;
116
115
  export declare function renderPresenceAnchor(names: readonly string[]): string;
117
- export declare function renderGrepActivitiesView(visible: StoredAct[], totalMatches: number, noTruncate: boolean | undefined, squarePath: string, pattern: string, fixed?: boolean, perception?: (act: StoredAct) => Perception): string;
116
+ export declare function renderGrepActivitiesView(visible: readonly StoredAct[], totalMatches: number, noTruncate: boolean | undefined, squarePath: string, pattern: string, fixed?: boolean, perception?: (act: StoredAct) => Perception): string;
118
117
  export declare function renderActivityLimit(opts: ActivityLimitOptions): string;
119
118
  export declare function renderWatchAlreadyActive(opts: ParticipantOutputOptions): string;
120
119
  export declare function renderWatchForceTakeover(_opts: ParticipantOutputOptions): string;
@@ -123,9 +122,3 @@ export declare function renderWatchReplaced(_opts: ParticipantOutputOptions): st
123
122
  export declare function renderWatchStatus(opts: WatchStatusOptions): string;
124
123
  export declare function renderDoctorClean(): string;
125
124
  export declare function renderDoctorUnfixable(reason: string): string;
126
- export declare function renderWatchOutput(history: StoredAct[], publicItems: PublicAct[], opts: {
127
- mention?: string;
128
- viewer: string;
129
- squareState?: SquareState;
130
- perceptions?: ReadonlyMap<number, Perception>;
131
- }): string;