@astrosheep/square 0.3.5 → 0.3.6
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/codex-plugin/.codex-plugin/plugin.json +3 -2
- package/dist/activity-feed.js +26 -18
- package/dist/activity.js +9 -10
- package/dist/artifact.js +126 -202
- package/dist/claude-hook.js +45 -21
- package/dist/cli/context.js +7 -7
- package/dist/cli/maintenance-commands.js +10 -26
- package/dist/cli/meta-commands.js +3 -6
- package/dist/cli/observation-commands.js +44 -52
- package/dist/cli/program.js +4 -4
- package/dist/cli/registry.js +5 -5
- package/dist/cli/square-commands.js +16 -18
- package/dist/cmd/notify-once.js +23 -21
- package/dist/compact.js +1 -1
- package/dist/decisions.js +53 -86
- package/dist/delivery-health.js +104 -210
- package/dist/delivery.js +68 -18
- package/dist/doctor.js +9 -8
- package/dist/harness-claude.js +38 -245
- package/dist/harness-codex.js +82 -616
- package/dist/harness-stage.js +36 -0
- package/dist/harness.js +3 -5
- package/dist/help.js +43 -35
- package/dist/inbox.js +12 -11
- package/dist/index.js +9 -121
- package/dist/list.js +1 -1
- package/dist/model.js +0 -6
- package/dist/notification-failures.js +54 -0
- package/dist/notifications.js +47 -62
- package/dist/paseo-timeline.js +58 -188
- package/dist/presentation.js +55 -63
- package/dist/presented.js +9 -8
- package/dist/registry.js +55 -45
- package/dist/runtime.js +27 -84
- package/dist/square-application.js +135 -130
- package/dist/square-core.js +3 -11
- package/dist/stream.js +27 -126
- package/dist/wake-sink.js +134 -188
- package/dist/watch.js +65 -122
- package/extensions/square-opencode.js +1 -1
- package/extensions/square-pi.js +8 -130
- package/guides/architect.md +3 -3
- package/guides/participant.md +25 -16
- package/package.json +2 -2
- package/skills/brainstorm/SKILL.md +25 -32
- package/skills/square/.claude-plugin/plugin.json +1 -1
- package/skills/square/SKILL.md +39 -107
- package/skills/square-feedback/SKILL.md +4 -4
- package/dist/harness-lifecycle.js +0 -102
- package/dist/square-store.js +0 -111
- package/dist/terminal.js +0 -125
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { cmdActivity } from '../activity.js';
|
|
2
2
|
import { loadSquare } from '../artifact.js';
|
|
3
3
|
import { cmdCompact } from '../compact.js';
|
|
4
|
-
import { SquareError, formatHardCap,
|
|
5
|
-
import { participantCommandPrefix, quoteShell, renderEventCli, renderPublicTail,
|
|
4
|
+
import { SquareError, formatHardCap, validateName, } from '../model.js';
|
|
5
|
+
import { participantCommandPrefix, quoteShell, renderEventCli, renderPublicTail, withPathOutput, } from '../presentation.js';
|
|
6
6
|
import { hasAutomaticDeliveryIdentity, recordLocalDone, recordLocalJoin } from '../registry.js';
|
|
7
7
|
import { inSquareCount, isCurrentlyJoined, nowMs, resolveRosterName } from '../runtime.js';
|
|
8
8
|
import { createSquare, execute } from '../square-application.js';
|
|
9
9
|
import { fail, parseHardCap, parsePositiveInteger, readPipedBodyFallback, readStdinSync, requireParticipant, requireValue, resolveBody, usage, } from './context.js';
|
|
10
10
|
function parseBuild(argv) {
|
|
11
|
-
const options = { force: false };
|
|
11
|
+
const options = { force: false, hardCap: null };
|
|
12
12
|
for (let index = 0; index < argv.length; index++) {
|
|
13
13
|
const flag = argv[index];
|
|
14
14
|
switch (flag) {
|
|
@@ -36,23 +36,21 @@ function parseBuild(argv) {
|
|
|
36
36
|
if (options.template !== undefined && !/^[a-zA-Z0-9-]+$/.test(options.template)) {
|
|
37
37
|
fail('Invalid template name: only letters, digits, and hyphens allowed.');
|
|
38
38
|
}
|
|
39
|
-
if (options.hardCap === undefined)
|
|
40
|
-
fail('Missing required build option: --cap must be a positive integer or -1.');
|
|
41
39
|
if (options.throttlePerMinute !== undefined && options.throttlePerMinute <= 0) {
|
|
42
40
|
fail('Invalid build option: --throttle must be a positive integer.');
|
|
43
41
|
}
|
|
44
42
|
const snippet = readStdinSync();
|
|
45
43
|
if (snippet.trim() === '')
|
|
46
44
|
fail('Missing Markdown body snippet on stdin.');
|
|
47
|
-
return { options
|
|
45
|
+
return { options, snippet };
|
|
48
46
|
}
|
|
49
47
|
export const buildCommand = {
|
|
50
48
|
parse: (argv) => parseBuild(argv),
|
|
51
49
|
async execute(intent, context) {
|
|
52
50
|
await createSquare(context.squarePath, intent.options, intent.snippet);
|
|
53
|
-
const cap = formatHardCap(intent.options.hardCap);
|
|
51
|
+
const cap = intent.options.hardCap === null ? 'unlimited' : formatHardCap(intent.options.hardCap);
|
|
54
52
|
const throttle = intent.options.throttlePerMinute === undefined ? [] : [` · throttle ${intent.options.throttlePerMinute}/min`];
|
|
55
|
-
return withPathOutput(context.squarePath, ['✓ built', ` · cap ${cap
|
|
53
|
+
return withPathOutput(context.squarePath, ['✓ built', ` · cap ${cap}`, ...throttle, ' · participants (none seeded — first join adds names)'].join('\n'), { participantCount: 0 });
|
|
56
54
|
},
|
|
57
55
|
present: (result) => process.stdout.write(result),
|
|
58
56
|
};
|
|
@@ -75,7 +73,7 @@ function parseJoin(argv, context) {
|
|
|
75
73
|
export const joinCommand = {
|
|
76
74
|
parse: parseJoin,
|
|
77
75
|
async execute(intent, context) {
|
|
78
|
-
|
|
76
|
+
validateName(intent.name);
|
|
79
77
|
try {
|
|
80
78
|
const committed = await execute(context.squarePath, { type: 'join', name: intent.name, now: nowMs() });
|
|
81
79
|
const joinedName = committed.result.joinedName;
|
|
@@ -95,7 +93,7 @@ export const joinCommand = {
|
|
|
95
93
|
...(isRejoin ? [] : ['', `» ${participantCommandPrefix(context.squarePath, joinedName)} warmup`]),
|
|
96
94
|
...fallback,
|
|
97
95
|
].join('\n');
|
|
98
|
-
return
|
|
96
|
+
return withPathOutput(context.squarePath, output, { participantCount: inSquareCount(after) });
|
|
99
97
|
}
|
|
100
98
|
catch (error) {
|
|
101
99
|
if (!(error instanceof SquareError) || error.code !== 'conflict')
|
|
@@ -108,7 +106,7 @@ export const joinCommand = {
|
|
|
108
106
|
const fallback = hasAutomaticDeliveryIdentity()
|
|
109
107
|
? ''
|
|
110
108
|
: `\n» ${participantCommandPrefix(context.squarePath, joinedName)} catch --idle 30m\n no session delivery detected — keep this catch open for new activity`;
|
|
111
|
-
return
|
|
109
|
+
return withPathOutput(context.squarePath, `● ${joinedName} is already in the square${fallback}`, { participantCount: inSquareCount(doc) });
|
|
112
110
|
}
|
|
113
111
|
},
|
|
114
112
|
present: (result) => process.stdout.write(result),
|
|
@@ -135,7 +133,7 @@ function parseActivity(argv, context) {
|
|
|
135
133
|
bodyArgs.push(argument);
|
|
136
134
|
}
|
|
137
135
|
if (bell && beside !== undefined)
|
|
138
|
-
fail('Invalid
|
|
136
|
+
fail('Invalid express options: --beside and --bell are mutually exclusive.');
|
|
139
137
|
const reach = bell ? 'bell' : beside === undefined ? undefined : { beside };
|
|
140
138
|
if (bodyArgs.length !== 1) {
|
|
141
139
|
if (bodyArgs.length === 0) {
|
|
@@ -143,11 +141,11 @@ function parseActivity(argv, context) {
|
|
|
143
141
|
if (piped !== undefined)
|
|
144
142
|
return { name: requireParticipant(context.name), activity: piped, force, noWait, reach };
|
|
145
143
|
}
|
|
146
|
-
fail("
|
|
144
|
+
fail("express requires a body argument (a quoted string or '-' with piped stdin)");
|
|
147
145
|
}
|
|
148
146
|
return { name: requireParticipant(context.name), activity: bodyArgs[0], force, noWait, reach };
|
|
149
147
|
}
|
|
150
|
-
export const
|
|
148
|
+
export const expressCommand = {
|
|
151
149
|
parse: parseActivity,
|
|
152
150
|
async execute(intent, context) {
|
|
153
151
|
const reachArg = intent.reach === 'bell' ? ' --bell' : intent.reach === undefined ? '' : ` --beside ${quoteShell(intent.reach.beside)}`;
|
|
@@ -155,7 +153,7 @@ export const actCommand = {
|
|
|
155
153
|
force: intent.force,
|
|
156
154
|
noWait: intent.noWait,
|
|
157
155
|
reach: intent.reach,
|
|
158
|
-
forceCommand: `${participantCommandPrefix(context.squarePath, intent.name)}
|
|
156
|
+
forceCommand: `${participantCommandPrefix(context.squarePath, intent.name)} express --force${reachArg} -`,
|
|
159
157
|
});
|
|
160
158
|
},
|
|
161
159
|
present: () => { },
|
|
@@ -170,7 +168,7 @@ export const doneCommand = {
|
|
|
170
168
|
async execute(intent, context) {
|
|
171
169
|
const body = resolveBody(intent.body ?? '').replace(/\r\n/g, '\n').trim();
|
|
172
170
|
const committed = await execute(context.squarePath, { type: 'done', name: intent.name, body, now: nowMs() });
|
|
173
|
-
const name = committed.acts[0].
|
|
171
|
+
const name = committed.acts[0].actor;
|
|
174
172
|
recordLocalDone(name, context.squarePath);
|
|
175
173
|
return withPathOutput(context.squarePath, `× ${name} steps out of the square — done · just now`, { participantCount: inSquareCount(loadSquare(context.squarePath)) });
|
|
176
174
|
},
|
|
@@ -186,7 +184,7 @@ export const holdCommand = {
|
|
|
186
184
|
async execute(intent, context) {
|
|
187
185
|
const committed = await execute(context.squarePath, { type: 'hold', actor: intent.name, body: resolveBody(intent.body ?? '').replace(/\r\n/g, '\n').trim(), now: nowMs() });
|
|
188
186
|
const doc = loadSquare(context.squarePath);
|
|
189
|
-
return withPathOutput(context.squarePath, renderEventCli(committed.acts[0]
|
|
187
|
+
return withPathOutput(context.squarePath, renderEventCli(committed.acts[0]), { participantCount: inSquareCount(doc), held: true });
|
|
190
188
|
},
|
|
191
189
|
present: (result) => process.stdout.write(result),
|
|
192
190
|
};
|
|
@@ -199,7 +197,7 @@ export const resumeCommand = {
|
|
|
199
197
|
async execute(intent, context) {
|
|
200
198
|
const committed = await execute(context.squarePath, { type: 'resume', actor: intent.name, now: nowMs() });
|
|
201
199
|
const doc = loadSquare(context.squarePath);
|
|
202
|
-
return withPathOutput(context.squarePath, renderEventCli(committed.acts[0]
|
|
200
|
+
return withPathOutput(context.squarePath, renderEventCli(committed.acts[0]), { participantCount: inSquareCount(doc) });
|
|
203
201
|
},
|
|
204
202
|
present: (result) => process.stdout.write(result),
|
|
205
203
|
};
|
package/dist/cmd/notify-once.js
CHANGED
|
@@ -1,37 +1,39 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { setTimeout as sleep } from 'node:timers/promises';
|
|
3
2
|
import { resolve } from 'node:path';
|
|
4
|
-
import {
|
|
5
|
-
|
|
3
|
+
import { setTimeout as sleep } from 'node:timers/promises';
|
|
4
|
+
import { recordNotificationFailure } from '../notification-failures.js';
|
|
5
|
+
import { notificationDeliveryWaitMs, processActNotificationsOnce } from '../notifications.js';
|
|
6
|
+
function args(argv) {
|
|
6
7
|
let squarePath;
|
|
7
8
|
let actIndex;
|
|
8
|
-
for (let index = 0; index < argv.length; index
|
|
9
|
-
|
|
10
|
-
if (argument === '--square-path' && argv[index + 1] !== undefined) {
|
|
9
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
10
|
+
if (argv[index] === '--square-path' && argv[index + 1] !== undefined)
|
|
11
11
|
squarePath = resolve(argv[++index]);
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
if (Number.isInteger(value) && value >= 0)
|
|
17
|
-
actIndex = value;
|
|
18
|
-
continue;
|
|
19
|
-
}
|
|
20
|
-
throw new Error(`Unknown notify-once argument: ${argument}`);
|
|
12
|
+
else if (argv[index] === '--act-index' && /^\d+$/.test(argv[index + 1] ?? ''))
|
|
13
|
+
actIndex = Number(argv[++index]);
|
|
14
|
+
else
|
|
15
|
+
throw new Error(`Unknown notify-once argument: ${argv[index]}`);
|
|
21
16
|
}
|
|
22
|
-
if (
|
|
17
|
+
if (squarePath === undefined || actIndex === undefined)
|
|
23
18
|
throw new Error('notify-once requires --square-path and --act-index.');
|
|
24
|
-
}
|
|
25
19
|
return { squarePath, actIndex };
|
|
26
20
|
}
|
|
27
21
|
async function main() {
|
|
28
|
-
if (process.env
|
|
22
|
+
if (process.env.SQUARE_DISABLE_PASEO_WAKE === '1')
|
|
29
23
|
return;
|
|
30
|
-
const { squarePath, actIndex } =
|
|
24
|
+
const { squarePath, actIndex } = args(process.argv.slice(2));
|
|
31
25
|
await sleep(notificationDeliveryWaitMs());
|
|
32
26
|
await processActNotificationsOnce(squarePath, actIndex);
|
|
33
27
|
}
|
|
34
|
-
main().catch(() => {
|
|
35
|
-
|
|
28
|
+
main().catch((error) => {
|
|
29
|
+
const squarePath = process.argv.includes('--square-path') ? process.argv[process.argv.indexOf('--square-path') + 1] : undefined;
|
|
30
|
+
if (squarePath) {
|
|
31
|
+
recordNotificationFailure(squarePath, {
|
|
32
|
+
actIndex: Number(process.argv[process.argv.indexOf('--act-index') + 1]) || 0,
|
|
33
|
+
sink: 'worker',
|
|
34
|
+
message: error instanceof Error ? error.message : String(error),
|
|
35
|
+
diagnostic: { phase: 'worker' },
|
|
36
|
+
});
|
|
37
|
+
}
|
|
36
38
|
process.exitCode = 0;
|
|
37
39
|
});
|
package/dist/compact.js
CHANGED
|
@@ -13,7 +13,7 @@ export async function cmdCompact(squarePath, opts) {
|
|
|
13
13
|
const result = committed.result;
|
|
14
14
|
archivedCount = result.archived.length;
|
|
15
15
|
keptCount = result.doc.acts.length;
|
|
16
|
-
const summary = ['✓ compacted', ` · archived ${archivedCount}
|
|
16
|
+
const summary = ['✓ compacted', ` · archived ${archivedCount} activities`, ` · kept ${keptCount} activities`, ...(archivedCount > 0 ? [` · sidecar ${archive}`] : [])].join('\n');
|
|
17
17
|
process.stdout.write(withPathOutput(squarePath, summary));
|
|
18
18
|
}
|
|
19
19
|
catch (err) {
|
package/dist/decisions.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SquareError, sameName, validateName, } from './model.js';
|
|
2
|
-
import { UNREAD_BLOCK_GRACE_MS,
|
|
3
|
-
import {
|
|
2
|
+
import { UNREAD_BLOCK_GRACE_MS, actId, actStableIndex, foldedState, freshWatchLease, getReadState, publicActs, readCursor, resolveRosterName, rosterNames, matchesMentionTarget, THROTTLE_WINDOW_MS, } from './runtime.js';
|
|
3
|
+
import { actDelta, peerPublicActs, peerRoomChanges } from './activity-feed.js';
|
|
4
4
|
import { validate } from './square-core.js';
|
|
5
5
|
import { deriveDeliveryModel } from './delivery.js';
|
|
6
6
|
import { compileSearchPattern } from './search.js';
|
|
@@ -36,14 +36,14 @@ export function decideAct(doc, input) {
|
|
|
36
36
|
const name = resolveKnownName(doc, input.name);
|
|
37
37
|
const body = input.body;
|
|
38
38
|
if (body.trim() === '')
|
|
39
|
-
throw new SquareError('invalid_args', '
|
|
39
|
+
throw new SquareError('invalid_args', 'express body cannot be empty');
|
|
40
40
|
const reach = input.reach;
|
|
41
41
|
const state = foldedState(doc);
|
|
42
42
|
const current = participantState(state, name);
|
|
43
43
|
const result = validate(state, { kind: 'say', actor: name, at: now, body, ...(reach !== undefined ? { reach } : {}) }, { hardCap: doc.hardCap, throttlePerMinute: doc.throttlePerMinute, throttleWindowMs: THROTTLE_WINDOW_MS });
|
|
44
44
|
if (!result.ok) {
|
|
45
45
|
if (result.reason === 'done')
|
|
46
|
-
throw new SquareError('conflict', `${name} is done; rejoin to
|
|
46
|
+
throw new SquareError('conflict', `${name} is done; rejoin to express again`);
|
|
47
47
|
if (result.reason === 'held')
|
|
48
48
|
return { type: 'held', reason: result.hold.reason };
|
|
49
49
|
if (result.reason === 'hard_cap')
|
|
@@ -55,26 +55,26 @@ export function decideAct(doc, input) {
|
|
|
55
55
|
if (result.reason === 'not_joined')
|
|
56
56
|
throw new SquareError('conflict', `${name} has not joined this square`);
|
|
57
57
|
}
|
|
58
|
-
const delta =
|
|
58
|
+
const delta = actDelta(doc.acts, readCursor(doc, name));
|
|
59
59
|
const unreadPublic = peerPublicActs(delta, name);
|
|
60
60
|
const unreadRoomChanges = peerRoomChanges(delta, name);
|
|
61
61
|
const sayCountByActor = new Map();
|
|
62
62
|
const unreadByParticipant = new Map();
|
|
63
63
|
for (const item of delta) {
|
|
64
|
-
if (item.
|
|
65
|
-
const key = item.
|
|
64
|
+
if (item.kind === 'say') {
|
|
65
|
+
const key = item.actor.toLocaleLowerCase();
|
|
66
66
|
sayCountByActor.set(key, (sayCountByActor.get(key) ?? 0) + 1);
|
|
67
67
|
}
|
|
68
|
-
if (item.
|
|
68
|
+
if (item.kind !== 'say' || sameName(item.actor, name))
|
|
69
69
|
continue;
|
|
70
|
-
const actorKey = item.
|
|
71
|
-
const currentSummary = unreadByParticipant.get(item.
|
|
72
|
-
unreadByParticipant.set(item.
|
|
70
|
+
const actorKey = item.actor.toLocaleLowerCase();
|
|
71
|
+
const currentSummary = unreadByParticipant.get(item.actor);
|
|
72
|
+
unreadByParticipant.set(item.actor, {
|
|
73
73
|
count: (currentSummary?.count ?? 0) + 1,
|
|
74
|
-
latestAt: currentSummary === undefined ? item.
|
|
74
|
+
latestAt: currentSummary === undefined ? item.at : Math.max(currentSummary.latestAt, item.at),
|
|
75
75
|
previews: [
|
|
76
76
|
...(currentSummary?.previews ?? []),
|
|
77
|
-
{ number: sayCountByActor.get(actorKey) ?? 1, act: item
|
|
77
|
+
{ number: sayCountByActor.get(actorKey) ?? 1, act: item },
|
|
78
78
|
].slice(-UNREAD_PREVIEW_LIMIT),
|
|
79
79
|
});
|
|
80
80
|
}
|
|
@@ -112,35 +112,23 @@ export function coreHold(_doc, actor, body, now) {
|
|
|
112
112
|
export function coreResume(_doc, actor, now) {
|
|
113
113
|
return { kind: 'resume', actor, at: now, body: '' };
|
|
114
114
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
if (lastAt === undefined)
|
|
127
|
-
return { name: participant, state: 'never-joined', lastAt: undefined };
|
|
128
|
-
return { name: participant, state: 'active', lastAt };
|
|
129
|
-
});
|
|
115
|
+
function presenceFor(doc, snapshot, name, now) {
|
|
116
|
+
if (snapshot?.done)
|
|
117
|
+
return { state: 'done', lastAt: snapshot.lastActiveAt };
|
|
118
|
+
const cursor = getReadState(doc, name);
|
|
119
|
+
const lease = freshWatchLease(doc, name, now);
|
|
120
|
+
if (lease !== undefined)
|
|
121
|
+
return { state: 'watching', lastAt: cursor?.updatedAt ?? lease.heartbeatAt };
|
|
122
|
+
const lastAt = cursor?.updatedAt ?? (snapshot?.joined ? snapshot.lastActiveAt : undefined);
|
|
123
|
+
return lastAt === undefined
|
|
124
|
+
? { state: 'never-joined', lastAt: undefined }
|
|
125
|
+
: { state: 'active', lastAt };
|
|
130
126
|
}
|
|
131
127
|
function buildParticipantStatuses(doc, now, state = foldedState(doc)) {
|
|
132
128
|
const delivery = deriveDeliveryModel(doc);
|
|
133
|
-
return state.participants.map((
|
|
134
|
-
const
|
|
135
|
-
const
|
|
136
|
-
const lease = freshWatchLease(doc, participant, now);
|
|
137
|
-
const presence = snapshot?.done
|
|
138
|
-
? { state: 'done', lastAt: snapshot.lastActiveAt }
|
|
139
|
-
: lease !== undefined
|
|
140
|
-
? { state: 'watching', lastAt: cursor?.updatedAt ?? lease.heartbeatAt }
|
|
141
|
-
: cursor !== undefined || snapshot?.joined
|
|
142
|
-
? { state: 'active', lastAt: cursor?.updatedAt ?? snapshot?.lastActiveAt }
|
|
143
|
-
: { state: 'never-joined', lastAt: undefined };
|
|
129
|
+
return state.participants.map((snapshot) => {
|
|
130
|
+
const participant = snapshot.name;
|
|
131
|
+
const presence = presenceFor(doc, snapshot, participant, now);
|
|
144
132
|
const participantStatus = snapshot?.done ? 'done' : snapshot?.joined ? 'active' : 'not joined';
|
|
145
133
|
const consumedThrough = readCursor(doc, participant);
|
|
146
134
|
let unreadActivityCount = 0;
|
|
@@ -170,7 +158,6 @@ export function coreStatus(doc, now) {
|
|
|
170
158
|
return {
|
|
171
159
|
hardCap: doc.hardCap,
|
|
172
160
|
throttlePerMinute: doc.throttlePerMinute,
|
|
173
|
-
participantCount: state.joined.length,
|
|
174
161
|
activeCount: state.joined.length,
|
|
175
162
|
doneCount: state.done.length,
|
|
176
163
|
holdActive: state.hold.active,
|
|
@@ -183,77 +170,62 @@ export function coreStatus(doc, now) {
|
|
|
183
170
|
};
|
|
184
171
|
}
|
|
185
172
|
export function coreParticipants(doc, now) {
|
|
186
|
-
return
|
|
187
|
-
}
|
|
188
|
-
/** True when a say is a bell or explicitly @viewer — not broadcast. */
|
|
189
|
-
function addressesViewer(act, viewer) {
|
|
190
|
-
if (act.kind !== 'say')
|
|
191
|
-
return false;
|
|
192
|
-
if (act.reach === 'bell')
|
|
193
|
-
return true;
|
|
194
|
-
return extractMentions(act.body).some((name) => sameName(name, viewer));
|
|
173
|
+
return buildParticipantStatuses(doc, now);
|
|
195
174
|
}
|
|
196
175
|
export function coreActivities(doc, opts) {
|
|
197
176
|
const participants = opts.participants ?? [];
|
|
198
177
|
const canonicalParticipants = participants.map((participant) => resolveKnownName(doc, participant));
|
|
199
178
|
const viewer = opts.viewer !== undefined ? resolveKnownName(doc, opts.viewer) : undefined;
|
|
200
|
-
let acts = doc.acts
|
|
179
|
+
let acts = [...doc.acts];
|
|
201
180
|
// --at establishes a context window first; other filters AND inside it.
|
|
202
181
|
if (opts.atIndex != null) {
|
|
203
182
|
const before = opts.beforeContext ?? 0;
|
|
204
183
|
const after = opts.afterContext ?? 0;
|
|
205
|
-
const centerPos = acts.findIndex((
|
|
184
|
+
const centerPos = acts.findIndex((act) => act.index === opts.atIndex);
|
|
206
185
|
if (centerPos < 0)
|
|
207
186
|
return [];
|
|
208
187
|
acts = acts.slice(Math.max(0, centerPos - before), centerPos + after + 1);
|
|
209
188
|
}
|
|
210
189
|
if (opts.ids !== undefined && opts.ids.length > 0) {
|
|
211
190
|
const wanted = new Set(opts.ids);
|
|
212
|
-
acts = acts.filter((
|
|
191
|
+
acts = acts.filter((act) => wanted.has(act.index));
|
|
213
192
|
}
|
|
214
193
|
if (opts.afterIndex != null)
|
|
215
|
-
acts = acts.filter((
|
|
194
|
+
acts = acts.filter((act) => act.index > opts.afterIndex);
|
|
216
195
|
if (canonicalParticipants.length > 0) {
|
|
217
|
-
acts = acts.filter((
|
|
218
|
-
(act.actor !== undefined && canonicalParticipants.some((participant) => sameName(participant, act.actor))));
|
|
196
|
+
acts = acts.filter((act) => act.actor !== undefined && canonicalParticipants.some((participant) => sameName(participant, act.actor)));
|
|
219
197
|
}
|
|
220
198
|
if (opts.before != null)
|
|
221
|
-
acts = acts.filter((
|
|
199
|
+
acts = acts.filter((act) => act.at < opts.before);
|
|
222
200
|
if (opts.after != null)
|
|
223
|
-
acts = acts.filter((
|
|
201
|
+
acts = acts.filter((act) => act.at > opts.after);
|
|
224
202
|
if (opts.mention != null) {
|
|
225
203
|
const mention = resolveKnownName(doc, opts.mention);
|
|
226
|
-
acts = acts.filter((
|
|
227
|
-
}
|
|
228
|
-
if (opts.mentionsViewer) {
|
|
229
|
-
if (viewer === undefined)
|
|
230
|
-
return [];
|
|
231
|
-
acts = acts.filter(({ act }) => addressesViewer(act, viewer));
|
|
204
|
+
acts = acts.filter((act) => act.kind === 'say' && (act.reach === 'bell' || matchesMentionTarget(act, mention)));
|
|
232
205
|
}
|
|
233
206
|
if (opts.pending) {
|
|
234
207
|
if (viewer === undefined)
|
|
235
208
|
return [];
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
return false;
|
|
239
|
-
if (!isPostJoinActivity(doc.acts, viewer, index))
|
|
240
|
-
return false;
|
|
241
|
-
return !isDeliveryDelivered(doc, viewer, index);
|
|
242
|
-
});
|
|
209
|
+
const pendingIndexes = new Set(deriveDeliveryModel(doc).pendingFor(viewer).map((notification) => notification.item.index));
|
|
210
|
+
acts = acts.filter((act) => pendingIndexes.has(act.index));
|
|
243
211
|
}
|
|
244
212
|
const search = opts.grep !== undefined ? { pattern: opts.grep, fixed: false } : opts.fixed !== undefined ? { pattern: opts.fixed, fixed: true } : undefined;
|
|
245
213
|
if (search !== undefined && search.pattern !== '') {
|
|
246
|
-
// Search
|
|
247
|
-
//
|
|
248
|
-
acts = acts.filter((
|
|
214
|
+
// Search only the public activity model rendered by history, but include all
|
|
215
|
+
// of its user-facing fields rather than coupling matching to rendered text.
|
|
216
|
+
acts = acts.filter((act) => act.kind === 'say' || act.kind === 'done');
|
|
249
217
|
const re = compileSearchPattern(search.pattern, search.fixed);
|
|
250
|
-
acts = acts.filter((
|
|
218
|
+
acts = acts.filter((act) => [
|
|
219
|
+
actId(act.index),
|
|
220
|
+
act.actor ?? '',
|
|
221
|
+
'body' in act && typeof act.body === 'string' ? act.body : '',
|
|
222
|
+
].some((field) => re.test(field)));
|
|
251
223
|
}
|
|
252
224
|
if (opts.order === 'desc') {
|
|
253
|
-
acts = [...acts].sort((a, b) => b.index - a.index || b.
|
|
225
|
+
acts = [...acts].sort((a, b) => b.index - a.index || b.at - a.at);
|
|
254
226
|
}
|
|
255
227
|
else {
|
|
256
|
-
acts = [...acts].sort((a, b) => a.index - b.index || a.
|
|
228
|
+
acts = [...acts].sort((a, b) => a.index - b.index || a.at - b.at);
|
|
257
229
|
}
|
|
258
230
|
return acts;
|
|
259
231
|
}
|
|
@@ -264,23 +236,18 @@ export function coreCompact(doc, keep) {
|
|
|
264
236
|
const archived = doc.acts.slice(0, splitAt);
|
|
265
237
|
const retained = doc.acts.slice(splitAt);
|
|
266
238
|
const cutoffIndex = actStableIndex(archived[archived.length - 1]);
|
|
267
|
-
const unread =
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
if (!foldedState(doc).participants.some((entry) => sameName(entry.name, participant) && entry.joined))
|
|
271
|
-
return false;
|
|
272
|
-
return readCursor(doc, participant) < cutoffIndex;
|
|
273
|
-
});
|
|
239
|
+
const unread = foldedState(doc).participants
|
|
240
|
+
.filter((participant) => participant.joined && readCursor(doc, participant.name) < cutoffIndex)
|
|
241
|
+
.map((participant) => participant.name);
|
|
274
242
|
if (unread.length > 0) {
|
|
275
|
-
throw new SquareError('conflict', `Refusing to compact: ${unread.join(', ')} ${unread.length === 1 ? 'has' : 'have'} not read through the
|
|
243
|
+
throw new SquareError('conflict', `Refusing to compact: ${unread.join(', ')} ${unread.length === 1 ? 'has' : 'have'} not read through the activities being archived.`);
|
|
276
244
|
}
|
|
277
|
-
const firstActIndex = retained.length > 0 ? actStableIndex(retained[0]) : doc.runtime.nextActIndex;
|
|
278
245
|
return {
|
|
279
246
|
archived,
|
|
280
247
|
doc: {
|
|
281
248
|
...doc,
|
|
282
249
|
acts: retained,
|
|
283
|
-
runtime:
|
|
250
|
+
runtime: doc.runtime,
|
|
284
251
|
},
|
|
285
252
|
};
|
|
286
253
|
}
|