@astrosheep/square 0.3.94 → 0.3.95
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.
- package/dist/artifact.js +1 -1
- package/dist/attention.d.ts +19 -0
- package/dist/attention.js +30 -0
- package/dist/boundary-presentation.js +33 -40
- package/dist/catch-decisions.d.ts +1 -2
- package/dist/catch-decisions.js +41 -21
- package/dist/cli/context.js +0 -2
- package/dist/cli/express-presentation.js +3 -3
- package/dist/cli/json.js +1 -1
- package/dist/cli/program.js +1 -1
- package/dist/cli/registry.js +1 -3
- package/dist/cli/square-commands.d.ts +0 -2
- package/dist/cli/square-commands.js +21 -27
- package/dist/decisions.d.ts +0 -5
- package/dist/decisions.js +0 -13
- package/dist/delivery-attempts.d.ts +52 -0
- package/dist/delivery-attempts.js +73 -0
- package/dist/delivery-effects.d.ts +139 -0
- package/dist/delivery-effects.js +163 -0
- package/dist/delivery-operations.js +43 -68
- package/dist/delivery-primitives.d.ts +5 -0
- package/dist/delivery-primitives.js +3 -0
- package/dist/delivery.d.ts +1 -1
- package/dist/delivery.js +6 -5
- package/dist/help.js +4 -6
- package/dist/http-delivery.d.ts +16 -0
- package/dist/http-delivery.js +101 -0
- package/dist/model.d.ts +4 -6
- package/dist/model.js +1 -1
- package/dist/notifications.js +2 -0
- package/dist/operation-presentation.d.ts +1 -2
- package/dist/operation-presentation.js +12 -20
- package/dist/ports.d.ts +5 -14
- package/dist/presentation-operations.d.ts +14 -19
- package/dist/presentation-operations.js +47 -57
- package/dist/presentation.d.ts +2 -9
- package/dist/presentation.js +13 -48
- package/dist/receive.d.ts +66 -0
- package/dist/receive.js +54 -0
- package/dist/runtime.d.ts +0 -1
- package/dist/runtime.js +2 -3
- package/dist/sqlite-square.d.ts +0 -1
- package/dist/sqlite-square.js +29 -55
- package/dist/square-api.d.ts +1 -1
- package/dist/square-api.js +1 -1
- package/dist/square-core.d.ts +0 -9
- package/dist/square-domain.js +9 -31
- package/dist/square-facade.d.ts +5 -21
- package/dist/square-legacy-framing.d.ts +2 -0
- package/dist/square-legacy-framing.js +8 -0
- package/dist/square-migration.d.ts +3 -1
- package/dist/square-migration.js +46 -13
- package/dist/square-receive.d.ts +6 -0
- package/dist/square-receive.js +18 -0
- package/dist/square-sqlite.d.ts +5 -3
- package/dist/square-sqlite.js +89 -90
- package/dist/square-tools.js +3 -11
- package/dist/views.d.ts +2 -3
- package/dist/views.js +13 -19
- package/dist/wakes.d.ts +0 -3
- package/dist/wakes.js +3 -5
- package/examples/symmetric-domains.mjs +250 -0
- package/guides/application-facade-delivery-evidence.md +5 -5
- package/guides/attention-primitive.md +32 -0
- package/guides/change-waits.md +2 -2
- package/guides/codex-delivery.md +1 -1
- package/guides/delivery-primitives.md +180 -0
- package/guides/mcp.md +1 -1
- package/guides/migration.md +12 -6
- package/guides/participant-ownership.md +1 -1
- package/guides/pi-tools.md +2 -2
- package/guides/primitive-catch-consolidation.md +5 -5
- package/guides/primitive-history-consolidation.md +2 -2
- package/guides/primitive-status-consolidation.md +3 -3
- package/guides/primitive-storage-consolidation.md +1 -1
- package/guides/receive-primitive.md +90 -0
- package/guides/row-runtime-delivery-evidence.md +1 -1
- package/guides/row-storage-design.md +17 -23
- package/guides/square-api.md +77 -10
- package/guides/symmetric-domains.md +92 -0
- package/package.json +14 -1
- package/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/plugins/claude/skills/square/SKILL.md +2 -6
- package/plugins/codex/.codex-plugin/plugin.json +1 -1
- package/skills/square/SKILL.md +2 -6
package/dist/presentation.js
CHANGED
|
@@ -4,7 +4,6 @@ import path from 'node:path';
|
|
|
4
4
|
import { participantIdentity } from './participant-identity.js';
|
|
5
5
|
export { participantIdentity } from './participant-identity.js';
|
|
6
6
|
import { formatActivityId, MAX_IDENTITY_SET_SIZE } from './square-core.js';
|
|
7
|
-
import { HELD_WAIT_BUDGET_MS } from './runtime.js';
|
|
8
7
|
import { formatDuration, formatRelativeTime, formatTimestamp } from './time.js';
|
|
9
8
|
import { grepSnippet } from './search.js';
|
|
10
9
|
import { style } from './tty-style.js';
|
|
@@ -27,8 +26,7 @@ export function displayPath(squarePath, cwd = process.cwd()) {
|
|
|
27
26
|
}
|
|
28
27
|
export function withPathOutput(squarePath, body = '', opts = {}) {
|
|
29
28
|
const count = opts.participantCount ?? 0;
|
|
30
|
-
const
|
|
31
|
-
const line = `· ${displayPath(squarePath)} — ${count} in the square${heldSuffix}`;
|
|
29
|
+
const line = `· ${displayPath(squarePath)} — ${count} in the square`;
|
|
32
30
|
return [opts.plain === true ? line : style('dim', line), ...(body === '' ? [] : ['', body])].join('\n') + '\n';
|
|
33
31
|
}
|
|
34
32
|
/** The same header/blank/body layout as withPathOutput, for refusal bodies written to stderr. */
|
|
@@ -123,10 +121,6 @@ export function renderRoomChangeText(event) {
|
|
|
123
121
|
return `${identity} stepped into the square`;
|
|
124
122
|
case 'done':
|
|
125
123
|
return `${identity} stepped out of the square`;
|
|
126
|
-
case 'hold':
|
|
127
|
-
return `${identity} raised a hand${event.body ? ` — ${event.body}` : ''}`;
|
|
128
|
-
case 'resume':
|
|
129
|
-
return `${identity} lowered the hand`;
|
|
130
124
|
case 'listen':
|
|
131
125
|
return `${identity} turned an ear toward ${participantIdentity(event.target ?? 'someone')}`;
|
|
132
126
|
case 'ignore':
|
|
@@ -159,10 +153,6 @@ function renderEventCliFacts(event, opts = {}) {
|
|
|
159
153
|
switch (event.kind) {
|
|
160
154
|
case 'join':
|
|
161
155
|
return { text: `· ${renderRoomChangeText(event)}`, bodyClipped: false };
|
|
162
|
-
case 'hold':
|
|
163
|
-
return { text: `· ${renderRoomChangeText(event)}`, bodyClipped: false };
|
|
164
|
-
case 'resume':
|
|
165
|
-
return { text: `${style('release', '✓')} ${renderRoomChangeText(event)}`, bodyClipped: false };
|
|
166
156
|
case 'listen':
|
|
167
157
|
case 'ignore':
|
|
168
158
|
case 'unlisten':
|
|
@@ -254,7 +244,7 @@ export function renderActivityBlocked(opts) {
|
|
|
254
244
|
`${readNowCommand}`,
|
|
255
245
|
' · take it in, then retry:',
|
|
256
246
|
withDraftInput(opts.retryCommand, opts.draftPath),
|
|
257
|
-
].join('\n'), { participantCount: opts.participantCount
|
|
247
|
+
].join('\n'), { participantCount: opts.participantCount });
|
|
258
248
|
}
|
|
259
249
|
/** An indeterminate or already-committed send only points to inspection, never resend. */
|
|
260
250
|
export function renderActivityUncertain(opts) {
|
|
@@ -268,47 +258,22 @@ export function renderActivityUncertain(opts) {
|
|
|
268
258
|
`${commandPrefix(opts.squarePath)} history${opts.landedId === undefined ? ' --limit 10' : ` --at ${opts.landedId}`} --no-truncate`,
|
|
269
259
|
], opts);
|
|
270
260
|
}
|
|
271
|
-
/** A blank or whitespace hold reason is absent, never an empty sensory line. */
|
|
272
|
-
function holdReasonLines(reason) {
|
|
273
|
-
const text = reason?.trim() ?? '';
|
|
274
|
-
return text === '' ? [] : [style('dim', ` · ${text}`)];
|
|
275
|
-
}
|
|
276
|
-
function heldMainLine(holder, styled) {
|
|
277
|
-
const glyph = styled ? style('blocked', '✕') : '·';
|
|
278
|
-
const who = holder === undefined ? 'a hand is raised' : `${participantIdentity(holder)} has a hand raised`;
|
|
279
|
-
return `${glyph} ${who} — voices drop, yours too`;
|
|
280
|
-
}
|
|
281
261
|
export function renderExpressWaiting(opts) {
|
|
282
|
-
if (opts.reason === 'throttled') {
|
|
283
|
-
return [
|
|
284
|
-
'· the square is packed — your activity is waiting',
|
|
285
|
-
` · a lull opens in ${formatDuration(opts.delayMs)}`,
|
|
286
|
-
' · --no-wait saves a draft and returns now',
|
|
287
|
-
].join('\n');
|
|
288
|
-
}
|
|
289
262
|
return [
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
` · your activity is waiting — after ${formatDuration(HELD_WAIT_BUDGET_MS)} it saves a draft and stops`,
|
|
263
|
+
'· the square is packed — your activity is waiting',
|
|
264
|
+
` · a lull opens in ${formatDuration(opts.delayMs)}`,
|
|
293
265
|
' · --no-wait saves a draft and returns now',
|
|
294
266
|
].join('\n');
|
|
295
267
|
}
|
|
296
268
|
export function renderExpressNoWait(opts) {
|
|
297
269
|
const retryCommand = opts.retryCommand;
|
|
298
|
-
const lines =
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
: [
|
|
306
|
-
heldMainLine(opts.holder, true),
|
|
307
|
-
...holdReasonLines(opts.holdReason),
|
|
308
|
-
...draftSavedLines(opts.draftPath),
|
|
309
|
-
`${withDraftInput(retryCommand, opts.draftPath)}`,
|
|
310
|
-
];
|
|
311
|
-
return withPathOutput(opts.squarePath, lines.join('\n'), { participantCount: opts.participantCount, held: opts.held });
|
|
270
|
+
const lines = [
|
|
271
|
+
`${style('blocked', '✕')} the square is packed — shoulder to shoulder`,
|
|
272
|
+
` · a lull opens in ${formatDuration(opts.delayMs)}`,
|
|
273
|
+
...draftSavedLines(opts.draftPath),
|
|
274
|
+
`${withDraftInput(retryCommand, opts.draftPath)}`,
|
|
275
|
+
];
|
|
276
|
+
return withPathOutput(opts.squarePath, lines.join('\n'), { participantCount: opts.participantCount });
|
|
312
277
|
}
|
|
313
278
|
export function renderPresenceAnchor(names) {
|
|
314
279
|
const visibleNames = names.slice(0, MAX_IDENTITY_SET_SIZE);
|
|
@@ -327,7 +292,7 @@ export function renderGrepActivitiesView(visible, totalMatches, noTruncate, squa
|
|
|
327
292
|
let truncated = false;
|
|
328
293
|
for (const act of publicVisible) {
|
|
329
294
|
const rawBody = act.body ?? '';
|
|
330
|
-
const header = `${style('dim', formatActivityId(act.index))} · ${
|
|
295
|
+
const header = `${style('dim', formatActivityId(act.index))} · ${participantIdentity(act.actor)} · ${style('dim', formatTimestamp(act.at))}`;
|
|
331
296
|
if (perception?.(act) === 'presence') {
|
|
332
297
|
chunks.push(header);
|
|
333
298
|
continue;
|
|
@@ -369,7 +334,7 @@ export function renderActivityLimit(opts) {
|
|
|
369
334
|
...draftSavedLines(opts.draftPath),
|
|
370
335
|
' · your draft stays unsent; done only steps out',
|
|
371
336
|
doneCommand,
|
|
372
|
-
].join('\n'), { participantCount: opts.participantCount
|
|
337
|
+
].join('\n'), { participantCount: opts.participantCount });
|
|
373
338
|
}
|
|
374
339
|
export function renderWatchAlreadyActive(opts) {
|
|
375
340
|
return [
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/** Synchronous capabilities borrowed from one caller-owned transaction. */
|
|
2
|
+
export interface Item<K extends string | number, D> {
|
|
3
|
+
readonly key: K;
|
|
4
|
+
readonly data: D;
|
|
5
|
+
}
|
|
6
|
+
export interface Address<V> {
|
|
7
|
+
readonly recipient: string;
|
|
8
|
+
readonly via: V;
|
|
9
|
+
}
|
|
10
|
+
export interface ReceivedHead<K extends string | number, V> {
|
|
11
|
+
readonly key: K;
|
|
12
|
+
readonly via: V;
|
|
13
|
+
}
|
|
14
|
+
export interface ReceiptRef<K extends string | number> {
|
|
15
|
+
readonly recipient: string;
|
|
16
|
+
readonly item: K;
|
|
17
|
+
}
|
|
18
|
+
export type Selection<K extends string | number, Q> = {
|
|
19
|
+
readonly kind: 'page';
|
|
20
|
+
readonly query: Q;
|
|
21
|
+
readonly limit: number;
|
|
22
|
+
} | {
|
|
23
|
+
readonly kind: 'exact';
|
|
24
|
+
readonly key: K;
|
|
25
|
+
readonly remainingQuery: Q;
|
|
26
|
+
};
|
|
27
|
+
export interface ReceiveRead<K extends string | number, D, V, Q> {
|
|
28
|
+
heads(recipient: string, query: Q, options: {
|
|
29
|
+
readonly unconsumed: boolean;
|
|
30
|
+
readonly limit: number;
|
|
31
|
+
}): readonly ReceivedHead<K, V>[];
|
|
32
|
+
addressed(recipient: string, item: K): ReceivedHead<K, V> | undefined;
|
|
33
|
+
load(keys: readonly K[]): ReadonlyMap<K, D>;
|
|
34
|
+
count(recipient: string, query: Q, unconsumed: boolean): number;
|
|
35
|
+
receipt(ref: ReceiptRef<K>): {
|
|
36
|
+
readonly at: number;
|
|
37
|
+
} | undefined;
|
|
38
|
+
}
|
|
39
|
+
export interface ReceiveWrite<K extends string | number, D, V, Q> extends ReceiveRead<K, D, V, Q> {
|
|
40
|
+
insertItem(item: Item<K, D>): void;
|
|
41
|
+
insertAddress(item: K, address: Address<V>, order: number): void;
|
|
42
|
+
/** True only for a new coordinate; an existing receipt retains its first timestamp. */
|
|
43
|
+
insertReceipt(ref: ReceiptRef<K>, at: number): boolean;
|
|
44
|
+
}
|
|
45
|
+
export declare class ReceiveError<K extends string | number = string | number> extends Error {
|
|
46
|
+
readonly code: 'not_addressed' | 'missing_item';
|
|
47
|
+
readonly key: K;
|
|
48
|
+
constructor(code: 'not_addressed' | 'missing_item', key: K);
|
|
49
|
+
}
|
|
50
|
+
/** Storage rejects duplicate items/addresses; the caller rolls back the whole admission. */
|
|
51
|
+
export declare function receive<K extends string | number, D, V>(tx: Pick<ReceiveWrite<K, D, V, unknown>, 'insertItem' | 'insertAddress'>, input: {
|
|
52
|
+
readonly item: Item<K, D>;
|
|
53
|
+
readonly audience: readonly Address<V>[];
|
|
54
|
+
}): void;
|
|
55
|
+
/** Queries filter before limiting. Exact selection ignores consumption, not addressing. */
|
|
56
|
+
export declare function selectReceivedHeads<K extends string | number, V, Q>(tx: Pick<ReceiveRead<K, unknown, V, Q>, 'heads' | 'addressed'>, recipient: string, selection: Selection<K, Q>): readonly ReceivedHead<K, V>[];
|
|
57
|
+
/** Record captured coordinates without reselecting or checking their current eligibility. */
|
|
58
|
+
export declare function recordConsumed<K extends string | number>(tx: Pick<ReceiveWrite<K, unknown, unknown, unknown>, 'insertReceipt'>, refs: readonly ReceiptRef<K>[], at: number): number;
|
|
59
|
+
/** Selection, hydration, receipts and remaining count must share the caller's transaction. */
|
|
60
|
+
export declare function consumeReceived<K extends string | number, D, V, Q>(tx: Pick<ReceiveWrite<K, D, V, Q>, 'heads' | 'addressed' | 'load' | 'insertReceipt' | 'count'>, recipient: string, selection: Selection<K, Q>, at: number): {
|
|
61
|
+
readonly items: readonly (Item<K, D> & {
|
|
62
|
+
readonly via: V;
|
|
63
|
+
})[];
|
|
64
|
+
readonly newlyConsumed: number;
|
|
65
|
+
readonly remaining: number;
|
|
66
|
+
};
|
package/dist/receive.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export class ReceiveError extends Error {
|
|
2
|
+
code;
|
|
3
|
+
key;
|
|
4
|
+
constructor(code, key) {
|
|
5
|
+
super(`${code}: ${String(key)}`);
|
|
6
|
+
this.code = code;
|
|
7
|
+
this.key = key;
|
|
8
|
+
this.name = 'ReceiveError';
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
/** Storage rejects duplicate items/addresses; the caller rolls back the whole admission. */
|
|
12
|
+
export function receive(tx, input) {
|
|
13
|
+
tx.insertItem(input.item);
|
|
14
|
+
input.audience.forEach((address, order) => tx.insertAddress(input.item.key, address, order));
|
|
15
|
+
}
|
|
16
|
+
/** Queries filter before limiting. Exact selection ignores consumption, not addressing. */
|
|
17
|
+
export function selectReceivedHeads(tx, recipient, selection) {
|
|
18
|
+
if (selection.kind === 'page') {
|
|
19
|
+
if (typeof selection.limit !== 'number')
|
|
20
|
+
throw new TypeError('Receive limit must be a number.');
|
|
21
|
+
if (!Number.isSafeInteger(selection.limit) || selection.limit < 1)
|
|
22
|
+
throw new RangeError('Receive limit must be a positive safe integer.');
|
|
23
|
+
return tx.heads(recipient, selection.query, { unconsumed: true, limit: selection.limit });
|
|
24
|
+
}
|
|
25
|
+
const head = tx.addressed(recipient, selection.key);
|
|
26
|
+
if (head === undefined)
|
|
27
|
+
throw new ReceiveError('not_addressed', selection.key);
|
|
28
|
+
return [head];
|
|
29
|
+
}
|
|
30
|
+
/** Record captured coordinates without reselecting or checking their current eligibility. */
|
|
31
|
+
export function recordConsumed(tx, refs, at) {
|
|
32
|
+
if (typeof at !== 'number')
|
|
33
|
+
throw new TypeError('Receipt time must be a number.');
|
|
34
|
+
if (!Number.isFinite(at))
|
|
35
|
+
throw new RangeError('Receipt time must be finite.');
|
|
36
|
+
let count = 0;
|
|
37
|
+
for (const ref of refs)
|
|
38
|
+
if (tx.insertReceipt(ref, at))
|
|
39
|
+
count++;
|
|
40
|
+
return count;
|
|
41
|
+
}
|
|
42
|
+
/** Selection, hydration, receipts and remaining count must share the caller's transaction. */
|
|
43
|
+
export function consumeReceived(tx, recipient, selection, at) {
|
|
44
|
+
const heads = selectReceivedHeads(tx, recipient, selection);
|
|
45
|
+
const data = tx.load(heads.map(head => head.key));
|
|
46
|
+
const items = heads.map(head => {
|
|
47
|
+
if (!data.has(head.key))
|
|
48
|
+
throw new ReceiveError('missing_item', head.key);
|
|
49
|
+
return { key: head.key, data: data.get(head.key), via: head.via };
|
|
50
|
+
});
|
|
51
|
+
const newlyConsumed = recordConsumed(tx, heads.map(head => ({ recipient, item: head.key })), at);
|
|
52
|
+
const remaining = tx.count(recipient, selection.kind === 'page' ? selection.query : selection.remainingQuery, true);
|
|
53
|
+
return { items, newlyConsumed, remaining };
|
|
54
|
+
}
|
package/dist/runtime.d.ts
CHANGED
|
@@ -4,7 +4,6 @@ export declare const WATCH_HEARTBEAT_MS: number;
|
|
|
4
4
|
export declare const WATCH_STALE_MS: number;
|
|
5
5
|
export declare const THROTTLE_WINDOW_MS: number;
|
|
6
6
|
export declare const UNREAD_BLOCK_GRACE_MS: number;
|
|
7
|
-
export declare const HELD_WAIT_BUDGET_MS: number;
|
|
8
7
|
export declare const FACT_PAGE_SIZE = 100;
|
|
9
8
|
export declare function inSquareCount(facts: SquareRead): number;
|
|
10
9
|
export declare function terminalStatusFor(current: ParticipantFact | undefined, hardCap: number | null, counts: ParticipantCounts): 'capped' | 'quorum' | undefined;
|
package/dist/runtime.js
CHANGED
|
@@ -12,7 +12,6 @@ export const WATCH_HEARTBEAT_MS = parseIntegerEnv('SQUARE_WATCH_HEARTBEAT_MS', 6
|
|
|
12
12
|
export const WATCH_STALE_MS = parseIntegerEnv('SQUARE_WATCH_STALE_MS', 180000);
|
|
13
13
|
export const THROTTLE_WINDOW_MS = parseIntegerEnv('SQUARE_THROTTLE_WINDOW_MS', 60000);
|
|
14
14
|
export const UNREAD_BLOCK_GRACE_MS = parseIntegerEnv('SQUARE_UNREAD_BLOCK_GRACE_MS', 90000);
|
|
15
|
-
export const HELD_WAIT_BUDGET_MS = parseIntegerEnv('SQUARE_HELD_WAIT_BUDGET_MS', 10 * 60 * 1000);
|
|
16
15
|
export const FACT_PAGE_SIZE = 100;
|
|
17
16
|
export function inSquareCount(facts) { return facts.participantCounts().joined; }
|
|
18
17
|
export function terminalStatusFor(current, hardCap, counts) {
|
|
@@ -33,8 +32,8 @@ export function readCursor(facts, name) {
|
|
|
33
32
|
const boundary = facts.participant(name)?.lastJoinIndex ?? -1;
|
|
34
33
|
const block = facts.directed(name, { afterIndex: boundary, unseenOnly: true }, { edge: 'oldest', limit: 1 }).items[0]?.index;
|
|
35
34
|
const last = facts.activityHeads({
|
|
36
|
-
afterIndex: boundary, ...(block === undefined ? {} : { beforeIndex: block }),
|
|
37
|
-
kinds: ['join', 'done', 'say', '
|
|
35
|
+
afterIndex: boundary, ...(block === undefined ? {} : { beforeIndex: block }),
|
|
36
|
+
kinds: ['join', 'done', 'say', 'listen', 'ignore', 'unlisten'],
|
|
38
37
|
}, { edge: 'newest', limit: 1 }).items[0]?.index;
|
|
39
38
|
return Math.max(boundary, last ?? boundary);
|
|
40
39
|
}
|
package/dist/sqlite-square.d.ts
CHANGED
|
@@ -4,7 +4,6 @@ export type * from './square-facade.js';
|
|
|
4
4
|
export { createSquareResourceOwner } from './artifact.js';
|
|
5
5
|
export { formatActivityId, parseActivityId, MAX_IDENTITY_SET_SIZE } from './square-core.js';
|
|
6
6
|
export { CATCH_DEFAULT_LIMIT, CATCH_MAX_LIMIT } from './catch-decisions.js';
|
|
7
|
-
export { HELD_WAIT_BUDGET_MS } from './runtime.js';
|
|
8
7
|
export { HISTORY_DEFAULT_LIMIT, HISTORY_MAX_LIMIT } from './views.js';
|
|
9
8
|
export { DEFAULT_LIST_DEPTH, MAX_LIST_DEPTH, DEFAULT_LIST_LIMIT, MAX_LIST_LIMIT } from './list.js';
|
|
10
9
|
export declare function squareClock(env: NodeJS.ProcessEnv, clock?: () => number): () => number;
|
package/dist/sqlite-square.js
CHANGED
|
@@ -6,8 +6,8 @@ import { squareLocation } from './square-location.js';
|
|
|
6
6
|
import crypto from 'node:crypto';
|
|
7
7
|
import { setTimeout as sleep } from 'node:timers/promises';
|
|
8
8
|
import { InternalSquareError, SquareError, isSquareError, nameKey, validateName } from './model.js';
|
|
9
|
-
import { coreDone,
|
|
10
|
-
import {
|
|
9
|
+
import { coreDone, coreIgnore, coreListen, coreListening, coreUnlisten, decideAct, decideJoin, resolveKnownName, validateDoneBody } from './decisions.js';
|
|
10
|
+
import { WATCH_HEARTBEAT_MS, inSquareCount, presenceAnchor } from './runtime.js';
|
|
11
11
|
import { catchLimit, decideCatch, observeCatchSelection, selectCatchPage } from './catch-decisions.js';
|
|
12
12
|
import { expressionContext } from './activity-feed.js';
|
|
13
13
|
import { expressHint } from './hints.js';
|
|
@@ -26,7 +26,6 @@ import { streamSquare } from './stream.js';
|
|
|
26
26
|
export { createSquareResourceOwner } from './artifact.js';
|
|
27
27
|
export { formatActivityId, parseActivityId, MAX_IDENTITY_SET_SIZE } from './square-core.js';
|
|
28
28
|
export { CATCH_DEFAULT_LIMIT, CATCH_MAX_LIMIT } from './catch-decisions.js';
|
|
29
|
-
export { HELD_WAIT_BUDGET_MS } from './runtime.js';
|
|
30
29
|
export { HISTORY_DEFAULT_LIMIT, HISTORY_MAX_LIMIT } from './views.js';
|
|
31
30
|
export { DEFAULT_LIST_DEPTH, MAX_LIST_DEPTH, DEFAULT_LIST_LIMIT, MAX_LIST_LIMIT } from './list.js';
|
|
32
31
|
function checkControl(control) {
|
|
@@ -115,12 +114,12 @@ export class SqliteSquare {
|
|
|
115
114
|
if (this.indexPath !== undefined)
|
|
116
115
|
await rememberSquarePath(this.location, owner, this.indexPath).catch(() => undefined);
|
|
117
116
|
}
|
|
118
|
-
observation(standingCount,
|
|
119
|
-
return { location: this.location, standingCount,
|
|
117
|
+
observation(standingCount, participant, now = this.clock()) {
|
|
118
|
+
return { location: this.location, standingCount, now,
|
|
120
119
|
...(participant === undefined ? {} : { participant }) };
|
|
121
120
|
}
|
|
122
121
|
async contextObservation(participant, control) {
|
|
123
|
-
return (await this.artifact.read(facts => this.observation(inSquareCount(facts),
|
|
122
|
+
return (await this.artifact.read(facts => this.observation(inSquareCount(facts), participant), control?.signal)).value;
|
|
124
123
|
}
|
|
125
124
|
handle(seat) { return new SqliteSquare.ParticipantHandle(seat.participant, this, seat); }
|
|
126
125
|
// The nested handle uses its resource's private dependencies without copying
|
|
@@ -142,7 +141,6 @@ export class SqliteSquare {
|
|
|
142
141
|
const control = input === undefined ? operationControl : optionsOrControl;
|
|
143
142
|
let name = this.name, effect = 'unsent';
|
|
144
143
|
let landedId;
|
|
145
|
-
let heldWaitedMs = 0;
|
|
146
144
|
try {
|
|
147
145
|
checkControl(control);
|
|
148
146
|
name = (await square.artifact.read(facts => resolveKnownName(facts, this.name), control?.signal)).value;
|
|
@@ -167,12 +165,9 @@ export class SqliteSquare {
|
|
|
167
165
|
// Decoding selected rows is storage work, not proof of safe admission refusal.
|
|
168
166
|
const proposed = decideAct(facts, { name, body, force: options.force ?? false, metadata: options.metadata, now, mentions: options.mentions,
|
|
169
167
|
...(options.reach === undefined ? {} : { reach: options.reach }), ...(options.noMention === undefined ? {} : { noMention: options.noMention }), ...(reply === undefined ? {} : { reply }) });
|
|
170
|
-
const holder = proposed.type === 'held' ? facts.hold().actor : undefined;
|
|
171
168
|
const decision = rejectUnsentActivity(() => {
|
|
172
169
|
if (proposed.type === 'blocked')
|
|
173
170
|
throw new SquareError('behind', `${participantIdentity(name)} has pending activity`, { pending: proposed.activitySummaries.reduce((count, summary) => count + summary.count, 0) });
|
|
174
|
-
if (proposed.type === 'held')
|
|
175
|
-
throw new SquareError('held', 'The square is held', { ...(holder === undefined ? {} : { holder }), ...(proposed.reason === undefined ? {} : { holdReason: proposed.reason }) });
|
|
176
171
|
if (proposed.type === 'capped')
|
|
177
172
|
throw new SquareError('capped', `${participantIdentity(name)} reached the activity cap`);
|
|
178
173
|
if (proposed.type === 'throttled')
|
|
@@ -181,7 +176,7 @@ export class SqliteSquare {
|
|
|
181
176
|
throw new SquareError('bell_quota', `${participantIdentity(name)} cannot ring the bell yet`, { retryAfterMs: Math.max(1, proposed.nextAt - now) });
|
|
182
177
|
return proposed;
|
|
183
178
|
});
|
|
184
|
-
return { stored: facts.land(decision.act), standingCount: inSquareCount(facts),
|
|
179
|
+
return { stored: facts.land(decision.act), standingCount: inSquareCount(facts),
|
|
185
180
|
ownActivityCount: facts.participant(name).sayCount, hardCap: facts.header().hardCap,
|
|
186
181
|
...(decision.unmatchedMentions === undefined ? {} : { unmatchedMentions: decision.unmatchedMentions }),
|
|
187
182
|
...(decision.replyAuthorNotHere === undefined ? {} : { replyAuthorNotHere: decision.replyAuthorNotHere }) };
|
|
@@ -190,33 +185,20 @@ export class SqliteSquare {
|
|
|
190
185
|
effect = 'landed';
|
|
191
186
|
if (square.wakeTransport !== undefined)
|
|
192
187
|
await deliverPending({ artifact: square.artifact, transport: square.wakeTransport, location: square.deliveryLocation, activity: committed.stored.index, now }).catch(() => undefined);
|
|
193
|
-
const { stored, standingCount,
|
|
188
|
+
const { stored, standingCount, ownActivityCount, hardCap, ...metadata } = committed;
|
|
194
189
|
const hint = expressHint(ownActivityCount);
|
|
195
190
|
return { activity: toPublicActivity(stored), ...metadata,
|
|
196
191
|
...(hint === undefined ? {} : { hint }), ...(context === undefined ? {} : { observation: {
|
|
197
|
-
...square.observation(standingCount,
|
|
192
|
+
...square.observation(standingCount, name, now), ownActivityCount, hardCap, context, participant: name,
|
|
198
193
|
} }) };
|
|
199
194
|
}
|
|
200
195
|
catch (error) {
|
|
201
196
|
if (effect !== 'landed' && isSquareError(error) && error.facts?.activityUnsent === true)
|
|
202
197
|
effect = 'unsent';
|
|
203
|
-
if (effect === 'landed' || !isSquareError(error) || options.noWait ||
|
|
198
|
+
if (effect === 'landed' || !isSquareError(error) || options.noWait || error.code !== 'throttled')
|
|
204
199
|
throw error;
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
control?.onProgress?.({ kind: 'waiting', reason: error.code,
|
|
208
|
-
...(error.code === 'throttled' ? { delayMs: error.facts?.retryAfterMs ?? 1 } : {}),
|
|
209
|
-
...(error.facts?.holder === undefined ? {} : { holder: error.facts.holder }),
|
|
210
|
-
...(error.facts?.holdReason === undefined ? {} : { holdReason: error.facts.holdReason }) });
|
|
211
|
-
if (error.code === 'throttled')
|
|
212
|
-
await sleep(error.facts?.retryAfterMs ?? 1, undefined, { signal: control?.signal });
|
|
213
|
-
else {
|
|
214
|
-
const started = Date.now();
|
|
215
|
-
const observation = await square.artifact.read(facts => facts.hold().active, control?.signal);
|
|
216
|
-
if (observation.value)
|
|
217
|
-
await square.artifact.changed(observation.version, Math.max(1, HELD_WAIT_BUDGET_MS - heldWaitedMs), control?.signal);
|
|
218
|
-
heldWaitedMs += Date.now() - started;
|
|
219
|
-
}
|
|
200
|
+
control?.onProgress?.({ kind: 'waiting', reason: 'throttled', delayMs: error.facts?.retryAfterMs ?? 1 });
|
|
201
|
+
await sleep(error.facts?.retryAfterMs ?? 1, undefined, { signal: control?.signal });
|
|
220
202
|
}
|
|
221
203
|
}
|
|
222
204
|
}
|
|
@@ -235,7 +217,7 @@ export class SqliteSquare {
|
|
|
235
217
|
const act = verb === 'listen' ? coreListen(facts, name, target, now) : verb === 'ignore' ? coreIgnore(facts, name, target, now) : coreUnlisten(facts, name, target, now);
|
|
236
218
|
const activity = act === undefined ? null : toPublicActivity(facts.land(act));
|
|
237
219
|
return { activity, ...(!options.observe ? {} : { observation: {
|
|
238
|
-
...square.observation(inSquareCount(facts),
|
|
220
|
+
...square.observation(inSquareCount(facts), name, now), participant: name,
|
|
239
221
|
target: activity?.target ?? target,
|
|
240
222
|
} }) };
|
|
241
223
|
}, control?.signal)).value;
|
|
@@ -247,7 +229,7 @@ export class SqliteSquare {
|
|
|
247
229
|
const square = this.owner;
|
|
248
230
|
return (await square.artifact.read(facts => {
|
|
249
231
|
const name = resolveKnownName(facts, this.name);
|
|
250
|
-
return { listening: coreListening(facts, name), ...(!options.observe ? {} : { observation: square.observation(inSquareCount(facts),
|
|
232
|
+
return { listening: coreListening(facts, name), ...(!options.observe ? {} : { observation: square.observation(inSquareCount(facts), name) }) };
|
|
251
233
|
}, control?.signal)).value;
|
|
252
234
|
}
|
|
253
235
|
/** Receipt selection rechecks the lease inside the writer, not at its last heartbeat. */
|
|
@@ -283,7 +265,7 @@ export class SqliteSquare {
|
|
|
283
265
|
}
|
|
284
266
|
return { activities: view.activities, remaining: view.remaining, reason,
|
|
285
267
|
...(view.hint === undefined ? {} : { hint: view.hint }), ...(!options.observe ? {} : { observation: {
|
|
286
|
-
...square.observation(view.standingCount, view.
|
|
268
|
+
...square.observation(view.standingCount, view.participant, view.now), participant: view.participant,
|
|
287
269
|
deliveryAvailable: view.deliveryAvailable,
|
|
288
270
|
ownActivityCount: view.ownActivityCount, hardCap: view.hardCap,
|
|
289
271
|
...(presence === undefined ? { enrichmentUnavailable: true } : { presence }),
|
|
@@ -325,7 +307,8 @@ export class SqliteSquare {
|
|
|
325
307
|
return await complete(undefined, 'already-active');
|
|
326
308
|
leaseId = start.leaseId;
|
|
327
309
|
control?.onProgress?.({ kind: 'catch-started', participant: name, idleMs: idle, replacementRequested: options.replace ?? false, replaced: start.replaced });
|
|
328
|
-
|
|
310
|
+
const quietSince = square.clock();
|
|
311
|
+
let nextHeartbeat = start.heartbeatAt + WATCH_HEARTBEAT_MS;
|
|
329
312
|
while (true) {
|
|
330
313
|
checkControl(control);
|
|
331
314
|
// Arm before selecting: a commit during selection must not be missed.
|
|
@@ -337,21 +320,16 @@ export class SqliteSquare {
|
|
|
337
320
|
return await complete(undefined, pulse.status);
|
|
338
321
|
if (pulse.heartbeatAt !== undefined)
|
|
339
322
|
nextHeartbeat = pulse.heartbeatAt + WATCH_HEARTBEAT_MS;
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
}
|
|
347
|
-
if (pulse.type === 'held' || wasHeld)
|
|
348
|
-
quietSince = square.clock();
|
|
349
|
-
wasHeld = pulse.type === 'held';
|
|
350
|
-
if (!wasHeld && square.clock() - quietSince >= idle) {
|
|
323
|
+
const caught = await this.catchPage(name, seat, selection, control, leaseId);
|
|
324
|
+
if (caught === null)
|
|
325
|
+
return await complete(undefined, 'replaced');
|
|
326
|
+
if (caught.activities.length > 0)
|
|
327
|
+
return await complete(caught);
|
|
328
|
+
if (square.clock() - quietSince >= idle) {
|
|
351
329
|
const owns = await ownsWatchLease(square.artifact, seat, square.clock(), leaseId, control?.signal);
|
|
352
330
|
return await complete(undefined, owns ? 'idle' : 'replaced');
|
|
353
331
|
}
|
|
354
|
-
const deadline = Math.min(nextHeartbeat,
|
|
332
|
+
const deadline = Math.min(nextHeartbeat, quietSince + idle);
|
|
355
333
|
await square.artifact.changed(baseline, Math.max(0, deadline - square.clock()), control?.signal);
|
|
356
334
|
}
|
|
357
335
|
}
|
|
@@ -392,21 +370,17 @@ export class SqliteSquare {
|
|
|
392
370
|
const square = this.owner, seat = this.seat;
|
|
393
371
|
return (await square.artifact.transactAs(participantAuthority(seat), facts => facts.releaseDeliveryDeferral(asSeatRef(seat), id, square.clock()), control?.signal)).value;
|
|
394
372
|
}
|
|
395
|
-
async
|
|
396
|
-
|
|
397
|
-
validateDoneBody(body);
|
|
373
|
+
async done(body, options = {}, control) {
|
|
374
|
+
validateDoneBody(body);
|
|
398
375
|
const square = this.owner;
|
|
399
376
|
const name = (await square.artifact.read(facts => resolveKnownName(facts, this.name), control?.signal)).value;
|
|
400
377
|
const seat = this.seat, now = square.clock();
|
|
401
378
|
return (await square.artifact.transactAs(participantAuthority(seat), facts => {
|
|
402
|
-
const stored = facts.land(
|
|
403
|
-
return { activity: toPublicActivity(stored), ...(!options.observe ? {} : { observation: square.observation(inSquareCount(facts),
|
|
379
|
+
const stored = facts.land(coreDone(facts, name, body ?? '', now));
|
|
380
|
+
return { activity: toPublicActivity(stored), ...(!options.observe ? {} : { observation: square.observation(inSquareCount(facts), name, now),
|
|
404
381
|
}) };
|
|
405
382
|
}, control?.signal)).value;
|
|
406
383
|
}
|
|
407
|
-
hold(reason, options = {}, control) { return this.lifecycle('hold', reason, options, control); }
|
|
408
|
-
resume(options = {}, control) { return this.lifecycle('resume', undefined, options, control); }
|
|
409
|
-
done(body, options = {}, control) { return this.lifecycle('done', body, options, control); }
|
|
410
384
|
};
|
|
411
385
|
async participant(name, { owner }) {
|
|
412
386
|
validateName(name);
|
|
@@ -467,7 +441,7 @@ export class SqliteSquare {
|
|
|
467
441
|
if (seat === undefined)
|
|
468
442
|
throw new SquareError('invalid_args', 'The join activity did not establish its artifact seat.');
|
|
469
443
|
return { participant: this.handle(seat), activity, kind,
|
|
470
|
-
...(!options.observe ? {} : { observation: { ...this.observation(inSquareCount(facts),
|
|
444
|
+
...(!options.observe ? {} : { observation: { ...this.observation(inSquareCount(facts), seat.participant, now),
|
|
471
445
|
deliveryAvailable: seat.wakeAddress !== undefined } }) };
|
|
472
446
|
}, control?.signal)).value;
|
|
473
447
|
await this.publishSeat(owner);
|
|
@@ -535,7 +509,7 @@ export class SqliteSquare {
|
|
|
535
509
|
if (indexes.includes(anchor))
|
|
536
510
|
(presenceAnchors[anchor] ??= []).push(participant.name);
|
|
537
511
|
}
|
|
538
|
-
return { activities, observation: { ...this.observation(inSquareCount(facts)
|
|
512
|
+
return { activities, observation: { ...this.observation(inSquareCount(facts)), ...selected,
|
|
539
513
|
sayNumbers: facts.sayOrdinals(indexes), presenceAnchors } };
|
|
540
514
|
}, control?.signal)).value;
|
|
541
515
|
}
|
package/dist/square-api.d.ts
CHANGED
|
@@ -16,4 +16,4 @@ export declare function selectOwnSeat(square: Pick<SqliteSquare, 'participant' |
|
|
|
16
16
|
}): Promise<Participant | undefined>;
|
|
17
17
|
export { ACTIVITY_METADATA_MAX_BYTES } from './model.js';
|
|
18
18
|
export { localUserOwner, harnessOwner, decodeHarnessOwner } from './owner.js';
|
|
19
|
-
export { squareClock, createSquareResourceOwner, formatActivityId, parseActivityId, MAX_IDENTITY_SET_SIZE, CATCH_DEFAULT_LIMIT, CATCH_MAX_LIMIT,
|
|
19
|
+
export { squareClock, createSquareResourceOwner, formatActivityId, parseActivityId, MAX_IDENTITY_SET_SIZE, CATCH_DEFAULT_LIMIT, CATCH_MAX_LIMIT, HISTORY_DEFAULT_LIMIT, HISTORY_MAX_LIMIT, DEFAULT_LIST_DEPTH, MAX_LIST_DEPTH, DEFAULT_LIST_LIMIT, MAX_LIST_LIMIT, diagnoseSquare, migrateSquare, discover, inbox } from './sqlite-square.js';
|
package/dist/square-api.js
CHANGED
|
@@ -27,4 +27,4 @@ export async function selectOwnSeat(square, owner, name, options) {
|
|
|
27
27
|
}
|
|
28
28
|
export { ACTIVITY_METADATA_MAX_BYTES } from './model.js';
|
|
29
29
|
export { localUserOwner, harnessOwner, decodeHarnessOwner } from './owner.js';
|
|
30
|
-
export { squareClock, createSquareResourceOwner, formatActivityId, parseActivityId, MAX_IDENTITY_SET_SIZE, CATCH_DEFAULT_LIMIT, CATCH_MAX_LIMIT,
|
|
30
|
+
export { squareClock, createSquareResourceOwner, formatActivityId, parseActivityId, MAX_IDENTITY_SET_SIZE, CATCH_DEFAULT_LIMIT, CATCH_MAX_LIMIT, HISTORY_DEFAULT_LIMIT, HISTORY_MAX_LIMIT, DEFAULT_LIST_DEPTH, MAX_LIST_DEPTH, DEFAULT_LIST_LIMIT, MAX_LIST_LIMIT, diagnoseSquare, migrateSquare, discover, inbox } from './sqlite-square.js';
|
package/dist/square-core.d.ts
CHANGED
|
@@ -29,15 +29,6 @@ export type Act = {
|
|
|
29
29
|
mentions?: readonly Participant[];
|
|
30
30
|
reach?: Reach;
|
|
31
31
|
reply?: number;
|
|
32
|
-
} | {
|
|
33
|
-
kind: 'hold';
|
|
34
|
-
actor?: Participant;
|
|
35
|
-
at?: number;
|
|
36
|
-
body?: string;
|
|
37
|
-
} | {
|
|
38
|
-
kind: 'resume';
|
|
39
|
-
actor?: Participant;
|
|
40
|
-
at?: number;
|
|
41
32
|
} | {
|
|
42
33
|
kind: 'read';
|
|
43
34
|
actor: Participant;
|
package/dist/square-domain.js
CHANGED
|
@@ -1,35 +1,13 @@
|
|
|
1
1
|
import { SquareError, formatActivityId, parseActivityId, sameName, validateOwner, validateName } from './model.js';
|
|
2
|
+
import { resolveAudience } from './attention.js';
|
|
2
3
|
/** The single landing transition, shared by both adapters and explicit rebuild. */
|
|
3
4
|
export function deriveLanding(activity, facts) {
|
|
4
|
-
const audience =
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|| audience.some((item) => sameName(item.recipient, recipient)))
|
|
10
|
-
return;
|
|
11
|
-
if (activity.reach !== 'bell' && facts.incoming.some((item) => item.kind === 'ignore' && sameName(item.listener, recipient)))
|
|
12
|
-
return;
|
|
13
|
-
audience.push({ recipient, route });
|
|
14
|
-
};
|
|
15
|
-
if (activity.reach === 'bell') {
|
|
16
|
-
for (const peer of facts.standing)
|
|
17
|
-
add(peer.name, 'bell');
|
|
18
|
-
}
|
|
19
|
-
else {
|
|
20
|
-
for (const mention of activity.mentions ?? [])
|
|
21
|
-
add(mention, 'mention');
|
|
22
|
-
if (facts.replyAuthor !== undefined)
|
|
23
|
-
add(facts.replyAuthor, 'reply');
|
|
24
|
-
for (const peer of facts.standing) {
|
|
25
|
-
if (facts.incoming.some((item) => item.kind === 'listen' && sameName(item.listener, peer.name)))
|
|
26
|
-
add(peer.name, 'attention');
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
}
|
|
5
|
+
const audience = activity.kind === 'say' ? resolveAudience({
|
|
6
|
+
actor: activity.actor, recipients: facts.standing.map((item) => item.name),
|
|
7
|
+
mentions: activity.mentions, replyAuthor: facts.replyAuthor, reach: activity.reach,
|
|
8
|
+
incoming: facts.incoming, sameIdentity: sameName,
|
|
9
|
+
}) : [];
|
|
30
10
|
const actor = activity.actor;
|
|
31
|
-
if (actor === undefined)
|
|
32
|
-
return { activity, audience };
|
|
33
11
|
const previous = facts.actor ?? { name: actor, firstIndex: activity.index, joined: false, done: false, sayCount: 0 };
|
|
34
12
|
const participant = {
|
|
35
13
|
...previous,
|
|
@@ -57,7 +35,7 @@ export function deriveLanding(activity, facts) {
|
|
|
57
35
|
}
|
|
58
36
|
export function landingFacts(read, activity) {
|
|
59
37
|
return {
|
|
60
|
-
actor:
|
|
38
|
+
actor: read.participant(activity.actor),
|
|
61
39
|
standing: activity.kind === 'say' ? read.roster().filter((item) => item.joined) : [],
|
|
62
40
|
incoming: activity.kind === 'say' ? read.incomingAttention(activity.actor) : [],
|
|
63
41
|
outgoing: activity.kind === 'listen' || activity.kind === 'ignore' || activity.kind === 'unlisten' ? read.attention(activity.actor) : [],
|
|
@@ -225,8 +203,8 @@ export function wrapSquareWrite(read, writes, authority) {
|
|
|
225
203
|
},
|
|
226
204
|
cancelThrough(seat, through) {
|
|
227
205
|
assertSeat(seat);
|
|
228
|
-
if (!Number.isSafeInteger(through) || through < 0 || through
|
|
229
|
-
throw new SquareError('invalid_args', 'Cancellation boundary must
|
|
206
|
+
if (!Number.isSafeInteger(through) || through < 0 || through >= read.header().nextActIndex)
|
|
207
|
+
throw new SquareError('invalid_args', 'Cancellation boundary must be an assigned activity index below the allocator.');
|
|
230
208
|
return mutations.cancelThrough(seat, through);
|
|
231
209
|
},
|
|
232
210
|
markSeen(name, indexes, at) {
|