@astrosheep/square 0.3.77 → 0.3.79
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/attention-presentation.d.ts +3 -1
- package/dist/attention-presentation.js +45 -13
- package/dist/automatic-session.js +1 -1
- package/dist/boundary-presentation.js +4 -6
- package/dist/claude-mod.js +1 -1
- package/dist/cli/catch-presentation.d.ts +2 -2
- package/dist/cli/catch-presentation.js +3 -3
- package/dist/cli/context.d.ts +11 -1
- package/dist/cli/context.js +14 -3
- package/dist/cli/express-presentation.d.ts +6 -5
- package/dist/cli/express-presentation.js +35 -30
- package/dist/cli/maintenance-commands.js +8 -8
- package/dist/cli/mcp-server-command.js +1 -1
- package/dist/cli/observation-commands.d.ts +4 -4
- package/dist/cli/observation-commands.js +20 -16
- package/dist/cli/program.d.ts +1 -1
- package/dist/cli/program.js +13 -7
- package/dist/cli/square-commands.js +35 -23
- package/dist/decisions.d.ts +9 -4
- package/dist/decisions.js +17 -8
- package/dist/delivery.d.ts +1 -1
- package/dist/help.js +15 -2
- package/dist/host-ledger-migration.js +2 -6
- package/dist/host-ledger-sqlite.js +2 -2
- package/dist/inbox.js +3 -3
- package/dist/index.d.ts +1 -2
- package/dist/index.js +1 -2
- package/dist/ledger-maintenance.d.ts +1 -1
- package/dist/ledger-maintenance.js +1 -1
- package/dist/list.d.ts +3 -3
- package/dist/list.js +1 -1
- package/dist/mcp.d.ts +2 -2
- package/dist/mcp.js +12 -7
- package/dist/mention-parse.d.ts +2 -8
- package/dist/mention-parse.js +6 -9
- package/dist/model.d.ts +5 -1
- package/dist/notifications.js +24 -8
- package/dist/participant-host.js +5 -0
- package/dist/plugins/opencode/index.js +1 -1
- package/dist/square-actions.d.ts +22 -9
- package/dist/square-actions.js +27 -19
- package/dist/square-api.d.ts +43 -0
- package/dist/square-api.js +471 -0
- package/dist/square-facade.d.ts +124 -203
- package/dist/square-sqlite.d.ts +2 -8
- package/dist/square-sqlite.js +3 -11
- package/dist/square-tools.d.ts +4 -3
- package/dist/square-tools.js +22 -16
- package/dist/stream.d.ts +2 -2
- package/dist/views.d.ts +16 -12
- package/dist/views.js +13 -27
- package/guides/application-demand-optimization.md +5 -0
- package/guides/application-facade-delivery-evidence.md +6 -1
- package/guides/claude-native.md +3 -1
- package/guides/mcp-connection-lifetime.md +2 -2
- package/guides/mcp.md +1 -1
- package/guides/migration.md +2 -1
- package/guides/operation-cancellation.md +2 -0
- package/guides/primitive-catch-consolidation.md +5 -0
- package/guides/primitive-history-consolidation.md +5 -0
- package/guides/primitive-status-consolidation.md +5 -0
- package/guides/primitive-storage-consolidation.md +5 -0
- package/guides/row-storage-design.md +2 -2
- package/guides/square-api.md +107 -0
- package/package.json +1 -1
- package/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/plugins/claude/skills/square/SKILL.md +3 -3
- package/plugins/codex/.codex-plugin/plugin.json +1 -1
- package/plugins/opencode/index.ts +1 -1
- package/plugins/paseo/client/markdown.tsx +16 -6
- package/plugins/pi/square-pi.js +70 -30
- package/skills/square/SKILL.md +3 -3
- package/dist/square-application.d.ts +0 -38
- package/dist/square-application.js +0 -683
- package/guides/application-boundary.md +0 -32
- package/guides/application-facade-design.md +0 -98
package/dist/square-tools.js
CHANGED
|
@@ -1,41 +1,47 @@
|
|
|
1
1
|
import { z } from 'zod/v4';
|
|
2
2
|
import { isSquareError } from './model.js';
|
|
3
3
|
const location = z.string().optional().describe('Path to the .square artifact, relative to the current working directory. Defaults to the configured square.');
|
|
4
|
-
const participant = z.string().optional().describe('Participant name
|
|
4
|
+
const participant = z.string().optional().describe('Participant name. Defaults to the configured participant or the current session’s unambiguous participant.');
|
|
5
5
|
const activityId = z.string().regex(/^act\/(0|[1-9][0-9]*)$/);
|
|
6
6
|
const stringArray = z.array(z.string());
|
|
7
7
|
function tool(name, description, shape, run) {
|
|
8
8
|
const inputSchema = z.object({ ...shape, location }).strict();
|
|
9
|
-
return { name, description, inputSchema, run: (
|
|
9
|
+
return { name, description, inputSchema, run: (square, args, control) => {
|
|
10
10
|
const { location: _location, ...operationArgs } = inputSchema.parse(args);
|
|
11
|
-
return run(
|
|
11
|
+
return run(square, operationArgs, control);
|
|
12
12
|
} };
|
|
13
13
|
}
|
|
14
14
|
export const SQUARE_TOOL_INSTRUCTIONS = 'The catch tool consumes activities. Use history with act/<index> cursors for read-only archive access.';
|
|
15
15
|
export const SQUARE_TOOLS = [
|
|
16
|
-
tool('join', 'Join the square as a participant.', { as: participant, takeover: z.boolean().optional().describe('Take the name from its current session owner.') }, (
|
|
17
|
-
|
|
16
|
+
tool('join', 'Join the square as a participant.', { as: participant, takeover: z.boolean().optional().describe('Take the name from its current session owner.') }, async (square, { as, takeover }, control) => {
|
|
17
|
+
const { participant, ...receipt } = await square.join(as, { takeover }, control);
|
|
18
|
+
return { ...receipt, participant: participant.name };
|
|
19
|
+
}),
|
|
20
|
+
tool('express', 'Express a message or embodied action in the square. Mentions and replies direct attention; bell reaches everyone and keeps body @names literal. Bell cannot combine with explicit mentions. Bare activity is valid even with no listeners.', {
|
|
18
21
|
as: participant, body: z.string(), mentions: stringArray.optional(), reach: z.enum(['bell']).optional(),
|
|
19
22
|
reply: activityId.optional(), noMention: z.boolean().optional().describe('Disable body @name scanning; explicit mentions and reply still apply.'),
|
|
20
23
|
force: z.boolean().optional().describe('Speak without first catching unread activity or satisfying attention etiquette.'),
|
|
21
24
|
noWait: z.boolean().optional().describe('Return an error rather than waiting when held or throttled.'),
|
|
22
|
-
}, (
|
|
25
|
+
}, async (square, { body, as, ...options }, control) => {
|
|
26
|
+
const { delivery: _delivery, ...result } = await (await square.participant(as)).express(body, options, control);
|
|
27
|
+
return result;
|
|
28
|
+
}),
|
|
23
29
|
tool('catch', 'Catch and consume activities perceptible to the participant. idle waits for relevant activity or this many milliseconds of quiet; omitted means immediate. id catches one activity and cannot be combined with filters, idle or limit.', {
|
|
24
30
|
as: participant, id: activityId.optional(), idle: z.number().finite().min(0).optional(), from: stringArray.optional(),
|
|
25
31
|
mention: z.boolean().optional(), limit: z.number().int().min(1).optional(),
|
|
26
|
-
}, (
|
|
32
|
+
}, async (square, { as, ...options }, control) => (await square.participant(as)).catch(options, control)),
|
|
27
33
|
tool('history', 'Read the activity stream without consuming it, using stable activity-id cursors. Default page size is 10, maximum 100. grep is a regex; fixed is literal text; use only one.', {
|
|
28
34
|
limit: z.number().int().min(1).optional(), order: z.enum(['asc', 'desc']).optional(), before: activityId.optional(), after: activityId.optional(),
|
|
29
35
|
grep: z.string().optional(), fixed: z.string().optional(), from: stringArray.optional(), mention: z.string().optional(),
|
|
30
|
-
}, (
|
|
31
|
-
tool('listen', 'Listen for future bare activity from a participant, clearing any ignore of them.', { as: participant, target: z.string() }, (
|
|
32
|
-
tool('ignore', 'Block a participant’s future mentions and bare activity. Listening again clears the ignore; already landed activity is unchanged.', { as: participant, target: z.string() }, (
|
|
33
|
-
tool('listening', 'List participants this participant is listening to.', { as: participant }, (
|
|
34
|
-
tool('hold', 'Raise a hand to hold the square.', { as: participant, reason: z.string().optional() }, (
|
|
35
|
-
tool('resume', 'Release the current hold.', { as: participant }, (
|
|
36
|
-
tool('done', 'Leave the square without adding a message.', { as: participant }, (
|
|
37
|
-
tool('status', 'Read the public status of the square.', {}, (
|
|
38
|
-
tool('participants', 'List participants standing in or done with the square.', {}, (
|
|
36
|
+
}, (square, args, control) => square.history(args, control)),
|
|
37
|
+
tool('listen', 'Listen for future bare activity from a participant, clearing any ignore of them.', { as: participant, target: z.string() }, async (square, { as, target }, control) => (await square.participant(as)).listen(target, {}, control)),
|
|
38
|
+
tool('ignore', 'Block a participant’s future mentions and bare activity. Listening again clears the ignore; already landed activity is unchanged.', { as: participant, target: z.string() }, async (square, { as, target }, control) => (await square.participant(as)).ignore(target, {}, control)),
|
|
39
|
+
tool('listening', 'List participants this participant is listening to.', { as: participant }, async (square, { as }, control) => (await square.participant(as)).listening({}, control)),
|
|
40
|
+
tool('hold', 'Raise a hand to hold the square.', { as: participant, reason: z.string().optional() }, async (square, { as, reason }, control) => (await square.participant(as)).hold(reason, {}, control)),
|
|
41
|
+
tool('resume', 'Release the current hold.', { as: participant }, async (square, { as }, control) => (await square.participant(as)).resume({}, control)),
|
|
42
|
+
tool('done', 'Leave the square without adding a message.', { as: participant }, async (square, { as }, control) => (await square.participant(as)).done(undefined, {}, control)),
|
|
43
|
+
tool('status', 'Read the public status of the square.', {}, (square, _args, control) => square.status({}, control)),
|
|
44
|
+
tool('participants', 'List participants standing in or done with the square.', {}, (square, _args, control) => square.participants({}, control)),
|
|
39
45
|
];
|
|
40
46
|
export function squareToolFailure(error, signal) {
|
|
41
47
|
const normalized = signal?.aborted
|
package/dist/stream.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type { OpenOptions, OperationControl,
|
|
1
|
+
import type { OpenOptions, OperationControl, StreamOptions, StreamRecord } from './square-facade.js';
|
|
2
2
|
/** Pull-based observation; return interrupts an outstanding pull before awaiting its cleanup. */
|
|
3
|
-
export declare function streamSquare(squarePath: string, options?:
|
|
3
|
+
export declare function streamSquare(squarePath: string, options?: StreamOptions, control?: OperationControl, openOptions?: OpenOptions): AsyncIterable<StreamRecord>;
|
package/dist/views.d.ts
CHANGED
|
@@ -2,8 +2,8 @@ import { type ActivityId } from './square-core.js';
|
|
|
2
2
|
import { type StoredAct } from './model.js';
|
|
3
3
|
import type { OpenSquare } from './open-square.js';
|
|
4
4
|
import type { ActivityFilter, SquareRead } from './ports.js';
|
|
5
|
-
import type { Activity,
|
|
6
|
-
export type {
|
|
5
|
+
import type { Activity, ArchiveRecord, HistoryQuery, HistoryContinuation, OperationControl, ParticipantsResult, SquareSnapshot, EntryPresentation, ListPresentation, StatusResult, SquareObservation, CatchPresence, StreamProjection } from './square-facade.js';
|
|
6
|
+
export type { EntryPresentation, ListPresentation, CatchPresence, StreamProjection } from './square-facade.js';
|
|
7
7
|
export declare const HISTORY_DEFAULT_LIMIT = 10;
|
|
8
8
|
export declare const HISTORY_MAX_LIMIT = 100;
|
|
9
9
|
export declare const NON_READ: readonly ["join", "done", "say", "hold", "resume", "listen", "ignore"];
|
|
@@ -11,29 +11,33 @@ export declare const NON_READ: readonly ["join", "done", "say", "hold", "resume"
|
|
|
11
11
|
export declare function toPublicActivity(stored: StoredAct): Activity;
|
|
12
12
|
export declare function parseRequiredActivityId(id: ActivityId): number;
|
|
13
13
|
/** Compile the public query directly to typed selectors; only search hydrates unselected bodies. */
|
|
14
|
-
export declare function selectHistory(facts: SquareRead, query:
|
|
14
|
+
export declare function selectHistory(facts: SquareRead, query: HistoryQuery, limit: number | null, kinds: ActivityFilter['kinds'], presentation?: boolean): {
|
|
15
15
|
continuation?: HistoryContinuation | undefined;
|
|
16
16
|
activities: StoredAct[];
|
|
17
17
|
visibleActivities: StoredAct[];
|
|
18
18
|
totalMatches: number;
|
|
19
19
|
};
|
|
20
20
|
export declare function publicActivities(activities: readonly ArchiveRecord[]): Activity[];
|
|
21
|
-
export declare function history(square: OpenSquare, query?: HistoryQuery, control?: OperationControl): Promise<Activity[]>;
|
|
22
21
|
export declare function resolveParticipant(square: OpenSquare, name: string, control?: OperationControl): Promise<{
|
|
23
22
|
name: string;
|
|
24
23
|
}>;
|
|
25
24
|
export declare function currentParticipant(square: OpenSquare, name: string, control?: OperationControl): Promise<string | undefined>;
|
|
26
|
-
export declare function participants(square: OpenSquare): Promise<ParticipantStatus[]>;
|
|
27
25
|
export declare function snapshot(square: OpenSquare, control?: OperationControl): Promise<SquareSnapshot>;
|
|
28
|
-
export declare function recoveryObservation(square: OpenSquare, name: string, control?: OperationControl): Promise<
|
|
26
|
+
export declare function recoveryObservation(square: OpenSquare, name: string, control?: OperationControl): Promise<{
|
|
27
|
+
ownActivityCount: number;
|
|
28
|
+
hardCap: import("./model.js").HardCap;
|
|
29
|
+
holdReason?: string | undefined;
|
|
30
|
+
participant: string;
|
|
31
|
+
roster: string[];
|
|
32
|
+
activitySummaries: import("./square-facade.js").UnreadActivitySummary[];
|
|
33
|
+
standingCount: number;
|
|
34
|
+
held: boolean;
|
|
35
|
+
}>;
|
|
29
36
|
export declare function entryPresentation(square: OpenSquare, name: string, lastN?: number | null, control?: OperationControl): Promise<EntryPresentation>;
|
|
30
|
-
type ParticipantQueryContext = Pick<
|
|
31
|
-
export declare function queryParticipants(facts: SquareRead, context: ParticipantQueryContext):
|
|
32
|
-
export declare function queryParticipants(facts: SquareRead, context: ParticipantQueryContext, presentation: boolean): ApplicationOutcome<ParticipantsApplicationData, ParticipantsProjection, boolean>;
|
|
37
|
+
type ParticipantQueryContext = Pick<SquareObservation, 'location' | 'now' | 'automaticDeliveryAvailable'>;
|
|
38
|
+
export declare function queryParticipants(facts: SquareRead, context: ParticipantQueryContext, observe?: boolean): ParticipantsResult;
|
|
33
39
|
export declare function listPresentation(square: OpenSquare, control?: OperationControl): Promise<ListPresentation>;
|
|
34
|
-
export declare function queryStatus(facts: SquareRead, context: ParticipantQueryContext, viewer?: string):
|
|
35
|
-
export declare function queryStatus(facts: SquareRead, context: ParticipantQueryContext, viewer: string, presentation: boolean): ApplicationOutcome<StatusApplicationData, StatusProjection, boolean>;
|
|
36
|
-
export declare function eventPresentation(square: OpenSquare, id: ActivityId, control?: OperationControl): Promise<EventPresentation>;
|
|
40
|
+
export declare function queryStatus(facts: SquareRead, context: ParticipantQueryContext, viewer?: string, observe?: boolean): StatusResult;
|
|
37
41
|
export declare function catchPresence(square: OpenSquare, control?: OperationControl): Promise<CatchPresence>;
|
|
38
42
|
export declare function streamProjection(square: OpenSquare, cursor: number, recipient?: string, control?: OperationControl): Promise<StreamProjection>;
|
|
39
43
|
export declare function streamTailProjection(square: OpenSquare, last?: number, recipient?: string, control?: OperationControl): Promise<StreamProjection>;
|
package/dist/views.js
CHANGED
|
@@ -104,9 +104,6 @@ export function selectHistory(facts, query, limit, kinds, presentation = true) {
|
|
|
104
104
|
export function publicActivities(activities) {
|
|
105
105
|
return activities.flatMap((activity) => activity.kind === 'read' || activity.actor === undefined ? [] : [toPublicActivity(activity)]);
|
|
106
106
|
}
|
|
107
|
-
export async function history(square, query = {}, control) {
|
|
108
|
-
return (await square.artifact.read((facts) => publicActivities(selectHistory(facts, query, query.limit ?? null, NON_READ).activities), control?.signal)).value;
|
|
109
|
-
}
|
|
110
107
|
export async function resolveParticipant(square, name, control) {
|
|
111
108
|
return (await square.artifact.read((facts) => ({ name: resolveKnownName(facts, name) }), control?.signal)).value;
|
|
112
109
|
}
|
|
@@ -119,9 +116,6 @@ function statuses(facts, now) {
|
|
|
119
116
|
return { name: p.name, state: p.state === 'done' ? 'done' : 'joined', consumedThrough: cursor < 0 ? null : formatActivityId(cursor), watching: p.presence === 'watching', listening: p.listening };
|
|
120
117
|
});
|
|
121
118
|
}
|
|
122
|
-
export async function participants(square) {
|
|
123
|
-
return (await square.artifact.read((facts) => statuses(facts, square.clock()))).value;
|
|
124
|
-
}
|
|
125
119
|
export async function snapshot(square, control) {
|
|
126
120
|
return (await square.artifact.read((facts) => {
|
|
127
121
|
const header = facts.header(), hold = facts.hold();
|
|
@@ -136,19 +130,19 @@ export async function snapshot(square, control) {
|
|
|
136
130
|
export async function recoveryObservation(square, name, control) {
|
|
137
131
|
return (await square.artifact.read((facts) => {
|
|
138
132
|
const known = resolveKnownName(facts, name), hold = facts.hold();
|
|
139
|
-
return {
|
|
133
|
+
return { participant: known, roster: rosterNames(facts), activitySummaries: unreadActivitySummaries(facts, known, square.clock()), standingCount: inSquareCount(facts), held: hold.active,
|
|
140
134
|
...(hold.reason === undefined ? {} : { holdReason: hold.reason }), ownActivityCount: facts.participant(known).sayCount, hardCap: facts.header().hardCap };
|
|
141
135
|
}, control?.signal)).value;
|
|
142
136
|
}
|
|
143
137
|
export async function entryPresentation(square, name, lastN = HISTORY_DEFAULT_LIMIT, control) {
|
|
144
138
|
return (await square.artifact.read((facts) => {
|
|
145
|
-
const header = facts.header()
|
|
139
|
+
const header = facts.header();
|
|
146
140
|
const recentActivities = selectHistory(facts, {}, lastN, ['say', 'done']).activities;
|
|
147
|
-
return {
|
|
148
|
-
scene: header.warmup.join('\n').trim(),
|
|
141
|
+
return { joinedBefore: facts.activityCount({ kinds: ['join'], actors: [name] }) > 1,
|
|
142
|
+
scene: header.warmup.join('\n').trim(),
|
|
149
143
|
joinContext: (header.preamble.at(-1) === '---' ? header.preamble.slice(0, -1) : header.preamble).join('\n').trim(), recentActivities,
|
|
150
144
|
perceptions: Object.fromEntries(recentActivities.map((a) => [a.index, perceiveActivity(facts, a, name)])),
|
|
151
|
-
sayNumbers: facts.sayOrdinals(recentActivities.map((a) => a.index))
|
|
145
|
+
sayNumbers: facts.sayOrdinals(recentActivities.map((a) => a.index)) };
|
|
152
146
|
}, control?.signal)).value;
|
|
153
147
|
}
|
|
154
148
|
function classifyPresence(participants, now) {
|
|
@@ -160,10 +154,10 @@ function classifyPresence(participants, now) {
|
|
|
160
154
|
classificationByName: Object.fromEntries(participants.map((participant) => [participant.name, participant.state === 'done' ? 'done'
|
|
161
155
|
: participant.state === 'not joined' ? 'not-joined' : hereNames.has(participant.name) ? 'here' : 'lingering'])) };
|
|
162
156
|
}
|
|
163
|
-
export function queryParticipants(facts, context,
|
|
157
|
+
export function queryParticipants(facts, context, observe = false) {
|
|
164
158
|
const participants = coreParticipants(facts, context.now);
|
|
165
|
-
return {
|
|
166
|
-
|
|
159
|
+
return { participants, ...(!observe ? {} : { observation: { ...context, ...classifyPresence(participants, context.now),
|
|
160
|
+
standingCount: inSquareCount(facts), held: facts.hold().active } }) };
|
|
167
161
|
}
|
|
168
162
|
export async function listPresentation(square, control) {
|
|
169
163
|
return (await square.artifact.read((facts) => {
|
|
@@ -174,7 +168,7 @@ export async function listPresentation(square, control) {
|
|
|
174
168
|
return { context: facts.header().preamble, participants, activities: facts.activityCount({ kinds: ['say'] }) };
|
|
175
169
|
}, control?.signal)).value;
|
|
176
170
|
}
|
|
177
|
-
export function queryStatus(facts, context, viewer = '',
|
|
171
|
+
export function queryStatus(facts, context, viewer = '', observe = false) {
|
|
178
172
|
const participants = coreParticipants(facts, context.now);
|
|
179
173
|
const header = facts.header(), hold = facts.hold(), standingCount = inSquareCount(facts);
|
|
180
174
|
const latest = facts.activityHeads({ kinds: ['say', 'done'] }, { edge: 'newest', limit: 1 }).items[0];
|
|
@@ -191,15 +185,15 @@ export function queryStatus(facts, context, viewer = '', presentation = true) {
|
|
|
191
185
|
participants: participants.filter((p) => p.state !== 'not joined'), now: context.now,
|
|
192
186
|
...(latestAct === undefined || latestAct.actor === undefined || latestAct.kind === 'read' ? {} : { latestActivity: toPublicActivity(latestAct) }),
|
|
193
187
|
};
|
|
194
|
-
if (!
|
|
195
|
-
return
|
|
188
|
+
if (!observe)
|
|
189
|
+
return data;
|
|
196
190
|
const afterIndex = latestAct?.index ?? -1;
|
|
197
191
|
const joins = facts.activities(facts.activityHeads({ kinds: ['join'], afterIndex }, { edge: 'newest', limit: 3 }).items.map((h) => h.index)).map((activity) => ({ activity,
|
|
198
192
|
rejoined: facts.activityCount({ kinds: ['done'], actors: [activity.actor], actorMatch: 'exact', beforeIndex: activity.index }) > 0 }));
|
|
199
193
|
const canonicalViewer = facts.participant(viewer)?.name ?? viewer;
|
|
200
194
|
return {
|
|
201
|
-
data,
|
|
202
|
-
|
|
195
|
+
...data,
|
|
196
|
+
observation: {
|
|
203
197
|
...context, ...classifyPresence(participants, context.now), standingCount, held: hold.active,
|
|
204
198
|
knownParticipantCount: participants.length, viewer: canonicalViewer,
|
|
205
199
|
...(canonicalViewer === '' ? {} : { participant: canonicalViewer }),
|
|
@@ -210,14 +204,6 @@ export function queryStatus(facts, context, viewer = '', presentation = true) {
|
|
|
210
204
|
},
|
|
211
205
|
};
|
|
212
206
|
}
|
|
213
|
-
export async function eventPresentation(square, id, control) {
|
|
214
|
-
return (await square.artifact.read((facts) => {
|
|
215
|
-
const activity = facts.activity(parseRequiredActivityId(id));
|
|
216
|
-
if (activity === undefined)
|
|
217
|
-
throw new SquareError('invalid_args', `Unknown activity id: ${id}`);
|
|
218
|
-
return { activity, participantCount: inSquareCount(facts), held: facts.hold().active };
|
|
219
|
-
}, control?.signal)).value;
|
|
220
|
-
}
|
|
221
207
|
export async function catchPresence(square, control) {
|
|
222
208
|
return (await square.artifact.read((facts) => {
|
|
223
209
|
const now = square.clock();
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Application presentation demand
|
|
2
2
|
|
|
3
|
+
> **Historical evidence, superseded API.** The factory/outcome shapes and split SDK defaults
|
|
4
|
+
> below describe the earlier implementation, not current instructions. Use the real
|
|
5
|
+
> [Square/Participant API](square-api.md); the independent factory and its design guide are deleted.
|
|
6
|
+
|
|
7
|
+
|
|
3
8
|
## Design
|
|
4
9
|
|
|
5
10
|
`createSquareApplication(context)` retains complete terminal-facing outcomes. MCP uses `createSquareApplication(context, { presentation: false })`; its outcome has the same `data` contract and `projection: undefined`.
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
# Shared application boundary: delivery evidence
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> **Historical evidence, superseded API.** The factory/outcome shapes and split SDK defaults
|
|
4
|
+
> below describe the earlier implementation, not current instructions. Use the real
|
|
5
|
+
> [Square/Participant API](square-api.md); the independent factory and its design guide are deleted.
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
Deliverer evidence, **not independent review or acceptance**. The historical caller/deletion map is recorded below; current ownership is described in [Square/Participant API](square-api.md).
|
|
4
9
|
|
|
5
10
|
## Candidate coordinates
|
|
6
11
|
|
package/guides/claude-native.md
CHANGED
|
@@ -75,7 +75,9 @@ old ends cannot retire that replacement. Unchanged reload preserves epochs.
|
|
|
75
75
|
## Body, admission, and presentation
|
|
76
76
|
|
|
77
77
|
The coordinator uses Square's canonical audience projection and bounded body
|
|
78
|
-
preview.
|
|
78
|
+
preview. Each notification identifies the square, receiving participant and
|
|
79
|
+
activity before its event and body. A clipped preview describes catching that
|
|
80
|
+
activity to read it in full; it never embeds shell commands or tool-call syntax.
|
|
79
81
|
Opaque text correlation resolves through bound memberships and the existing
|
|
80
82
|
attempt ledger, never a path or asserted source from incoming text.
|
|
81
83
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# MCP connection lifetime
|
|
2
2
|
|
|
3
|
-
An MCP server owns one artifact connection for its configured square. Its lifetime ends on close or disconnect; create a new server instance for a new connection. Replacing an artifact file under a running server remains unsupported. Every tool call
|
|
3
|
+
An MCP server owns one artifact connection for its configured square. Its lifetime ends on close or disconnect; create a new server instance for a new connection. Replacing an artifact file under a running server remains unsupported. Every tool call opens an actual `Square` handle with that request's captured environment, default participant and harness session; `square.participant()` performs nonmutating name selection. Calls with an explicit `location` override instead open and close a per-call connection, leaving the configured-square owner untouched. Only the default connection is shared: authorization, presence, activity facts and cancellation remain operation-scoped. The artifact port already serializes transactions on its connection.
|
|
4
4
|
|
|
5
|
-
`createSquareResourceOwner()` is the explicit lifetime boundary. `
|
|
5
|
+
`createSquareResourceOwner()` is the explicit lifetime boundary. `Square.at({resources, ...})` borrows its connection; closing a request handle leaves the owner open. Ordinary callers open and close their own Square. Cross-square `inbox()` reads remain independent. The connection captures no request abort signal. Explicit server close and transport disconnect share one shutdown path: the SDK cancels requests, handlers finish their cleanup, then the connection closes.
|
|
6
6
|
|
|
7
7
|
## Local measurement
|
|
8
8
|
|
package/guides/mcp.md
CHANGED
|
@@ -8,6 +8,6 @@ The server exposes `join`, `express`, `catch`, `history`, `listen`, `ignore`, `l
|
|
|
8
8
|
|
|
9
9
|
Claude's bundled plugin configures this server automatically. Its mod supplies live session/cwd/subagent attribution through an out-of-band tool-use-ID association, not AI-visible arguments; see [Claude call identity](claude-native.md#mcp-tools-and-call-identity). The bundled endpoint requires that association rather than falling back to its startup environment.
|
|
10
10
|
|
|
11
|
-
Tool definitions and argument-to
|
|
11
|
+
Tool definitions and argument-to-`Square`/`Participant` calls live in `square-tools.ts`, shared with the [Pi native tools](pi-tools.md). Join serializes the handle's name and receipt fields, never resource internals. All callers use the [same concrete resource API](square-api.md), with no alternate factory or generic outcome envelope. `express.noMention` disables body @name scanning without disabling explicit mentions or reply attention.
|
|
12
12
|
|
|
13
13
|
Configure a client with command `npx` and arguments `--yes --package @astrosheep/square square mcp-server`, or use the installed `square` executable with argument `mcp-server`. The MCP server writes protocol messages only to stdout; diagnostics go to stderr.
|
package/guides/migration.md
CHANGED
|
@@ -6,7 +6,8 @@ sparse receipts, catch leases, ordered routes and the small configuration
|
|
|
6
6
|
facts — in one in-place database.
|
|
7
7
|
|
|
8
8
|
Existing artifacts are **not** converted automatically. A normal read of a
|
|
9
|
-
version 1 artifact refuses with
|
|
9
|
+
version 1 artifact refuses with a descriptive offline-migration requirement.
|
|
10
|
+
The CLI adds the concrete recovery command; native tools retain the description:
|
|
10
11
|
|
|
11
12
|
```sh
|
|
12
13
|
square --location /path/to/SQUARE.square migrate
|
|
@@ -14,4 +14,6 @@ The single-root HostLedgerPort passes `(input, signal)` for cancellable claims;
|
|
|
14
14
|
|
|
15
15
|
Ownership and delivery have the same limitation: cancellation can stop work that has not entered the boundary, but it cannot withdraw an ownership effect or a transport send that already happened. The returned committed result and existing degradation classification remain authoritative.
|
|
16
16
|
|
|
17
|
+
The same `Square`/`Participant` methods serve SDK and adapters; see [resource API](square-api.md). Join, listener and lifecycle essential observations use commit-time facts. Join's optional entry enrichment may be unavailable without rejecting its successful receipt. CLI express passes its abort signal through the initial `Square.at`; raw draft saving precedes a separate uncancelled recovery reopen.
|
|
18
|
+
|
|
17
19
|
Pi notification cancellation is separate and unchanged.
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Canonical catch selection — delivery evidence
|
|
2
2
|
|
|
3
|
+
> **Historical evidence, superseded API.** The factory/outcome shapes and split SDK defaults
|
|
4
|
+
> below describe the earlier implementation, not current instructions. Use the real
|
|
5
|
+
> [Square/Participant API](square-api.md); the independent factory and its design guide are deleted.
|
|
6
|
+
|
|
7
|
+
|
|
3
8
|
Deliverer evidence only, **not independent review or acceptance**.
|
|
4
9
|
|
|
5
10
|
## Coordinates and lane attribution
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# History query consolidation — delivery evidence
|
|
2
2
|
|
|
3
|
+
> **Historical evidence, superseded API.** The factory/outcome shapes and split SDK defaults
|
|
4
|
+
> below describe the earlier implementation, not current instructions. Use the real
|
|
5
|
+
> [Square/Participant API](square-api.md); the independent factory and its design guide are deleted.
|
|
6
|
+
|
|
7
|
+
|
|
3
8
|
Deliverer evidence, not an independent review or acceptance.
|
|
4
9
|
|
|
5
10
|
## Coordinates and lane-local reduction
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Canonical status and participant queries — delivery evidence
|
|
2
2
|
|
|
3
|
+
> **Historical evidence, superseded API.** The factory/outcome shapes and split SDK defaults
|
|
4
|
+
> below describe the earlier implementation, not current instructions. Use the real
|
|
5
|
+
> [Square/Participant API](square-api.md); the independent factory and its design guide are deleted.
|
|
6
|
+
|
|
7
|
+
|
|
3
8
|
Deliverer evidence, not independent review or acceptance.
|
|
4
9
|
|
|
5
10
|
## Coordinates and lane attribution
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Artifact and resource primitive consolidation
|
|
2
2
|
|
|
3
|
+
> **Historical evidence, superseded API.** The factory/outcome shapes and split SDK defaults
|
|
4
|
+
> below describe the earlier implementation, not current instructions. Use the real
|
|
5
|
+
> [Square/Participant API](square-api.md); the independent factory and its design guide are deleted.
|
|
6
|
+
|
|
7
|
+
|
|
3
8
|
Deliverer evidence, not independent review or acceptance.
|
|
4
9
|
|
|
5
10
|
## Coordinates and scope
|
|
@@ -414,7 +414,7 @@ No production structure below carries `.state`, all `.activities`, or a deferred
|
|
|
414
414
|
|---|---|
|
|
415
415
|
| openSquare/openSquareArtifact, wait baselines, watch headers, cli/program | header/revision only; remove preliminary whole-state validation/load. Every normal selected-row decode remains validated. |
|
|
416
416
|
| square-actions join/implicitJoin/takeover/done/endOwnedSession, registry-associated route cleanup | participant/roster as needed, routes, land/replaceRoutes; same host claim lock scopes and cleanup sequences. Takeover done+join is one txn. No full SDK snapshot. |
|
|
417
|
-
|
|
|
417
|
+
| Join's before/after snapshot and CLI hold/resume/done count calls | Current `Square.join` returns its concrete receipt with a Participant handle; optional observations use transaction-captured counts, held state and time. Listener/lifecycle commit facts likewise come from the mutation transaction; join's extra entry history is best-effort. Known joined-or-done `participantCount` remains distinct from the standing count. See [Square/Participant API](square-api.md). |
|
|
418
418
|
| express/decideAct and behind rendering (activity.ts) | header, actor fact, roster for longest standing-body mention resolution, hold, reply head/existence, recentSayTimes, continuous cursor, directed suffix. Admission normalization/error order unchanged. A valid reply coordinate is <nextActIndex, even if a gap: preserve current acceptance of assigned-but-missing reply. ActivityPresentation supplies own count/cap/hold/roster, suffix items with lifetime ordinals + settled full perception, and behind summaries. No whole history used just to number previews. |
|
|
419
419
|
| listen/ignore/listening, watch leases and terminal checks | actor fact, outgoing attention; roster only for quorum; land or lease methods. Unknown valid attention target still allowed. Pulse keeps hold-before-terminal priority. |
|
|
420
420
|
| catch / native seen | directed selectors/activity exact, markSeen, cursor/counts in txn; native `current` becomes synchronous `(facts: SquareRead)=>boolean`, evaluated ONLY within reads/receipt txn. It cannot escape or call host I/O. |
|
|
@@ -465,7 +465,7 @@ These terms apply together with the domain specification above.
|
|
|
465
465
|
## Migration protocol
|
|
466
466
|
|
|
467
467
|
- Only `square --location <path.square> migrate`. No --check, --backup, --force, auto-migrate, bulk mode, separate lock database, sidecar, daemon, or runtime dual reader. An operator can make an offline backup with ordinary tooling; the command does not invent a backup lifecycle.
|
|
468
|
-
- application_id remains 0x53515245; user_version 2 identifies rows. Normal new-format reads/writes refuse version 1 with invalid_args / reason migration_required and
|
|
468
|
+
- application_id remains 0x53515245; user_version 2 identifies rows. Normal new-format reads/writes refuse version 1 with invalid_args / reason migration_required and descriptive offline-migration guidance. The CLI adds the shell-quoted recovery command; native tool errors do not embed invocation syntax. build --force does not bypass it. Unknown/unrelated/invalid formats are refused, not repaired.
|
|
469
469
|
- `doctor` remains read-only: recognizes legacy v1 and prints the migration recovery command (exit 2); validates v2 fully, including a replay comparison of derived indices. It must not imply that legacy v1 is damaged. Directory/list probing retains omission semantics for unsupported files; no new implicit migration/discovery subsystem.
|
|
470
470
|
- Open existing canonical path create-disabled. A read-only preflight establishes owned known schema/version before any persistent pragma or DDL. Require existing DELETE journal mode, refuse another mode without changing it; the shipped v1 creator/writer already establishes DELETE. Set connection busy_timeout=0 and synchronous=FULL. Acquire BEGIN IMMEDIATE, re-check format/schema under the lock, and validate the full v1 snapshot inside it. If v2 is already present and has valid framing/header, return already-format-2 without changing revision. A second migrator follows this same in-lock check.
|
|
471
471
|
- Use v1 validation retained ONLY in migration (test oracle may also retain it), plus one explicit restriction: non-finite numbers anywhere in a route (including nested extras) are unsupported input, refused before DDL/inserts with the artifact untouched. They are not normally persisted as non-finite values by the v1 JSON.stringify writer; do not add a bespoke overflow-number codec or silently coerce to null. Copy normally persisted authoritative facts losslessly, preserving revision and nextActIndex, activity indices/gaps and optional field distinctions, raw receipt/lease owner keys, route order/duplicates, strings and finite fractional timestamps. Absent routes and [] both become zero rows; empty receipt-owner maps are behaviorless and may become zero receipt rows. Do not canonicalize receipts or add foreign keys that reject accepted v1 facts.
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# Square and Participant API
|
|
2
|
+
|
|
3
|
+
`Square` is the actual resource handle shared by SDK, CLI, MCP and Pi. Its implementation is
|
|
4
|
+
`square-api.ts`; `square.ts` remains the executable CLI entrypoint. `square-facade.ts` contains
|
|
5
|
+
only public types. There is no separate operation factory, dispatcher or `{data, projection}` envelope.
|
|
6
|
+
|
|
7
|
+
## Open, select, join, close
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { Square } from '@astrosheep/square';
|
|
11
|
+
|
|
12
|
+
const square = await Square.at({ path: './PUBLIC.square', env: { ...process.env } });
|
|
13
|
+
try {
|
|
14
|
+
const joined = await square.join('Alice');
|
|
15
|
+
const alice = joined.participant;
|
|
16
|
+
const sent = await alice.express('Hello @Bob', { force: true });
|
|
17
|
+
const page = await square.history();
|
|
18
|
+
console.log(sent.activity.id, page.activities);
|
|
19
|
+
} finally {
|
|
20
|
+
await square.close();
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
- `Square.build({path, markdown, hardCap?, throttlePerMinute?, template?, force?, ...})` returns
|
|
25
|
+
the same open resource. `Square.inMemory({markdown, ...})` is synchronous and creates no default
|
|
26
|
+
native wake transport.
|
|
27
|
+
- `Square.at` accepts `path`, optional `cwd`, captured `env`, `clock`, `hostLedger`, `wakeTransport`
|
|
28
|
+
and default `participant`. An omitted path uses captured `SQUARE_LOCATION`; relative paths use
|
|
29
|
+
`cwd`. Explicit participant, configured default, nonblank captured `SQUARE_PARTICIPANT_NAME`,
|
|
30
|
+
then an unambiguous current-session ledger binding determine a name, in that order.
|
|
31
|
+
- `await square.participant(name?)` selects a handle **without joining, claiming or reentering**.
|
|
32
|
+
Discovery happens on each selection, not through a cached session-to-name decision.
|
|
33
|
+
- `square.join(name?, options?, control?)` returns one `JoinResult`: `{participant, activity, kind,
|
|
34
|
+
participantCount, observation?}`. `participant` is a handle, not a string. `takeover: true` reclaims
|
|
35
|
+
a standing name; fresh/departed names join normally. There is no separate `Participant.join()`
|
|
36
|
+
or public `takeover()` API. `participantCount` counts known joined-or-done names.
|
|
37
|
+
- A Square owns its connection unless `resources: createSquareResourceOwner()` lends it one.
|
|
38
|
+
Closing a borrowing handle leaves that owner open. Request signals never become the borrowed
|
|
39
|
+
connection's lifetime. The MCP server drains cancelled/in-flight work before closing its owner;
|
|
40
|
+
explicit location overrides get independent request-owned connections.
|
|
41
|
+
|
|
42
|
+
## One set of operations and defaults
|
|
43
|
+
|
|
44
|
+
Square owns `history`, `status`, `participants`, `snapshot`, `stream`, and recovery observations.
|
|
45
|
+
Participant owns `express`, `catch`, `listen`, `ignore`, `listening`, `hold`, `resume`, `done`;
|
|
46
|
+
its `history` delegates to the same Square history operation.
|
|
47
|
+
|
|
48
|
+
- Express waits while held/throttled unless `noWait: true`, for **every** caller. `ExpressResult`
|
|
49
|
+
includes the committed `activity`, `waited`, optional `delivery`, and landing-time warnings
|
|
50
|
+
(`unmatchedMentions`, `replyAuthorNotHere`). Warnings are not stored on the activity.
|
|
51
|
+
- Catch returns `{activities, consumedThrough, remaining, idleExpired, status?, observation?}`.
|
|
52
|
+
Omitted/zero idle is immediate; positive idle uses the single leased quiet wait. Receipt selection,
|
|
53
|
+
ordinals, counts and terminal facts settle in the receipt transaction. Cancellation/replacement
|
|
54
|
+
releases only that waiter's lease and never consumes future activity.
|
|
55
|
+
- All history callers return a `HistoryResult` object with `activities`. Default page size is ten;
|
|
56
|
+
`limit: null` explicitly requests an unbounded archive. Numeric limits must be 1–100. Contextual
|
|
57
|
+
queries still reject an implicit merged window over 100 activities; an explicit numeric limit
|
|
58
|
+
bounds it. CLI/tool schemas accept numeric limits only. History never writes receipts.
|
|
59
|
+
- `participants()` returns `{participants, observation?}` and `listening()` returns
|
|
60
|
+
`{listening, observation?}`, not arrays carrying metadata.
|
|
61
|
+
- Lifecycle operations keep contextual reentry/ownership policy. In particular, done retains its
|
|
62
|
+
native owner epoch fence instead of blindly joining first. Done accepts no message.
|
|
63
|
+
|
|
64
|
+
Every operation accepts its final `OperationControl` (`signal`, optional typed `onProgress`).
|
|
65
|
+
Listener/lifecycle operations take optional observation options before that control argument.
|
|
66
|
+
|
|
67
|
+
## Optional observations, not another execution path
|
|
68
|
+
|
|
69
|
+
Request `observe: true` on the **same method** when rendering needs additional facts. Each operation
|
|
70
|
+
has a concrete optional observation type, without conditional generics or a generic outcome wrapper.
|
|
71
|
+
Data-only calls do not hydrate join history, the pre-expression feed, catch presence enrichment,
|
|
72
|
+
or history/status presentation annotations.
|
|
73
|
+
|
|
74
|
+
Express captures its optional directed feed before the attempt and returns count/held/time facts
|
|
75
|
+
from the committed transaction. Listener and lifecycle observations likewise use their mutation's
|
|
76
|
+
facts, with no postcommit context read. Join uses its mutation's essential facts and treats optional
|
|
77
|
+
entry/history enrichment as best-effort: `observation.enrichmentUnavailable` is explicit and the
|
|
78
|
+
successful receipt survives. Catch preserves its committed page even if optional nearby presence
|
|
79
|
+
fails. A cancellation or failed clock after commit cannot turn these receipts into an unsent retry.
|
|
80
|
+
|
|
81
|
+
`OperationError.operation` supplements authoritative error facts with caller/filter/recipient and
|
|
82
|
+
express certainty. Selected-row/execution failures remain unknown unless admission proved the
|
|
83
|
+
expression unsent. CLI saves raw drafts even when opening the resource fails; its initial open
|
|
84
|
+
receives the express abort signal, while recovery reopening is deliberately uncancelled.
|
|
85
|
+
|
|
86
|
+
## Adapters and standalone functions
|
|
87
|
+
|
|
88
|
+
CLI and tools only adapt input, identity, resource lifetime and output. They do not import private
|
|
89
|
+
actions, storage, admission, scanners or query execution. MCP/Pi use the shared `square-tools.ts`
|
|
90
|
+
definitions. Join JSON contains `participant.name` and the receipt fields, never the handle or its
|
|
91
|
+
resources. Express wire output omits the internal delivery diagnostic as before.
|
|
92
|
+
|
|
93
|
+
`discover(input, options?, control?)`, `inbox(input, sessionId, options?, control?)`,
|
|
94
|
+
`diagnoseSquare(input, control?)`, and `migrateSquare(input, control?)` are ordinary public exports.
|
|
95
|
+
They are not methods on a synthetic cross-square object. Diagnose/migrate resolve a path without
|
|
96
|
+
opening a valid current-format Square first.
|
|
97
|
+
|
|
98
|
+
## Deliberate migration breaks
|
|
99
|
+
|
|
100
|
+
The independent `createSquareApplication` API, its options/results/aliases, and `square-application.ts`
|
|
101
|
+
are deleted. Consumers must open/close an actual Square and select a Participant. SDK joins must use
|
|
102
|
+
`(await square.join(name)).participant`; history callers must use `.activities` and opt into full
|
|
103
|
+
archive reads with `limit: null`. SDK express now waits by default. MCP constructor configuration
|
|
104
|
+
uses `path`, consistent with `Square.at`, rather than `squarePath`.
|
|
105
|
+
|
|
106
|
+
External consumers must migrate before taking this source version. Editing source does not update
|
|
107
|
+
an installed plugin, build, or running extension.
|
package/package.json
CHANGED
|
@@ -37,9 +37,9 @@ I disagree — the cache is the wrong layer for this.
|
|
|
37
37
|
|
|
38
38
|
**Reach.** Every activity chooses who it is meant for:
|
|
39
39
|
|
|
40
|
-
- **Mention** participants to address them. An `@name` in the body that matches a standing participant counts too; code spans, emails, and URLs never do. Mentioned participants hear the full body; everyone else sees you walk over to them.
|
|
41
|
-
- **Reply** to an activity to reach its author the
|
|
42
|
-
- **Ring the bell** only when every participant needs it.
|
|
40
|
+
- **Mention** participants to address them. An `@name` in the body that matches a standing participant counts too; code spans, emails, and URLs never do. Unmatched names stay literal, with a notice to you. Mentioned participants hear the full body; everyone else sees you walk over to them.
|
|
41
|
+
- **Reply** to an activity to also reach its author, regardless of the other reach you choose.
|
|
42
|
+
- **Ring the bell** only when every participant needs it. Body `@names` stay literal; explicit mentions cannot combine with a bell.
|
|
43
43
|
- **Land it bare** when it is meant for no one in particular. Only participants listening to you receive it.
|
|
44
44
|
|
|
45
45
|
Addressing is not a secrecy boundary. Everything stays readable through `history`.
|
|
@@ -3,7 +3,7 @@ import { hostLedgerForEnv } from '../../registry.js';
|
|
|
3
3
|
import { canonicalPath } from '../../canonical-path.js';
|
|
4
4
|
import { openSquare } from '../../open-square.js';
|
|
5
5
|
import { closeOpenSquare } from '../../open-square.js';
|
|
6
|
-
import { Square } from '../../square-
|
|
6
|
+
import { Square } from '../../square-api.js';
|
|
7
7
|
import { connectNative, type OpenCodeNativeSession, type OpenCodeSessionCapability } from '../../packages/agent-delivery/src/opencode-native.js';
|
|
8
8
|
import { observeOpenCodeContext, publishOpenCodeRoutes, receiveOpenCodePending, type OpenCodeContextMessage } from './delivery.js';
|
|
9
9
|
|
|
@@ -84,20 +84,30 @@ function parseBlocks(markdown: string): Block[] {
|
|
|
84
84
|
|
|
85
85
|
interface SquareBlock { meta?: string; actor?: string; body: string }
|
|
86
86
|
|
|
87
|
+
function parseAttributes(source: string): Record<string, string> {
|
|
88
|
+
const attributes: Record<string, string> = {};
|
|
89
|
+
for (const match of source.matchAll(/([a-z]+)="([^"]*)"/g)) {
|
|
90
|
+
attributes[match[1]!] = match[2]!.replace(/"/g, '"').replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&");
|
|
91
|
+
}
|
|
92
|
+
return attributes;
|
|
93
|
+
}
|
|
94
|
+
|
|
87
95
|
// A square notification is one or more <square-activity> blocks. Split them out so the
|
|
88
|
-
// envelope
|
|
96
|
+
// envelope becomes styled chrome and only the body goes through markdown: where and which
|
|
97
|
+
// activity come from tag attributes, and the first inner line is the route sentence.
|
|
89
98
|
function parseSquareBlocks(text: string): SquareBlock[] {
|
|
90
99
|
const blocks: SquareBlock[] = [];
|
|
91
|
-
const re = /<square-activity>\n([\s\S]*?)\n<\/square-activity>/g;
|
|
100
|
+
const re = /<square-activity([^>]*)>\n([\s\S]*?)\n<\/square-activity>/g;
|
|
92
101
|
let last = 0;
|
|
93
102
|
for (const match of text.matchAll(re)) {
|
|
94
103
|
const index = match.index ?? 0;
|
|
95
104
|
const before = text.slice(last, index).trim();
|
|
96
105
|
if (before !== "") blocks.push({ body: before });
|
|
97
|
-
const
|
|
98
|
-
const innerLines =
|
|
99
|
-
const
|
|
100
|
-
const
|
|
106
|
+
const attributes = parseAttributes(match[1]!);
|
|
107
|
+
const innerLines = match[2]!.split("\n");
|
|
108
|
+
const described = [attributes.square, attributes.as, attributes.id].filter((value) => value !== undefined);
|
|
109
|
+
const meta = described.length > 0 ? `· ${described.join(" · ")}` : undefined;
|
|
110
|
+
const actor = innerLines.shift();
|
|
101
111
|
blocks.push({ meta, actor, body: innerLines.join("\n").trim() });
|
|
102
112
|
last = index + match[0].length;
|
|
103
113
|
}
|