@astrosheep/square 0.3.11 → 0.3.13

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 (127) hide show
  1. package/codex-plugin/.codex-plugin/plugin.json +1 -1
  2. package/codex-plugin/hooks/hooks.json +9 -0
  3. package/dist/activity-feed.d.ts +14 -0
  4. package/dist/activity-feed.js +5 -5
  5. package/dist/activity.d.ts +9 -0
  6. package/dist/activity.js +112 -96
  7. package/dist/artifact.d.ts +20 -0
  8. package/dist/artifact.js +317 -648
  9. package/dist/automatic-session.d.ts +5 -0
  10. package/dist/automatic-session.js +86 -0
  11. package/dist/boundary-presentation.d.ts +5 -0
  12. package/dist/boundary-presentation.js +6 -5
  13. package/dist/claude-hook.d.ts +9 -0
  14. package/dist/claude-hook.js +35 -3
  15. package/dist/cli/context.d.ts +35 -0
  16. package/dist/cli/context.js +26 -31
  17. package/dist/cli/harness-command.d.ts +28 -0
  18. package/dist/cli/harness-command.js +30 -15
  19. package/dist/cli/maintenance-commands.d.ts +7 -0
  20. package/dist/cli/maintenance-commands.js +12 -69
  21. package/dist/cli/meta-commands.d.ts +5 -0
  22. package/dist/cli/observation-commands.d.ts +20 -0
  23. package/dist/cli/observation-commands.js +159 -139
  24. package/dist/cli/program.d.ts +2 -0
  25. package/dist/cli/program.js +3 -12
  26. package/dist/cli/registry.d.ts +9 -0
  27. package/dist/cli/registry.js +2 -4
  28. package/dist/cli/square-commands.d.ts +34 -0
  29. package/dist/cli/square-commands.js +90 -84
  30. package/dist/cmd/notify-once.d.ts +2 -0
  31. package/dist/codex-hook.d.ts +10 -0
  32. package/dist/codex-hook.js +35 -3
  33. package/dist/decisions.d.ts +92 -0
  34. package/dist/decisions.js +93 -81
  35. package/dist/delivery-health.d.ts +21 -0
  36. package/dist/delivery-health.js +41 -34
  37. package/dist/delivery.d.ts +81 -0
  38. package/dist/delivery.js +71 -69
  39. package/dist/file-lock.d.ts +6 -0
  40. package/dist/harness-claude.d.ts +19 -0
  41. package/dist/harness-claude.js +8 -3
  42. package/dist/harness-codex.d.ts +29 -0
  43. package/dist/harness-codex.js +2 -2
  44. package/dist/harness-links.d.ts +17 -0
  45. package/dist/harness-links.js +2 -2
  46. package/dist/harness-pi.d.ts +11 -0
  47. package/dist/harness-pi.js +2 -2
  48. package/dist/harness-stage.d.ts +6 -0
  49. package/dist/harness.d.ts +20 -0
  50. package/dist/harness.js +5 -3
  51. package/dist/help.d.ts +6 -0
  52. package/dist/help.js +12 -13
  53. package/dist/identity.d.ts +13 -0
  54. package/dist/inbox.d.ts +2 -0
  55. package/dist/inbox.js +15 -19
  56. package/dist/index.d.ts +13 -0
  57. package/dist/index.js +31 -67
  58. package/dist/landing.d.ts +10 -0
  59. package/dist/landing.js +95 -0
  60. package/dist/list.d.ts +1 -0
  61. package/dist/list.js +39 -61
  62. package/dist/model.d.ts +147 -0
  63. package/dist/model.js +12 -8
  64. package/dist/notifications.d.ts +30 -0
  65. package/dist/notifications.js +83 -91
  66. package/dist/open-square.d.ts +11 -0
  67. package/dist/open-square.js +4 -0
  68. package/dist/participant-identity.d.ts +1 -0
  69. package/dist/participant-identity.js +3 -0
  70. package/dist/paseo-connection.d.ts +15 -0
  71. package/dist/paseo-delivery.d.ts +14 -0
  72. package/dist/paseo-state.d.ts +11 -0
  73. package/dist/paseo-timeline.d.ts +16 -0
  74. package/dist/presence.d.ts +3 -0
  75. package/dist/presence.js +42 -0
  76. package/dist/presentation.d.ts +91 -0
  77. package/dist/presentation.js +52 -63
  78. package/dist/presented.d.ts +11 -0
  79. package/dist/registry.d.ts +58 -0
  80. package/dist/registry.js +36 -3
  81. package/dist/routes.d.ts +42 -0
  82. package/dist/runtime.d.ts +45 -0
  83. package/dist/runtime.js +48 -55
  84. package/dist/search.d.ts +11 -0
  85. package/dist/square-core.d.ts +111 -0
  86. package/dist/square-core.js +63 -9
  87. package/dist/square-facade.d.ts +86 -0
  88. package/dist/square-facade.js +1 -0
  89. package/dist/square-file-adapter.d.ts +18 -0
  90. package/dist/square-file-adapter.js +70 -0
  91. package/dist/square-storage.d.ts +20 -0
  92. package/dist/square-storage.js +171 -0
  93. package/dist/square-wiring.d.ts +19 -0
  94. package/dist/square-wiring.js +70 -0
  95. package/dist/square.d.ts +2 -0
  96. package/dist/state-cell.d.ts +14 -0
  97. package/dist/state-cell.js +1 -0
  98. package/dist/stream.d.ts +3 -0
  99. package/dist/stream.js +19 -23
  100. package/dist/time.d.ts +6 -0
  101. package/dist/views.d.ts +95 -0
  102. package/dist/views.js +81 -0
  103. package/dist/wake-attempts.d.ts +39 -0
  104. package/dist/wake-attempts.js +8 -4
  105. package/dist/wake-evidence.d.ts +12 -0
  106. package/dist/wake-evidence.js +7 -10
  107. package/dist/wake-port.d.ts +20 -0
  108. package/dist/wake-sink.d.ts +12 -0
  109. package/dist/wakes.d.ts +40 -0
  110. package/dist/wakes.js +62 -0
  111. package/dist/watch.d.ts +2 -0
  112. package/dist/watch.js +89 -210
  113. package/extensions/square-opencode.js +27 -1
  114. package/extensions/square-pi.js +13 -0
  115. package/guides/participant.md +7 -174
  116. package/package.json +5 -4
  117. package/skills/brainstorm/SKILL.md +30 -30
  118. package/skills/square/.claude-plugin/plugin.json +1 -1
  119. package/skills/square/SKILL.md +56 -29
  120. package/skills/square/hooks/hooks.json +9 -0
  121. package/skills/square-feedback/SKILL.md +9 -9
  122. package/dist/compact.js +0 -26
  123. package/dist/doctor.js +0 -36
  124. package/dist/square-application.js +0 -262
  125. package/template.md +0 -4
  126. package/templates/architect.md +0 -4
  127. package/templates/brainstorm.md +0 -4
@@ -0,0 +1,11 @@
1
+ export declare function compileGrepPattern(pattern: string): RegExp;
2
+ export declare function compileFixedPattern(pattern: string): RegExp;
3
+ export declare function compileSearchPattern(pattern: string, fixed: boolean): RegExp;
4
+ export interface GrepSnippet {
5
+ before: string;
6
+ match: string;
7
+ after: string;
8
+ beforeOmitted: number;
9
+ afterOmitted: number;
10
+ }
11
+ export declare function grepSnippet(body: string, pattern: string, maxChars: number, fixed?: boolean): GrepSnippet | undefined;
@@ -0,0 +1,111 @@
1
+ export type Participant = string;
2
+ export type Reach = 'bell';
3
+ export type Audience = {
4
+ readonly kind: 'bell';
5
+ } | {
6
+ readonly kind: 'mentions';
7
+ readonly names: readonly string[];
8
+ };
9
+ export type ActivityId = `act/${number}`;
10
+ export declare function formatActivityId(index: number): ActivityId;
11
+ export declare function parseActivityId(value: string): number | undefined;
12
+ export interface Hold {
13
+ active: boolean;
14
+ at?: number;
15
+ reason?: string;
16
+ actor?: Participant;
17
+ }
18
+ export type Act = {
19
+ kind: 'join';
20
+ actor: Participant;
21
+ at?: number;
22
+ } | {
23
+ kind: 'done';
24
+ actor: Participant;
25
+ at?: number;
26
+ body?: string;
27
+ } | {
28
+ kind: 'say';
29
+ actor: Participant;
30
+ at?: number;
31
+ body: string;
32
+ reach?: Reach;
33
+ reply?: number;
34
+ } | {
35
+ kind: 'hold';
36
+ actor?: Participant;
37
+ at?: number;
38
+ body?: string;
39
+ } | {
40
+ kind: 'resume';
41
+ actor?: Participant;
42
+ at?: number;
43
+ } | {
44
+ kind: 'read';
45
+ actor: Participant;
46
+ at?: number;
47
+ through: number;
48
+ };
49
+ export interface SquareValidationOptions {
50
+ hardCap?: number | null;
51
+ throttlePerMinute?: number;
52
+ throttleWindowMs?: number;
53
+ }
54
+ export type Perception = 'full' | 'presence';
55
+ interface ParticipantSnapshot {
56
+ name: Participant;
57
+ key: string;
58
+ joined: boolean;
59
+ done: boolean;
60
+ activityCount: number;
61
+ lastActiveAt?: number;
62
+ lastReadThrough: number;
63
+ }
64
+ export interface FoldedSquareState {
65
+ participants: ParticipantSnapshot[];
66
+ hold: Hold;
67
+ joined: Participant[];
68
+ done: Participant[];
69
+ throttleActivityAts: number[];
70
+ bellSayAtsByActor: Map<string, number[]>;
71
+ }
72
+ export type ValidationResult = {
73
+ ok: true;
74
+ } | {
75
+ ok: false;
76
+ reason: 'already_joined';
77
+ } | {
78
+ ok: false;
79
+ reason: 'not_joined';
80
+ } | {
81
+ ok: false;
82
+ reason: 'done';
83
+ } | {
84
+ ok: false;
85
+ reason: 'held';
86
+ hold: Hold;
87
+ } | {
88
+ ok: false;
89
+ reason: 'hard_cap';
90
+ count: number;
91
+ hardCap: number;
92
+ } | {
93
+ ok: false;
94
+ reason: 'throttled';
95
+ delayMs: number;
96
+ } | {
97
+ ok: false;
98
+ reason: 'bell_quota';
99
+ nextAt: number;
100
+ };
101
+ export declare function extractMentions(body: string): string[];
102
+ export declare function audienceOf(say: {
103
+ body: string;
104
+ reach?: Reach;
105
+ }): Audience;
106
+ export declare function audienceIncludes(audience: Audience, name: string): boolean;
107
+ export declare function resolveAudience(audience: Audience, candidateNames: readonly string[]): string[];
108
+ export declare function fold(acts: readonly Act[]): FoldedSquareState;
109
+ export declare function validate(state: FoldedSquareState, act: Act, options?: SquareValidationOptions): ValidationResult;
110
+ export declare function perceive(act: Act, viewer: Participant | string): Perception;
111
+ export {};
@@ -1,9 +1,62 @@
1
+ export function formatActivityId(index) {
2
+ if (!Number.isSafeInteger(index) || index < 0) {
3
+ throw new Error(`Invalid activity index: ${index}`);
4
+ }
5
+ return `act/${index}`;
6
+ }
7
+ export function parseActivityId(value) {
8
+ if (value === 'act/0')
9
+ return 0;
10
+ if (typeof value !== 'string' || !/^act\/[1-9]\d*$/.test(value))
11
+ return undefined;
12
+ const index = Number(value.slice(4));
13
+ return Number.isSafeInteger(index) ? index : undefined;
14
+ }
1
15
  function nameKey(name) {
2
16
  return name.toLocaleLowerCase();
3
17
  }
4
18
  function sameName(a, b) {
5
19
  return nameKey(a) === nameKey(b);
6
20
  }
21
+ export function extractMentions(body) {
22
+ const matches = [];
23
+ const re = /@([\p{L}\p{N}_-]+)/gu;
24
+ let match;
25
+ while ((match = re.exec(body)) !== null)
26
+ matches.push(match[1]);
27
+ return matches;
28
+ }
29
+ function uniqueMentionNames(names) {
30
+ const unique = [];
31
+ for (const name of names) {
32
+ if (unique.some((existing) => sameName(existing, name)))
33
+ continue;
34
+ unique.push(name);
35
+ }
36
+ return unique;
37
+ }
38
+ export function audienceOf(say) {
39
+ if (say.reach === 'bell')
40
+ return { kind: 'bell' };
41
+ return { kind: 'mentions', names: uniqueMentionNames(extractMentions(say.body)) };
42
+ }
43
+ export function audienceIncludes(audience, name) {
44
+ if (audience.kind === 'bell')
45
+ return true;
46
+ return audience.names.some((mentioned) => sameName(mentioned, name));
47
+ }
48
+ export function resolveAudience(audience, candidateNames) {
49
+ if (audience.kind === 'bell')
50
+ return [...candidateNames];
51
+ const resolved = [];
52
+ for (const mention of audience.names) {
53
+ const known = candidateNames.find((candidate) => sameName(candidate, mention));
54
+ if (known !== undefined && !resolved.some((existing) => sameName(existing, known))) {
55
+ resolved.push(known);
56
+ }
57
+ }
58
+ return resolved;
59
+ }
7
60
  function actorOf(act) {
8
61
  if ('actor' in act && typeof act.actor === 'string')
9
62
  return act.actor;
@@ -128,6 +181,8 @@ export function validate(state, act, options = {}) {
128
181
  case 'say': {
129
182
  if (current?.done)
130
183
  return { ok: false, reason: 'done' };
184
+ if (current?.joined !== true)
185
+ return { ok: false, reason: 'not_joined' };
131
186
  if (state.hold.active)
132
187
  return { ok: false, reason: 'held', hold: state.hold };
133
188
  const activityCount = current?.activityCount ?? 0;
@@ -156,20 +211,19 @@ export function validate(state, act, options = {}) {
156
211
  }
157
212
  case 'hold':
158
213
  case 'resume':
214
+ if (current?.done)
215
+ return { ok: false, reason: 'done' };
216
+ if (current?.joined !== true)
217
+ return { ok: false, reason: 'not_joined' };
218
+ return { ok: true };
159
219
  case 'read':
160
220
  return { ok: true };
161
221
  }
162
222
  }
163
- export function perceive(state, act, viewer) {
164
- void state;
223
+ export function perceive(act, viewer) {
165
224
  if (act.kind !== 'say')
166
225
  return 'full';
167
- const actor = act.actor;
168
- if (sameName(actor, viewer))
169
- return 'full';
170
- if (act.reach === undefined || act.reach === 'bell')
171
- return 'full';
172
- if (sameName(act.reach.beside, viewer))
226
+ if (sameName(act.actor, viewer))
173
227
  return 'full';
174
- return 'presence';
228
+ return audienceIncludes(audienceOf(act), viewer) ? 'full' : 'presence';
175
229
  }
@@ -0,0 +1,86 @@
1
+ import type { ActivityId, Perception, Reach } from './square-core.js';
2
+ export interface Activity {
3
+ readonly id: ActivityId;
4
+ readonly at: number;
5
+ readonly kind: 'join' | 'say' | 'done' | 'hold' | 'resume';
6
+ readonly actor: string;
7
+ readonly body?: string;
8
+ readonly mentions: readonly string[];
9
+ readonly reply?: ActivityId;
10
+ }
11
+ export interface WakeNotifier {
12
+ wake(recipients: readonly string[], activity: Activity): void;
13
+ }
14
+ export interface PerceivedActivity extends Activity {
15
+ readonly perception: Perception;
16
+ }
17
+ export interface ExpressOptions {
18
+ readonly force?: boolean;
19
+ readonly reach?: Reach;
20
+ readonly reply?: ActivityId;
21
+ }
22
+ export interface ExpressResult {
23
+ readonly activity: Activity;
24
+ }
25
+ export interface CatchOptions {
26
+ readonly idle?: number;
27
+ readonly from?: readonly string[];
28
+ readonly mention?: boolean;
29
+ }
30
+ export interface CatchResult {
31
+ readonly activities: readonly PerceivedActivity[];
32
+ readonly consumedThrough: ActivityId | null;
33
+ readonly idleExpired: boolean;
34
+ }
35
+ export interface HistoryQuery {
36
+ readonly limit?: number;
37
+ readonly order?: 'asc' | 'desc';
38
+ readonly all?: boolean;
39
+ readonly full?: boolean;
40
+ readonly grep?: string;
41
+ readonly from?: readonly string[];
42
+ readonly mention?: boolean;
43
+ }
44
+ export interface ParticipantStatus {
45
+ readonly name: string;
46
+ readonly state: 'joined' | 'done';
47
+ readonly consumedThrough: ActivityId | null;
48
+ readonly watching: boolean;
49
+ }
50
+ export interface SquareSnapshot {
51
+ readonly context: string;
52
+ readonly actCount: number;
53
+ readonly hardCap: number | null;
54
+ readonly throttlePerMinute?: number;
55
+ readonly held: {
56
+ readonly by: string;
57
+ readonly reason?: string;
58
+ } | null;
59
+ readonly participants: readonly ParticipantStatus[];
60
+ delivered(name: string, id: ActivityId): boolean;
61
+ }
62
+ export type SquareSource = {
63
+ path: string;
64
+ };
65
+ export interface OpenOptions {
66
+ clock?: () => number;
67
+ notifier?: WakeNotifier;
68
+ }
69
+ export interface SquareAtInput extends SquareSource, OpenOptions {
70
+ }
71
+ export interface SquareBuildInput extends SquareSource {
72
+ markdown: string;
73
+ hardCap?: number | null;
74
+ throttlePerMinute?: number;
75
+ clock?: () => number;
76
+ notifier?: WakeNotifier;
77
+ }
78
+ export interface Participant {
79
+ readonly name: string;
80
+ express(body: string, options?: ExpressOptions): Promise<ExpressResult>;
81
+ catch(options?: CatchOptions): Promise<CatchResult>;
82
+ history(query?: HistoryQuery): Promise<PerceivedActivity[]>;
83
+ hold(reason?: string): Promise<ExpressResult>;
84
+ resume(): Promise<ExpressResult>;
85
+ done(body?: string): Promise<ExpressResult>;
86
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,18 @@
1
+ import { type BuildOptions, type HardCap } from './model.js';
2
+ import type { OpenSquare } from './open-square.js';
3
+ import type { WakeNotifier } from './square-facade.js';
4
+ /** File-owned artifact creation for the CLI and path-backed public facade. */
5
+ export declare function createSquare(squarePath: string, options: BuildOptions & {
6
+ hardCap: HardCap;
7
+ }, snippet: string): Promise<void>;
8
+ export interface SquareBuildOptions {
9
+ markdown: string;
10
+ hardCap?: number | null;
11
+ throttlePerMinute?: number;
12
+ clock?: () => number;
13
+ notifier?: WakeNotifier;
14
+ }
15
+ export declare function openSquare(squarePath: string, options?: Pick<SquareBuildOptions, 'clock' | 'notifier'>): Promise<OpenSquare>;
16
+ export declare function probeSquare(squarePath: string): OpenSquare | undefined;
17
+ export declare function buildSquare(squarePath: string, options: SquareBuildOptions): Promise<OpenSquare>;
18
+ export declare function buildMemorySquare(options: SquareBuildOptions): OpenSquare;
@@ -0,0 +1,70 @@
1
+ import fs from 'node:fs';
2
+ import { createSquareState, probeSquareFile, writeSquareSnapshot, withSquareFileLock, openSquareCell, createMemoryCell, } from './square-storage.js';
3
+ import { InternalSquareError, SquareError, } from './model.js';
4
+ /** The current CLI file mutation boundary. */
5
+ function writeSquareState(squarePath, squareState) {
6
+ writeSquareSnapshot(squarePath, squareState);
7
+ }
8
+ /** File-owned artifact creation for the CLI and path-backed public facade. */
9
+ export async function createSquare(squarePath, options, snippet) {
10
+ await withSquareFileLock(squarePath, () => {
11
+ if (fs.existsSync(squarePath) && !options.force) {
12
+ throw new InternalSquareError('conflict', `Refusing to overwrite existing square: ${squarePath}\nPass -f to overwrite.`);
13
+ }
14
+ writeSquareSnapshot(squarePath, createSquareState(options, snippet));
15
+ });
16
+ }
17
+ function validateBuildOptions(options) {
18
+ if (options.hardCap !== undefined && options.hardCap !== null
19
+ && (!Number.isSafeInteger(options.hardCap) || options.hardCap < 1)) {
20
+ throw new SquareError('invalid_args', 'hardCap must be a positive integer or null');
21
+ }
22
+ if (options.throttlePerMinute !== undefined
23
+ && (!Number.isSafeInteger(options.throttlePerMinute) || options.throttlePerMinute < 1)) {
24
+ throw new SquareError('invalid_args', 'throttlePerMinute must be a positive integer');
25
+ }
26
+ }
27
+ export async function openSquare(squarePath, options = {}) {
28
+ const cell = openSquareCell(squarePath);
29
+ try {
30
+ await cell.read();
31
+ return { cell, clock: options.clock ?? Date.now, ...(options.notifier === undefined ? {} : { notifier: options.notifier }), location: squarePath };
32
+ }
33
+ catch (error) {
34
+ await cell.close();
35
+ if (error instanceof InternalSquareError && error.code === 'not_found') {
36
+ throw new SquareError('unavailable', `Square is unavailable at ${squarePath}`);
37
+ }
38
+ throw error;
39
+ }
40
+ }
41
+ export function probeSquare(squarePath) {
42
+ const state = probeSquareFile(squarePath);
43
+ return state === undefined ? undefined : { cell: createMemoryCell(state), clock: Date.now, location: squarePath };
44
+ }
45
+ export async function buildSquare(squarePath, options) {
46
+ validateBuildOptions(options);
47
+ try {
48
+ await createSquare(squarePath, {
49
+ force: false,
50
+ hardCap: options.hardCap ?? null,
51
+ ...(options.throttlePerMinute === undefined ? {} : { throttlePerMinute: options.throttlePerMinute }),
52
+ }, options.markdown);
53
+ }
54
+ catch (error) {
55
+ if (error instanceof InternalSquareError && error.code === 'conflict') {
56
+ throw new SquareError('io', `Cannot build over an existing square at ${squarePath}`);
57
+ }
58
+ throw error;
59
+ }
60
+ return openSquare(squarePath, options);
61
+ }
62
+ export function buildMemorySquare(options) {
63
+ validateBuildOptions(options);
64
+ const squareState = createSquareState({
65
+ force: false,
66
+ hardCap: options.hardCap ?? null,
67
+ ...(options.throttlePerMinute === undefined ? {} : { throttlePerMinute: options.throttlePerMinute }),
68
+ }, options.markdown);
69
+ return { cell: createMemoryCell(squareState), clock: options.clock ?? Date.now, ...(options.notifier === undefined ? {} : { notifier: options.notifier }), location: 'memory' };
70
+ }
@@ -0,0 +1,20 @@
1
+ import { createSquareState, diagnoseSquareFile as diagnoseArtifactFile } from './artifact.js';
2
+ import type { StateCell } from './state-cell.js';
3
+ import { type SquareState } from './model.js';
4
+ /**
5
+ * The only production module allowed to cross the .square byte boundary.
6
+ * Consumers receive a SquareState and never need to know which codec or lock
7
+ * protects it.
8
+ */
9
+ export { createSquareState, };
10
+ export declare function readSquareFile(squarePath: string): SquareState;
11
+ export declare function probeSquareFile(squarePath: string): SquareState | undefined;
12
+ export declare function diagnoseSquareFile(squarePath: string): ReturnType<typeof diagnoseArtifactFile>;
13
+ export declare function writeSquareSnapshot(squarePath: string, squareState: SquareState): void;
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. */
16
+ export declare function createMemoryCell(initial: SquareState): StateCell;
17
+ /** File-backed cell retaining the existing .square codec and lock boundary. */
18
+ export declare function createFileCell(squarePath: string): StateCell;
19
+ /** Application-facing file cell factory; keeps storage choice behind this module. */
20
+ export declare function openSquareCell(squarePath: string): StateCell;
@@ -0,0 +1,171 @@
1
+ import { setTimeout as sleep } from 'node:timers/promises';
2
+ import fs from 'node:fs';
3
+ import { createSquareState, diagnoseSquareFile as diagnoseArtifactFile, loadSquare, probeSquare, writeSquareFile, } from './artifact.js';
4
+ import { withFileLock } from './file-lock.js';
5
+ import { LOCK_RETRY_MS, LOCK_STALE_MS } from './runtime.js';
6
+ /**
7
+ * The only production module allowed to cross the .square byte boundary.
8
+ * Consumers receive a SquareState and never need to know which codec or lock
9
+ * protects it.
10
+ */
11
+ export { createSquareState, };
12
+ export function readSquareFile(squarePath) {
13
+ return loadSquare(squarePath);
14
+ }
15
+ export function probeSquareFile(squarePath) {
16
+ return probeSquare(squarePath);
17
+ }
18
+ export function diagnoseSquareFile(squarePath) {
19
+ return diagnoseArtifactFile(squarePath);
20
+ }
21
+ export function writeSquareSnapshot(squarePath, squareState) {
22
+ writeSquareFile(squarePath, squareState);
23
+ }
24
+ export function withSquareFileLock(squarePath, fn) {
25
+ return withFileLock(`${squarePath}.lock`, { retryMs: LOCK_RETRY_MS, staleMs: LOCK_STALE_MS }, fn);
26
+ }
27
+ function cloneState(squareState) {
28
+ return structuredClone(squareState);
29
+ }
30
+ function assertCellOpen(closed) {
31
+ if (closed)
32
+ throw new Error('StateCell is closed');
33
+ }
34
+ /** In-process cell for fast application tests and embedded consumers. */
35
+ export function createMemoryCell(initial) {
36
+ let state = cloneState(initial);
37
+ let version = 0;
38
+ let closed = false;
39
+ let tail = Promise.resolve();
40
+ const waiters = new Set();
41
+ function publish() {
42
+ for (const waiter of [...waiters]) {
43
+ if (version <= waiter.since)
44
+ continue;
45
+ clearTimeout(waiter.timer);
46
+ waiters.delete(waiter);
47
+ waiter.resolve(true);
48
+ }
49
+ }
50
+ const cell = {
51
+ transact(fn) {
52
+ assertCellOpen(closed);
53
+ let operation;
54
+ operation = tail.then(() => {
55
+ assertCellOpen(closed);
56
+ const working = cloneState(state);
57
+ const outcome = fn(working, version);
58
+ if (outcome.state !== undefined) {
59
+ state = cloneState(outcome.state);
60
+ version += 1;
61
+ publish();
62
+ }
63
+ return outcome.result;
64
+ });
65
+ tail = operation.then(() => undefined, () => undefined);
66
+ return operation;
67
+ },
68
+ async read() {
69
+ assertCellOpen(closed);
70
+ await tail;
71
+ return { state: cloneState(state), version };
72
+ },
73
+ changed(sinceVersion, timeoutMs) {
74
+ assertCellOpen(closed);
75
+ if (version > sinceVersion)
76
+ return Promise.resolve(true);
77
+ if (timeoutMs <= 0)
78
+ return Promise.resolve(false);
79
+ return new Promise((resolve) => {
80
+ const waiter = {
81
+ since: sinceVersion,
82
+ resolve,
83
+ timer: setTimeout(() => {
84
+ waiters.delete(waiter);
85
+ resolve(false);
86
+ }, timeoutMs),
87
+ };
88
+ waiters.add(waiter);
89
+ });
90
+ },
91
+ async close() {
92
+ if (closed)
93
+ return;
94
+ closed = true;
95
+ await tail;
96
+ for (const waiter of [...waiters]) {
97
+ clearTimeout(waiter.timer);
98
+ waiters.delete(waiter);
99
+ waiter.resolve(false);
100
+ }
101
+ },
102
+ };
103
+ return cell;
104
+ }
105
+ function fileFingerprint(squarePath) {
106
+ try {
107
+ const stat = fs.statSync(squarePath);
108
+ return `${stat.ino}:${stat.size}:${stat.mtimeMs}:${stat.ctimeMs}`;
109
+ }
110
+ catch {
111
+ return 'missing';
112
+ }
113
+ }
114
+ /** File-backed cell retaining the existing .square codec and lock boundary. */
115
+ export function createFileCell(squarePath) {
116
+ let closed = false;
117
+ let version = 0;
118
+ let fingerprint = fileFingerprint(squarePath);
119
+ function observe() {
120
+ const next = fileFingerprint(squarePath);
121
+ if (next === fingerprint)
122
+ return;
123
+ fingerprint = next;
124
+ version += 1;
125
+ }
126
+ return {
127
+ async transact(fn) {
128
+ assertCellOpen(closed);
129
+ return withFileLock(`${squarePath}.lock`, { retryMs: LOCK_RETRY_MS, staleMs: LOCK_STALE_MS }, () => {
130
+ assertCellOpen(closed);
131
+ observe();
132
+ const current = readSquareFile(squarePath);
133
+ const working = cloneState(current);
134
+ const outcome = fn(working, version);
135
+ if (outcome.state !== undefined) {
136
+ writeSquareSnapshot(squarePath, outcome.state);
137
+ fingerprint = fileFingerprint(squarePath);
138
+ version += 1;
139
+ }
140
+ return outcome.result;
141
+ });
142
+ },
143
+ async read() {
144
+ assertCellOpen(closed);
145
+ observe();
146
+ const state = readSquareFile(squarePath);
147
+ return { state: cloneState(state), version };
148
+ },
149
+ async changed(sinceVersion, timeoutMs) {
150
+ assertCellOpen(closed);
151
+ const deadline = Date.now() + Math.max(0, timeoutMs);
152
+ while (true) {
153
+ observe();
154
+ if (version > sinceVersion)
155
+ return true;
156
+ const remaining = deadline - Date.now();
157
+ if (remaining <= 0)
158
+ return false;
159
+ await sleep(Math.min(25, remaining));
160
+ assertCellOpen(closed);
161
+ }
162
+ },
163
+ async close() {
164
+ closed = true;
165
+ },
166
+ };
167
+ }
168
+ /** Application-facing file cell factory; keeps storage choice behind this module. */
169
+ export function openSquareCell(squarePath) {
170
+ return createFileCell(squarePath);
171
+ }
@@ -0,0 +1,19 @@
1
+ import type { Activity, HistoryQuery, ParticipantStatus, SquareSnapshot } from './square-facade.js';
2
+ import type { Participant, SquareAtInput, SquareBuildInput } from './square-facade.js';
3
+ export declare class Square {
4
+ readonly location: string;
5
+ private readonly square;
6
+ private constructor();
7
+ static at(input: SquareAtInput): Promise<Square>;
8
+ static build(input: SquareBuildInput): Promise<Square>;
9
+ static inMemory(input: Omit<SquareBuildInput, 'path'>): Square;
10
+ join(name: string): Promise<Participant>;
11
+ participants(): Promise<ParticipantStatus[]>;
12
+ snapshot(): Promise<SquareSnapshot>;
13
+ history(query?: HistoryQuery): Promise<Activity[]>;
14
+ close(): Promise<void>;
15
+ }
16
+ export declare function openParticipant(input: SquareAtInput, name: string): Promise<{
17
+ readonly participant: Participant;
18
+ close(): Promise<void>;
19
+ }>;
@@ -0,0 +1,70 @@
1
+ import { closeOpenSquare } from './open-square.js';
2
+ import { buildMemorySquare, buildSquare, openSquare } from './square-file-adapter.js';
3
+ import { done, express, hold, join, resume } from './landing.js';
4
+ import { catchUp } from './presence.js';
5
+ import { history, participantHistory, participants, resolveParticipant, snapshot } from './views.js';
6
+ class ParticipantHandle {
7
+ name;
8
+ square;
9
+ constructor(name, square) {
10
+ this.name = name;
11
+ this.square = square;
12
+ }
13
+ express(body, options) {
14
+ return express(this.square, this.name, body, options);
15
+ }
16
+ catch(options) {
17
+ return catchUp(this.square, this.name, options);
18
+ }
19
+ history(query) {
20
+ return participantHistory(this.square, this.name, query);
21
+ }
22
+ hold(reason) {
23
+ return hold(this.square, this.name, reason);
24
+ }
25
+ resume() {
26
+ return resume(this.square, this.name);
27
+ }
28
+ done(body) {
29
+ return done(this.square, this.name, body);
30
+ }
31
+ }
32
+ export class Square {
33
+ location;
34
+ square;
35
+ constructor(location, square) {
36
+ this.location = location;
37
+ this.square = square;
38
+ }
39
+ static async at(input) {
40
+ return new Square(input.path, await openSquare(input.path, input));
41
+ }
42
+ static async build(input) {
43
+ return new Square(input.path, await buildSquare(input.path, input));
44
+ }
45
+ static inMemory(input) {
46
+ return new Square('memory', buildMemorySquare(input));
47
+ }
48
+ async join(name) {
49
+ const joined = await join(this.square, name);
50
+ return new ParticipantHandle(joined.name, this.square);
51
+ }
52
+ participants() { return participants(this.square); }
53
+ snapshot() { return snapshot(this.square); }
54
+ history(query) { return history(this.square, query); }
55
+ close() { return closeOpenSquare(this.square); }
56
+ }
57
+ export async function openParticipant(input, name) {
58
+ const square = await openSquare(input.path, input);
59
+ try {
60
+ const known = await resolveParticipant(square, name);
61
+ return {
62
+ participant: new ParticipantHandle(known.name, square),
63
+ close: () => closeOpenSquare(square),
64
+ };
65
+ }
66
+ catch (error) {
67
+ await closeOpenSquare(square);
68
+ throw error;
69
+ }
70
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};