@astrosheep/square 0.3.93 → 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/activity-feed.d.ts +3 -5
- package/dist/activity-feed.js +16 -14
- package/dist/artifact.js +3 -3
- package/dist/attention.d.ts +19 -0
- package/dist/attention.js +30 -0
- package/dist/boundary-presentation.d.ts +2 -2
- package/dist/boundary-presentation.js +33 -40
- package/dist/catch-decisions.d.ts +5 -8
- package/dist/catch-decisions.js +45 -25
- package/dist/claude-delivery-status.js +10 -4
- package/dist/claude-delivery.js +19 -11
- package/dist/claude-mod.js +24 -53
- package/dist/cli/context.d.ts +9 -4
- package/dist/cli/context.js +30 -27
- package/dist/cli/express-presentation.d.ts +3 -1
- package/dist/cli/express-presentation.js +28 -16
- package/dist/cli/json.d.ts +3 -0
- package/dist/cli/json.js +14 -0
- package/dist/cli/mcp-server-command.js +2 -2
- package/dist/cli/observation-commands.d.ts +1 -3
- package/dist/cli/observation-commands.js +43 -56
- package/dist/cli/program.js +3 -3
- package/dist/cli/registry.js +7 -6
- package/dist/cli/square-commands.d.ts +0 -2
- package/dist/cli/square-commands.js +30 -44
- package/dist/codex-hook.d.ts +3 -2
- package/dist/codex-hook.js +13 -4
- package/dist/codex-queue.js +4 -4
- package/dist/decisions.d.ts +5 -16
- package/dist/decisions.js +9 -33
- package/dist/delivery-attempts.d.ts +52 -0
- package/dist/delivery-attempts.js +73 -0
- package/dist/delivery-deferral.js +2 -2
- package/dist/delivery-effects.d.ts +139 -0
- package/dist/delivery-effects.js +163 -0
- package/dist/delivery-health.d.ts +0 -1
- package/dist/delivery-health.js +1 -3
- package/dist/delivery-operations.d.ts +2 -2
- package/dist/delivery-operations.js +76 -101
- 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 +7 -6
- package/dist/help.js +14 -14
- package/dist/http-delivery.d.ts +16 -0
- package/dist/http-delivery.js +101 -0
- package/dist/inbox.d.ts +9 -22
- package/dist/inbox.js +23 -27
- package/dist/index.d.ts +1 -3
- package/dist/index.js +1 -1
- package/dist/mcp.d.ts +3 -0
- package/dist/mcp.js +23 -24
- package/dist/model.d.ts +12 -21
- package/dist/model.js +5 -8
- package/dist/notifications.d.ts +1 -1
- package/dist/notifications.js +8 -5
- package/dist/operation-presentation.d.ts +6 -6
- package/dist/operation-presentation.js +55 -58
- package/dist/owner.d.ts +11 -0
- package/dist/owner.js +32 -0
- package/dist/paseo-delivery.d.ts +4 -1
- package/dist/paseo-delivery.js +7 -12
- package/dist/paseo-state.d.ts +2 -2
- package/dist/paseo-state.js +6 -4
- package/dist/plugins/opencode/delivery.js +27 -15
- package/dist/ports.d.ts +18 -47
- package/dist/presentation-operations.d.ts +14 -19
- package/dist/presentation-operations.js +51 -61
- package/dist/presentation.d.ts +7 -17
- package/dist/presentation.js +46 -91
- package/dist/receive.d.ts +66 -0
- package/dist/receive.js +54 -0
- package/dist/runtime.d.ts +1 -8
- package/dist/runtime.js +2 -17
- package/dist/seats.d.ts +2 -3
- package/dist/seats.js +7 -15
- package/dist/session-lifecycle.d.ts +6 -2
- package/dist/session-lifecycle.js +39 -11
- package/dist/sqlite-square.d.ts +36 -23
- package/dist/sqlite-square.js +162 -211
- package/dist/square-api.d.ts +16 -11
- package/dist/square-api.js +27 -23
- package/dist/square-core.d.ts +0 -14
- package/dist/square-core.js +0 -9
- package/dist/square-domain.d.ts +4 -2
- package/dist/square-domain.js +65 -52
- package/dist/square-facade.d.ts +47 -105
- package/dist/square-index.d.ts +5 -13
- package/dist/square-index.js +13 -38
- package/dist/square-legacy-framing.d.ts +2 -0
- package/dist/square-legacy-framing.js +8 -0
- package/dist/square-location.d.ts +1 -14
- package/dist/square-location.js +4 -70
- package/dist/square-migration-host-ledger.d.ts +2 -2
- package/dist/square-migration-host-ledger.js +6 -5
- package/dist/square-migration.d.ts +4 -1
- package/dist/square-migration.js +93 -38
- package/dist/square-receive.d.ts +6 -0
- package/dist/square-receive.js +18 -0
- package/dist/square-sqlite.d.ts +9 -3
- package/dist/square-sqlite.js +139 -114
- package/dist/square-tools.d.ts +7 -10
- package/dist/square-tools.js +21 -61
- package/dist/stream.js +4 -5
- package/dist/views.d.ts +23 -13
- package/dist/views.js +47 -53
- package/dist/wakes.d.ts +1 -5
- package/dist/wakes.js +4 -6
- package/examples/symmetric-domains.mjs +250 -0
- package/guides/application-facade-delivery-evidence.md +6 -6
- package/guides/attention-primitive.md +32 -0
- package/guides/change-waits.md +2 -2
- package/guides/claude-native.md +18 -9
- package/guides/codex-delivery.md +17 -6
- package/guides/delivery-primitives.md +180 -0
- package/guides/mcp-connection-lifetime.md +1 -1
- package/guides/mcp.md +3 -3
- package/guides/migration.md +148 -151
- package/guides/operation-cancellation.md +1 -1
- package/guides/participant-ownership.md +78 -47
- package/guides/pi-tools.md +34 -5
- 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 +22 -28
- package/guides/square-api.md +314 -91
- 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/plugins/opencode/delivery.ts +27 -17
- package/plugins/paseo/client/palette.ts +18 -18
- package/plugins/paseo/client/square-message.tsx +17 -23
- package/plugins/pi/square-pi.js +17 -17
- package/skills/square/SKILL.md +2 -6
- package/dist/square-backend.d.ts +0 -13
- package/dist/square-backend.js +0 -1
- package/dist/square-stdio.d.ts +0 -22
- package/dist/square-stdio.js +0 -235
- package/dist/stdio-server.d.ts +0 -3
- package/dist/stdio-server.js +0 -212
- package/guides/backends.md +0 -193
package/dist/square-tools.js
CHANGED
|
@@ -1,51 +1,22 @@
|
|
|
1
1
|
import { z } from 'zod/v4';
|
|
2
2
|
import { isSquareError } from './model.js';
|
|
3
|
-
import { CATCH_DEFAULT_LIMIT } from './square-api.js';
|
|
3
|
+
import { requireOwner, requireJoinName, selectOwnSeat, CATCH_DEFAULT_LIMIT } from './square-api.js';
|
|
4
4
|
import { renderCatchOutcome, renderExpression, renderFailureLines, renderHistoryResult, renderJoinResult, renderLifecycleResult, renderListenerChange, renderStatusResult, } from './operation-presentation.js';
|
|
5
|
-
const location = z.string().optional().describe('Which square: a .square file path
|
|
5
|
+
const location = z.string().optional().describe('Which square: a .square file path. Relative paths start from the working directory. Leave it out for the configured square.');
|
|
6
6
|
const participant = z.string().optional().describe('Your name in the square. Leave it out if you hold exactly one name here.');
|
|
7
7
|
const activityId = z.string().regex(/^act\/(0|[1-9][0-9]*)$/);
|
|
8
8
|
const stringArray = z.array(z.string());
|
|
9
|
-
/**
|
|
10
|
-
function observation(control) {
|
|
11
|
-
return control.observe === true ? { observe: true } : {};
|
|
12
|
-
}
|
|
13
|
-
/** Tool receipts are plain JSON; each renderer re-declares the facts it reads. */
|
|
9
|
+
/** Each renderer declares the operation facts it reads. */
|
|
14
10
|
function rendering(render) {
|
|
15
11
|
return (result, args) => render(result, args);
|
|
16
12
|
}
|
|
17
|
-
/**
|
|
18
|
-
* Tool receipts are the one machine surface without activity metadata: a receipt copy drops every
|
|
19
|
-
* property named `metadata` while domain activities keep theirs for SDK, CLI, stdio and stream readers.
|
|
20
|
-
*/
|
|
21
|
-
function withoutActivityMetadata(value) {
|
|
22
|
-
if (Array.isArray(value))
|
|
23
|
-
return value.map(withoutActivityMetadata);
|
|
24
|
-
if (value === null || typeof value !== 'object')
|
|
25
|
-
return value;
|
|
26
|
-
const prototype = Object.getPrototypeOf(value);
|
|
27
|
-
if (prototype !== Object.prototype && prototype !== null)
|
|
28
|
-
return value;
|
|
29
|
-
return Object.fromEntries(Object.entries(value).filter(([key]) => key !== 'metadata')
|
|
30
|
-
.map(([key, item]) => [key, withoutActivityMetadata(item)]));
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Human text renders from the unfiltered receipt, so metadata-only says keep the placeholder the
|
|
34
|
-
* CLI prints and every other sentence stays identical. Machines only ever receive the filtered copy.
|
|
35
|
-
*/
|
|
36
|
-
const renderingReceipts = new WeakMap();
|
|
37
|
-
function renderingReceipt(receipt) {
|
|
38
|
-
return (renderingReceipts.get(receipt) ?? receipt);
|
|
39
|
-
}
|
|
40
13
|
function tool(name, description, shape, run, render) {
|
|
41
14
|
const inputSchema = z.object({ ...shape, location }).strict();
|
|
42
|
-
return { name, description, inputSchema,
|
|
43
|
-
run: async (square, args, control) => {
|
|
15
|
+
return { name, description, inputSchema,
|
|
16
|
+
run: async (square, args, control, identity) => {
|
|
44
17
|
const { location: _location, ...operationArgs } = inputSchema.parse(args);
|
|
45
|
-
const receipt = await run(square, operationArgs, control);
|
|
46
|
-
|
|
47
|
-
renderingReceipts.set(filtered, receipt);
|
|
48
|
-
return filtered;
|
|
18
|
+
const receipt = await run(square, operationArgs, control, identity);
|
|
19
|
+
return render(receipt, operationArgs) ?? '';
|
|
49
20
|
} };
|
|
50
21
|
}
|
|
51
22
|
export const SQUARE_TOOL_INSTRUCTIONS = 'Join once. Then express to speak, and catch to take in what reached you. History looks back without taking anything in.';
|
|
@@ -53,10 +24,7 @@ const target = z.string().describe('The participant to turn toward or away from.
|
|
|
53
24
|
export const SQUARE_TOOLS = [
|
|
54
25
|
tool('join', 'Step into the square under your name. Do this once before you speak or catch. You arrive seeing what was said most recently.', {
|
|
55
26
|
as: z.string().optional().describe('Your name in the square. Give it explicitly when joining.'), takeover: z.boolean().optional().describe('Claim this name even though another session holds it now. That session loses it.'),
|
|
56
|
-
},
|
|
57
|
-
const { participant, ...receipt } = await square.join(as, { takeover, ...observation(control) }, control);
|
|
58
|
-
return { ...receipt, participant: participant.name };
|
|
59
|
-
}, rendering((result) => renderJoinResult(result))),
|
|
27
|
+
}, (square, { as, takeover }, control, identity) => square.join(requireJoinName(as), { ...identity, owner: requireOwner(identity.owner), takeover, observe: true }, control), rendering((result) => renderJoinResult(result))),
|
|
60
28
|
tool('express', 'Speak or act in the square. Plain words, half-formed ideas and *actions* all belong here. Mention or reply to someone to be heard by them; ring the bell to be heard by everyone. Said to no one in particular, your words reach those who listen to you.', {
|
|
61
29
|
as: participant,
|
|
62
30
|
body: z.string().describe('What you say or do. Markdown works; *asterisks* mark an action.'),
|
|
@@ -65,11 +33,8 @@ export const SQUARE_TOOLS = [
|
|
|
65
33
|
reply: activityId.optional().describe('The activity you are answering, like act/12. Its author hears you if still here.'),
|
|
66
34
|
noMention: z.boolean().optional().describe('Leave @names in the body as plain text. Mentions and reply still reach people.'),
|
|
67
35
|
force: z.boolean().optional().describe('Speak now, even if something reached you that you have not caught yet.'),
|
|
68
|
-
noWait: z.boolean().optional().describe('If
|
|
69
|
-
}, async (square, { body, as, ...options }, control) => {
|
|
70
|
-
const { delivery: _delivery, ...result } = await (await square.participant(as)).express(body, { ...options, ...observation(control) }, control);
|
|
71
|
-
return result;
|
|
72
|
-
}, rendering((result) => renderExpression(result.observation?.location ?? '', result))),
|
|
36
|
+
noWait: z.boolean().optional().describe('If you are speaking too fast, return at once instead of waiting.'),
|
|
37
|
+
}, async (square, { body, as, ...options }, control, identity) => (await selectOwnSeat(square, identity.owner, as)).express(body, { ...options, observe: true }, control), rendering((result) => renderExpression(result.observation?.location ?? '', result))),
|
|
73
38
|
tool('catch', 'Take in what reached you since you last looked: mentions, replies, bells, and words from those you listen to. What you catch no longer waits for you; history lets you look back.', {
|
|
74
39
|
as: participant,
|
|
75
40
|
id: activityId.optional().describe('Take in this activity in full, like act/12, even if you caught it before. Do not combine with idle, from, mention or limit.'),
|
|
@@ -77,7 +42,7 @@ export const SQUARE_TOOLS = [
|
|
|
77
42
|
from: stringArray.optional().describe('Only what these participants said.'),
|
|
78
43
|
mention: z.boolean().optional().describe('Only what was addressed to you, and bells.'),
|
|
79
44
|
limit: z.number().int().min(1).optional().describe('At most this many at once (default 10, up to 100). The rest keeps waiting.'),
|
|
80
|
-
}, async (square, { as, ...options }, control) => (await square.
|
|
45
|
+
}, async (square, { as, ...options }, control, identity) => (await selectOwnSeat(square, identity.owner, as)).catch({ ...options, observe: true }, control), rendering((result, args) => {
|
|
81
46
|
// The CLI always spells the default cap in its continuation, so the tool does too.
|
|
82
47
|
const options = args;
|
|
83
48
|
const renderOptions = options.id === undefined ? { ...options, limit: options.limit ?? CATCH_DEFAULT_LIMIT } : options;
|
|
@@ -92,25 +57,20 @@ export const SQUARE_TOOLS = [
|
|
|
92
57
|
fixed: z.string().optional().describe('Only activities containing this exact text.'),
|
|
93
58
|
from: stringArray.optional().describe('Only what these participants said.'),
|
|
94
59
|
mention: z.string().optional().describe('Only what was addressed to this participant.'),
|
|
95
|
-
}, (square, args, control) => square.history({ ...args,
|
|
60
|
+
}, (square, args, control) => square.history({ ...args, observe: true }, control), rendering((result, args) => renderHistoryResult(result.observation?.location ?? '', result, { query: args }))),
|
|
96
61
|
tool('attention', 'Turn toward someone or away from them, from now on. Listen: hear everything they say, even to others. Unlisten: stop listening without clearing an ignore; mentions and replies still reach you unless ignored. Ignore: hear nothing from them but bells. What was already said stays as it was.', {
|
|
97
62
|
as: participant, action: z.enum(['listen', 'unlisten', 'ignore']).describe('"listen", "unlisten" or "ignore". Listening again ends an ignore.'), target,
|
|
98
|
-
}, async (square, { as, action, target }, control) => {
|
|
99
|
-
const self = await square.
|
|
100
|
-
return self[action](target,
|
|
63
|
+
}, async (square, { as, action, target }, control, identity) => {
|
|
64
|
+
const self = await selectOwnSeat(square, identity.owner, as);
|
|
65
|
+
return self[action](target, { observe: true }, control);
|
|
101
66
|
}, rendering((result, args) => renderListenerChange(result.observation?.location ?? '', args.action, result))),
|
|
102
|
-
tool('
|
|
103
|
-
|
|
104
|
-
reason: z.string().optional().describe('Why you are pausing. Everyone sees it.'),
|
|
105
|
-
release: z.boolean().optional().describe('true lowers the raised hand so the conversation goes on.'),
|
|
106
|
-
}, async (square, { as, reason, release }, control) => {
|
|
107
|
-
const self = await square.participant(as);
|
|
108
|
-
return release === true ? self.resume(observation(control), control) : self.hold(reason, observation(control), control);
|
|
109
|
-
}, rendering((result, args) => renderLifecycleResult(result.observation?.location ?? '', args.release === true ? 'resume' : 'hold', result))),
|
|
110
|
-
tool('done', 'Step out of the square. Everyone sees you leave, and you stop listening to anyone. Leaving carries no words, so say goodbye with express first. Stay instead if someone may still need you; you can join again later.', { as: participant }, async (square, { as }, control) => (await square.participant(as)).done(undefined, observation(control), control), rendering((result) => renderLifecycleResult(result.observation?.location ?? '', 'done', result))),
|
|
111
|
-
tool('status', 'Look around the square: who is here, whether a hand is raised, the latest activity, and who you listen to.', {
|
|
67
|
+
tool('done', 'Step out of the square. Everyone sees you leave, and you stop listening to anyone. Leaving carries no words, so say goodbye with express first. Stay instead if someone may still need you; you can join again later.', { as: participant }, async (square, { as }, control, identity) => (await selectOwnSeat(square, identity.owner, as)).done(undefined, { observe: true }, control), rendering((result) => renderLifecycleResult(result.observation?.location ?? '', result))),
|
|
68
|
+
tool('status', 'Look around the square: who is here, the latest activity, and who you listen to.', {
|
|
112
69
|
as: z.string().optional().describe('Whose listening to show. Leave it out if you hold exactly one name here.'),
|
|
113
|
-
}, (square, { as }, control
|
|
70
|
+
}, async (square, { as }, control, identity) => {
|
|
71
|
+
const viewer = (await selectOwnSeat(square, identity.owner, as, { optional: true }))?.name;
|
|
72
|
+
return square.status({ viewer, observe: true }, control);
|
|
73
|
+
}, rendering((result) => renderStatusResult(result.observation?.location ?? '', result))),
|
|
114
74
|
];
|
|
115
75
|
export function squareToolFailure(error, signal) {
|
|
116
76
|
const normalized = signal?.aborted
|
package/dist/stream.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import { SquareError } from './model.js';
|
|
3
3
|
import { createArtifactPort, observeSquareRevision } from './artifact.js';
|
|
4
|
-
import { streamProjection, streamTailProjection } from './views.js';
|
|
4
|
+
import { parseRequiredActivityId, streamProjection, streamTailProjection } from './views.js';
|
|
5
5
|
/** Pull-based observation; return interrupts an outstanding pull before awaiting its cleanup. */
|
|
6
6
|
export function streamSquare(squarePath, options = {}, control, borrowed) {
|
|
7
7
|
return {
|
|
@@ -28,7 +28,7 @@ export function streamSquare(squarePath, options = {}, control, borrowed) {
|
|
|
28
28
|
}
|
|
29
29
|
signal.throwIfAborted();
|
|
30
30
|
const start = options.start ?? { kind: 'tail', last: 10 };
|
|
31
|
-
let cursor = start.kind === 'after' ? start.after : -1;
|
|
31
|
+
let cursor = start.kind === 'after' ? parseRequiredActivityId(start.after) : -1;
|
|
32
32
|
let initial = start.kind === 'tail' ? start : undefined;
|
|
33
33
|
const observer = await observeSquareRevision(squarePath);
|
|
34
34
|
const close = () => observer.close();
|
|
@@ -60,10 +60,9 @@ export function streamSquare(squarePath, options = {}, control, borrowed) {
|
|
|
60
60
|
await observer.hinted(generation, Infinity, signal);
|
|
61
61
|
continue;
|
|
62
62
|
}
|
|
63
|
-
for (const
|
|
63
|
+
for (const activity of projection.activities) {
|
|
64
64
|
signal.throwIfAborted();
|
|
65
|
-
yield
|
|
66
|
-
...(item.route === undefined ? {} : { route: item.route }) };
|
|
65
|
+
yield activity;
|
|
67
66
|
}
|
|
68
67
|
cursor = projection.cursor;
|
|
69
68
|
initial = undefined;
|
package/dist/views.d.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { type ActivityId } from './square-core.js';
|
|
2
2
|
import { type StoredAct } from './model.js';
|
|
3
3
|
import type { ActivityFilter, SquareRead } from './ports.js';
|
|
4
|
-
import type { Activity, ArchiveRecord, HistoryQuery, HistoryContinuation, ParticipantsResult, SquareSnapshot, EntryPresentation,
|
|
5
|
-
export type { EntryPresentation, ListPresentation, CatchPresence, StreamProjection } from './square-facade.js';
|
|
4
|
+
import type { Activity, ArchiveRecord, PerceivedActivity, HistoryQuery, HistoryContinuation, ParticipantsResult, SquareSnapshot, EntryPresentation, StatusResult, SquareObservation, CatchPresence } from './square-facade.js';
|
|
6
5
|
export declare const HISTORY_DEFAULT_LIMIT = 10;
|
|
7
6
|
export declare const HISTORY_MAX_LIMIT = 100;
|
|
8
|
-
export declare const NON_READ: readonly ["join", "done", "say", "
|
|
9
|
-
/** The one StoredAct-to-public-Activity field mapping. Rejects read
|
|
7
|
+
export declare const NON_READ: readonly ["join", "done", "say", "listen", "ignore", "unlisten"];
|
|
8
|
+
/** The one StoredAct-to-public-Activity field mapping. Rejects read bookkeeping. */
|
|
10
9
|
export declare function toPublicActivity(stored: StoredAct): Activity;
|
|
10
|
+
export declare function toPerceivedActivity(facts: SquareRead, stored: StoredAct, viewer: string): PerceivedActivity;
|
|
11
11
|
export declare function parseRequiredActivityId(id: ActivityId): number;
|
|
12
12
|
/** Compile the public query directly to typed selectors; only search hydrates unselected bodies. */
|
|
13
13
|
export declare function selectHistory(facts: SquareRead, query: HistoryQuery, limit: number | null, kinds: ActivityFilter['kinds'], presentation?: boolean): {
|
|
@@ -19,20 +19,30 @@ export declare function selectHistory(facts: SquareRead, query: HistoryQuery, li
|
|
|
19
19
|
export declare function publicActivities(activities: readonly ArchiveRecord[]): Activity[];
|
|
20
20
|
export declare function snapshot(facts: SquareRead, now: number): SquareSnapshot;
|
|
21
21
|
export declare function recoveryObservation(facts: SquareRead, name: string, now: number): {
|
|
22
|
-
ownActivityCount: number;
|
|
23
|
-
hardCap: import("./model.js").HardCap;
|
|
24
|
-
holdReason?: string | undefined;
|
|
25
22
|
participant: string;
|
|
26
|
-
roster: string[];
|
|
27
23
|
activitySummaries: import("./square-facade.js").UnreadActivitySummary[];
|
|
28
24
|
standingCount: number;
|
|
29
|
-
|
|
25
|
+
ownActivityCount: number;
|
|
26
|
+
hardCap: import("./model.js").HardCap;
|
|
30
27
|
};
|
|
31
28
|
export declare function entryPresentation(facts: SquareRead, name: string, lastN?: number | null): EntryPresentation;
|
|
32
|
-
type ParticipantQueryContext = Pick<SquareObservation, 'location' | 'now'
|
|
29
|
+
type ParticipantQueryContext = Pick<SquareObservation, 'location' | 'now'>;
|
|
33
30
|
export declare function queryParticipants(facts: SquareRead, context: ParticipantQueryContext, observe?: boolean): ParticipantsResult;
|
|
34
|
-
export declare function listPresentation(facts: SquareRead):
|
|
31
|
+
export declare function listPresentation(facts: SquareRead): {
|
|
32
|
+
context: readonly string[];
|
|
33
|
+
participants: string[];
|
|
34
|
+
activities: number;
|
|
35
|
+
};
|
|
35
36
|
export declare function queryStatus(facts: SquareRead, context: ParticipantQueryContext, viewer?: string, observe?: boolean): StatusResult;
|
|
36
37
|
export declare function catchPresence(facts: SquareRead, now: number): CatchPresence;
|
|
37
|
-
export declare function streamProjection(facts: SquareRead, cursor: number, recipient?: string):
|
|
38
|
-
|
|
38
|
+
export declare function streamProjection(facts: SquareRead, cursor: number, recipient?: string): {
|
|
39
|
+
activities: Activity[];
|
|
40
|
+
cursor: number;
|
|
41
|
+
hasMore: boolean;
|
|
42
|
+
};
|
|
43
|
+
export declare function streamTailProjection(facts: SquareRead, last?: number, recipient?: string): {
|
|
44
|
+
activities: Activity[];
|
|
45
|
+
cursor: number;
|
|
46
|
+
hasMore: boolean;
|
|
47
|
+
};
|
|
48
|
+
export {};
|
package/dist/views.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { formatActivityId, parseActivityId } from './square-core.js';
|
|
2
2
|
import { compareParticipantActivity, coreParticipants, resolveKnownName, unreadActivitySummaries } from './decisions.js';
|
|
3
3
|
import { SquareError, nameKey } from './model.js';
|
|
4
|
-
import { inSquareCount
|
|
4
|
+
import { inSquareCount } from './runtime.js';
|
|
5
5
|
import { perceiveActivity } from './perception-projection.js';
|
|
6
6
|
import { compileSearchPattern } from './search.js';
|
|
7
7
|
const PAGE_SIZE = 100;
|
|
@@ -9,11 +9,11 @@ export const HISTORY_DEFAULT_LIMIT = 10;
|
|
|
9
9
|
export const HISTORY_MAX_LIMIT = 100;
|
|
10
10
|
const STATUS_HERE_WINDOW_MS = 30 * 60_000;
|
|
11
11
|
const PRESENCE_WINDOW_MS = 8 * 60 * 60_000;
|
|
12
|
-
export const NON_READ = ['join', 'done', 'say', '
|
|
13
|
-
const ARCHIVE = new Set(['join', 'done', 'say'
|
|
14
|
-
/** The one StoredAct-to-public-Activity field mapping. Rejects read
|
|
12
|
+
export const NON_READ = ['join', 'done', 'say', 'listen', 'ignore', 'unlisten'];
|
|
13
|
+
const ARCHIVE = new Set(['join', 'done', 'say']);
|
|
14
|
+
/** The one StoredAct-to-public-Activity field mapping. Rejects read bookkeeping. */
|
|
15
15
|
export function toPublicActivity(stored) {
|
|
16
|
-
if (stored.kind === 'read'
|
|
16
|
+
if (stored.kind === 'read')
|
|
17
17
|
throw new Error(`Cannot expose stored activity ${formatActivityId(stored.index)}`);
|
|
18
18
|
return { id: formatActivityId(stored.index), at: stored.at, kind: stored.kind, actor: stored.actor,
|
|
19
19
|
...(stored.kind === 'say' && stored.reach !== undefined ? { reach: stored.reach } : {}),
|
|
@@ -22,6 +22,11 @@ export function toPublicActivity(stored) {
|
|
|
22
22
|
mentions: stored.kind === 'say' ? stored.mentions ?? [] : [], ...('target' in stored ? { target: stored.target } : {}),
|
|
23
23
|
...(stored.kind === 'say' && stored.reply !== undefined ? { reply: formatActivityId(stored.reply) } : {}) };
|
|
24
24
|
}
|
|
25
|
+
export function toPerceivedActivity(facts, stored, viewer) {
|
|
26
|
+
const via = facts.directedAt(viewer, stored.index)?.route;
|
|
27
|
+
return { ...toPublicActivity(stored), perception: perceiveActivity(facts, stored, viewer),
|
|
28
|
+
...(via === undefined ? {} : { via }) };
|
|
29
|
+
}
|
|
25
30
|
export function parseRequiredActivityId(id) {
|
|
26
31
|
const index = parseActivityId(id);
|
|
27
32
|
if (index === undefined)
|
|
@@ -103,50 +108,44 @@ export function selectHistory(facts, query, limit, kinds, presentation = true) {
|
|
|
103
108
|
return { activities, visibleActivities, totalMatches, ...(continuation === undefined ? {} : { continuation }) };
|
|
104
109
|
}
|
|
105
110
|
export function publicActivities(activities) {
|
|
106
|
-
return activities.flatMap((activity) => activity.kind === 'read'
|
|
107
|
-
}
|
|
108
|
-
function statuses(facts, now) {
|
|
109
|
-
return coreParticipants(facts, now).filter((p) => p.state !== 'not joined').map((p) => {
|
|
110
|
-
const cursor = readCursor(facts, p.name);
|
|
111
|
-
return { name: p.name, state: p.state === 'done' ? 'done' : 'joined', consumedThrough: cursor < 0 ? null : formatActivityId(cursor), watching: p.presence === 'watching', listening: p.listening };
|
|
112
|
-
});
|
|
111
|
+
return activities.flatMap((activity) => activity.kind === 'read' ? [] : [toPublicActivity(activity)]);
|
|
113
112
|
}
|
|
114
113
|
export function snapshot(facts, now) {
|
|
115
|
-
const header = facts.header()
|
|
114
|
+
const header = facts.header();
|
|
116
115
|
const names = new Map(facts.roster().map((p) => [nameKey(p.name), p.name]));
|
|
117
116
|
const receipts = new Set(facts.receiptAssignments().map((r) => JSON.stringify([r.owner, r.index])));
|
|
118
117
|
return { context: [...header.preamble, ...header.warmup].join('\n'), actCount: facts.activityCount({ kinds: NON_READ }), hardCap: header.hardCap,
|
|
119
118
|
...(header.throttlePerMinute === undefined ? {} : { throttlePerMinute: header.throttlePerMinute }),
|
|
120
|
-
|
|
121
|
-
|
|
119
|
+
participants: coreParticipants(facts, now).filter((p) => p.presence !== 'never-joined').map(({ name, presence, listening }) => ({ name, presence, listening })),
|
|
120
|
+
delivered(name, id) { return receipts.has(JSON.stringify([names.get(nameKey(name)) ?? name, parseRequiredActivityId(id)])); } };
|
|
122
121
|
}
|
|
123
122
|
export function recoveryObservation(facts, name, now) {
|
|
124
|
-
const known = resolveKnownName(facts, name)
|
|
125
|
-
return { participant: known,
|
|
126
|
-
|
|
123
|
+
const known = resolveKnownName(facts, name);
|
|
124
|
+
return { participant: known, activitySummaries: unreadActivitySummaries(facts, known, now), standingCount: inSquareCount(facts),
|
|
125
|
+
ownActivityCount: facts.participant(known).sayCount, hardCap: facts.header().hardCap };
|
|
127
126
|
}
|
|
128
127
|
export function entryPresentation(facts, name, lastN = HISTORY_DEFAULT_LIMIT) {
|
|
129
128
|
const header = facts.header();
|
|
130
129
|
const recentActivities = selectHistory(facts, {}, lastN, ['say', 'done']).activities;
|
|
131
130
|
return { joinedBefore: facts.activityCount({ kinds: ['join'], actors: [name] }) > 1,
|
|
132
131
|
scene: header.warmup.join('\n').trim(),
|
|
133
|
-
joinContext: (header.preamble.at(-1) === '---' ? header.preamble.slice(0, -1) : header.preamble).join('\n').trim(),
|
|
134
|
-
|
|
135
|
-
sayNumbers: facts.sayOrdinals(recentActivities.map((a) => a.index)) };
|
|
132
|
+
joinContext: (header.preamble.at(-1) === '---' ? header.preamble.slice(0, -1) : header.preamble).join('\n').trim(),
|
|
133
|
+
recentActivities: recentActivities.map((activity) => ({ ...toPublicActivity(activity), perception: perceiveActivity(facts, activity, name) })) };
|
|
136
134
|
}
|
|
137
135
|
function classifyPresence(participants, now) {
|
|
138
|
-
const
|
|
136
|
+
const standing = (participant) => participant.presence === 'active' || participant.presence === 'watching';
|
|
137
|
+
const here = participants.filter((participant) => standing(participant) && (participant.presence === 'watching'
|
|
139
138
|
|| (participant.lastActiveAt !== undefined && now - participant.lastActiveAt <= STATUS_HERE_WINDOW_MS)));
|
|
140
139
|
const hereNames = new Set(here.map((participant) => participant.name));
|
|
141
140
|
return { orderedHereNames: [...here.filter((participant) => participant.presence === 'watching'), ...here.filter((participant) => participant.presence !== 'watching')].map((participant) => participant.name),
|
|
142
|
-
lingeringCount: participants.filter((participant) => participant
|
|
143
|
-
classificationByName: Object.fromEntries(participants.map((participant) => [participant.name, participant.
|
|
144
|
-
: participant.
|
|
141
|
+
lingeringCount: participants.filter((participant) => standing(participant) && !hereNames.has(participant.name)).length,
|
|
142
|
+
classificationByName: Object.fromEntries(participants.map((participant) => [participant.name, participant.presence === 'done' ? 'done'
|
|
143
|
+
: participant.presence === 'never-joined' ? 'not-joined' : participant.presence === 'away' ? 'away' : hereNames.has(participant.name) ? 'here' : 'lingering'])) };
|
|
145
144
|
}
|
|
146
145
|
export function queryParticipants(facts, context, observe = false) {
|
|
147
146
|
const participants = coreParticipants(facts, context.now);
|
|
148
147
|
return { participants, ...(!observe ? {} : { observation: { ...context, ...classifyPresence(participants, context.now),
|
|
149
|
-
standingCount: inSquareCount(facts)
|
|
148
|
+
standingCount: inSquareCount(facts) } }) };
|
|
150
149
|
}
|
|
151
150
|
export function listPresentation(facts) {
|
|
152
151
|
const participants = facts.roster().filter((p) => p.joined).map((p) => {
|
|
@@ -157,62 +156,57 @@ export function listPresentation(facts) {
|
|
|
157
156
|
}
|
|
158
157
|
export function queryStatus(facts, context, viewer = '', observe = false) {
|
|
159
158
|
const participants = coreParticipants(facts, context.now);
|
|
160
|
-
const header = facts.header(),
|
|
161
|
-
const listener = participants.find((p) => p.
|
|
159
|
+
const header = facts.header(), standingCount = inSquareCount(facts);
|
|
160
|
+
const listener = participants.find((p) => p.presence !== 'never-joined' && nameKey(p.name) === nameKey(viewer));
|
|
162
161
|
const latest = facts.activityHeads({ kinds: ['say', 'done'] }, { edge: 'newest', limit: 1 }).items[0];
|
|
163
162
|
const latestAct = latest === undefined ? undefined : facts.activity(latest.index);
|
|
164
163
|
const data = {
|
|
165
164
|
hardCap: header.hardCap,
|
|
166
165
|
...(header.throttlePerMinute === undefined ? {} : { throttlePerMinute: header.throttlePerMinute }),
|
|
167
|
-
activeCount: standingCount, doneCount: participants.filter((p) => p.state === 'done').length,
|
|
168
|
-
hold: hold.active ? {
|
|
169
|
-
...(hold.actor === undefined ? {} : { by: hold.actor }),
|
|
170
|
-
...(hold.reason === undefined ? {} : { reason: hold.reason }),
|
|
171
|
-
...(hold.at === undefined ? {} : { at: hold.at }),
|
|
172
|
-
} : null,
|
|
173
166
|
...(listener === undefined ? {} : { listening: listener.listening }),
|
|
174
|
-
participants: participants.filter((p) => p.
|
|
175
|
-
...(latestAct === undefined || latestAct.
|
|
167
|
+
participants: participants.filter((p) => p.presence !== 'never-joined'), now: context.now,
|
|
168
|
+
...(latestAct === undefined || latestAct.kind === 'read' ? {} : { latestActivity: toPublicActivity(latestAct) }),
|
|
176
169
|
};
|
|
177
170
|
if (!observe)
|
|
178
171
|
return data;
|
|
179
172
|
const afterIndex = latestAct?.index ?? -1;
|
|
180
|
-
const joins = facts.activities(facts.activityHeads({ kinds: ['join'], afterIndex }, { edge: 'newest', limit: 3 }).items.map((h) => h.index)).map((activity) => ({
|
|
181
|
-
rejoined: facts.activityCount({ kinds: ['done'], actors: [activity.actor], actorMatch: 'exact', beforeIndex: activity.index }) > 0
|
|
173
|
+
const joins = facts.activities(facts.activityHeads({ kinds: ['join'], afterIndex }, { edge: 'newest', limit: 3 }).items.map((h) => h.index)).map((activity) => ({
|
|
174
|
+
rejoined: facts.activityCount({ kinds: ['done'], actors: [activity.actor], actorMatch: 'exact', beforeIndex: activity.index }) > 0,
|
|
175
|
+
activity: toPublicActivity(activity)
|
|
176
|
+
}));
|
|
182
177
|
const canonicalViewer = facts.participant(viewer)?.name ?? viewer;
|
|
183
178
|
return {
|
|
184
179
|
...data,
|
|
185
180
|
observation: {
|
|
186
|
-
|
|
181
|
+
location: context.location,
|
|
182
|
+
...classifyPresence(participants, context.now), standingCount,
|
|
187
183
|
knownParticipantCount: participants.length, viewer: canonicalViewer,
|
|
188
184
|
...(canonicalViewer === '' ? {} : { participant: canonicalViewer }),
|
|
189
185
|
joins, joinCount: facts.activityCount({ kinds: ['join'], afterIndex }),
|
|
190
186
|
attentionNames: participants.filter((p) => canonicalViewer === '' || nameKey(p.name) === nameKey(canonicalViewer)).map((p) => p.name),
|
|
191
187
|
...(latestAct === undefined ? {} : { latestPerception: perceiveActivity(facts, latestAct, canonicalViewer) }),
|
|
192
|
-
...(latestAct?.kind === 'say' ? { latestActNumber: facts.sayOrdinals([latestAct.index])[latestAct.index] } : {}),
|
|
193
188
|
},
|
|
194
189
|
};
|
|
195
190
|
}
|
|
196
191
|
export function catchPresence(facts, now) {
|
|
197
|
-
const participants = coreParticipants(facts, now).filter((participant) => participant.
|
|
192
|
+
const participants = coreParticipants(facts, now).filter((participant) => participant.presence === 'done' || participant.presence === 'away' || participant.presence === 'watching'
|
|
198
193
|
|| (participant.lastActiveAt !== undefined && now - participant.lastActiveAt <= PRESENCE_WINDOW_MS));
|
|
199
194
|
return { participants, now };
|
|
200
195
|
}
|
|
201
196
|
export function streamProjection(facts, cursor, recipient) {
|
|
202
|
-
const page = facts.activityHeads({ afterIndex: cursor }, { edge: 'oldest', limit: PAGE_SIZE });
|
|
203
|
-
const heads = page.items.
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
}
|
|
207
|
-
return { activities: facts.activities(heads.map((h) => h.index)).map((activity) => {
|
|
208
|
-
const route = heads.find((h) => h.index === activity.index).route;
|
|
209
|
-
return { activity, ...(route === undefined ? {} : { route }) };
|
|
210
|
-
}), cursor: page.items.at(-1)?.index ?? cursor, hasMore: page.hasMore };
|
|
197
|
+
const page = facts.activityHeads({ afterIndex: cursor, kinds: NON_READ }, { edge: 'oldest', limit: PAGE_SIZE });
|
|
198
|
+
const heads = recipient === undefined ? page.items : page.items.filter((head) => facts.directedAt(recipient, head.index) !== undefined);
|
|
199
|
+
const activities = facts.activities(heads.map((head) => head.index)).map((activity) => recipient === undefined
|
|
200
|
+
? toPublicActivity(activity) : toPerceivedActivity(facts, activity, recipient));
|
|
201
|
+
return { activities, cursor: page.items.at(-1)?.index ?? cursor, hasMore: page.hasMore };
|
|
211
202
|
}
|
|
212
203
|
export function streamTailProjection(facts, last = HISTORY_DEFAULT_LIMIT, recipient) {
|
|
213
204
|
if (!Number.isSafeInteger(last) || last < 0 || last > PAGE_SIZE)
|
|
214
205
|
throw new SquareError('invalid_args', `Invalid stream tail: expected a non-negative safe integer no greater than ${PAGE_SIZE}.`);
|
|
215
|
-
const heads = last === 0 ? [] : recipient === undefined
|
|
216
|
-
|
|
217
|
-
|
|
206
|
+
const heads = last === 0 ? [] : recipient === undefined
|
|
207
|
+
? facts.activityHeads({ kinds: NON_READ }, { edge: 'newest', limit: last }).items
|
|
208
|
+
: facts.directed(recipient, {}, { edge: 'newest', limit: last }).items;
|
|
209
|
+
const activities = facts.activities(heads.map((head) => head.index)).map((activity) => recipient === undefined
|
|
210
|
+
? toPublicActivity(activity) : toPerceivedActivity(facts, activity, recipient));
|
|
211
|
+
return { activities, cursor: facts.header().lastActIndex, hasMore: false };
|
|
218
212
|
}
|
package/dist/wakes.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type WatchOptions } from './model.js';
|
|
2
2
|
import type { SquareArtifactPort } from './ports.js';
|
|
3
3
|
import type { Seat } from './model.js';
|
|
4
4
|
export type WatchLeaseStart = {
|
|
@@ -8,13 +8,9 @@ export type WatchLeaseStart = {
|
|
|
8
8
|
readonly heartbeatAt: number;
|
|
9
9
|
} | {
|
|
10
10
|
readonly type: 'active';
|
|
11
|
-
readonly lease: WatchLease;
|
|
12
11
|
};
|
|
13
12
|
export type WatchLeasePulse = {
|
|
14
13
|
readonly type: 'replaced';
|
|
15
|
-
} | {
|
|
16
|
-
readonly type: 'held';
|
|
17
|
-
readonly heartbeatAt?: number;
|
|
18
14
|
} | {
|
|
19
15
|
readonly type: 'terminal';
|
|
20
16
|
readonly status: 'capped' | 'quorum';
|
package/dist/wakes.js
CHANGED
|
@@ -9,7 +9,7 @@ export async function acquireWatchLease(artifact, seat, at, leaseId, options, si
|
|
|
9
9
|
const { value } = await artifact.transactAs({ kind: 'participant', seat }, facts => {
|
|
10
10
|
const known = resolveKnownName(facts, name), existing = freshWatchLease(facts, known, at);
|
|
11
11
|
if (existing !== undefined && !options.replace)
|
|
12
|
-
return { type: 'active'
|
|
12
|
+
return { type: 'active' };
|
|
13
13
|
facts.putLease(known, newLease(leaseId, at, options), seat.generation);
|
|
14
14
|
return { type: 'started', leaseId, replaced: existing !== undefined, heartbeatAt: at };
|
|
15
15
|
}, signal);
|
|
@@ -21,15 +21,13 @@ export async function pulseWatchLease(artifact, seat, at, leaseId, options, hear
|
|
|
21
21
|
const known = resolveKnownName(facts, name), lease = freshWatchLease(facts, known, at);
|
|
22
22
|
if (lease?.leaseId !== leaseId)
|
|
23
23
|
return { type: 'replaced' };
|
|
24
|
-
const
|
|
25
|
-
const terminal = held ? undefined : watchTerminalStatus(facts, known);
|
|
24
|
+
const terminal = watchTerminalStatus(facts, known);
|
|
26
25
|
if (terminal !== undefined)
|
|
27
26
|
return { type: 'terminal', status: terminal };
|
|
28
|
-
const type = held ? 'held' : 'sleep';
|
|
29
27
|
if (!heartbeatDue)
|
|
30
|
-
return { type };
|
|
28
|
+
return { type: 'sleep' };
|
|
31
29
|
facts.putLease(known, newLease(leaseId, at, options), seat.generation);
|
|
32
|
-
return { type, heartbeatAt: at };
|
|
30
|
+
return { type: 'sleep', heartbeatAt: at };
|
|
33
31
|
}, signal);
|
|
34
32
|
return value;
|
|
35
33
|
}
|