@astrosheep/square 0.3.33 → 0.3.35
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/claude-plugin/.claude-plugin/plugin.json +1 -1
- package/claude-plugin/skills/square/SKILL.md +10 -10
- package/codex-plugin/.codex-plugin/plugin.json +1 -1
- package/dist/activity-feed.js +1 -1
- package/dist/activity.d.ts +1 -0
- package/dist/activity.js +3 -2
- package/dist/artifact.js +2 -1
- package/dist/automatic-session.js +17 -1
- package/dist/boundary-presentation.d.ts +1 -0
- package/dist/boundary-presentation.js +16 -1
- package/dist/catch-decisions.d.ts +4 -0
- package/dist/catch-decisions.js +23 -7
- package/dist/cli/context.d.ts +1 -0
- package/dist/cli/context.js +26 -1
- package/dist/cli/observation-commands.d.ts +7 -1
- package/dist/cli/observation-commands.js +149 -63
- package/dist/cli/program.js +8 -2
- package/dist/cli/square-commands.d.ts +3 -1
- package/dist/cli/square-commands.js +42 -16
- package/dist/codex-queue.d.ts +1 -0
- package/dist/codex-queue.js +1 -1
- package/dist/decisions.d.ts +1 -0
- package/dist/decisions.js +29 -14
- package/dist/delivery-health.d.ts +1 -0
- package/dist/delivery-health.js +19 -6
- package/dist/delivery-operations.js +5 -5
- package/dist/delivery.d.ts +2 -0
- package/dist/delivery.js +1 -0
- package/dist/harness-links.js +15 -3
- package/dist/harness.js +3 -1
- package/dist/help.js +25 -16
- package/dist/inbox.d.ts +2 -0
- package/dist/inbox.js +4 -2
- package/dist/list.js +77 -24
- package/dist/model.d.ts +1 -3
- package/dist/paseo-connection.js +9 -3
- package/dist/paseo-state.d.ts +4 -1
- package/dist/paseo-state.js +2 -2
- package/dist/presentation.d.ts +4 -0
- package/dist/presentation.js +41 -12
- package/dist/runtime.d.ts +1 -0
- package/dist/square-actions.d.ts +5 -0
- package/dist/square-actions.js +31 -7
- package/dist/square-core.d.ts +11 -1
- package/dist/square-core.js +12 -9
- package/dist/square-facade.d.ts +5 -2
- package/dist/square-file-adapter.d.ts +1 -1
- package/dist/square-file-adapter.js +11 -4
- package/dist/square-wiring.d.ts +1 -0
- package/dist/square-wiring.js +5 -1
- package/dist/stream.d.ts +8 -1
- package/dist/stream.js +17 -6
- package/dist/views.d.ts +11 -5
- package/dist/views.js +42 -16
- package/dist/wake-sink.d.ts +2 -0
- package/dist/wake-sink.js +10 -1
- package/dist/watch.js +26 -9
- package/extensions/square-pi.js +41 -7
- package/package.json +1 -1
- package/skills/brainstorm/SKILL.md +8 -6
- package/skills/square/SKILL.md +10 -10
package/dist/paseo-state.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process';
|
|
2
2
|
import { waitForPaseoToolBoundary } from './paseo-timeline.js';
|
|
3
|
-
export function discoverPaseoAgents(timeoutMs = 5000) {
|
|
3
|
+
export function discoverPaseoAgents(timeoutMs = 5000, opts = {}) {
|
|
4
4
|
try {
|
|
5
|
-
const raw = execFileSync(process.env.SQUARE_PASEO_BIN
|
|
5
|
+
const raw = execFileSync(opts.bin ?? process.env.SQUARE_PASEO_BIN ?? 'paseo', [...(opts.args ?? []), 'ls', '--global', '--json'], {
|
|
6
6
|
encoding: 'utf8',
|
|
7
7
|
timeout: timeoutMs,
|
|
8
8
|
stdio: ['ignore', 'pipe', 'pipe'],
|
package/dist/presentation.d.ts
CHANGED
|
@@ -30,6 +30,7 @@ interface ExpressWaitingOptions {
|
|
|
30
30
|
}
|
|
31
31
|
interface ExpressNoWaitOptions extends ParticipantOutputOptions {
|
|
32
32
|
reason: 'throttled' | 'held';
|
|
33
|
+
forceCommand: string;
|
|
33
34
|
delayMs?: number;
|
|
34
35
|
holdReason?: string;
|
|
35
36
|
draftPath?: string;
|
|
@@ -54,6 +55,7 @@ export declare function truncateChars(body: string, maxChars: number): {
|
|
|
54
55
|
text: string;
|
|
55
56
|
remaining: number;
|
|
56
57
|
};
|
|
58
|
+
export declare function truncateExternalDiagnostic(diagnostic: string): string;
|
|
57
59
|
export declare function previewActivityBody(body: string): string;
|
|
58
60
|
export declare function renderRoomChangeText(event: RoomChangeAct): string;
|
|
59
61
|
export declare function renderEventCli(event: StoredAct, opts?: {
|
|
@@ -65,6 +67,7 @@ export declare function renderEventCli(event: StoredAct, opts?: {
|
|
|
65
67
|
export declare function renderAmbientEvent(event: StoredAct, viewer: string, opts?: {
|
|
66
68
|
now?: number;
|
|
67
69
|
preview?: number;
|
|
70
|
+
presencePreview?: number;
|
|
68
71
|
actNumber?: number;
|
|
69
72
|
mention?: string;
|
|
70
73
|
squareState?: SquareState;
|
|
@@ -81,6 +84,7 @@ export declare function renderGrepActivitiesView(visible: StoredAct[], totalMatc
|
|
|
81
84
|
export declare function renderActivityLimit(opts: ActivityLimitOptions): string;
|
|
82
85
|
export declare function renderWatchAlreadyActive(opts: ParticipantOutputOptions): string;
|
|
83
86
|
export declare function renderWatchForceTakeover(_opts: ParticipantOutputOptions): string;
|
|
87
|
+
export declare function renderWatchReplaceMissing(_opts: ParticipantOutputOptions): string;
|
|
84
88
|
export declare function renderWatchReplaced(_opts: ParticipantOutputOptions): string;
|
|
85
89
|
export declare function renderWatchStatus(opts: WatchStatusOptions): string;
|
|
86
90
|
export declare function renderDoctorClean(): string;
|
package/dist/presentation.js
CHANGED
|
@@ -3,7 +3,7 @@ import fs from 'node:fs';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { participantIdentity } from './participant-identity.js';
|
|
5
5
|
export { participantIdentity } from './participant-identity.js';
|
|
6
|
-
import { audienceIncludes, audienceOf } from './square-core.js';
|
|
6
|
+
import { audienceIncludes, audienceOf, MAX_IDENTITY_SET_SIZE } from './square-core.js';
|
|
7
7
|
import { perceiveActivity } from './delivery.js';
|
|
8
8
|
import { actId, publicActs, readCursor, rosterNames, sayNumberFor } from './runtime.js';
|
|
9
9
|
import { formatDuration, formatRelativeTime, formatTimestamp } from './time.js';
|
|
@@ -15,7 +15,7 @@ function headerLine(squarePath, opts = {}) {
|
|
|
15
15
|
}
|
|
16
16
|
export function displayPath(squarePath, cwd = process.cwd()) {
|
|
17
17
|
if (!path.isAbsolute(squarePath))
|
|
18
|
-
return squarePath;
|
|
18
|
+
return squarePath.split(path.sep).join('/');
|
|
19
19
|
const comparableCwd = fs.realpathSync.native(cwd);
|
|
20
20
|
let comparableSquarePath = squarePath;
|
|
21
21
|
try {
|
|
@@ -25,7 +25,8 @@ export function displayPath(squarePath, cwd = process.cwd()) {
|
|
|
25
25
|
// Some error outputs name a path before it exists; lexical comparison remains useful there.
|
|
26
26
|
}
|
|
27
27
|
const relative = path.relative(comparableCwd, comparableSquarePath);
|
|
28
|
-
|
|
28
|
+
const displayed = relative !== '' && relative !== '..' && !relative.startsWith(`..${path.sep}`) ? relative : squarePath;
|
|
29
|
+
return displayed.split(path.sep).join('/');
|
|
29
30
|
}
|
|
30
31
|
export function withPathOutput(squarePath, body = '', opts = {}) {
|
|
31
32
|
return [headerLine(squarePath, opts), ...(body === '' ? [] : ['', body])].join('\n') + '\n';
|
|
@@ -105,6 +106,12 @@ export function truncateChars(body, maxChars) {
|
|
|
105
106
|
return { text: body, remaining: 0 };
|
|
106
107
|
return { text: chars.slice(0, maxChars).join('').trimEnd(), remaining: chars.length - maxChars };
|
|
107
108
|
}
|
|
109
|
+
const EXTERNAL_DIAGNOSTIC_MAX_CHARS = 160;
|
|
110
|
+
export function truncateExternalDiagnostic(diagnostic) {
|
|
111
|
+
if (truncateChars(diagnostic, EXTERNAL_DIAGNOSTIC_MAX_CHARS).remaining === 0)
|
|
112
|
+
return diagnostic;
|
|
113
|
+
return `${truncateChars(diagnostic, EXTERNAL_DIAGNOSTIC_MAX_CHARS - 1).text}…`;
|
|
114
|
+
}
|
|
108
115
|
function previewBody(body, maxLen = BODY_PREVIEW_LENGTH) {
|
|
109
116
|
const preview = truncateChars(body, maxLen);
|
|
110
117
|
return preview.remaining === 0 ? preview.text : `${preview.text}\n… ${preview.remaining} more chars`;
|
|
@@ -181,7 +188,12 @@ function renderPresenceOnlySay(event, opts = {}) {
|
|
|
181
188
|
if (targets.length === 0) {
|
|
182
189
|
return `● ${participantIdentity(event.actor)} #${opts.actNumber ?? 1} · ${actId(event)} · ${formatRelativeTime(event.at, opts.now)}`;
|
|
183
190
|
}
|
|
184
|
-
const
|
|
191
|
+
const visibleTargets = targets.slice(0, MAX_IDENTITY_SET_SIZE).map((name) => participantIdentity(name));
|
|
192
|
+
const remainingTargets = targets.length - visibleTargets.length;
|
|
193
|
+
const dest = ` ${[
|
|
194
|
+
...visibleTargets,
|
|
195
|
+
...(remainingTargets === 0 ? [] : [`${remainingTargets} ${remainingTargets === 1 ? 'other' : 'others'}`]),
|
|
196
|
+
].join(' and ')}`;
|
|
185
197
|
return `● ${participantIdentity(event.actor)} #${opts.actNumber ?? 1} · ${actId(event)} · ${formatRelativeTime(event.at, opts.now)}\n talked to${dest}`;
|
|
186
198
|
}
|
|
187
199
|
export function renderAmbientEvent(event, viewer, opts = {}) {
|
|
@@ -191,8 +203,11 @@ export function renderAmbientEvent(event, viewer, opts = {}) {
|
|
|
191
203
|
throw new Error('Ambient say rendering requires a settled perception or SquareState');
|
|
192
204
|
}
|
|
193
205
|
const seen = opts.perception ?? perceiveActivity(opts.squareState, event, viewer);
|
|
194
|
-
if (seen === 'presence')
|
|
195
|
-
|
|
206
|
+
if (seen === 'presence') {
|
|
207
|
+
const presence = renderPresenceOnlySay(event, opts);
|
|
208
|
+
const body = opts.presencePreview === undefined ? '' : renderedBody(event.body, opts.presencePreview);
|
|
209
|
+
return `${presence}${bodySuffix(body)}`;
|
|
210
|
+
}
|
|
196
211
|
return renderEventCli(event, opts);
|
|
197
212
|
}
|
|
198
213
|
function draftSavedLines(draftPath) {
|
|
@@ -202,8 +217,10 @@ function withDraftInput(command, draftPath) {
|
|
|
202
217
|
return draftPath === undefined ? command : `${command} < ${quoteShell(draftPath)}`;
|
|
203
218
|
}
|
|
204
219
|
function renderUnreadSummary(opts) {
|
|
220
|
+
const visibleSummaries = opts.activitySummaries.slice(0, MAX_IDENTITY_SET_SIZE);
|
|
221
|
+
const remainingSummaries = opts.activitySummaries.length - visibleSummaries.length;
|
|
205
222
|
return [
|
|
206
|
-
...
|
|
223
|
+
...visibleSummaries.flatMap((item) => [
|
|
207
224
|
...item.previews.slice(-1).map((preview) => {
|
|
208
225
|
const rendered = renderAmbientEvent(preview.act, opts.viewer, { actNumber: preview.number, perception: preview.perception });
|
|
209
226
|
if (rendered === '')
|
|
@@ -214,6 +231,7 @@ function renderUnreadSummary(opts) {
|
|
|
214
231
|
return ` · ${participantIdentity(item.name)} spoke — ${formatAge(item.latestActivityAgeMs)} ago · "${previewActivityBody(preview.act.body)}"`;
|
|
215
232
|
}),
|
|
216
233
|
]),
|
|
234
|
+
...(remainingSummaries === 0 ? [] : [` · ${remainingSummaries} more participants have unread activity`]),
|
|
217
235
|
...opts.roomChanges.map((act) => ` · ${renderRoomChangeText(act)}`),
|
|
218
236
|
];
|
|
219
237
|
}
|
|
@@ -254,7 +272,7 @@ export function renderExpressWaiting(opts) {
|
|
|
254
272
|
return ["✕ your activity doesn't land — a hand is raised", ' · your activity is waiting'].join('\n');
|
|
255
273
|
}
|
|
256
274
|
export function renderExpressNoWait(opts) {
|
|
257
|
-
const retryCommand =
|
|
275
|
+
const retryCommand = opts.forceCommand;
|
|
258
276
|
const lines = opts.reason === 'throttled'
|
|
259
277
|
? [
|
|
260
278
|
'✕ the square is packed',
|
|
@@ -292,8 +310,11 @@ function lastPresenceAnchor(squareState, name) {
|
|
|
292
310
|
return -1;
|
|
293
311
|
}
|
|
294
312
|
export function renderPresenceAnchor(names) {
|
|
295
|
-
const
|
|
296
|
-
|
|
313
|
+
const visibleNames = names.slice(0, MAX_IDENTITY_SET_SIZE);
|
|
314
|
+
const participants = visibleNames.map((name) => participantIdentity(name)).join(', ');
|
|
315
|
+
const remaining = names.length - visibleNames.length;
|
|
316
|
+
const suffix = remaining === 0 ? '' : ` and ${remaining} more`;
|
|
317
|
+
return names.length === 1 ? `→ ${participants} was here` : `→ ${participants}${suffix} were here`;
|
|
297
318
|
}
|
|
298
319
|
export function renderActivitiesView(squareState, visible, lastN, noTruncate, squarePath, viewer = '', mode = 'ambient') {
|
|
299
320
|
const publicVisible = visible.filter((act) => act.kind === 'say' || act.kind === 'done');
|
|
@@ -389,11 +410,19 @@ export function renderActivityLimit(opts) {
|
|
|
389
410
|
return withPathOutput(opts.squarePath, renderActivityLimitBody(opts), { participantCount: opts.participantCount, held: opts.held });
|
|
390
411
|
}
|
|
391
412
|
export function renderWatchAlreadyActive(opts) {
|
|
392
|
-
return [
|
|
413
|
+
return [
|
|
414
|
+
'✕ you are already catching',
|
|
415
|
+
` · an active catch is already running for ${participantIdentity(opts.name)}`,
|
|
416
|
+
' · wait for it to finish before starting another catch',
|
|
417
|
+
`» ${commandPrefix(opts.squarePath)} participants`,
|
|
418
|
+
].join('\n');
|
|
393
419
|
}
|
|
394
420
|
export function renderWatchForceTakeover(_opts) {
|
|
395
421
|
return '✓ your new catch takes over';
|
|
396
422
|
}
|
|
423
|
+
export function renderWatchReplaceMissing(_opts) {
|
|
424
|
+
return '⚠ nothing to replace — no active catch was running; your catch started normally';
|
|
425
|
+
}
|
|
397
426
|
export function renderWatchReplaced(_opts) {
|
|
398
427
|
return '✕ a newer catch took over';
|
|
399
428
|
}
|
|
@@ -434,7 +463,7 @@ export function renderDoctorClean() {
|
|
|
434
463
|
return '✓ no problems found';
|
|
435
464
|
}
|
|
436
465
|
export function renderDoctorUnfixable(reason) {
|
|
437
|
-
return ['✕ unreadable artifact', ` · ${reason}`].join('\n');
|
|
466
|
+
return ['✕ unreadable artifact', ` · ${truncateExternalDiagnostic(reason)}`].join('\n');
|
|
438
467
|
}
|
|
439
468
|
export function renderWatchOutput(history, publicItems, roomChanges, opts) {
|
|
440
469
|
const sections = [];
|
package/dist/runtime.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export declare function nowMs(): number;
|
|
|
12
12
|
/** Pure directed-activity filter. Bell matches every viewer; mentions match by audience. */
|
|
13
13
|
export declare function matchesMentionTarget(act: {
|
|
14
14
|
body: string;
|
|
15
|
+
mentions?: readonly string[];
|
|
15
16
|
reach?: Reach;
|
|
16
17
|
}, mention: string | true): boolean;
|
|
17
18
|
export declare function foldedState(squareState: SquareState): import("./square-core.js").FoldedSquareState;
|
package/dist/square-actions.d.ts
CHANGED
|
@@ -16,6 +16,11 @@ export declare function join(square: OperationContext, name: string): Promise<{
|
|
|
16
16
|
readonly name: string;
|
|
17
17
|
readonly activity: Activity | null;
|
|
18
18
|
}>;
|
|
19
|
+
/** End the standing participant and immediately let the caller reclaim the name. */
|
|
20
|
+
export declare function takeover(square: OperationContext, name: string, oldSessionIds?: readonly string[]): Promise<{
|
|
21
|
+
readonly name: string;
|
|
22
|
+
readonly activities: readonly Activity[];
|
|
23
|
+
}>;
|
|
19
24
|
export declare function implicitJoin(square: OperationContext, name: string): Promise<{
|
|
20
25
|
readonly name: string;
|
|
21
26
|
readonly state: 'joined' | 'active' | 'done';
|
package/dist/square-actions.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { coreDone, coreHold, coreIgnore, coreListen, coreListening, coreResume, decideAct, decideImplicitJoin, decideJoin } from './decisions.js';
|
|
3
|
-
import { SquareError } from './model.js';
|
|
1
|
+
import { formatActivityId, parseActivityId } from './square-core.js';
|
|
2
|
+
import { coreDone, coreHold, coreIgnore, coreListen, coreListening, coreResume, decideAct, decideImplicitJoin, decideJoin, resolveKnownName } from './decisions.js';
|
|
3
|
+
import { nameKey, SquareError } from './model.js';
|
|
4
4
|
import { participantIdentity } from './participant-identity.js';
|
|
5
5
|
import { deliverPending } from './delivery-operations.js';
|
|
6
6
|
import { decideCatch } from './catch-decisions.js';
|
|
@@ -48,7 +48,7 @@ function exposeCaught(activity, perception) {
|
|
|
48
48
|
throw new Error(`Cannot expose stored activity ${formatActivityId(activity.index)}`);
|
|
49
49
|
const result = {
|
|
50
50
|
id: formatActivityId(activity.index), at: activity.at, kind: activity.kind, actor: activity.actor,
|
|
51
|
-
mentions: activity.kind === 'say' ?
|
|
51
|
+
mentions: activity.kind === 'say' ? activity.mentions ?? [] : [],
|
|
52
52
|
...('body' in activity && activity.body !== undefined ? { body: activity.body } : {}),
|
|
53
53
|
...('target' in activity ? { target: activity.target } : {}),
|
|
54
54
|
...(activity.kind === 'say' && activity.reply !== undefined ? { reply: formatActivityId(activity.reply) } : {}),
|
|
@@ -75,11 +75,12 @@ export async function catchUp(square, name, options = {}, project) {
|
|
|
75
75
|
activities: attempt.decision.delivered.map((activity) => exposeCaught(activity, attempt.decision.perceptions.get(activity.index) ?? 'full')),
|
|
76
76
|
consumedThrough: attempt.decision.consumedThrough,
|
|
77
77
|
idleExpired: false,
|
|
78
|
+
remaining: attempt.decision.remaining,
|
|
78
79
|
};
|
|
79
80
|
}
|
|
80
81
|
const remaining = deadline - Date.now();
|
|
81
82
|
if (remaining <= 0 || !await square.artifact.changed(attempt.version, remaining)) {
|
|
82
|
-
return { activities: [], consumedThrough: attempt.decision.consumedThrough, idleExpired: true };
|
|
83
|
+
return { activities: [], consumedThrough: attempt.decision.consumedThrough, idleExpired: true, remaining: 0 };
|
|
83
84
|
}
|
|
84
85
|
}
|
|
85
86
|
}
|
|
@@ -105,7 +106,7 @@ function exposeActivity(stored) {
|
|
|
105
106
|
return {
|
|
106
107
|
id: formatActivityId(stored.index), at: stored.at, kind: stored.kind, actor: stored.actor,
|
|
107
108
|
...('body' in stored && stored.body !== undefined ? { body: stored.body } : {}),
|
|
108
|
-
mentions: stored.kind === 'say' ?
|
|
109
|
+
mentions: stored.kind === 'say' ? stored.mentions ?? [] : [],
|
|
109
110
|
...('target' in stored ? { target: stored.target } : {}),
|
|
110
111
|
...(stored.kind === 'say' && stored.reply !== undefined ? { reply: formatActivityId(stored.reply) } : {}),
|
|
111
112
|
};
|
|
@@ -128,6 +129,29 @@ export async function join(square, name) {
|
|
|
128
129
|
await publishIdentityRoute(square, committed.name);
|
|
129
130
|
return { name: committed.name, activity: committed.stored === null ? null : exposeActivity(committed.stored) };
|
|
130
131
|
}
|
|
132
|
+
/** End the standing participant and immediately let the caller reclaim the name. */
|
|
133
|
+
export async function takeover(square, name, oldSessionIds = []) {
|
|
134
|
+
const now = square.clock();
|
|
135
|
+
const oldSessions = new Set(oldSessionIds);
|
|
136
|
+
const committed = await square.artifact.transact((state) => {
|
|
137
|
+
const joinedName = resolveKnownName(state, name);
|
|
138
|
+
const done = coreDone(state, joinedName, '', now);
|
|
139
|
+
const storedDone = committedActivity(storeActs(state, [done]), 'kick');
|
|
140
|
+
const decision = decideJoin(state, joinedName, now);
|
|
141
|
+
if (decision.joinAct === undefined)
|
|
142
|
+
throw new SquareError('already_joined', `${participantIdentity(joinedName)} could not be reclaimed`);
|
|
143
|
+
const storedJoin = committedActivity(storeActs(state, [decision.joinAct]), 'join');
|
|
144
|
+
const participantSessions = new Set([
|
|
145
|
+
...oldSessions,
|
|
146
|
+
...(state.routes ?? []).filter((route) => nameKey(route.participant) === nameKey(joinedName)).map((route) => route.sessionId),
|
|
147
|
+
]);
|
|
148
|
+
state.routes = (state.routes ?? []).filter((route) => nameKey(route.participant) !== nameKey(joinedName) && !participantSessions.has(route.sessionId));
|
|
149
|
+
return { state, result: { name: joinedName, stored: [storedDone, storedJoin] } };
|
|
150
|
+
});
|
|
151
|
+
await ensureLocalPresence(square, committed.name);
|
|
152
|
+
await publishIdentityRoute(square, committed.name);
|
|
153
|
+
return { name: committed.name, activities: committed.stored.map(exposeActivity) };
|
|
154
|
+
}
|
|
131
155
|
export async function implicitJoin(square, name) {
|
|
132
156
|
const now = square.clock();
|
|
133
157
|
const committed = await square.artifact.transact((state) => {
|
|
@@ -147,7 +171,7 @@ export async function express(square, name, body, options = {}) {
|
|
|
147
171
|
const now = square.clock();
|
|
148
172
|
const reply = options.reply === undefined ? undefined : parseRequiredActivityId(options.reply);
|
|
149
173
|
const committed = await square.artifact.transact((state) => {
|
|
150
|
-
const decision = decideAct(state, { name, body, force: options.force ?? false, now, ...(options.reach === undefined ? {} : { reach: options.reach }), ...(reply === undefined ? {} : { reply }) });
|
|
174
|
+
const decision = decideAct(state, { name, body, force: options.force ?? false, now, mentions: options.mentions, ...(options.reach === undefined ? {} : { reach: options.reach }), ...(reply === undefined ? {} : { reply }) });
|
|
151
175
|
if (decision.type === 'blocked') {
|
|
152
176
|
const pending = decision.activitySummaries.reduce((count, summary) => count + summary.count, 0) + decision.unreadRoomChanges.length;
|
|
153
177
|
throw new SquareError('behind', `${participantIdentity(name)} has pending activity`, { pending });
|
package/dist/square-core.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export type Audience = {
|
|
|
7
7
|
readonly names: readonly string[];
|
|
8
8
|
};
|
|
9
9
|
export type ActivityId = `act/${number}`;
|
|
10
|
+
export declare const MAX_IDENTITY_SET_SIZE = 10;
|
|
10
11
|
export declare function formatActivityId(index: number): ActivityId;
|
|
11
12
|
export declare function parseActivityId(value: string): number | undefined;
|
|
12
13
|
export interface Hold {
|
|
@@ -29,6 +30,7 @@ export type Act = {
|
|
|
29
30
|
actor: Participant;
|
|
30
31
|
at?: number;
|
|
31
32
|
body: string;
|
|
33
|
+
mentions: readonly Participant[];
|
|
32
34
|
reach?: Reach;
|
|
33
35
|
reply?: number;
|
|
34
36
|
} | {
|
|
@@ -119,10 +121,18 @@ export type ValidationResult = {
|
|
|
119
121
|
ok: false;
|
|
120
122
|
reason: 'bell_quota';
|
|
121
123
|
nextAt: number;
|
|
124
|
+
} | {
|
|
125
|
+
ok: false;
|
|
126
|
+
reason: 'mention_limit';
|
|
127
|
+
limit: number;
|
|
128
|
+
} | {
|
|
129
|
+
ok: false;
|
|
130
|
+
reason: 'listening_limit';
|
|
131
|
+
limit: number;
|
|
122
132
|
};
|
|
123
|
-
export declare function extractMentions(body: string): string[];
|
|
124
133
|
export declare function audienceOf(say: {
|
|
125
134
|
body: string;
|
|
135
|
+
mentions?: readonly Participant[];
|
|
126
136
|
reach?: Reach;
|
|
127
137
|
}): Audience;
|
|
128
138
|
export declare function audienceIncludes(audience: Audience, name: string): boolean;
|
package/dist/square-core.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export const MAX_IDENTITY_SET_SIZE = 10;
|
|
1
2
|
export function formatActivityId(index) {
|
|
2
3
|
if (!Number.isSafeInteger(index) || index < 0) {
|
|
3
4
|
throw new Error(`Invalid activity index: ${index}`);
|
|
@@ -18,14 +19,6 @@ function nameKey(name) {
|
|
|
18
19
|
function sameName(a, b) {
|
|
19
20
|
return nameKey(a) === nameKey(b);
|
|
20
21
|
}
|
|
21
|
-
export function extractMentions(body) {
|
|
22
|
-
const matches = [];
|
|
23
|
-
const re = /@([\p{L}\p{N}_-]+(?:\/[\p{L}\p{N}_-]+)*)/gu;
|
|
24
|
-
let match;
|
|
25
|
-
while ((match = re.exec(body)) !== null)
|
|
26
|
-
matches.push(match[1]);
|
|
27
|
-
return matches;
|
|
28
|
-
}
|
|
29
22
|
function uniqueMentionNames(names) {
|
|
30
23
|
const unique = [];
|
|
31
24
|
for (const name of names) {
|
|
@@ -38,7 +31,7 @@ function uniqueMentionNames(names) {
|
|
|
38
31
|
export function audienceOf(say) {
|
|
39
32
|
if (say.reach === 'bell')
|
|
40
33
|
return { kind: 'bell' };
|
|
41
|
-
return { kind: 'mentions', names: uniqueMentionNames(
|
|
34
|
+
return { kind: 'mentions', names: uniqueMentionNames(say.mentions ?? []) };
|
|
42
35
|
}
|
|
43
36
|
export function audienceIncludes(audience, name) {
|
|
44
37
|
if (audience.kind === 'bell')
|
|
@@ -300,12 +293,22 @@ export function validate(state, act, options = {}) {
|
|
|
300
293
|
return { ok: false, reason: 'done' };
|
|
301
294
|
if (current?.joined !== true)
|
|
302
295
|
return { ok: false, reason: 'not_joined' };
|
|
296
|
+
if (act.kind === 'listen'
|
|
297
|
+
&& !isListening(state, act.actor, act.target)
|
|
298
|
+
&& listeningTo(state, act.actor).length >= MAX_IDENTITY_SET_SIZE) {
|
|
299
|
+
return { ok: false, reason: 'listening_limit', limit: MAX_IDENTITY_SET_SIZE };
|
|
300
|
+
}
|
|
303
301
|
return { ok: true };
|
|
304
302
|
case 'say': {
|
|
305
303
|
if (current?.done)
|
|
306
304
|
return { ok: false, reason: 'done' };
|
|
307
305
|
if (current?.joined !== true)
|
|
308
306
|
return { ok: false, reason: 'not_joined' };
|
|
307
|
+
const audience = audienceOf(act);
|
|
308
|
+
const mentionCount = audience.kind === 'mentions' ? audience.names.length : 0;
|
|
309
|
+
if (mentionCount > MAX_IDENTITY_SET_SIZE) {
|
|
310
|
+
return { ok: false, reason: 'mention_limit', limit: MAX_IDENTITY_SET_SIZE };
|
|
311
|
+
}
|
|
309
312
|
if (state.hold.active)
|
|
310
313
|
return { ok: false, reason: 'held', hold: state.hold };
|
|
311
314
|
const activityCount = current?.activityCount ?? 0;
|
package/dist/square-facade.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export interface PerceivedActivity extends Activity {
|
|
|
15
15
|
}
|
|
16
16
|
export interface ExpressOptions {
|
|
17
17
|
readonly force?: boolean;
|
|
18
|
+
readonly mentions?: readonly string[];
|
|
18
19
|
readonly reach?: Reach;
|
|
19
20
|
readonly reply?: ActivityId;
|
|
20
21
|
}
|
|
@@ -29,11 +30,13 @@ export interface CatchOptions {
|
|
|
29
30
|
readonly idle?: number;
|
|
30
31
|
readonly from?: readonly string[];
|
|
31
32
|
readonly mention?: boolean;
|
|
33
|
+
readonly limit?: number;
|
|
32
34
|
}
|
|
33
35
|
export interface CatchResult {
|
|
34
36
|
readonly activities: readonly PerceivedActivity[];
|
|
35
37
|
readonly consumedThrough: ActivityId | null;
|
|
36
38
|
readonly idleExpired: boolean;
|
|
39
|
+
readonly remaining: number;
|
|
37
40
|
}
|
|
38
41
|
export interface HistoryQuery {
|
|
39
42
|
readonly limit?: number;
|
|
@@ -42,7 +45,7 @@ export interface HistoryQuery {
|
|
|
42
45
|
readonly after?: ActivityId;
|
|
43
46
|
readonly grep?: string;
|
|
44
47
|
readonly from?: readonly string[];
|
|
45
|
-
readonly mention?:
|
|
48
|
+
readonly mention?: string;
|
|
46
49
|
}
|
|
47
50
|
export interface ParticipantStatus {
|
|
48
51
|
readonly name: string;
|
|
@@ -90,7 +93,7 @@ export interface Participant {
|
|
|
90
93
|
ignore(target: string): Promise<ListenerChangeResult>;
|
|
91
94
|
listening(): Promise<readonly string[]>;
|
|
92
95
|
catch(options?: CatchOptions): Promise<CatchResult>;
|
|
93
|
-
history(query?: HistoryQuery): Promise<
|
|
96
|
+
history(query?: HistoryQuery): Promise<Activity[]>;
|
|
94
97
|
hold(reason?: string): Promise<ExpressResult>;
|
|
95
98
|
resume(): Promise<ExpressResult>;
|
|
96
99
|
done(body?: string): Promise<ExpressResult>;
|
|
@@ -27,4 +27,4 @@ export type SquareChangeWaitResult<T> = {
|
|
|
27
27
|
} | {
|
|
28
28
|
status: 'expired';
|
|
29
29
|
};
|
|
30
|
-
export declare function waitForSquareChanges<T>(squarePaths: readonly string[], timeoutMs: number, signal?: AbortSignal, afterReady?: () => Promise<T | undefined
|
|
30
|
+
export declare function waitForSquareChanges<T>(squarePaths: readonly string[], timeoutMs: number, signal?: AbortSignal, afterReady?: () => Promise<T | undefined>, onArmed?: (armed: boolean) => void): Promise<SquareChangeWaitResult<T>>;
|
|
@@ -83,9 +83,11 @@ export function buildMemorySquare(options) {
|
|
|
83
83
|
function memoryArtifact(cell) {
|
|
84
84
|
return { read: () => cell.read(), transact: (fn) => cell.transact(fn), changed: (since, timeout) => cell.changed(since, timeout), close: () => cell.close() };
|
|
85
85
|
}
|
|
86
|
-
export async function waitForSquareChanges(squarePaths, timeoutMs, signal, afterReady) {
|
|
87
|
-
if (timeoutMs <= 0 || signal?.aborted || squarePaths.length === 0)
|
|
86
|
+
export async function waitForSquareChanges(squarePaths, timeoutMs, signal, afterReady, onArmed) {
|
|
87
|
+
if (timeoutMs <= 0 || signal?.aborted || squarePaths.length === 0) {
|
|
88
|
+
onArmed?.(false);
|
|
88
89
|
return { status: 'expired' };
|
|
90
|
+
}
|
|
89
91
|
const squares = [];
|
|
90
92
|
try {
|
|
91
93
|
for (const squarePath of [...new Set(squarePaths)]) {
|
|
@@ -94,21 +96,26 @@ export async function waitForSquareChanges(squarePaths, timeoutMs, signal, after
|
|
|
94
96
|
}
|
|
95
97
|
catch { /* stale binding */ }
|
|
96
98
|
}
|
|
97
|
-
if (squares.length === 0)
|
|
99
|
+
if (squares.length === 0) {
|
|
100
|
+
onArmed?.(false);
|
|
98
101
|
return { status: 'expired' };
|
|
102
|
+
}
|
|
99
103
|
const baselines = await Promise.all(squares.map(async (square) => ({
|
|
100
104
|
square,
|
|
101
105
|
version: (await square.artifact.read()).version,
|
|
102
106
|
})));
|
|
103
107
|
const ready = await afterReady?.();
|
|
104
|
-
if (ready !== undefined)
|
|
108
|
+
if (ready !== undefined) {
|
|
109
|
+
onArmed?.(false);
|
|
105
110
|
return { status: 'ready', value: ready };
|
|
111
|
+
}
|
|
106
112
|
const waits = baselines.map(async ({ square, version }) => {
|
|
107
113
|
const changed = await square.artifact.changed(version, timeoutMs).catch(() => false);
|
|
108
114
|
if (changed)
|
|
109
115
|
return true;
|
|
110
116
|
throw new Error('square wait expired');
|
|
111
117
|
});
|
|
118
|
+
onArmed?.(true);
|
|
112
119
|
let abortWait;
|
|
113
120
|
const abort = new Promise((resolve) => {
|
|
114
121
|
abortWait = () => resolve(false);
|
package/dist/square-wiring.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ export declare class Square {
|
|
|
13
13
|
readonly participant: Participant;
|
|
14
14
|
readonly activity: Activity | null;
|
|
15
15
|
}>;
|
|
16
|
+
takeover(name: string, oldSessionIds?: readonly string[]): Promise<Participant>;
|
|
16
17
|
implicitJoin(name: string): Promise<{
|
|
17
18
|
readonly state: 'joined' | 'active' | 'done';
|
|
18
19
|
readonly participant?: Participant;
|
package/dist/square-wiring.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { closeOpenSquare } from './open-square.js';
|
|
2
2
|
import { buildMemorySquare, buildSquare, openSquare } from './square-file-adapter.js';
|
|
3
|
-
import { catchUp, done, express, hold, ignore, implicitJoin, join, listen, listening, resume, } from './square-actions.js';
|
|
3
|
+
import { catchUp, done, express, hold, ignore, implicitJoin, join, takeover, listen, listening, resume, } from './square-actions.js';
|
|
4
4
|
import { markBoundarySeen as recordBoundarySeen } from './presence.js';
|
|
5
5
|
import { history, participantHistory, participants, resolveParticipant, snapshot } from './views.js';
|
|
6
6
|
import { currentParticipant } from './views.js';
|
|
@@ -69,6 +69,10 @@ export class Square {
|
|
|
69
69
|
const joined = await join(this.context, name);
|
|
70
70
|
return { participant: new ParticipantHandle(joined.name, this.square, this.context), activity: joined.activity };
|
|
71
71
|
}
|
|
72
|
+
async takeover(name, oldSessionIds = []) {
|
|
73
|
+
const result = await takeover(this.context, name, oldSessionIds);
|
|
74
|
+
return new ParticipantHandle(result.name, this.square, this.context);
|
|
75
|
+
}
|
|
72
76
|
async implicitJoin(name) {
|
|
73
77
|
const joined = await implicitJoin(this.context, name);
|
|
74
78
|
return joined.state === 'done'
|
package/dist/stream.d.ts
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
export type StreamStart = {
|
|
2
|
+
readonly kind: 'tail';
|
|
3
|
+
readonly last: number;
|
|
4
|
+
} | {
|
|
5
|
+
readonly kind: 'after';
|
|
6
|
+
readonly after: number;
|
|
7
|
+
};
|
|
1
8
|
/** Machine-readable tailing stays available; interactive terminal rendering was retired. */
|
|
2
|
-
export declare function cmdStreamNdjson(squarePath: string, recipient?: string): Promise<void>;
|
|
9
|
+
export declare function cmdStreamNdjson(squarePath: string, recipient?: string, start?: StreamStart): Promise<void>;
|
|
3
10
|
export declare function cmdStream(squarePath: string): Promise<void>;
|
package/dist/stream.js
CHANGED
|
@@ -1,33 +1,44 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { once } from 'node:events';
|
|
3
4
|
import { setTimeout as sleep } from 'node:timers/promises';
|
|
4
5
|
import { quoteShell } from './presentation.js';
|
|
5
6
|
import { SLEEP_MS } from './runtime.js';
|
|
6
7
|
import { openSquare } from './square-file-adapter.js';
|
|
7
8
|
import { closeOpenSquare } from './open-square.js';
|
|
8
|
-
import { streamProjection } from './views.js';
|
|
9
|
+
import { streamProjection, streamTailProjection } from './views.js';
|
|
10
|
+
async function writeNdjson(value) {
|
|
11
|
+
if (!process.stdout.write(`${JSON.stringify(value)}\n`))
|
|
12
|
+
await once(process.stdout, 'drain');
|
|
13
|
+
}
|
|
9
14
|
/** Machine-readable tailing stays available; interactive terminal rendering was retired. */
|
|
10
|
-
export async function cmdStreamNdjson(squarePath, recipient) {
|
|
15
|
+
export async function cmdStreamNdjson(squarePath, recipient, start = { kind: 'tail', last: 10 }) {
|
|
11
16
|
if (!fs.existsSync(squarePath)) {
|
|
12
17
|
process.stderr.write(`square not found: ${squarePath}\n`);
|
|
13
18
|
process.exitCode = 2;
|
|
14
19
|
return;
|
|
15
20
|
}
|
|
16
|
-
let cursor = -1;
|
|
21
|
+
let cursor = start.kind === 'after' ? start.after : -1;
|
|
22
|
+
let initial = start.kind === 'tail' ? start : undefined;
|
|
17
23
|
while (true) {
|
|
18
24
|
try {
|
|
19
25
|
const square = await openSquare(squarePath);
|
|
20
26
|
try {
|
|
21
|
-
const projection =
|
|
27
|
+
const projection = initial === undefined
|
|
28
|
+
? await streamProjection(square, cursor, recipient)
|
|
29
|
+
: await streamTailProjection(square, initial.last, recipient);
|
|
22
30
|
for (const item of projection.activities) {
|
|
23
|
-
|
|
31
|
+
await writeNdjson({
|
|
24
32
|
seq: item.activity.index,
|
|
25
33
|
square: squarePath,
|
|
26
34
|
...item.activity,
|
|
27
35
|
...(item.route === undefined ? {} : { route: item.route }),
|
|
28
|
-
})
|
|
36
|
+
});
|
|
29
37
|
}
|
|
30
38
|
cursor = projection.cursor;
|
|
39
|
+
initial = undefined;
|
|
40
|
+
if (projection.hasMore)
|
|
41
|
+
continue;
|
|
31
42
|
}
|
|
32
43
|
finally {
|
|
33
44
|
await closeOpenSquare(square);
|
package/dist/views.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { coreParticipants, coreStatus } from './decisions.js';
|
|
|
3
3
|
import { type DeliveryModel, type PlannedNotification } from './delivery.js';
|
|
4
4
|
import { type ActivitiesOptions, type ActivityObservation, type InboxNotification, type PublicAct, type RoomChangeAct, type SquareState, type StoredAct } from './model.js';
|
|
5
5
|
import type { OpenSquare } from './open-square.js';
|
|
6
|
-
import type { Activity, HistoryQuery, ParticipantStatus,
|
|
6
|
+
import type { Activity, HistoryQuery, ParticipantStatus, SquareSnapshot } from './square-facade.js';
|
|
7
7
|
export interface ActivityPresentation {
|
|
8
8
|
readonly name: string;
|
|
9
9
|
readonly roster: readonly string[];
|
|
@@ -28,9 +28,7 @@ export interface EntryPresentation {
|
|
|
28
28
|
readonly participantCount: number;
|
|
29
29
|
}
|
|
30
30
|
export interface HistoryPresentation {
|
|
31
|
-
readonly activities: readonly
|
|
32
|
-
readonly perception: 'full' | 'presence';
|
|
33
|
-
})[];
|
|
31
|
+
readonly activities: readonly StoredAct[];
|
|
34
32
|
readonly sayNumbers: Readonly<Record<number, number>>;
|
|
35
33
|
readonly presenceAnchors: Readonly<Record<number, readonly string[]>>;
|
|
36
34
|
readonly participantCount: number;
|
|
@@ -67,13 +65,14 @@ export interface StreamProjection {
|
|
|
67
65
|
readonly route?: string;
|
|
68
66
|
}[];
|
|
69
67
|
readonly cursor: number;
|
|
68
|
+
readonly hasMore: boolean;
|
|
70
69
|
}
|
|
71
70
|
export interface PendingDeliveryProjection {
|
|
72
71
|
readonly recipient: string;
|
|
73
72
|
readonly notifications: readonly PlannedNotification[];
|
|
74
73
|
}
|
|
75
74
|
export declare function history(square: OpenSquare, query?: HistoryQuery): Promise<Activity[]>;
|
|
76
|
-
export declare function participantHistory(square: OpenSquare,
|
|
75
|
+
export declare function participantHistory(square: OpenSquare, _name: string, query?: HistoryQuery): Promise<Activity[]>;
|
|
77
76
|
export declare function resolveParticipant(square: OpenSquare, name: string): Promise<{
|
|
78
77
|
readonly name: string;
|
|
79
78
|
readonly roster: readonly string[];
|
|
@@ -101,6 +100,13 @@ export declare function streamProjection(square: OpenSquare | {
|
|
|
101
100
|
}>;
|
|
102
101
|
};
|
|
103
102
|
}, cursor: number, recipient?: string): Promise<StreamProjection>;
|
|
103
|
+
export declare function streamTailProjection(square: OpenSquare | {
|
|
104
|
+
readonly cell: {
|
|
105
|
+
read(): Promise<{
|
|
106
|
+
state: SquareState;
|
|
107
|
+
}>;
|
|
108
|
+
};
|
|
109
|
+
}, last?: number, recipient?: string): Promise<StreamProjection>;
|
|
104
110
|
export declare function notificationForAct(square: OpenSquare, actIndex: number): Promise<readonly PlannedNotification[]>;
|
|
105
111
|
export declare function pendingDeliveriesFromState(state: SquareState, delivery?: DeliveryModel): readonly PendingDeliveryProjection[];
|
|
106
112
|
export declare function pendingDeliveries(square: OpenSquare): Promise<readonly PendingDeliveryProjection[]>;
|