@astrosheep/square 0.3.14 → 0.3.16
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 +1 -1
- package/dist/activity-feed.d.ts +2 -3
- package/dist/activity-feed.js +6 -15
- package/dist/activity.js +1 -1
- package/dist/artifact.js +17 -26
- package/dist/attention-presentation.d.ts +13 -0
- package/dist/attention-presentation.js +22 -0
- package/dist/automatic-session.js +6 -8
- package/dist/boundary-presentation.js +35 -14
- package/dist/claude-hook.js +5 -3
- package/dist/cli/context.d.ts +4 -4
- package/dist/cli/context.js +25 -4
- package/dist/cli/harness-command.js +1 -1
- package/dist/cli/maintenance-commands.js +5 -4
- package/dist/cli/observation-commands.js +24 -16
- package/dist/cli/program.js +1 -1
- package/dist/cli/registry.js +4 -1
- package/dist/cli/square-commands.d.ts +7 -0
- package/dist/cli/square-commands.js +123 -27
- package/dist/codex-hook.js +5 -3
- package/dist/decisions.d.ts +19 -0
- package/dist/decisions.js +45 -12
- package/dist/delivery.d.ts +14 -21
- package/dist/delivery.js +40 -74
- package/dist/help.js +8 -5
- package/dist/inbox.js +1 -0
- package/dist/index.d.ts +1 -10
- package/dist/index.js +0 -32
- package/dist/landing.d.ts +12 -0
- package/dist/landing.js +35 -4
- package/dist/model.d.ts +8 -11
- package/dist/model.js +2 -2
- package/dist/notifications.js +35 -11
- package/dist/paseo-delivery.js +7 -4
- package/dist/paseo.d.ts +6 -0
- package/dist/paseo.js +5 -0
- package/dist/presence.d.ts +3 -0
- package/dist/presence.js +40 -10
- package/dist/presentation.d.ts +6 -2
- package/dist/presentation.js +22 -9
- package/dist/presented.d.ts +2 -0
- package/dist/presented.js +20 -0
- package/dist/registry.js +4 -1
- package/dist/routes.d.ts +5 -0
- package/dist/routes.js +17 -0
- package/dist/runtime.d.ts +4 -5
- package/dist/runtime.js +35 -18
- package/dist/square-core.d.ts +17 -0
- package/dist/square-core.js +51 -1
- package/dist/square-facade.d.ts +9 -1
- package/dist/square-wiring.d.ts +8 -0
- package/dist/square-wiring.js +32 -3
- package/dist/views.d.ts +9 -3
- package/dist/views.js +16 -14
- package/dist/wake-port.d.ts +4 -1
- package/dist/wake-port.js +5 -0
- package/dist/wakes.js +3 -5
- package/dist/watch.js +1 -0
- package/package.json +19 -3
- package/skills/brainstorm/SKILL.md +1 -1
- package/skills/square/.claude-plugin/plugin.json +1 -1
- package/skills/square/SKILL.md +1 -1
|
@@ -6,9 +6,9 @@ import { sweepPendingNotifications, wakeNotifierForSquare } from '../notificatio
|
|
|
6
6
|
import { nowMs } from '../runtime.js';
|
|
7
7
|
import { createSquare, openSquare } from '../square-file-adapter.js';
|
|
8
8
|
import { closeOpenSquare } from '../open-square.js';
|
|
9
|
-
import { Square } from '../square-wiring.js';
|
|
10
|
-
import { entryPresentation, eventPresentation } from '../views.js';
|
|
11
|
-
import { fail, parseHardCap, parsePositiveInteger, readPipedBodyFallback, readStdinSync, requireParticipant, requireValue, resolveBody, usage, } from './context.js';
|
|
9
|
+
import { openParticipant, Square } from '../square-wiring.js';
|
|
10
|
+
import { admitsBareExpress, entryPresentation, eventPresentation } from '../views.js';
|
|
11
|
+
import { fail, parseHardCap, parsePositiveInteger, readPipedBodyFallback, readStdinSync, requireParticipant, requireSquarePath, requireValue, resolveBody, usage, } from './context.js';
|
|
12
12
|
function parseBuild(argv) {
|
|
13
13
|
const options = { force: false, hardCap: null };
|
|
14
14
|
for (let index = 0; index < argv.length; index++) {
|
|
@@ -49,10 +49,11 @@ function parseBuild(argv) {
|
|
|
49
49
|
export const buildCommand = {
|
|
50
50
|
parse: (argv) => parseBuild(argv),
|
|
51
51
|
async execute(intent, context) {
|
|
52
|
-
|
|
52
|
+
const squarePath = requireSquarePath(context);
|
|
53
|
+
await createSquare(squarePath, intent.options, intent.snippet);
|
|
53
54
|
const cap = intent.options.hardCap === null ? 'unlimited' : formatHardCap(intent.options.hardCap);
|
|
54
55
|
const throttle = intent.options.throttlePerMinute === undefined ? [] : [` · throttle ${intent.options.throttlePerMinute}/min`];
|
|
55
|
-
return withPathOutput(
|
|
56
|
+
return withPathOutput(squarePath, ['✓ built', ` · cap ${cap}`, ...throttle, ' · participants (none seeded — first join adds names)'].join('\n'), { participantCount: 0 });
|
|
56
57
|
},
|
|
57
58
|
present: (result) => process.stdout.write(result),
|
|
58
59
|
};
|
|
@@ -79,38 +80,40 @@ function parseJoin(argv, context) {
|
|
|
79
80
|
export const joinCommand = {
|
|
80
81
|
parse: parseJoin,
|
|
81
82
|
async execute(intent, context) {
|
|
82
|
-
const
|
|
83
|
+
const squarePath = requireSquarePath(context);
|
|
84
|
+
const beforeSquare = await openSquare(squarePath, { clock: nowMs });
|
|
83
85
|
const before = await entryPresentation(beforeSquare, intent.name, intent.lastN);
|
|
84
86
|
await closeOpenSquare(beforeSquare);
|
|
85
|
-
const square = await Square.at({ path:
|
|
87
|
+
const square = await Square.at({ path: squarePath, clock: nowMs, notifier: wakeNotifierForSquare(squarePath) });
|
|
86
88
|
try {
|
|
87
89
|
const participant = await square.join(intent.name);
|
|
88
90
|
const joinedName = participant.name;
|
|
89
91
|
const isRejoin = before.joined;
|
|
90
92
|
const reconnect = isRejoin
|
|
91
|
-
&& localParticipantOwner(
|
|
93
|
+
&& localParticipantOwner(squarePath, joinedName) !== undefined;
|
|
92
94
|
if (isRejoin && !intent.kick && !reconnect) {
|
|
93
95
|
fail([
|
|
94
96
|
`✕ ${participantIdentity(joinedName)} shoos you out of the square`,
|
|
95
97
|
` · a same-named participant stands here — the name is taken`,
|
|
96
98
|
` · --kick banishes her and the name becomes yours`,
|
|
97
|
-
`» ${participantCommandPrefix(
|
|
99
|
+
`» ${participantCommandPrefix(squarePath, joinedName)} join --kick`,
|
|
98
100
|
].join('\n'));
|
|
99
101
|
}
|
|
100
|
-
const afterSquare = await openSquare(
|
|
102
|
+
const afterSquare = await openSquare(squarePath, { clock: nowMs });
|
|
101
103
|
const after = await entryPresentation(afterSquare, joinedName, intent.lastN);
|
|
102
104
|
await closeOpenSquare(afterSquare);
|
|
103
|
-
recordLocalJoin(joinedName,
|
|
104
|
-
await sweepPendingNotifications(
|
|
105
|
+
recordLocalJoin(joinedName, squarePath);
|
|
106
|
+
await sweepPendingNotifications(squarePath);
|
|
105
107
|
const activities = after.recentActivities.map((event) => renderAmbientEvent(event, joinedName, {
|
|
106
108
|
now: nowMs(),
|
|
107
109
|
preview: intent.lastN === null ? undefined : 200,
|
|
108
110
|
actNumber: event.kind === 'say' ? after.sayNumbers[event.index] : undefined,
|
|
111
|
+
squareState: after.state,
|
|
109
112
|
})).filter(Boolean).join('\n\n');
|
|
110
113
|
const contextText = after.joinContext;
|
|
111
114
|
const fallback = hasAutomaticDeliveryIdentity()
|
|
112
115
|
? []
|
|
113
|
-
: ['', `» ${participantCommandPrefix(
|
|
116
|
+
: ['', `» ${participantCommandPrefix(squarePath, joinedName)} catch --idle 30m`, ' no session delivery detected — keep this catch open for new activity'];
|
|
114
117
|
const scene = after.scene;
|
|
115
118
|
const entryLine = !isRejoin
|
|
116
119
|
? '● You stepped into the square'
|
|
@@ -124,7 +127,7 @@ export const joinCommand = {
|
|
|
124
127
|
...(isRejoin || activities === '' ? [] : ['', 'recent activity', activities]),
|
|
125
128
|
...fallback,
|
|
126
129
|
].join('\n');
|
|
127
|
-
return withPathOutput(
|
|
130
|
+
return withPathOutput(squarePath, output, { participantCount: after.participantCount });
|
|
128
131
|
}
|
|
129
132
|
finally {
|
|
130
133
|
await square.close();
|
|
@@ -172,18 +175,108 @@ function parseActivity(argv, context) {
|
|
|
172
175
|
export const expressCommand = {
|
|
173
176
|
parse: parseActivity,
|
|
174
177
|
async execute(intent, context) {
|
|
175
|
-
|
|
178
|
+
const squarePath = requireSquarePath(context);
|
|
179
|
+
await sweepPendingNotifications(squarePath);
|
|
180
|
+
const body = resolveBody(intent.activity);
|
|
181
|
+
if (!intent.force && intent.reach !== 'bell') {
|
|
182
|
+
const reader = await openSquare(squarePath, { clock: nowMs });
|
|
183
|
+
try {
|
|
184
|
+
if (!await admitsBareExpress(reader, intent.name, body)) {
|
|
185
|
+
fail(`✕ no one is turned toward you\n · address someone with @name, ring the bell, or use --force\n» ${participantCommandPrefix(squarePath, intent.name)} express --force -`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
finally {
|
|
189
|
+
await closeOpenSquare(reader);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
176
192
|
const reachArg = intent.reach === 'bell' ? ' --bell' : '';
|
|
177
|
-
await cmdActivity(
|
|
193
|
+
await cmdActivity(squarePath, intent.name, body, (value) => value, {
|
|
178
194
|
force: intent.force,
|
|
179
195
|
noWait: intent.noWait,
|
|
180
196
|
reach: intent.reach,
|
|
181
197
|
reply: intent.reply,
|
|
182
|
-
forceCommand: `${participantCommandPrefix(
|
|
198
|
+
forceCommand: `${participantCommandPrefix(squarePath, intent.name)} express --force${reachArg}${intent.reply === undefined ? '' : ` --reply ${formatActivityId(intent.reply)}`} -`,
|
|
183
199
|
});
|
|
184
200
|
},
|
|
185
201
|
present: () => { },
|
|
186
202
|
};
|
|
203
|
+
function parseListener(argv, context, targetRequired) {
|
|
204
|
+
if (targetRequired ? argv.length !== 1 : argv.length !== 0)
|
|
205
|
+
usage(context.command);
|
|
206
|
+
return { name: requireParticipant(context.name), ...(targetRequired ? { target: argv[0] } : {}) };
|
|
207
|
+
}
|
|
208
|
+
function listenerPresentation(squarePath, actor, target, verb, activity, participantCount) {
|
|
209
|
+
if (activity !== null) {
|
|
210
|
+
const action = verb === 'listen'
|
|
211
|
+
? `${participantIdentity(actor)} turns an ear toward ${participantIdentity(target)}`
|
|
212
|
+
: `${participantIdentity(actor)} turns away from ${participantIdentity(target)}`;
|
|
213
|
+
return withPathOutput(squarePath, `· ${action}`, { participantCount });
|
|
214
|
+
}
|
|
215
|
+
const action = verb === 'listen'
|
|
216
|
+
? `${participantIdentity(actor)} already turns an ear toward ${participantIdentity(target)}`
|
|
217
|
+
: `${participantIdentity(actor)} is not turned toward ${participantIdentity(target)}`;
|
|
218
|
+
return withPathOutput(squarePath, `○ ${action}`, { participantCount });
|
|
219
|
+
}
|
|
220
|
+
export const listenCommand = {
|
|
221
|
+
parse(argv, context) { return parseListener(argv, context, true); },
|
|
222
|
+
async execute(intent, context) {
|
|
223
|
+
const squarePath = requireSquarePath(context);
|
|
224
|
+
const square = await Square.at({ path: squarePath, clock: nowMs });
|
|
225
|
+
const facade = await openParticipant({ path: squarePath, clock: nowMs }, intent.name);
|
|
226
|
+
try {
|
|
227
|
+
const participant = facade.participant;
|
|
228
|
+
const result = await participant.listen(intent.target);
|
|
229
|
+
const participantCount = (await square.snapshot()).participants.filter((item) => item.state === 'joined').length;
|
|
230
|
+
return listenerPresentation(squarePath, participant.name, intent.target, 'listen', result.activity, participantCount);
|
|
231
|
+
}
|
|
232
|
+
finally {
|
|
233
|
+
await facade.close();
|
|
234
|
+
await square.close();
|
|
235
|
+
}
|
|
236
|
+
},
|
|
237
|
+
present: (result) => process.stdout.write(result),
|
|
238
|
+
};
|
|
239
|
+
export const ignoreCommand = {
|
|
240
|
+
parse(argv, context) { return parseListener(argv, context, true); },
|
|
241
|
+
async execute(intent, context) {
|
|
242
|
+
const squarePath = requireSquarePath(context);
|
|
243
|
+
const square = await Square.at({ path: squarePath, clock: nowMs });
|
|
244
|
+
const facade = await openParticipant({ path: squarePath, clock: nowMs }, intent.name);
|
|
245
|
+
try {
|
|
246
|
+
const participant = facade.participant;
|
|
247
|
+
const result = await participant.ignore(intent.target);
|
|
248
|
+
const participantCount = (await square.snapshot()).participants.filter((item) => item.state === 'joined').length;
|
|
249
|
+
return listenerPresentation(squarePath, participant.name, intent.target, 'ignore', result.activity, participantCount);
|
|
250
|
+
}
|
|
251
|
+
finally {
|
|
252
|
+
await facade.close();
|
|
253
|
+
await square.close();
|
|
254
|
+
}
|
|
255
|
+
},
|
|
256
|
+
present: (result) => process.stdout.write(result),
|
|
257
|
+
};
|
|
258
|
+
export const listeningCommand = {
|
|
259
|
+
parse(argv, context) { return parseListener(argv, context, false); },
|
|
260
|
+
async execute(intent, context) {
|
|
261
|
+
const squarePath = requireSquarePath(context);
|
|
262
|
+
const square = await Square.at({ path: squarePath, clock: nowMs });
|
|
263
|
+
const facade = await openParticipant({ path: squarePath, clock: nowMs }, intent.name);
|
|
264
|
+
try {
|
|
265
|
+
const participant = facade.participant;
|
|
266
|
+
const targets = await participant.listening();
|
|
267
|
+
const participantCount = (await square.snapshot()).participants.filter((item) => item.state === 'joined').length;
|
|
268
|
+
const body = targets.length === 0
|
|
269
|
+
? `○ ${participantIdentity(participant.name)} is not turned toward anyone`
|
|
270
|
+
: ['listening', ...targets.map((target) => ` · ${participantIdentity(target)}`)].join('\n');
|
|
271
|
+
return withPathOutput(squarePath, body, { participantCount });
|
|
272
|
+
}
|
|
273
|
+
finally {
|
|
274
|
+
await facade.close();
|
|
275
|
+
await square.close();
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
present: (result) => process.stdout.write(result),
|
|
279
|
+
};
|
|
187
280
|
function parseDone(argv, context) {
|
|
188
281
|
if (argv.length > 1)
|
|
189
282
|
usage(context.command);
|
|
@@ -192,16 +285,17 @@ function parseDone(argv, context) {
|
|
|
192
285
|
export const doneCommand = {
|
|
193
286
|
parse: parseDone,
|
|
194
287
|
async execute(intent, context) {
|
|
288
|
+
const squarePath = requireSquarePath(context);
|
|
195
289
|
const body = resolveBody(intent.body ?? '').replace(/\r\n/g, '\n').trim();
|
|
196
|
-
const square = await Square.at({ path:
|
|
290
|
+
const square = await Square.at({ path: squarePath, clock: nowMs, notifier: wakeNotifierForSquare(squarePath) });
|
|
197
291
|
const participant = await square.join(intent.name);
|
|
198
292
|
const result = await participant.done(body);
|
|
199
293
|
await square.close();
|
|
200
294
|
const name = result.activity.actor;
|
|
201
|
-
recordLocalDone(name,
|
|
202
|
-
const presentation = await openSquare(
|
|
295
|
+
recordLocalDone(name, squarePath);
|
|
296
|
+
const presentation = await openSquare(squarePath, { clock: nowMs });
|
|
203
297
|
const participantCount = (await entryPresentation(presentation, name).finally(() => closeOpenSquare(presentation))).participantCount;
|
|
204
|
-
return withPathOutput(
|
|
298
|
+
return withPathOutput(squarePath, `○ ${participantIdentity(name)} steps out of the square — done · just now`, { participantCount });
|
|
205
299
|
},
|
|
206
300
|
present: (result) => process.stdout.write(result),
|
|
207
301
|
};
|
|
@@ -213,15 +307,16 @@ function parseHold(argv, context) {
|
|
|
213
307
|
export const holdCommand = {
|
|
214
308
|
parse: parseHold,
|
|
215
309
|
async execute(intent, context) {
|
|
216
|
-
const
|
|
310
|
+
const squarePath = requireSquarePath(context);
|
|
311
|
+
const square = await Square.at({ path: squarePath, clock: nowMs, notifier: wakeNotifierForSquare(squarePath) });
|
|
217
312
|
try {
|
|
218
313
|
const participant = await square.join(intent.name);
|
|
219
314
|
const result = await participant.hold(resolveBody(intent.body ?? '').replace(/\r\n/g, '\n').trim());
|
|
220
315
|
await square.close();
|
|
221
|
-
const presentationSquare = await openSquare(
|
|
316
|
+
const presentationSquare = await openSquare(squarePath, { clock: nowMs });
|
|
222
317
|
const presentation = await eventPresentation(presentationSquare, result.activity.id);
|
|
223
318
|
await closeOpenSquare(presentationSquare);
|
|
224
|
-
return withPathOutput(
|
|
319
|
+
return withPathOutput(squarePath, renderEventCli(presentation.activity), { participantCount: presentation.participantCount, held: true });
|
|
225
320
|
}
|
|
226
321
|
finally {
|
|
227
322
|
await square.close();
|
|
@@ -236,15 +331,16 @@ export const resumeCommand = {
|
|
|
236
331
|
return { name: requireParticipant(context.name) };
|
|
237
332
|
},
|
|
238
333
|
async execute(intent, context) {
|
|
239
|
-
const
|
|
334
|
+
const squarePath = requireSquarePath(context);
|
|
335
|
+
const square = await Square.at({ path: squarePath, clock: nowMs, notifier: wakeNotifierForSquare(squarePath) });
|
|
240
336
|
try {
|
|
241
337
|
const participant = await square.join(intent.name);
|
|
242
338
|
const result = await participant.resume();
|
|
243
339
|
await square.close();
|
|
244
|
-
const presentationSquare = await openSquare(
|
|
340
|
+
const presentationSquare = await openSquare(squarePath, { clock: nowMs });
|
|
245
341
|
const presentation = await eventPresentation(presentationSquare, result.activity.id);
|
|
246
342
|
await closeOpenSquare(presentationSquare);
|
|
247
|
-
return withPathOutput(
|
|
343
|
+
return withPathOutput(squarePath, renderEventCli(presentation.activity), { participantCount: presentation.participantCount });
|
|
248
344
|
}
|
|
249
345
|
finally {
|
|
250
346
|
await square.close();
|
package/dist/codex-hook.js
CHANGED
|
@@ -32,11 +32,12 @@ export async function runCodexHookAsync(inputText, env = process.env) {
|
|
|
32
32
|
if (input === null || typeof input !== 'object')
|
|
33
33
|
return '';
|
|
34
34
|
const value = input;
|
|
35
|
-
if (typeof value.session_id !== 'string'
|
|
35
|
+
if (typeof value.session_id !== 'string')
|
|
36
36
|
return runCodexHook(inputText, env);
|
|
37
37
|
if (value.hook_event_name === 'SessionStart' || value.hook_event_name === 'SessionResume') {
|
|
38
|
+
const cwd = typeof value.cwd === 'string' ? value.cwd : process.cwd();
|
|
38
39
|
try {
|
|
39
|
-
const context = await automaticSessionStart('codex', value.session_id,
|
|
40
|
+
const context = await automaticSessionStart('codex', value.session_id, cwd, env);
|
|
40
41
|
return context === undefined ? '' : `${JSON.stringify({ hookSpecificOutput: { hookEventName: value.hook_event_name, additionalContext: context } })}\n`;
|
|
41
42
|
}
|
|
42
43
|
catch {
|
|
@@ -44,8 +45,9 @@ export async function runCodexHookAsync(inputText, env = process.env) {
|
|
|
44
45
|
}
|
|
45
46
|
}
|
|
46
47
|
if (value.hook_event_name === 'SessionEnd') {
|
|
48
|
+
const cwd = typeof value.cwd === 'string' ? value.cwd : process.cwd();
|
|
47
49
|
try {
|
|
48
|
-
await automaticSessionEnd('codex', value.session_id,
|
|
50
|
+
await automaticSessionEnd('codex', value.session_id, cwd, env);
|
|
49
51
|
}
|
|
50
52
|
catch { /* end remains bounded */ }
|
|
51
53
|
return '';
|
package/dist/decisions.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type ActivitiesOptions, type Act, type StoredAct, type SquareState, type Reach, type HardCap } from './model.js';
|
|
2
2
|
import { peerPublicActs, peerRoomChanges } from './activity-feed.js';
|
|
3
|
+
import { type Perception } from './square-core.js';
|
|
3
4
|
export interface UnreadActivitySummary {
|
|
4
5
|
name: string;
|
|
5
6
|
count: number;
|
|
@@ -11,6 +12,7 @@ export interface UnreadActivityPreview {
|
|
|
11
12
|
act: Extract<StoredAct, {
|
|
12
13
|
kind: 'say';
|
|
13
14
|
}>;
|
|
15
|
+
perception: Perception;
|
|
14
16
|
}
|
|
15
17
|
export declare function resolveKnownName(squareState: SquareState, name: string): string;
|
|
16
18
|
export interface DecideJoinResult {
|
|
@@ -20,7 +22,16 @@ export interface DecideJoinResult {
|
|
|
20
22
|
kind: 'join';
|
|
21
23
|
}>;
|
|
22
24
|
}
|
|
25
|
+
export interface DecideImplicitJoinResult {
|
|
26
|
+
joinedName: string;
|
|
27
|
+
state: 'joined' | 'active' | 'done';
|
|
28
|
+
joinAct?: Extract<Act, {
|
|
29
|
+
kind: 'join';
|
|
30
|
+
}>;
|
|
31
|
+
}
|
|
23
32
|
export declare function decideJoin(squareState: SquareState, name: string, now: number): DecideJoinResult;
|
|
33
|
+
/** Automatic presence may enter only once; an explicit join remains the re-entry path. */
|
|
34
|
+
export declare function decideImplicitJoin(squareState: SquareState, name: string, now: number): DecideImplicitJoinResult;
|
|
24
35
|
export type ActDecision = {
|
|
25
36
|
type: 'sent';
|
|
26
37
|
act: Act;
|
|
@@ -57,6 +68,13 @@ export declare function decideAct(squareState: SquareState, input: {
|
|
|
57
68
|
export declare function coreDone(squareState: SquareState, name: string, body: string, now: number): Extract<Act, {
|
|
58
69
|
kind: 'done';
|
|
59
70
|
}>;
|
|
71
|
+
export declare function coreListen(squareState: SquareState, actor: string, target: string, now: number): Extract<Act, {
|
|
72
|
+
kind: 'listen';
|
|
73
|
+
}> | undefined;
|
|
74
|
+
export declare function coreIgnore(squareState: SquareState, actor: string, target: string, now: number): Extract<Act, {
|
|
75
|
+
kind: 'ignore';
|
|
76
|
+
}> | undefined;
|
|
77
|
+
export declare function coreListening(squareState: SquareState, actor: string): readonly string[];
|
|
60
78
|
export declare function coreHold(squareState: SquareState, actor: string, body: string, now: number): Extract<Act, {
|
|
61
79
|
kind: 'hold';
|
|
62
80
|
}>;
|
|
@@ -66,6 +84,7 @@ export declare function coreResume(squareState: SquareState, actor: string, now:
|
|
|
66
84
|
export interface ParticipantStatus {
|
|
67
85
|
name: string;
|
|
68
86
|
state: 'active' | 'done' | 'not joined';
|
|
87
|
+
listening: readonly string[];
|
|
69
88
|
activityCount: number;
|
|
70
89
|
lastActiveAt: number | undefined;
|
|
71
90
|
presence: CorePresenceState;
|
package/dist/decisions.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { SquareError, sameName, validateName, } from './model.js';
|
|
2
2
|
import { participantIdentity } from './participant-identity.js';
|
|
3
|
-
import { UNREAD_BLOCK_GRACE_MS, actId, actStableIndex, foldedState, freshWatchLease,
|
|
3
|
+
import { UNREAD_BLOCK_GRACE_MS, actId, actStableIndex, foldedState, freshWatchLease, publicActs, readCursor, resolveRosterName, rosterNames, THROTTLE_WINDOW_MS, } from './runtime.js';
|
|
4
4
|
import { actDelta, peerPublicActs, peerRoomChanges } from './activity-feed.js';
|
|
5
|
-
import {
|
|
6
|
-
import { deriveDeliveryModel } from './delivery.js';
|
|
5
|
+
import { formatActivityId, isListening, listeningTo, validate } from './square-core.js';
|
|
6
|
+
import { deriveDeliveryModel, perceiveActivity } from './delivery.js';
|
|
7
7
|
import { compileSearchPattern } from './search.js';
|
|
8
8
|
export function resolveKnownName(squareState, name) {
|
|
9
9
|
validateName(name);
|
|
@@ -32,6 +32,21 @@ export function decideJoin(squareState, name, now) {
|
|
|
32
32
|
joinAct: { kind: 'join', actor: joinedName, at: now },
|
|
33
33
|
};
|
|
34
34
|
}
|
|
35
|
+
/** Automatic presence may enter only once; an explicit join remains the re-entry path. */
|
|
36
|
+
export function decideImplicitJoin(squareState, name, now) {
|
|
37
|
+
validateName(name);
|
|
38
|
+
const knownName = resolveRosterName(squareState, name);
|
|
39
|
+
const joinedName = knownName ?? name;
|
|
40
|
+
if (knownName !== undefined) {
|
|
41
|
+
const current = participantState(foldedState(squareState), knownName);
|
|
42
|
+
return { joinedName, state: current?.joined === true ? 'active' : 'done' };
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
joinedName,
|
|
46
|
+
state: 'joined',
|
|
47
|
+
joinAct: { kind: 'join', actor: joinedName, at: now },
|
|
48
|
+
};
|
|
49
|
+
}
|
|
35
50
|
function resolveStandingName(squareState, requestedName) {
|
|
36
51
|
validateName(requestedName);
|
|
37
52
|
const name = resolveRosterName(squareState, requestedName);
|
|
@@ -65,10 +80,6 @@ export function decideAct(squareState, input) {
|
|
|
65
80
|
}
|
|
66
81
|
}
|
|
67
82
|
const state = foldedState(squareState);
|
|
68
|
-
if (reach !== 'bell'
|
|
69
|
-
&& extractMentions(body).length === 0) {
|
|
70
|
-
throw new SquareError('invalid_args', 'express requires an @mention unless using --bell');
|
|
71
|
-
}
|
|
72
83
|
const current = participantState(state, name);
|
|
73
84
|
const result = validate(state, {
|
|
74
85
|
kind: 'say', actor: name, at: now, body,
|
|
@@ -108,7 +119,7 @@ export function decideAct(squareState, input) {
|
|
|
108
119
|
latestAt: currentSummary === undefined ? item.at : Math.max(currentSummary.latestAt, item.at),
|
|
109
120
|
previews: [
|
|
110
121
|
...(currentSummary?.previews ?? []),
|
|
111
|
-
{ number: sayCountByActor.get(actorKey) ?? 1, act: item },
|
|
122
|
+
{ number: sayCountByActor.get(actorKey) ?? 1, act: item, perception: perceiveActivity(squareState, item, name) },
|
|
112
123
|
].slice(-UNREAD_PREVIEW_LIMIT),
|
|
113
124
|
});
|
|
114
125
|
}
|
|
@@ -147,6 +158,26 @@ export function coreDone(squareState, name, body, now) {
|
|
|
147
158
|
requireStanding(squareState, act);
|
|
148
159
|
return act;
|
|
149
160
|
}
|
|
161
|
+
export function coreListen(squareState, actor, target, now) {
|
|
162
|
+
const resolvedActor = resolveStandingName(squareState, actor);
|
|
163
|
+
validateName(target);
|
|
164
|
+
const state = foldedState(squareState);
|
|
165
|
+
const act = { kind: 'listen', actor: resolvedActor, target, at: now };
|
|
166
|
+
requireStanding(squareState, act);
|
|
167
|
+
return isListening(state, resolvedActor, target) ? undefined : act;
|
|
168
|
+
}
|
|
169
|
+
export function coreIgnore(squareState, actor, target, now) {
|
|
170
|
+
const resolvedActor = resolveStandingName(squareState, actor);
|
|
171
|
+
validateName(target);
|
|
172
|
+
const state = foldedState(squareState);
|
|
173
|
+
const act = { kind: 'ignore', actor: resolvedActor, target, at: now };
|
|
174
|
+
requireStanding(squareState, act);
|
|
175
|
+
return isListening(state, resolvedActor, target) ? act : undefined;
|
|
176
|
+
}
|
|
177
|
+
export function coreListening(squareState, actor) {
|
|
178
|
+
const resolvedActor = resolveStandingName(squareState, actor);
|
|
179
|
+
return listeningTo(foldedState(squareState), resolvedActor);
|
|
180
|
+
}
|
|
150
181
|
export function coreHold(squareState, actor, body, now) {
|
|
151
182
|
const resolvedName = resolveStandingName(squareState, actor);
|
|
152
183
|
const act = { kind: 'hold', actor: resolvedName, at: now, body: body.replace(/\r\n/g, '\n').trim() };
|
|
@@ -162,11 +193,11 @@ export function coreResume(squareState, actor, now) {
|
|
|
162
193
|
function presenceFor(squareState, snapshot, name, now) {
|
|
163
194
|
if (snapshot?.done)
|
|
164
195
|
return { state: 'done', lastAt: snapshot.lastActiveAt };
|
|
165
|
-
const
|
|
196
|
+
const cursorAt = squareState.acts.findLast((act) => act.index <= readCursor(squareState, name))?.at;
|
|
166
197
|
const lease = freshWatchLease(squareState, name, now);
|
|
167
198
|
if (lease !== undefined)
|
|
168
|
-
return { state: 'watching', lastAt:
|
|
169
|
-
const lastAt =
|
|
199
|
+
return { state: 'watching', lastAt: cursorAt ?? lease.heartbeatAt };
|
|
200
|
+
const lastAt = cursorAt ?? (snapshot?.joined ? snapshot.lastActiveAt : undefined);
|
|
170
201
|
return lastAt === undefined
|
|
171
202
|
? { state: 'never-joined', lastAt: undefined }
|
|
172
203
|
: { state: 'active', lastAt };
|
|
@@ -190,6 +221,7 @@ function buildParticipantStatuses(squareState, now, state = foldedState(squareSt
|
|
|
190
221
|
return {
|
|
191
222
|
name: participant,
|
|
192
223
|
state: participantStatus,
|
|
224
|
+
listening: listeningTo(state, participant),
|
|
193
225
|
activityCount: snapshot?.activityCount ?? 0,
|
|
194
226
|
lastActiveAt: snapshot?.lastActiveAt,
|
|
195
227
|
presence: presence.state,
|
|
@@ -249,7 +281,8 @@ export function coreActivities(squareState, opts) {
|
|
|
249
281
|
acts = acts.filter((act) => act.at > opts.after);
|
|
250
282
|
if (opts.mention != null) {
|
|
251
283
|
const mention = resolveKnownName(squareState, opts.mention);
|
|
252
|
-
|
|
284
|
+
const delivery = deriveDeliveryModel(squareState);
|
|
285
|
+
acts = acts.filter((act) => act.kind === 'say' && delivery.plan(act).some((item) => sameName(item.recipient, mention)));
|
|
253
286
|
}
|
|
254
287
|
if (opts.pending) {
|
|
255
288
|
if (viewer === undefined)
|
package/dist/delivery.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type DirectedNotificationRoute, type SquareState, type StoredAct, type WatchLease, type WatchLeaseFilter, type WakeRouteKind, type Reach } from './model.js';
|
|
2
|
+
import { type Perception } from './square-core.js';
|
|
2
3
|
export type { DirectedNotificationRoute } from './model.js';
|
|
3
4
|
export type SayItem = StoredAct & {
|
|
4
5
|
kind: 'say';
|
|
@@ -28,6 +29,12 @@ export type WakeDispatchResult = {
|
|
|
28
29
|
signature: string;
|
|
29
30
|
message: string;
|
|
30
31
|
diagnostic?: unknown;
|
|
32
|
+
} | {
|
|
33
|
+
outcome: 'unavailable';
|
|
34
|
+
signature: string;
|
|
35
|
+
message: string;
|
|
36
|
+
diagnostic?: unknown;
|
|
37
|
+
retainRoute?: boolean;
|
|
31
38
|
} | {
|
|
32
39
|
outcome: 'cancelled';
|
|
33
40
|
};
|
|
@@ -39,43 +46,29 @@ export interface RoutedNotification {
|
|
|
39
46
|
actor: string;
|
|
40
47
|
body: string;
|
|
41
48
|
route: DirectedNotificationRoute;
|
|
49
|
+
recipient?: string;
|
|
42
50
|
}
|
|
43
51
|
export interface CatchFilterShape {
|
|
44
52
|
actor: string;
|
|
45
53
|
body: string;
|
|
46
54
|
reach?: Reach;
|
|
55
|
+
recipients?: readonly string[];
|
|
47
56
|
}
|
|
48
57
|
export interface DeliveryModel {
|
|
49
58
|
plan(item: StoredAct): PlannedNotification[];
|
|
50
59
|
pendingFor(recipient: string): PlannedNotification[];
|
|
51
60
|
}
|
|
52
|
-
|
|
53
|
-
export declare function deliveryReceipt(squareState: SquareState, name: string, actOrIndex: StoredAct | number): DeliveryReceipt | undefined;
|
|
54
|
-
export declare function deliveryReceiptFromRuntime(runtime: SquareRuntimeState, recipient: string, actOrIndex: StoredAct | number): DeliveryReceipt | undefined;
|
|
55
|
-
export declare function isDeliveryDelivered(squareState: SquareState, name: string, actOrIndex: StoredAct | number): boolean;
|
|
56
|
-
export declare function recordDeliveredDelivery(squareState: SquareState, name: string, actOrIndex: StoredAct | number, receipt: Omit<DeliveryReceipt, 'status'>): boolean;
|
|
57
|
-
export declare function recordDeliveredRuntime(runtime: SquareRuntimeState, recipient: string, actOrIndex: StoredAct | number, receipt: Omit<DeliveryReceipt, 'status'>): boolean;
|
|
58
|
-
export declare function markDeliveredDelivery(squareState: SquareState, name: string, actOrIndex: StoredAct | number, at?: number): boolean;
|
|
61
|
+
export declare function isActivitySeen(squareState: SquareState, name: string, actOrIndex: StoredAct | number): boolean;
|
|
59
62
|
/**
|
|
60
63
|
* Derive delivery behavior once from the decoded Square state.
|
|
61
64
|
* All consumers share these targets instead of reinterpreting artifact text or cursor state.
|
|
62
65
|
*/
|
|
63
66
|
export declare function deriveDeliveryModel(squareState: SquareState): DeliveryModel;
|
|
64
67
|
export declare function planActNotifications(squareState: SquareState, item: StoredAct): PlannedNotification[];
|
|
65
|
-
|
|
66
|
-
|
|
68
|
+
export declare function perceiveActivity(squareState: SquareState, item: StoredAct, viewer: string): Perception;
|
|
69
|
+
/** Mark only the notifications selected by the canonical catch projection as fully seen. */
|
|
70
|
+
export declare function markSeenNotifications(squareState: SquareState, recipient: string, delivered: StoredAct[], at?: number): boolean;
|
|
67
71
|
/** Canonical say-activity filter shared by catch selection and hook ownership. */
|
|
68
72
|
export declare function matchesCatchFilter(activity: CatchFilterShape, filter: WatchLeaseFilter): boolean;
|
|
69
73
|
/** True only when the live catch's own filters would deliver this notification. */
|
|
70
74
|
export declare function leaseOwnsNotification(lease: WatchLease, notification: RoutedNotification): boolean;
|
|
71
|
-
export type SquareRoute = Readonly<unknown>;
|
|
72
|
-
interface ParsedSquareRoute {
|
|
73
|
-
readonly v: 1;
|
|
74
|
-
readonly squarePath: string;
|
|
75
|
-
readonly name: string;
|
|
76
|
-
}
|
|
77
|
-
export declare function parseSquareRoute(value: unknown): ParsedSquareRoute;
|
|
78
|
-
export declare function captureRoute(input: {
|
|
79
|
-
cwd: string;
|
|
80
|
-
env?: NodeJS.ProcessEnv;
|
|
81
|
-
}): SquareRoute | null;
|