@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,14 +0,0 @@
1
- export interface ActivityOptions {
2
- cwd?: string;
3
- env?: NodeJS.ProcessEnv;
4
- force?: boolean;
5
- noWait?: boolean;
6
- noMention?: boolean;
7
- mentions?: readonly string[];
8
- reach?: import('./model.js').Reach;
9
- reply?: string;
10
- }
11
- export declare function saveActivityDraft(squarePath: string, name: string, body: string): string;
12
- /** Retry commands preserve the original reach, reply, wait and force choices. */
13
- export declare function activityRetryCommand(squarePath: string, name: string, opts: ActivityOptions, force?: boolean): string;
14
- export declare function cmdActivity(squarePath: string, name: string, activity: string, resolveBody: (arg: string) => string, opts: ActivityOptions): Promise<void>;
package/dist/activity.js DELETED
@@ -1,248 +0,0 @@
1
- import crypto from 'node:crypto';
2
- import fs from 'node:fs';
3
- import path from 'node:path';
4
- import { SquareError, isSquareError, validateName } from './model.js';
5
- import { expressHintLine, formatRefusal, renderActivityBlocked, renderActivityLimit, renderActivityUncertain, renderExpressNoWait, renderExpressWaiting, renderPendingFeed, joinRecoveryCommand, participantIdentity, participantsRecoveryCommand, participantCommandPrefix, commandPrefix, quoteShell, takeoverRecoveryLines, withPathOutput, } from './presentation.js';
6
- import { nowMs, SLEEP_MS } from './runtime.js';
7
- import { unreadActivitySummaries } from './decisions.js';
8
- import { openSquare } from './square-file-adapter.js';
9
- import { closeOpenSquare } from './open-square.js';
10
- import { createSquareApplication } from './square-application.js';
11
- import { activityPresentation, resolveParticipant } from './views.js';
12
- import { formatActivityId, parseActivityId } from './square-core.js';
13
- import { formatDuration } from './time.js';
14
- import { style } from './tty-style.js';
15
- import { hostLedgerForEnv } from './registry.js';
16
- import { createDefaultWakeTransport } from './notifications.js';
17
- export function saveActivityDraft(squarePath, name, body) {
18
- const draftDir = path.join(path.dirname(squarePath), 'drafts');
19
- fs.mkdirSync(draftDir, { recursive: true });
20
- const hash = crypto.createHash('sha256').update(body).digest('hex').slice(0, 8);
21
- const stamp = new Date(nowMs()).toISOString().replace(/\.\d{3}Z$/, 'Z').replace(/[:.]/g, '-');
22
- const namePart = name.replace(/[^\p{L}\p{N}_-]+/gu, '-').replace(/^-+|-+$/g, '') || 'participant';
23
- const base = `${stamp}-${namePart}-${hash}`;
24
- for (let i = 0;; i++) {
25
- const filename = `${base}${i === 0 ? '' : `-${i}`}.md`;
26
- const target = path.join(draftDir, filename);
27
- try {
28
- fs.writeFileSync(target, body, { flag: 'wx' });
29
- return target;
30
- }
31
- catch (error) {
32
- if (error.code !== 'EEXIST')
33
- throw error;
34
- }
35
- }
36
- }
37
- /** Retry commands preserve the original reach, reply, wait and force choices. */
38
- export function activityRetryCommand(squarePath, name, opts, force = opts.force ?? false) {
39
- const reach = opts.reach === 'bell' ? ' --bell'
40
- : opts.mentions?.length ? opts.mentions.map((target) => ` --mention ${quoteShell(target)}`).join('')
41
- : opts.noMention === true ? ' --no-mention'
42
- : '';
43
- const replyIndex = opts.reply === undefined ? undefined : parseActivityId(opts.reply);
44
- const reply = opts.reply === undefined ? '' : ` --reply ${replyIndex === undefined ? quoteShell(opts.reply) : formatActivityId(replyIndex)}`;
45
- return `${participantCommandPrefix(squarePath, name)} express${force ? ' --force' : ''}${opts.noWait ? ' --no-wait' : ''}${reach}${reply} -`;
46
- }
47
- /**
48
- * A recipient conflict is the speaker's to edit: the printed recovery drops every
49
- * mention flag (or keeps the bell) so the retry never repeats the rejected target.
50
- */
51
- function mentionRepairCommand(squarePath, name, opts, bell) {
52
- const replyIndex = opts.reply === undefined ? undefined : parseActivityId(opts.reply);
53
- const reply = opts.reply === undefined ? '' : ` --reply ${replyIndex === undefined ? quoteShell(opts.reply) : formatActivityId(replyIndex)}`;
54
- const reach = bell ? ' --bell' : ' --no-mention';
55
- return `${participantCommandPrefix(squarePath, name)} express${opts.force ? ' --force' : ''}${opts.noWait ? ' --no-wait' : ''}${reach}${reply} -`;
56
- }
57
- /** A body that is nothing but *gestures* reads as an action, not as speech. */
58
- function isGestureOnly(body) {
59
- return /\*[^*]*\*/.test(body) && body.replace(/\*[^*]*\*/g, '').trim() === '';
60
- }
61
- export async function cmdActivity(squarePath, name, activity, resolveBody, opts) {
62
- const rawInput = String(resolveBody(activity));
63
- const env = { ...(opts.env ?? process.env) };
64
- const hostLedger = hostLedgerForEnv(env);
65
- const retryCommand = activityRetryCommand(squarePath, name, opts);
66
- const forceCommand = activityRetryCommand(squarePath, name, opts, true);
67
- let knownName = name;
68
- let callerMissing = false;
69
- let expressAttempted = false;
70
- let landedId;
71
- let announcedWait;
72
- async function presentation() {
73
- const square = await openSquare(squarePath, { clock: nowMs, env, hostLedger });
74
- try {
75
- return await activityPresentation(square, knownName);
76
- }
77
- finally {
78
- await closeOpenSquare(square);
79
- }
80
- }
81
- try {
82
- validateName(name);
83
- for (const target of opts.mentions ?? [])
84
- validateName(target);
85
- if (opts.reply !== undefined && parseActivityId(opts.reply) === undefined) {
86
- throw new SquareError('invalid_args', 'Invalid --reply: expected an activity id like act/12');
87
- }
88
- const reader = await openSquare(squarePath, { clock: nowMs, env, hostLedger });
89
- try {
90
- knownName = (await resolveParticipant(reader, name)).name;
91
- }
92
- catch (error) {
93
- // The caller name was validated above; invalid_args here means it is absent
94
- // from the roster. Keep the join recovery distinct from recipient validation.
95
- callerMissing = isSquareError(error) && error.code === 'invalid_args';
96
- throw error;
97
- }
98
- finally {
99
- await closeOpenSquare(reader);
100
- }
101
- const application = createSquareApplication({ cwd: opts.cwd ?? process.cwd(), env, squarePath, clock: nowMs, hostLedger, wakeTransport: await createDefaultWakeTransport(hostLedger, nowMs), participant: name });
102
- // The caller is roster-known at this point, so a landed join act is always a
103
- // re-entry after done — never a first arrival. Announce it; a silent return
104
- // would make `done` a fake exit.
105
- const entry = await application.join();
106
- const reentered = entry.kind === 'joined' && entry.activity !== null;
107
- const before = await presentation();
108
- const pendingPublic = before.pendingPublic;
109
- expressAttempted = true;
110
- const landed = await application.express(rawInput.replace(/\r\n/g, '\n').trim(), {
111
- force: opts.force ?? false,
112
- noWait: opts.noWait ?? false,
113
- ...(opts.mentions === undefined ? {} : { mentions: opts.mentions }),
114
- ...(opts.noMention === undefined ? {} : { noMention: opts.noMention }),
115
- ...(opts.reach === undefined ? {} : { reach: opts.reach }),
116
- // The action boundary validates the raw id before attempting a commit.
117
- ...(opts.reply === undefined ? {} : { reply: opts.reply }),
118
- }, {
119
- onProgress: (progress) => {
120
- if (announcedWait === progress.reason)
121
- return;
122
- const delayMs = progress.delayMs ?? SLEEP_MS;
123
- process.stdout.write(renderExpressWaiting({
124
- reason: progress.reason,
125
- ...(progress.reason === 'throttled' ? { delayMs } : {}),
126
- ...(progress.holder === undefined ? {} : { holder: progress.holder }),
127
- ...(progress.holdReason === undefined ? {} : { holdReason: progress.holdReason }),
128
- }) + '\n');
129
- announcedWait = progress.reason;
130
- },
131
- });
132
- landedId = landed.activity.id;
133
- const fresh = await presentation();
134
- const headerCount = fresh.participantCount;
135
- const held = fresh.held;
136
- const ownActCount = fresh.ownActivityCount;
137
- const hasPending = pendingPublic.length > 0;
138
- const pendingSpeaker = pendingPublic[0]?.actor;
139
- const pendingLead = pendingSpeaker === undefined
140
- ? ''
141
- : `▲ while you spoke, ${participantIdentity(pendingSpeaker)} said something behind you\n\n`;
142
- const pending = hasPending ? `\n\n${pendingLead}${renderPendingFeed([...fresh.activities], [...pendingPublic], knownName, fresh.state)}` : '';
143
- const hint = expressHintLine(ownActCount);
144
- const receiptId = landed.activity.id;
145
- const capCount = fresh.hardCap === null ? '' : ` · ${ownActCount}/${fresh.hardCap}`;
146
- const confirmation = landed.activity.reach === 'bell'
147
- ? `● you ring the bell — everyone turns · ${receiptId}${style('dim', capCount)}`
148
- : `● ${landed.activity.mentions.length > 0
149
- ? `you walk over to ${landed.activity.mentions.map((target) => participantIdentity(target)).join(', ')}`
150
- : isGestureOnly(landed.activity.body ?? '')
151
- ? 'your gesture lands'
152
- : 'your words land'} — ${receiptId}${style('dim', capCount)}`;
153
- const withHint = hint ? `${confirmation}\n${style('dim', hint)}` : confirmation;
154
- const replyNotice = landed.replyAuthorNotHere === undefined
155
- ? ''
156
- : `\n○ ${participantIdentity(landed.replyAuthorNotHere)} isn't in the square right now — the reply landed without reaching them`;
157
- const reentry = reentered ? '● you stepped back into the square\n' : '';
158
- process.stdout.write(withPathOutput(squarePath, reentry + withHint + replyNotice + pending, { participantCount: headerCount, held }));
159
- }
160
- catch (error) {
161
- // Save before any recovery read: even a broken artifact must not eat the body.
162
- const draftPath = saveActivityDraft(squarePath, name, rawInput);
163
- const confirmedUnsent = landedId === undefined && (!expressAttempted || (isSquareError(error) && error.facts?.activityUnsent === true));
164
- const fresh = await presentation().catch(() => undefined);
165
- const output = { squarePath, name: knownName, draftPath, participantCount: fresh?.participantCount, held: fresh?.held };
166
- if (!confirmedUnsent) {
167
- process.stderr.write(renderActivityUncertain({ ...output, landedId, detail: error instanceof Error ? error.message : String(error) }));
168
- process.exit(1);
169
- return;
170
- }
171
- if (isSquareError(error) && fresh !== undefined) {
172
- if (error.code === 'behind') {
173
- process.stdout.write(renderActivityBlocked({
174
- ...output,
175
- retryCommand,
176
- forceCommand,
177
- activitySummaries: unreadActivitySummaries(fresh.state, knownName, nowMs()),
178
- }));
179
- process.exit(1);
180
- return;
181
- }
182
- if (error.code === 'capped') {
183
- process.stdout.write(renderActivityLimit({
184
- ...output,
185
- count: fresh.ownActivityCount,
186
- ...(fresh.hardCap === null ? {} : { hardCap: fresh.hardCap }),
187
- }));
188
- process.exit(1);
189
- return;
190
- }
191
- if (error.code === 'throttled' || error.code === 'held') {
192
- process.stdout.write(renderExpressNoWait({
193
- ...output,
194
- reason: error.code,
195
- delayMs: error.facts?.retryAfterMs ?? SLEEP_MS,
196
- ...(error.facts?.holder === undefined ? {} : { holder: error.facts.holder }),
197
- ...(error.facts?.holdReason === undefined ? {} : { holdReason: error.facts.holdReason }),
198
- retryCommand,
199
- }));
200
- process.exit(1);
201
- return;
202
- }
203
- if (error.code === 'bell_quota') {
204
- process.stdout.write(withPathOutput(squarePath, [
205
- '✕ the bell stays quiet for now',
206
- ` · you can ring it again in ${formatDuration(error.facts?.retryAfterMs ?? 1)}`,
207
- `· draft kept: ${draftPath}`,
208
- ` · retry when the bell is ready:`,
209
- `${retryCommand} < ${quoteShell(draftPath)}`,
210
- ].join('\n'), output));
211
- process.exit(1);
212
- return;
213
- }
214
- }
215
- const lines = [error instanceof Error ? error.message : String(error), `· draft kept: ${draftPath}`];
216
- const reason = isSquareError(error) ? error.facts?.reason : undefined;
217
- // `not_standing` is a flag mention; the body-scan and bell conflicts carry an @name in the draft.
218
- const mentionRepair = reason === 'bell_mention_conflict' ? { bell: true, fromBody: true }
219
- : reason === 'unmatched_mention' ? { bell: false, fromBody: true }
220
- : reason === 'not_standing' ? { bell: false, fromBody: false }
221
- : undefined;
222
- if (isSquareError(error)) {
223
- if (callerMissing || error.code === 'unknown_participant' || error.code === 'not_joined')
224
- lines.push(joinRecoveryCommand(squarePath, name));
225
- if (error.code === 'already_joined')
226
- lines.push(...takeoverRecoveryLines(squarePath, name));
227
- if (mentionRepair !== undefined) {
228
- // The draft stays, but the retry no longer carries the target that was refused.
229
- lines.push(participantsRecoveryCommand(squarePath));
230
- lines.push(mentionRepair.fromBody
231
- ? " · edit the draft first — the body's @name must be removed or wrapped in backticks:"
232
- : ' · pick someone standing here, or land it bare:');
233
- lines.push(`${mentionRepairCommand(squarePath, name, opts, mentionRepair.bell)} < ${quoteShell(draftPath)}`);
234
- }
235
- else if (error.code === 'invalid_args' || error.code === 'invalid_name') {
236
- if (opts.mentions?.length)
237
- lines.push(participantsRecoveryCommand(squarePath));
238
- if (opts.reply !== undefined)
239
- lines.push(`${commandPrefix(squarePath)} history --limit 10`);
240
- }
241
- }
242
- if (mentionRepair === undefined) {
243
- lines.push(' · correct the problem above before retrying; keep your intended recipients and reply:', `${retryCommand} < ${quoteShell(draftPath)}`);
244
- }
245
- process.stderr.write(formatRefusal(squarePath, lines, output));
246
- process.exit(2);
247
- }
248
- }
@@ -1,26 +0,0 @@
1
- export { observeSquareRevision as observeSquareChanges } from './artifact.js';
2
- import { type BuildOptions, type HardCap } from './model.js';
3
- import type { OpenSquare } from './open-square.js';
4
- import type { OperationControl } from './square-facade.js';
5
- import type { HostLedgerPort, SquareArtifactPort } from './ports.js';
6
- /** File-owned artifact creation for the CLI and path-backed public facade. */
7
- export declare function createSquare(squarePath: string, options: BuildOptions & {
8
- hardCap: HardCap;
9
- }, snippet: string): Promise<void>;
10
- export interface SquareBuildOptions {
11
- markdown: string;
12
- hardCap?: number | null;
13
- throttlePerMinute?: number;
14
- clock?: () => number;
15
- hostLedger?: HostLedgerPort;
16
- wakeTransport?: import('./ports.js').WakeTransportPort;
17
- env?: NodeJS.ProcessEnv;
18
- /** One caller deadline (e.g. a native hook budget) propagated into artifact busy retries. */
19
- signal?: AbortSignal;
20
- }
21
- export declare function openSquare(squarePath: string, options?: Pick<SquareBuildOptions, 'clock' | 'hostLedger' | 'wakeTransport' | 'env' | 'signal'>, control?: OperationControl): Promise<OpenSquare>;
22
- export declare function probeSquare(squarePath: string): Promise<OpenSquare | undefined>;
23
- export declare function buildSquare(squarePath: string, options: SquareBuildOptions): Promise<OpenSquare>;
24
- export declare function buildMemorySquare(options: SquareBuildOptions): OpenSquare;
25
- /** A projection reads and validates its snapshot once; no preliminary duplicate read. */
26
- export declare function openSquareArtifact(squarePath: string, signal?: AbortSignal): SquareArtifactPort;
@@ -1,85 +0,0 @@
1
- export { observeSquareRevision as observeSquareChanges } from './artifact.js';
2
- import { hostLedgerRoot } from './host-ledger-root.js';
3
- import { createSquareState, createSquareSnapshot, probeSquareFile, writeSquareSnapshot, createFileCell, createMemoryCell, } from './square-storage.js';
4
- import { InternalSquareError, SquareError, } from './model.js';
5
- import { createHostLedgerPort } from './host-ledger-file-adapter.js';
6
- /** File-owned artifact creation for the CLI and path-backed public facade. */
7
- export async function createSquare(squarePath, options, snippet) {
8
- const state = createSquareState(options, snippet);
9
- if (options.force) {
10
- await writeSquareSnapshot(squarePath, state);
11
- return;
12
- }
13
- if (!await createSquareSnapshot(squarePath, state)) {
14
- throw new InternalSquareError('conflict', `Refusing to overwrite existing square: ${squarePath}\nPass -f to overwrite.`);
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 = {}, control) {
28
- const env = options.env ?? process.env;
29
- const ledgerRoot = hostLedgerRoot(env);
30
- const artifact = openSquareArtifact(squarePath, options.signal);
31
- try {
32
- await artifact.read(control?.signal);
33
- return {
34
- artifact,
35
- clock: options.clock ?? Date.now,
36
- location: squarePath,
37
- env,
38
- hostLedger: options.hostLedger ?? createHostLedgerPort({
39
- rootPath: ledgerRoot,
40
- }),
41
- wakeTransport: options.wakeTransport,
42
- };
43
- }
44
- catch (error) {
45
- await artifact.close();
46
- if (error instanceof InternalSquareError && error.code === 'not_found') {
47
- throw new SquareError('unavailable', `Square is unavailable at ${squarePath}`);
48
- }
49
- throw error;
50
- }
51
- }
52
- export async function probeSquare(squarePath) {
53
- const state = await probeSquareFile(squarePath);
54
- return state === undefined ? undefined : { artifact: createMemoryCell(state), clock: Date.now, location: squarePath, hostLedger: createHostLedgerPort({ rootPath: hostLedgerRoot(process.env) }) };
55
- }
56
- export async function buildSquare(squarePath, options) {
57
- validateBuildOptions(options);
58
- try {
59
- await createSquare(squarePath, {
60
- force: false,
61
- hardCap: options.hardCap ?? null,
62
- ...(options.throttlePerMinute === undefined ? {} : { throttlePerMinute: options.throttlePerMinute }),
63
- }, options.markdown);
64
- }
65
- catch (error) {
66
- if (error instanceof InternalSquareError && error.code === 'conflict') {
67
- throw new SquareError('io', `Cannot build over an existing square at ${squarePath}`);
68
- }
69
- throw error;
70
- }
71
- return openSquare(squarePath, options);
72
- }
73
- export function buildMemorySquare(options) {
74
- validateBuildOptions(options);
75
- const squareState = createSquareState({
76
- force: false,
77
- hardCap: options.hardCap ?? null,
78
- ...(options.throttlePerMinute === undefined ? {} : { throttlePerMinute: options.throttlePerMinute }),
79
- }, options.markdown);
80
- return { artifact: createMemoryCell(squareState), clock: options.clock ?? Date.now, location: 'memory', hostLedger: options.hostLedger, wakeTransport: options.wakeTransport };
81
- }
82
- /** A projection reads and validates its snapshot once; no preliminary duplicate read. */
83
- export function openSquareArtifact(squarePath, signal) {
84
- return createFileCell(squarePath, signal);
85
- }
@@ -1,6 +0,0 @@
1
- import { type BuildOptions, type HardCap, type SquareRuntimeState, type SquareState } from './model.js';
2
- export declare function validateSquareState(value: unknown): SquareState;
3
- export declare function emptyRuntimeState(nextActIndex?: number): SquareRuntimeState;
4
- export declare function createSquareState(options: BuildOptions & {
5
- hardCap: HardCap;
6
- }, snippet: string): SquareState;
@@ -1,138 +0,0 @@
1
- import fs from 'node:fs';
2
- import { isWakeRouteKind, SquareError, } from './model.js';
3
- import { parseActivityId } from './square-core.js';
4
- const guideNames = ['participant', 'architect', 'brainstorm'];
5
- const guideContents = new Map(await Promise.all(guideNames.map(async (name) => [name, (await fs.promises.readFile(new URL(`../guides/${name}.md`, import.meta.url), 'utf8')).trim()])));
6
- function invalidArtifact(detail) {
7
- return new SquareError('invalid_args', `Invalid square artifact: ${detail}`);
8
- }
9
- function isObject(value) {
10
- return typeof value === 'object' && value !== null && !Array.isArray(value);
11
- }
12
- function hasExactKeys(value, required, optional = []) {
13
- const allowed = new Set([...required, ...optional]);
14
- return required.every((key) => Object.hasOwn(value, key)) && Object.keys(value).every((key) => allowed.has(key));
15
- }
16
- function isFiniteNumber(value) {
17
- return typeof value === 'number' && Number.isFinite(value);
18
- }
19
- function isNonNegativeInteger(value) {
20
- return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;
21
- }
22
- function isNonblankString(value) {
23
- return typeof value === 'string' && value.length > 0;
24
- }
25
- function isStringArray(value) {
26
- return Array.isArray(value) && value.every((item) => typeof item === 'string');
27
- }
28
- function validateWatchLease(value) {
29
- if (!isObject(value) || !hasExactKeys(value, ['leaseId', 'heartbeatAt', 'expiresAt'], ['filter'])
30
- || !isNonblankString(value.leaseId) || !isFiniteNumber(value.heartbeatAt)
31
- || !isFiniteNumber(value.expiresAt) || value.expiresAt < value.heartbeatAt)
32
- return false;
33
- if (value.filter === undefined)
34
- return true;
35
- return isObject(value.filter) && hasExactKeys(value.filter, [], ['participants', 'mention'])
36
- && (value.filter.participants === undefined || isStringArray(value.filter.participants))
37
- && (value.filter.mention === undefined || typeof value.filter.mention === 'string');
38
- }
39
- function validateRecord(value, item) {
40
- return isObject(value) && Object.entries(value).every(([key, candidate]) => key.length > 0 && item(candidate));
41
- }
42
- function validateRuntime(value) {
43
- return isObject(value) && hasExactKeys(value, ['nextActIndex', 'observations', 'leases'])
44
- && isNonNegativeInteger(value.nextActIndex)
45
- && validateRecord(value.observations, (candidate) => isObject(candidate)
46
- && Object.entries(candidate).every(([id, observation]) => parseActivityId(id) !== undefined && isObject(observation) && hasExactKeys(observation, ['state', 'at']) && observation.state === 'seen' && isFiniteNumber(observation.at)))
47
- && validateRecord(value.leases, validateWatchLease);
48
- }
49
- function validateAssignedRuntimeReferences(runtime) {
50
- for (const observations of Object.values(runtime.observations)) {
51
- for (const id of Object.keys(observations)) {
52
- const index = parseActivityId(id);
53
- if (index === undefined)
54
- return 'malformed';
55
- if (index >= runtime.nextActIndex)
56
- return 'future';
57
- }
58
- }
59
- return 'ok';
60
- }
61
- function validateActor(value, required) {
62
- return required ? isNonblankString(value) : value === undefined || isNonblankString(value);
63
- }
64
- function validateStoredAct(value) {
65
- if (!isObject(value) || typeof value.kind !== 'string' || !isNonNegativeInteger(value.index) || !isFiniteNumber(value.at))
66
- return false;
67
- switch (value.kind) {
68
- case 'join': return hasExactKeys(value, ['kind', 'actor', 'at', 'index']) && validateActor(value.actor, true);
69
- case 'done': return hasExactKeys(value, ['kind', 'actor', 'at', 'index'], ['body']) && validateActor(value.actor, true) && (value.body === undefined || typeof value.body === 'string');
70
- case 'say': return hasExactKeys(value, ['kind', 'actor', 'at', 'body', 'index'], ['mentions', 'reach', 'reply'])
71
- && validateActor(value.actor, true) && typeof value.body === 'string'
72
- && (value.mentions === undefined || isStringArray(value.mentions)) && (value.reach === undefined || value.reach === 'bell')
73
- && (value.reply === undefined || isNonNegativeInteger(value.reply));
74
- case 'hold': return hasExactKeys(value, ['kind', 'at', 'index'], ['actor', 'body']) && validateActor(value.actor, false) && (value.body === undefined || typeof value.body === 'string');
75
- case 'resume': return hasExactKeys(value, ['kind', 'at', 'index'], ['actor']) && validateActor(value.actor, false);
76
- case 'read': return hasExactKeys(value, ['kind', 'actor', 'at', 'through', 'index']) && validateActor(value.actor, true) && isNonNegativeInteger(value.through);
77
- case 'listen':
78
- case 'ignore': return hasExactKeys(value, ['kind', 'actor', 'target', 'at', 'index']) && validateActor(value.actor, true) && validateActor(value.target, true);
79
- default: return false;
80
- }
81
- }
82
- function validateActs(value) {
83
- if (!Array.isArray(value) || !value.every(validateStoredAct))
84
- return false;
85
- let previous = -1;
86
- for (const act of value) {
87
- if (act.index <= previous || (act.kind === 'say' && act.reply !== undefined && act.reply >= act.index))
88
- return false;
89
- previous = act.index;
90
- }
91
- return true;
92
- }
93
- export function validateSquareState(value) {
94
- if (!isObject(value) || !hasExactKeys(value, ['hardCap', 'preamble', 'warmup', 'acts', 'runtime'], ['throttlePerMinute', 'routes'])
95
- || !(value.hardCap === null || (Number.isSafeInteger(value.hardCap) && value.hardCap > 0))
96
- || (value.throttlePerMinute !== undefined && (!Number.isSafeInteger(value.throttlePerMinute) || value.throttlePerMinute <= 0))
97
- || !isStringArray(value.preamble) || !isStringArray(value.warmup) || !validateActs(value.acts)
98
- || (value.routes !== undefined && (!Array.isArray(value.routes) || !value.routes.every((route) => isObject(route) && isWakeRouteKind(route.kind)
99
- && typeof route.location === 'string' && typeof route.participant === 'string' && typeof route.sessionId === 'string'
100
- && typeof route.channel === 'string' && isObject(route.address) && Object.values(route.address).every((item) => typeof item === 'string')
101
- && typeof route.updatedAt === 'number')))
102
- || !validateRuntime(value.runtime))
103
- throw invalidArtifact('snapshot schema is malformed.');
104
- const historyBoundary = value.acts.at(-1)?.index ?? -1;
105
- if (value.runtime.nextActIndex <= historyBoundary)
106
- throw invalidArtifact('nextActIndex is behind the activity history.');
107
- const references = validateAssignedRuntimeReferences(value.runtime);
108
- if (references === 'malformed')
109
- throw invalidArtifact('snapshot schema is malformed.');
110
- if (references === 'future')
111
- throw invalidArtifact('runtime references an unassigned activity index.');
112
- return value;
113
- }
114
- export function emptyRuntimeState(nextActIndex = 0) {
115
- return { nextActIndex, observations: {}, leases: {} };
116
- }
117
- function normalizedLines(value) {
118
- const normalized = value.replace(/\r\n/g, '\n').trim();
119
- return normalized === '' ? [] : normalized.split('\n');
120
- }
121
- function readGuide(name) {
122
- const guide = guideContents.get(name);
123
- if (guide === undefined)
124
- throw new SquareError('invalid_args', `Unknown square guide: ${name}`);
125
- return guide;
126
- }
127
- export function createSquareState(options, snippet) {
128
- const guides = [readGuide('participant')];
129
- if (options.template !== undefined)
130
- guides.push(readGuide(options.template));
131
- return {
132
- hardCap: options.hardCap,
133
- ...(options.throttlePerMinute === undefined ? {} : { throttlePerMinute: options.throttlePerMinute }),
134
- preamble: normalizedLines(snippet),
135
- warmup: normalizedLines(guides.join('\n\n')),
136
- acts: [], routes: [], runtime: emptyRuntimeState(),
137
- };
138
- }
@@ -1,14 +0,0 @@
1
- import { createSquareState, diagnoseSquareFile as diagnoseArtifactFile } from './artifact.js';
2
- import type { SquareState } from './model.js';
3
- import type { SquareArtifactPort } from './ports.js';
4
- export { createSquareState };
5
- export declare function probeSquareFile(squarePath: string): Promise<SquareState | undefined>;
6
- export declare function diagnoseSquareFile(squarePath: string): ReturnType<typeof diagnoseArtifactFile>;
7
- /** Test-helper snapshot replacement backed by one SQLite artifact. */
8
- export declare function writeSquareSnapshot(squarePath: string, squareState: SquareState): Promise<void>;
9
- /** Race-safe non-force creation for the file adapter. */
10
- export declare function createSquareSnapshot(squarePath: string, squareState: SquareState): Promise<boolean>;
11
- /** In-process artifact for fast behavior tests and embedded consumers. */
12
- export declare function createMemoryCell(initial: SquareState): SquareArtifactPort;
13
- /** SQLite-backed artifact. Revisions are read from the authoritative database, not file metadata. */
14
- export declare function createFileCell(squarePath: string, externalSignal?: AbortSignal): SquareArtifactPort;