@astrosheep/square 0.3.30 → 0.3.31

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 (77) hide show
  1. package/claude-plugin/.claude-plugin/plugin.json +1 -1
  2. package/claude-plugin/hooks/hooks.json +0 -3
  3. package/claude-plugin/skills/square/SKILL.md +5 -4
  4. package/codex-plugin/.codex-plugin/plugin.json +1 -1
  5. package/dist/activity.js +7 -3
  6. package/dist/artifact.js +7 -50
  7. package/dist/automatic-session.js +31 -14
  8. package/dist/boundary-presentation.d.ts +1 -1
  9. package/dist/boundary-presentation.js +58 -14
  10. package/dist/catch-decisions.d.ts +17 -0
  11. package/dist/catch-decisions.js +53 -0
  12. package/dist/claude-hook.d.ts +1 -1
  13. package/dist/cli/context.js +10 -2
  14. package/dist/cli/observation-commands.d.ts +5 -1
  15. package/dist/cli/observation-commands.js +68 -33
  16. package/dist/cli/square-commands.js +17 -10
  17. package/dist/codex-hook.d.ts +1 -1
  18. package/dist/decisions.js +2 -2
  19. package/dist/delivery-health.d.ts +1 -1
  20. package/dist/delivery-operations.d.ts +25 -0
  21. package/dist/delivery-operations.js +124 -0
  22. package/dist/help.js +1 -1
  23. package/dist/host-ledger-file-adapter.d.ts +34 -0
  24. package/dist/host-ledger-file-adapter.js +165 -0
  25. package/dist/host-ledger.d.ts +160 -0
  26. package/dist/host-ledger.js +1 -0
  27. package/dist/inbox.d.ts +2 -2
  28. package/dist/inbox.js +22 -14
  29. package/dist/index.d.ts +4 -1
  30. package/dist/index.js +1 -0
  31. package/dist/landing.d.ts +18 -14
  32. package/dist/landing.js +29 -113
  33. package/dist/model.d.ts +6 -17
  34. package/dist/notifications.d.ts +6 -10
  35. package/dist/notifications.js +71 -192
  36. package/dist/open-square.d.ts +4 -4
  37. package/dist/open-square.js +1 -1
  38. package/dist/ports.d.ts +127 -0
  39. package/dist/ports.js +1 -0
  40. package/dist/presence.d.ts +1 -2
  41. package/dist/presence.js +13 -46
  42. package/dist/presentation-operations.d.ts +3 -0
  43. package/dist/presentation-operations.js +51 -0
  44. package/dist/presentation.d.ts +2 -2
  45. package/dist/presentation.js +11 -7
  46. package/dist/presented.d.ts +1 -12
  47. package/dist/presented.js +6 -75
  48. package/dist/registry.d.ts +10 -10
  49. package/dist/registry.js +48 -113
  50. package/dist/routes.d.ts +6 -33
  51. package/dist/routes.js +6 -170
  52. package/dist/runtime.d.ts +1 -1
  53. package/dist/runtime.js +3 -4
  54. package/dist/square-actions.d.ts +32 -0
  55. package/dist/square-actions.js +167 -0
  56. package/dist/square-facade.d.ts +7 -7
  57. package/dist/square-file-adapter.d.ts +4 -3
  58. package/dist/square-file-adapter.js +22 -5
  59. package/dist/square-projections.d.ts +68 -0
  60. package/dist/square-projections.js +87 -0
  61. package/dist/square-storage.d.ts +2 -2
  62. package/dist/square-storage.js +2 -2
  63. package/dist/square-wiring.d.ts +3 -3
  64. package/dist/square-wiring.js +44 -29
  65. package/dist/views.d.ts +8 -2
  66. package/dist/views.js +28 -24
  67. package/dist/wake-attempts.d.ts +29 -22
  68. package/dist/wake-attempts.js +36 -119
  69. package/dist/wake-evidence.d.ts +6 -18
  70. package/dist/wake-evidence.js +17 -80
  71. package/dist/wakes.d.ts +2 -16
  72. package/dist/wakes.js +7 -27
  73. package/dist/watch.js +2 -3
  74. package/extensions/square-pi.js +7 -2
  75. package/package.json +1 -1
  76. package/skills/brainstorm/SKILL.md +2 -2
  77. package/skills/square/SKILL.md +5 -4
@@ -0,0 +1,68 @@
1
+ import { type SquareState } from './model.js';
2
+ import type { HostLedgerPort, SquareArtifactPort, PresentationEvidenceProjection, PresentationProjection, SessionBindingProjection } from './ports.js';
3
+ import { deriveDeliveryModel } from './delivery.js';
4
+ import type { WakeRoute, WakeRouteKind } from './model.js';
5
+ export declare function projectSessionBindings(input: {
6
+ readonly hostLedger: HostLedgerPort;
7
+ readonly sessionId: string;
8
+ readonly location?: string;
9
+ readonly scopes?: readonly import('./host-ledger.js').HostLedgerScope[];
10
+ readonly now?: number;
11
+ }): Promise<readonly SessionBindingProjection[]>;
12
+ export declare function sessionIdsFromEnvironment(env?: NodeJS.ProcessEnv): readonly string[];
13
+ export declare function projectLocalParticipantBinding(input: {
14
+ readonly hostLedger: HostLedgerPort;
15
+ readonly location: string;
16
+ readonly participant: string;
17
+ readonly sessionIds: readonly string[];
18
+ readonly now?: number;
19
+ }): Promise<SessionBindingProjection | undefined>;
20
+ export declare function projectPresentation(input: {
21
+ readonly artifact: SquareArtifactPort;
22
+ readonly binding: SessionBindingProjection;
23
+ readonly now?: number;
24
+ }): Promise<PresentationProjection>;
25
+ export declare function projectPresentationEvidence(input: {
26
+ readonly hostLedger: HostLedgerPort;
27
+ readonly location?: string;
28
+ readonly participant?: string;
29
+ readonly sessionId?: string;
30
+ readonly activity?: string;
31
+ readonly now?: number;
32
+ }): Promise<readonly PresentationEvidenceProjection[]>;
33
+ export interface WakeAttempt {
34
+ readonly at: number;
35
+ readonly attention: {
36
+ readonly squarePath: string;
37
+ readonly actIndex: number;
38
+ readonly recipient: string;
39
+ };
40
+ readonly routeKind: WakeRouteKind;
41
+ readonly outcome: 'accepted' | 'unknown' | 'failed';
42
+ readonly signature?: string;
43
+ readonly attemptN: number;
44
+ readonly session?: string;
45
+ readonly message?: string;
46
+ readonly diagnostic?: unknown;
47
+ }
48
+ export declare function terminalWakeEvidence(attempts: readonly WakeAttempt[]): WakeAttempt | undefined;
49
+ export declare function isWakeRouteAttemptable(route: Pick<WakeRoute, 'kind' | 'updatedAt'>, attempts: readonly WakeAttempt[]): boolean;
50
+ export declare function hasAttemptableWakeRoute(routes: readonly Pick<WakeRoute, 'kind' | 'updatedAt'>[], attempts: readonly WakeAttempt[]): boolean;
51
+ export interface WakeEvidence {
52
+ readonly delivered: boolean;
53
+ readonly presented: boolean;
54
+ readonly attempts: readonly WakeAttempt[];
55
+ readonly terminal?: WakeAttempt;
56
+ readonly attemptableRoutes: readonly WakeRoute[];
57
+ }
58
+ export interface WakeEvidenceProjection {
59
+ evidence(recipient: string, actIndex: number): WakeEvidence;
60
+ }
61
+ export declare function projectWakeEvidenceFromState(input: {
62
+ readonly location: string;
63
+ readonly state: SquareState;
64
+ readonly hostLedger: HostLedgerPort;
65
+ readonly now: number;
66
+ readonly delivery?: ReturnType<typeof deriveDeliveryModel>;
67
+ }): Promise<WakeEvidenceProjection>;
68
+ export declare function wakeIsEligible(evidence: WakeEvidence): boolean;
@@ -0,0 +1,87 @@
1
+ import { formatActivityId, parseActivityId } from './square-core.js';
2
+ import { nameKey } from './model.js';
3
+ import { deriveDeliveryModel } from './delivery.js';
4
+ import { freshWatchLease } from './runtime.js';
5
+ function bindingProjection(record) {
6
+ return {
7
+ location: record.location,
8
+ participant: record.participant,
9
+ sessionId: record.session,
10
+ channel: record.channel,
11
+ ...(record.route === undefined ? {} : {
12
+ route: {
13
+ location: record.location,
14
+ participant: record.participant,
15
+ sessionId: record.session,
16
+ channel: record.channel,
17
+ kind: record.route.kind,
18
+ address: { ...record.route.address },
19
+ updatedAt: record.updatedAt ?? 0,
20
+ },
21
+ }),
22
+ updatedAt: record.updatedAt ?? 0,
23
+ };
24
+ }
25
+ export async function projectSessionBindings(input) {
26
+ const rows = await input.hostLedger.listPresence({ location: input.location, session: input.sessionId, scopes: input.scopes ?? ['user', 'local'], now: input.now });
27
+ return rows.map(bindingProjection);
28
+ }
29
+ export function sessionIdsFromEnvironment(env = process.env) {
30
+ return [env.CLAUDE_CODE_SESSION_ID, env.CODEX_THREAD_ID, env.OPENCODE_SESSION_ID, env.SQUARE_PI_SESSION_ID, env.PASEO_AGENT_ID]
31
+ .map((value) => value?.trim()).filter((value) => Boolean(value));
32
+ }
33
+ export async function projectLocalParticipantBinding(input) {
34
+ const bindings = (await Promise.all(input.sessionIds.map((sessionId) => projectSessionBindings({ hostLedger: input.hostLedger, location: input.location, sessionId, scopes: ['user', 'local'], now: input.now })))).flat();
35
+ return bindings.find((binding) => binding.participant.toLocaleLowerCase() === input.participant.toLocaleLowerCase());
36
+ }
37
+ export async function projectPresentation(input) {
38
+ const { state } = await input.artifact.read();
39
+ const delivery = deriveDeliveryModel(state);
40
+ const known = delivery.knownParticipant(input.binding.participant);
41
+ if (known === undefined || !delivery.joinedRecipients().some((recipient) => recipient.toLocaleLowerCase() === known.toLocaleLowerCase()))
42
+ return { binding: input.binding, joined: false, notifications: [] };
43
+ const binding = input.binding.participant === known ? input.binding : { ...input.binding, participant: known };
44
+ const notifications = delivery.pendingFor(known).map(({ item, route }) => ({ actIndex: item.index, actor: item.actor, at: item.at, route, body: item.body }));
45
+ const lease = freshWatchLease(state, known, input.now ?? Date.now());
46
+ return { binding, joined: true, notifications, ...(lease === undefined ? {} : { catchLease: lease }) };
47
+ }
48
+ export async function projectPresentationEvidence(input) {
49
+ const rows = await input.hostLedger.listEvidence({ kind: 'presentation', location: input.location, participant: input.participant, session: input.sessionId, activity: input.activity, now: input.now });
50
+ return rows.map((row) => ({ location: row.location, participant: row.participant, sessionId: row.session, activity: row.activity, outcome: row.outcome, ...(row.at === undefined ? {} : { at: row.at }) }));
51
+ }
52
+ export function terminalWakeEvidence(attempts) { return attempts.findLast((attempt) => attempt.outcome === 'accepted'); }
53
+ export function isWakeRouteAttemptable(route, attempts) {
54
+ if (terminalWakeEvidence(attempts) !== undefined)
55
+ return false;
56
+ const failed = attempts.findLast((attempt) => attempt.routeKind === route.kind && attempt.outcome === 'failed');
57
+ return failed === undefined || route.updatedAt > failed.at;
58
+ }
59
+ export function hasAttemptableWakeRoute(routes, attempts) { return routes.some((route) => isWakeRouteAttemptable(route, attempts)); }
60
+ export async function projectWakeEvidenceFromState(input) {
61
+ const delivery = input.delivery ?? deriveDeliveryModel(input.state);
62
+ const bindings = (await input.hostLedger.listPresence({ location: input.location, scopes: ['user'], now: input.now })).map(bindingProjection);
63
+ const wakeRecords = await input.hostLedger.listEvidence({ location: input.location, kind: 'wake', now: input.now });
64
+ const attemptsByBinding = new Map();
65
+ for (const record of wakeRecords) {
66
+ const actIndex = parseActivityId(record.activity);
67
+ if (actIndex === undefined || record.routeKind === undefined || typeof record.attemptN !== 'number')
68
+ continue;
69
+ const attempt = { attention: { squarePath: record.location, recipient: record.participant, actIndex }, outcome: record.outcome, at: record.at ?? input.now, routeKind: record.routeKind, attemptN: record.attemptN, ...(record.signature === undefined ? {} : { signature: record.signature }), ...(record.session === undefined ? {} : { session: record.session }), ...(record.message === undefined ? {} : { message: record.message }), ...(record.diagnostic === undefined ? {} : { diagnostic: record.diagnostic }) };
70
+ const key = JSON.stringify([nameKey(record.participant), actIndex, record.session]);
71
+ const existing = attemptsByBinding.get(key) ?? [];
72
+ existing.push(attempt);
73
+ attemptsByBinding.set(key, existing);
74
+ }
75
+ const presentedRows = await projectPresentationEvidence({ hostLedger: input.hostLedger, location: input.location, now: input.now });
76
+ return {
77
+ evidence(recipient, actIndex) {
78
+ const recipientBindings = bindings.filter((binding) => nameKey(binding.participant) === nameKey(recipient));
79
+ const routes = recipientBindings.flatMap((binding) => binding.route === undefined ? [] : [binding.route]);
80
+ const attempts = recipientBindings.flatMap((binding) => attemptsByBinding.get(JSON.stringify([nameKey(recipient), actIndex, binding.sessionId])) ?? []);
81
+ const terminal = terminalWakeEvidence(attempts);
82
+ const presented = presentedRows.some((row) => row.activity === formatActivityId(actIndex) && row.participant.toLocaleLowerCase() === recipient.toLocaleLowerCase() && recipientBindings.some((binding) => binding.sessionId === row.sessionId));
83
+ return { delivered: delivery.isSeen(recipient, actIndex), presented, attempts, ...(terminal === undefined ? {} : { terminal }), attemptableRoutes: terminal === undefined ? routes.filter((route) => isWakeRouteAttemptable(route, attempts)) : [] };
84
+ },
85
+ };
86
+ }
87
+ export function wakeIsEligible(evidence) { return !evidence.delivered && evidence.terminal === undefined && evidence.attemptableRoutes.length > 0; }
@@ -12,9 +12,9 @@ export declare function probeSquareFile(squarePath: string): Promise<SquareState
12
12
  export declare function diagnoseSquareFile(squarePath: string): ReturnType<typeof diagnoseArtifactFile>;
13
13
  export declare function writeSquareSnapshot(squarePath: string, squareState: SquareState): Promise<void>;
14
14
  export declare function withSquareFileLock<T>(squarePath: string, fn: () => T | Promise<T>): Promise<T>;
15
- /** In-process cell for fast application tests and embedded consumers. */
15
+ /** In-process cell for fast behavior tests and embedded consumers. */
16
16
  export declare function createMemoryCell(initial: SquareState): StateCell;
17
17
  /** File-backed cell retaining the existing .square codec and lock boundary. */
18
18
  export declare function createFileCell(squarePath: string): StateCell;
19
- /** Application-facing file cell factory; keeps storage choice behind this module. */
19
+ /** Consumer-facing file cell factory; keeps storage choice behind this module. */
20
20
  export declare function openSquareCell(squarePath: string): StateCell;
@@ -33,7 +33,7 @@ function assertCellOpen(closed) {
33
33
  if (closed)
34
34
  throw new Error('StateCell is closed');
35
35
  }
36
- /** In-process cell for fast application tests and embedded consumers. */
36
+ /** In-process cell for fast behavior tests and embedded consumers. */
37
37
  export function createMemoryCell(initial) {
38
38
  let state = cloneState(initial);
39
39
  let version = 0;
@@ -182,7 +182,7 @@ export function createFileCell(squarePath) {
182
182
  },
183
183
  };
184
184
  }
185
- /** Application-facing file cell factory; keeps storage choice behind this module. */
185
+ /** Consumer-facing file cell factory; keeps storage choice behind this module. */
186
186
  export function openSquareCell(squarePath) {
187
187
  return createFileCell(squarePath);
188
188
  }
@@ -1,9 +1,9 @@
1
- import { type OpenSquare } from './open-square.js';
2
1
  import type { Activity, HistoryQuery, ParticipantStatus, SquareSnapshot } from './square-facade.js';
3
2
  import type { Participant, SquareAtInput, SquareBuildInput } from './square-facade.js';
4
3
  export declare class Square {
5
4
  readonly location: string;
6
5
  private readonly square;
6
+ private readonly context;
7
7
  private constructor();
8
8
  static at(input: SquareAtInput): Promise<Square>;
9
9
  static build(input: SquareBuildInput): Promise<Square>;
@@ -22,9 +22,9 @@ export declare class Square {
22
22
  history(query?: HistoryQuery): Promise<Activity[]>;
23
23
  recognize(env: NodeJS.ProcessEnv): Promise<Participant | null>;
24
24
  close(): Promise<void>;
25
+ reconcileBinding(): Promise<import("./host-ledger.js").ReconcileBindingResult>;
25
26
  }
26
- export declare function markBoundarySeen(squarePath: string, name: string, ownerId: string | undefined, actIndexes: readonly number[], at?: number): Promise<void>;
27
- export declare function markNotificationNotified(square: OpenSquare, name: string, actIndex: number, ownerId: string | undefined, at?: number): Promise<void>;
27
+ export declare function markBoundarySeen(squarePath: string, name: string, actIndexes: readonly number[], at?: number): Promise<void>;
28
28
  export declare function openParticipant(input: SquareAtInput, name: string): Promise<{
29
29
  readonly participant: Participant;
30
30
  close(): Promise<void>;
@@ -1,50 +1,56 @@
1
1
  import { closeOpenSquare } from './open-square.js';
2
2
  import { buildMemorySquare, buildSquare, openSquare } from './square-file-adapter.js';
3
- import { done, express, hold, ignore, implicitJoin, join, listen, listening, resume } from './landing.js';
4
- import { catchUp, markBoundarySeen as recordBoundarySeen, markNotificationNotified as recordNotificationNotified } from './presence.js';
5
- import { currentParticipant, history, participantHistory, participants, resolveParticipant, snapshot } from './views.js';
6
- import { localParticipantName } from './registry.js';
3
+ import { catchUp, done, express, hold, ignore, implicitJoin, join, listen, listening, resume, } from './square-actions.js';
4
+ import { markBoundarySeen as recordBoundarySeen } from './presence.js';
5
+ import { history, participantHistory, participants, resolveParticipant, snapshot } from './views.js';
6
+ import { currentParticipant } from './views.js';
7
+ import { projectSessionBindings } from './square-projections.js';
8
+ import { reconcileBinding as reconcileBindingOperation } from './delivery-operations.js';
7
9
  class ParticipantHandle {
8
10
  name;
9
11
  square;
10
- constructor(name, square) {
12
+ context;
13
+ constructor(name, square, context) {
11
14
  this.name = name;
12
15
  this.square = square;
16
+ this.context = context;
13
17
  }
14
18
  express(body, options) {
15
- return express(this.square, this.name, body, options);
19
+ return express(this.context, this.name, body, options);
16
20
  }
17
21
  listen(target) {
18
- return listen(this.square, this.name, target);
22
+ return listen(this.context, this.name, target);
19
23
  }
20
24
  ignore(target) {
21
- return ignore(this.square, this.name, target);
25
+ return ignore(this.context, this.name, target);
22
26
  }
23
27
  listening() {
24
- return listening(this.square, this.name);
28
+ return listening(this.context, this.name);
25
29
  }
26
30
  catch(options) {
27
- return catchUp(this.square, this.name, options);
31
+ return catchUp(this.context, this.name, options);
28
32
  }
29
33
  history(query) {
30
34
  return participantHistory(this.square, this.name, query);
31
35
  }
32
36
  hold(reason) {
33
- return hold(this.square, this.name, reason);
37
+ return hold(this.context, this.name, reason);
34
38
  }
35
39
  resume() {
36
- return resume(this.square, this.name);
40
+ return resume(this.context, this.name);
37
41
  }
38
42
  done(body) {
39
- return done(this.square, this.name, body);
43
+ return done(this.context, this.name, body);
40
44
  }
41
45
  }
42
46
  export class Square {
43
47
  location;
44
48
  square;
49
+ context;
45
50
  constructor(location, square) {
46
51
  this.location = location;
47
52
  this.square = square;
53
+ this.context = { ...square, location };
48
54
  }
49
55
  static async at(input) {
50
56
  return new Square(input.path, await openSquare(input.path, input));
@@ -56,43 +62,52 @@ export class Square {
56
62
  return new Square('memory', buildMemorySquare(input));
57
63
  }
58
64
  async join(name) {
59
- const joined = await join(this.square, name);
60
- return new ParticipantHandle(joined.name, this.square);
65
+ const joined = await join(this.context, name);
66
+ return new ParticipantHandle(joined.name, this.square, this.context);
61
67
  }
62
68
  async joinWithActivity(name) {
63
- const joined = await join(this.square, name);
64
- return { participant: new ParticipantHandle(joined.name, this.square), activity: joined.activity };
69
+ const joined = await join(this.context, name);
70
+ return { participant: new ParticipantHandle(joined.name, this.square, this.context), activity: joined.activity };
65
71
  }
66
72
  async implicitJoin(name) {
67
- const joined = await implicitJoin(this.square, name);
73
+ const joined = await implicitJoin(this.context, name);
68
74
  return joined.state === 'done'
69
75
  ? { state: joined.state }
70
- : { state: joined.state, participant: new ParticipantHandle(joined.name, this.square) };
76
+ : { state: joined.state, participant: new ParticipantHandle(joined.name, this.square, this.context) };
71
77
  }
72
78
  participants() { return participants(this.square); }
73
79
  snapshot() { return snapshot(this.square); }
74
80
  history(query) { return history(this.square, query); }
75
81
  async recognize(env) {
76
- const registered = await localParticipantName(this.square.location, env);
77
- if (registered === undefined)
82
+ if (this.square.hostLedger === undefined)
78
83
  return null;
79
- const canonicalName = await currentParticipant(this.square, registered);
80
- return canonicalName === undefined ? null : new ParticipantHandle(canonicalName, this.square);
84
+ const sessions = [env.CLAUDE_CODE_SESSION_ID, env.CODEX_THREAD_ID, env.OPENCODE_SESSION_ID, env.SQUARE_PI_SESSION_ID, env.PASEO_AGENT_ID]
85
+ .map((value) => value?.trim()).filter((value) => Boolean(value));
86
+ if (sessions.length === 0)
87
+ return null;
88
+ const candidates = (await Promise.all(sessions.map((sessionId) => projectSessionBindings({
89
+ hostLedger: this.square.hostLedger,
90
+ location: this.square.location,
91
+ sessionId,
92
+ scopes: ['user', 'local'],
93
+ })))).flat();
94
+ if (candidates.length !== 1)
95
+ return null;
96
+ const canonicalName = await currentParticipant(this.square, candidates[0].participant);
97
+ return canonicalName === undefined ? null : new ParticipantHandle(canonicalName, this.square, this.context);
81
98
  }
82
99
  close() { return closeOpenSquare(this.square); }
100
+ reconcileBinding() { return reconcileBindingOperation({ artifact: this.square.artifact, hostLedger: this.square.hostLedger, location: this.location }); }
83
101
  }
84
- export function markBoundarySeen(squarePath, name, ownerId, actIndexes, at) {
85
- return recordBoundarySeen(squarePath, name, ownerId, actIndexes, at);
86
- }
87
- export function markNotificationNotified(square, name, actIndex, ownerId, at) {
88
- return recordNotificationNotified(square, name, actIndex, ownerId, at);
102
+ export function markBoundarySeen(squarePath, name, actIndexes, at) {
103
+ return recordBoundarySeen(squarePath, name, actIndexes, at);
89
104
  }
90
105
  export async function openParticipant(input, name) {
91
106
  const square = await openSquare(input.path, input);
92
107
  try {
93
108
  const known = await resolveParticipant(square, name);
94
109
  return {
95
- participant: new ParticipantHandle(known.name, square),
110
+ participant: new ParticipantHandle(known.name, square, square),
96
111
  close: () => closeOpenSquare(square),
97
112
  };
98
113
  }
package/dist/views.d.ts CHANGED
@@ -93,8 +93,14 @@ export declare function eventPresentation(square: OpenSquare, id: ActivityId): P
93
93
  readonly held: boolean;
94
94
  }>;
95
95
  export declare function watchPresentation(square: OpenSquare, name: string): Promise<WatchPresentation>;
96
- export declare function inboxProjection(square: OpenSquare, name: string, ownerId: string): Promise<InboxProjection>;
97
- export declare function streamProjection(square: OpenSquare, cursor: number, recipient?: string): Promise<StreamProjection>;
96
+ export declare function inboxProjection(square: OpenSquare, name: string, _sessionId?: string): Promise<InboxProjection>;
97
+ export declare function streamProjection(square: OpenSquare | {
98
+ readonly cell: {
99
+ read(): Promise<{
100
+ state: SquareState;
101
+ }>;
102
+ };
103
+ }, cursor: number, recipient?: string): Promise<StreamProjection>;
98
104
  export declare function notificationForAct(square: OpenSquare, actIndex: number): Promise<readonly PlannedNotification[]>;
99
105
  export declare function pendingDeliveriesFromState(state: SquareState, delivery?: DeliveryModel): readonly PendingDeliveryProjection[];
100
106
  export declare function pendingDeliveries(square: OpenSquare): Promise<readonly PendingDeliveryProjection[]>;
package/dist/views.js CHANGED
@@ -24,14 +24,18 @@ function parseRequiredActivityId(id) {
24
24
  return index;
25
25
  }
26
26
  function historyOptions(query, viewer) {
27
- return { ...(query.from === undefined ? {} : { participants: [...query.from] }), ...(query.grep === undefined ? {} : { grep: query.grep }), ...(query.mention === true && viewer !== undefined ? { mention: viewer } : {}), order: 'asc' };
27
+ if (query.before !== undefined && query.after !== undefined)
28
+ throw new SquareError('invalid_args', 'History cannot combine before and after cursors');
29
+ const afterIndex = query.after === undefined ? undefined : parseRequiredActivityId(query.after);
30
+ const beforeIndex = query.before === undefined ? undefined : parseRequiredActivityId(query.before);
31
+ return { ...(query.from === undefined ? {} : { participants: [...query.from] }), ...(query.grep === undefined ? {} : { grep: query.grep }), ...(query.mention === true && viewer !== undefined ? { mention: viewer } : {}), ...(afterIndex === undefined ? {} : { afterIndex }), ...(beforeIndex === undefined ? {} : { beforeIndex }), order: 'asc' };
28
32
  }
29
33
  function selectHistory(stored, query) {
30
34
  let selected = stored.filter((activity) => activity.kind !== 'read');
31
- if (query.all !== true && query.limit !== undefined) {
35
+ if (query.limit !== undefined) {
32
36
  if (!Number.isSafeInteger(query.limit) || query.limit < 1)
33
37
  throw new SquareError('invalid_args', 'History limit must be a positive integer');
34
- selected = selected.slice(-query.limit);
38
+ selected = query.after === undefined ? selected.slice(-query.limit) : selected.slice(0, query.limit);
35
39
  }
36
40
  return query.order === 'desc' ? selected.reverse() : selected;
37
41
  }
@@ -62,27 +66,27 @@ function sayNumbers(state) {
62
66
  }
63
67
  return result;
64
68
  }
65
- export async function history(square, query = {}) { const { state } = await square.cell.read(); return selectHistory(coreActivities(state, historyOptions(query)), query).map(expose); }
66
- export async function participantHistory(square, name, query = {}) { const { state } = await square.cell.read(); const viewer = resolveKnownName(state, name); const delivery = deriveDeliveryModel(state); const effective = query.all === true || query.limit !== undefined ? query : { ...query, limit: 10 }; return selectHistory(coreActivities(state, historyOptions(effective, viewer), delivery), effective).map((activity) => exposePerceived(activity, viewer, delivery)); }
67
- export async function resolveParticipant(square, name) { const { state } = await square.cell.read(); return { name: resolveKnownName(state, name), roster: rosterNames(state) }; }
68
- export async function currentParticipant(square, name) { const { state } = await square.cell.read(); const known = resolveRosterName(state, name); return known !== undefined && isCurrentlyJoined(state.acts, known) ? known : undefined; }
69
- export async function participants(square) { const { state } = await square.cell.read(); return statuses(square, state); }
70
- export async function snapshot(square) { const { state } = await square.cell.read(); const folded = foldedState(state); return { context: [...state.preamble, ...state.warmup].join('\n'), actCount: state.acts.filter((activity) => activity.kind !== 'read').length, hardCap: state.hardCap, ...(state.throttlePerMinute === undefined ? {} : { throttlePerMinute: state.throttlePerMinute }), held: folded.hold.active && folded.hold.actor !== undefined ? { by: folded.hold.actor, ...(folded.hold.reason === undefined ? {} : { reason: folded.hold.reason }) } : null, participants: statuses(square, state), delivered(name, id) { return isActivitySeen(state, name, parseRequiredActivityId(id)); } }; }
71
- export async function activityPresentation(square, name) { const { state } = await square.cell.read(); const known = resolveKnownName(state, name); const delivery = deriveDeliveryModel(state); const delta = deliveryDelta(state, known, delivery); const hold = currentHold(state.acts); return { name: known, roster: rosterNames(state), pendingPublic: directedPeerSays(state, delta, known, delivery), pendingRoomChanges: [], activities: state.acts, state, participantCount: inSquareCount(state), held: hold.active, ...(hold.reason === undefined ? {} : { holdReason: hold.reason }), ownActivityCount: countSays(state.acts, known), hardCap: state.hardCap }; }
72
- export async function entryPresentation(square, name, lastN = 10) { const { state } = await square.cell.read(); const known = resolveRosterName(state, name) ?? name; const publicActivities = state.acts.filter((activity) => activity.kind === 'say' || activity.kind === 'done'); return { joined: isCurrentlyJoined(state.acts, known), scene: state.warmup.join('\n').trim(), context: state.preamble.join('\n').trim(), joinContext: (state.preamble.at(-1) === '---' ? state.preamble.slice(0, -1) : state.preamble).join('\n').trim(), recentActivities: lastN === null ? publicActivities : publicActivities.slice(-lastN), state, sayNumbers: sayNumbers(state), participantCount: inSquareCount(state) }; }
73
- export async function historyPresentation(square, options) { const { state } = await square.cell.read(); const delivery = deriveDeliveryModel(state); return { activities: coreActivities(state, options, delivery).map((activity) => ({ ...activity, perception: options.viewer === undefined ? 'full' : delivery.perceive(activity, options.viewer) })), sayNumbers: sayNumbers(state), presenceAnchors: anchors(state, delivery), participantCount: inSquareCount(state) }; }
74
- export async function participantsPresentation(square) { const { state } = await square.cell.read(); const delivery = deriveDeliveryModel(state); return coreParticipants(state, square.clock(), delivery); }
75
- export async function listPresentation(square) { const { state } = await square.cell.read(); return { context: state.preamble, participants: foldedState(state).participants.filter((participant) => participant.joined).sort((left, right) => (right.lastActiveAt ?? -Infinity) - (left.lastActiveAt ?? -Infinity) || left.name.localeCompare(right.name)).map((participant) => participant.name), activities: state.acts.filter((activity) => activity.kind === 'say').length }; }
76
- export async function statusPresentation(square) { const { state } = await square.cell.read(); const delivery = deriveDeliveryModel(state); const status = coreStatus(state, square.clock(), delivery); return { state, status, ...(status.latestAct?.kind === 'say' ? { latestActNumber: countSays(state.acts, status.latestAct.actor) } : {}) }; }
77
- export async function eventPresentation(square, id) { const { state } = await square.cell.read(); const activity = state.acts.find((candidate) => candidate.index === parseRequiredActivityId(id)); if (activity === undefined)
69
+ export async function history(square, query = {}) { const { state } = await square.artifact.read(); return selectHistory(coreActivities(state, historyOptions(query)), query).map(expose); }
70
+ export async function participantHistory(square, name, query = {}) { const { state } = await square.artifact.read(); const viewer = resolveKnownName(state, name); const delivery = deriveDeliveryModel(state); const effective = query.limit !== undefined ? query : { ...query, limit: 10 }; return selectHistory(coreActivities(state, historyOptions(effective, viewer), delivery), effective).map((activity) => exposePerceived(activity, viewer, delivery)); }
71
+ export async function resolveParticipant(square, name) { const { state } = await square.artifact.read(); return { name: resolveKnownName(state, name), roster: rosterNames(state) }; }
72
+ export async function currentParticipant(square, name) { const { state } = await square.artifact.read(); const known = resolveRosterName(state, name); return known !== undefined && isCurrentlyJoined(state.acts, known) ? known : undefined; }
73
+ export async function participants(square) { const { state } = await square.artifact.read(); return statuses(square, state); }
74
+ export async function snapshot(square) { const { state } = await square.artifact.read(); const folded = foldedState(state); return { context: [...state.preamble, ...state.warmup].join('\n'), actCount: state.acts.filter((activity) => activity.kind !== 'read').length, hardCap: state.hardCap, ...(state.throttlePerMinute === undefined ? {} : { throttlePerMinute: state.throttlePerMinute }), held: folded.hold.active && folded.hold.actor !== undefined ? { by: folded.hold.actor, ...(folded.hold.reason === undefined ? {} : { reason: folded.hold.reason }) } : null, participants: statuses(square, state), delivered(name, id) { return isActivitySeen(state, name, parseRequiredActivityId(id)); } }; }
75
+ export async function activityPresentation(square, name) { const { state } = await square.artifact.read(); const known = resolveKnownName(state, name); const delivery = deriveDeliveryModel(state); const delta = deliveryDelta(state, known, delivery); const hold = currentHold(state.acts); return { name: known, roster: rosterNames(state), pendingPublic: directedPeerSays(state, delta, known, delivery), pendingRoomChanges: [], activities: state.acts, state, participantCount: inSquareCount(state), held: hold.active, ...(hold.reason === undefined ? {} : { holdReason: hold.reason }), ownActivityCount: countSays(state.acts, known), hardCap: state.hardCap }; }
76
+ export async function entryPresentation(square, name, lastN = 10) { const { state } = await square.artifact.read(); const known = resolveRosterName(state, name) ?? name; const publicActivities = state.acts.filter((activity) => activity.kind === 'say' || activity.kind === 'done'); return { joined: isCurrentlyJoined(state.acts, known), scene: state.warmup.join('\n').trim(), context: state.preamble.join('\n').trim(), joinContext: (state.preamble.at(-1) === '---' ? state.preamble.slice(0, -1) : state.preamble).join('\n').trim(), recentActivities: lastN === null ? publicActivities : publicActivities.slice(-lastN), state, sayNumbers: sayNumbers(state), participantCount: inSquareCount(state) }; }
77
+ export async function historyPresentation(square, options) { const { state } = await square.artifact.read(); const delivery = deriveDeliveryModel(state); return { activities: coreActivities(state, options, delivery).map((activity) => ({ ...activity, perception: options.viewer === undefined ? 'full' : delivery.perceive(activity, options.viewer) })), sayNumbers: sayNumbers(state), presenceAnchors: anchors(state, delivery), participantCount: inSquareCount(state) }; }
78
+ export async function participantsPresentation(square) { const { state } = await square.artifact.read(); const delivery = deriveDeliveryModel(state); return coreParticipants(state, square.clock(), delivery); }
79
+ export async function listPresentation(square) { const { state } = await square.artifact.read(); return { context: state.preamble, participants: foldedState(state).participants.filter((participant) => participant.joined).sort((left, right) => (right.lastActiveAt ?? -Infinity) - (left.lastActiveAt ?? -Infinity) || left.name.localeCompare(right.name)).map((participant) => participant.name), activities: state.acts.filter((activity) => activity.kind === 'say').length }; }
80
+ export async function statusPresentation(square) { const { state } = await square.artifact.read(); const delivery = deriveDeliveryModel(state); const status = coreStatus(state, square.clock(), delivery); return { state, status, ...(status.latestAct?.kind === 'say' ? { latestActNumber: countSays(state.acts, status.latestAct.actor) } : {}) }; }
81
+ export async function eventPresentation(square, id) { const { state } = await square.artifact.read(); const activity = state.acts.find((candidate) => candidate.index === parseRequiredActivityId(id)); if (activity === undefined)
78
82
  throw new SquareError('invalid_args', `Unknown activity id: ${id}`); return { activity, participantCount: inSquareCount(state), held: currentHold(state.acts).active }; }
79
- export async function watchPresentation(square, name) { const { state } = await square.cell.read(); const known = resolveKnownName(state, name); const now = square.clock(); const terminal = watchTerminalStatus(state, known); const delivery = deriveDeliveryModel(state); return { activities: state.acts, state, participantCount: inSquareCount(state), presence: { participants: coreParticipants(state, now, delivery), now }, ...(terminal === undefined ? {} : { terminalStatus: terminal }) }; }
80
- export async function inboxProjection(square, name, ownerId) { const { state } = await square.cell.read(); const delivery = deriveDeliveryModel(state); const known = delivery.knownParticipant(name); if (known === undefined || !delivery.joinedRecipients().some((recipient) => nameKey(recipient) === nameKey(known)))
81
- return { name, joined: false, notifications: [] }; const lease = freshWatchLease(state, known, square.clock()); return { name: known, joined: true, notifications: delivery.pendingFor(known).map(({ item, route }) => ({ actIndex: item.index, actor: item.actor, at: item.at, route, body: item.body })), ...(lease?.ownerId === ownerId ? { catchLease: lease } : {}) }; }
82
- export async function streamProjection(square, cursor, recipient) { const { state } = await square.cell.read(); const delivery = recipient === undefined ? undefined : deriveDeliveryModel(state); return { activities: state.acts.filter((activity) => activity.index > cursor).flatMap((activity) => { if (delivery === undefined || recipient === undefined)
83
+ export async function watchPresentation(square, name) { const { state } = await square.artifact.read(); const known = resolveKnownName(state, name); const now = square.clock(); const terminal = watchTerminalStatus(state, known); const delivery = deriveDeliveryModel(state); return { activities: state.acts, state, participantCount: inSquareCount(state), presence: { participants: coreParticipants(state, now, delivery), now }, ...(terminal === undefined ? {} : { terminalStatus: terminal }) }; }
84
+ export async function inboxProjection(square, name, _sessionId) { const { state } = await square.artifact.read(); const delivery = deriveDeliveryModel(state); const known = delivery.knownParticipant(name); if (known === undefined || !delivery.joinedRecipients().some((recipient) => nameKey(recipient) === nameKey(known)))
85
+ return { name, joined: false, notifications: [] }; const lease = freshWatchLease(state, known, square.clock()); return { name: known, joined: true, notifications: delivery.pendingFor(known).map(({ item, route }) => ({ actIndex: item.index, actor: item.actor, at: item.at, route, body: item.body })), ...(lease === undefined ? {} : { catchLease: lease }) }; }
86
+ export async function streamProjection(square, cursor, recipient) { const artifact = 'artifact' in square ? square.artifact : square.cell; const { state } = await artifact.read(); const delivery = recipient === undefined ? undefined : deriveDeliveryModel(state); return { activities: state.acts.filter((activity) => activity.index > cursor).flatMap((activity) => { if (delivery === undefined || recipient === undefined)
83
87
  return [{ activity }]; const notification = delivery.plan(activity).find((candidate) => nameKey(candidate.recipient) === nameKey(recipient)); return notification === undefined ? [] : [{ activity, route: notification.route }]; }), cursor: Math.max(cursor, ...state.acts.map((activity) => activity.index)) }; }
84
- export async function notificationForAct(square, actIndex) { const { state } = await square.cell.read(); const activity = state.acts.find((candidate) => candidate.index === actIndex); return activity === undefined ? [] : deriveDeliveryModel(state).plan(activity); }
88
+ export async function notificationForAct(square, actIndex) { const { state } = await square.artifact.read(); const activity = state.acts.find((candidate) => candidate.index === actIndex); return activity === undefined ? [] : deriveDeliveryModel(state).plan(activity); }
85
89
  export function pendingDeliveriesFromState(state, delivery = deriveDeliveryModel(state)) { return delivery.joinedRecipients().map((recipient) => ({ recipient, notifications: delivery.pendingFor(recipient) })); }
86
- export async function pendingDeliveries(square) { const { state } = await square.cell.read(); return pendingDeliveriesFromState(state); }
87
- export async function notificationEvidence(square, recipient, actIndex) { const { state } = await square.cell.read(); const delivery = deriveDeliveryModel(state); const known = delivery.knownParticipant(recipient) ?? recipient; return { delivered: delivery.isSeen(known, actIndex), observation: state.runtime.observations?.[known]?.[formatActivityId(actIndex)] }; }
88
- export async function notificationDelivered(square, recipient, actIndex) { const { state } = await square.cell.read(); return deriveDeliveryModel(state).isSeen(recipient, actIndex); }
90
+ export async function pendingDeliveries(square) { const { state } = await square.artifact.read(); return pendingDeliveriesFromState(state); }
91
+ export async function notificationEvidence(square, recipient, actIndex) { const { state } = await square.artifact.read(); const delivery = deriveDeliveryModel(state); const known = delivery.knownParticipant(recipient) ?? recipient; return { delivered: delivery.isSeen(known, actIndex), observation: state.runtime.observations?.[known]?.[formatActivityId(actIndex)] }; }
92
+ export async function notificationDelivered(square, recipient, actIndex) { const { state } = await square.artifact.read(); return deriveDeliveryModel(state).isSeen(recipient, actIndex); }
@@ -1,39 +1,46 @@
1
- import { type NotifyLease, type WakeRoute, type WakeRouteKind } from './model.js';
1
+ import { type WakeRouteKind } from './model.js';
2
+ import type { WakeAttempt as ProjectedWakeAttempt } from './square-projections.js';
3
+ export { hasAttemptableWakeRoute, isWakeRouteAttemptable, terminalWakeEvidence } from './square-projections.js';
2
4
  export type WakeOutcome = 'accepted' | 'unknown' | 'failed';
3
5
  export interface WakeAttention {
4
6
  squarePath: string;
5
7
  actIndex: number;
6
8
  recipient: string;
7
9
  }
8
- export interface WakeAttempt {
9
- at: number;
10
- attention: WakeAttention;
11
- routeKind: WakeRouteKind;
12
- outcome: WakeOutcome;
13
- signature?: string;
14
- attemptN: number;
15
- message?: string;
16
- diagnostic?: unknown;
10
+ export type WakeAttempt = ProjectedWakeAttempt;
11
+ export interface WakeDispatchLease {
12
+ readonly leaseId: string;
13
+ readonly expiresAt: number;
14
+ readonly phase: 'claimed' | 'dispatching';
15
+ readonly routeKind?: WakeRouteKind;
16
+ readonly attemptN?: number;
17
+ readonly session?: string;
17
18
  }
19
+ export type WakeDispatchClaim = {
20
+ readonly type: 'acquired';
21
+ readonly leaseId: string;
22
+ } | {
23
+ readonly type: 'busy';
24
+ } | {
25
+ readonly type: 'ambiguous';
26
+ readonly lease: WakeDispatchLease;
27
+ };
18
28
  export declare function wakeAttemptsPath(env?: NodeJS.ProcessEnv): string;
19
29
  export declare function wakeAttentionKey(attention: WakeAttention): Promise<string>;
30
+ export declare function claimWakeDispatch(attention: WakeAttention, leaseId: string, leaseMs: number, env?: NodeJS.ProcessEnv, at?: number, session?: string): Promise<WakeDispatchClaim>;
31
+ export declare function transitionWakeDispatch(attention: WakeAttention, leaseId: string, phase: WakeDispatchLease['phase'], leaseMs: number, routeKind: WakeRouteKind | undefined, attemptN: number | undefined, session: string | undefined, env?: NodeJS.ProcessEnv, at?: number): Promise<boolean>;
32
+ export declare function releaseWakeDispatch(attention: WakeAttention, leaseId: string, env?: NodeJS.ProcessEnv, at?: number): Promise<void>;
20
33
  export declare function readWakeAttempts(opts?: {
21
34
  attention?: WakeAttention;
35
+ sessionId?: string;
22
36
  now?: number;
23
37
  env?: NodeJS.ProcessEnv;
24
38
  }): Promise<WakeAttempt[]>;
25
- export declare function terminalWakeEvidence(attempts: readonly WakeAttempt[]): WakeAttempt | undefined;
26
- export declare function terminalWakeAttempt(attention: WakeAttention, opts?: {
27
- now?: number;
28
- env?: NodeJS.ProcessEnv;
29
- }): Promise<WakeAttempt | undefined>;
30
- export declare function isWakeRouteAttemptable(route: Pick<WakeRoute, 'kind' | 'updatedAt'>, attempts: readonly WakeAttempt[]): boolean;
31
- export declare function hasAttemptableWakeRoute(routes: readonly Pick<WakeRoute, 'kind' | 'updatedAt'>[], attempts: readonly WakeAttempt[]): boolean;
32
- export declare function nextWakeAttemptNumber(attention: WakeAttention, opts?: {
33
- now?: number;
34
- env?: NodeJS.ProcessEnv;
35
- }): Promise<number>;
36
39
  export declare function recordWakeAttempt(attempt: Omit<WakeAttempt, 'at'> & {
37
40
  at?: number;
38
41
  }, env?: NodeJS.ProcessEnv): Promise<WakeAttempt>;
39
- export declare function recordRecoveredUnknown(attention: WakeAttention, lease: Pick<NotifyLease, 'attemptN' | 'routeKind'>, env?: NodeJS.ProcessEnv, at?: number): Promise<WakeAttempt | undefined>;
42
+ export declare function recordRecoveredUnknown(attention: WakeAttention, lease: {
43
+ attemptN?: number;
44
+ routeKind?: WakeRouteKind;
45
+ session?: string;
46
+ }, env?: NodeJS.ProcessEnv, at?: number): Promise<WakeAttempt | undefined>;