@astrosheep/square 0.3.61 → 0.3.62
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 +5 -7
- package/codex-plugin/.codex-plugin/plugin.json +1 -1
- package/dist/activity.d.ts +6 -2
- package/dist/activity.js +168 -133
- package/dist/artifact.d.ts +4 -0
- package/dist/artifact.js +43 -14
- package/dist/attention-presentation.d.ts +1 -0
- package/dist/attention-presentation.js +8 -6
- package/dist/automatic-session.js +14 -24
- package/dist/boundary-presentation.d.ts +1 -2
- package/dist/boundary-presentation.js +4 -19
- package/dist/cli/context.d.ts +2 -0
- package/dist/cli/context.js +2 -18
- package/dist/cli/harness-command.js +1 -1
- package/dist/cli/mcp-server-command.d.ts +2 -0
- package/dist/cli/mcp-server-command.js +13 -0
- package/dist/cli/observation-commands.js +47 -19
- package/dist/cli/program.js +5 -2
- package/dist/cli/registry.js +2 -0
- package/dist/cli/square-commands.d.ts +1 -1
- package/dist/cli/square-commands.js +93 -125
- package/dist/decisions.d.ts +2 -0
- package/dist/decisions.js +44 -3
- package/dist/delivery-operations.d.ts +1 -2
- package/dist/delivery-operations.js +1 -4
- package/dist/file-changes.d.ts +12 -0
- package/dist/file-changes.js +235 -0
- package/dist/help.js +4 -8
- package/dist/host-ledger-file-adapter.d.ts +15 -13
- package/dist/host-ledger-file-adapter.js +131 -79
- package/dist/host-ledger-root.d.ts +2 -0
- package/dist/host-ledger-root.js +9 -0
- package/dist/host-ledger.d.ts +4 -24
- package/dist/inbox.d.ts +10 -5
- package/dist/inbox.js +152 -67
- package/dist/index.d.ts +3 -1
- package/dist/index.js +1 -0
- package/dist/mcp.d.ts +7 -0
- package/dist/mcp.js +122 -0
- package/dist/mention-parse.d.ts +8 -0
- package/dist/mention-parse.js +107 -0
- package/dist/model.d.ts +5 -0
- package/dist/model.js +4 -1
- package/dist/notifications.d.ts +1 -0
- package/dist/notifications.js +25 -20
- package/dist/participant-host.js +2 -2
- package/dist/ports.d.ts +2 -9
- package/dist/presentation.d.ts +12 -1
- package/dist/presentation.js +42 -12
- package/dist/presented.js +2 -1
- package/dist/registry.d.ts +8 -4
- package/dist/registry.js +41 -74
- package/dist/routes.d.ts +19 -2
- package/dist/routes.js +48 -5
- package/dist/runtime.d.ts +2 -0
- package/dist/runtime.js +2 -0
- package/dist/square-actions.d.ts +26 -13
- package/dist/square-actions.js +200 -84
- package/dist/square-application.d.ts +58 -0
- package/dist/square-application.js +183 -0
- package/dist/square-facade.d.ts +24 -9
- package/dist/square-file-adapter.d.ts +5 -12
- package/dist/square-file-adapter.js +12 -64
- package/dist/square-projections.d.ts +3 -2
- package/dist/square-projections.js +12 -3
- package/dist/square-storage.js +27 -34
- package/dist/square-wiring.d.ts +12 -7
- package/dist/square-wiring.js +36 -32
- package/dist/state-cell.d.ts +1 -1
- package/dist/stream.js +30 -26
- package/dist/views.d.ts +3 -3
- package/dist/views.js +4 -4
- package/dist/wake-attempts.js +2 -1
- package/dist/wake-evidence.js +2 -8
- package/dist/wakes.d.ts +1 -0
- package/dist/wakes.js +5 -5
- package/dist/watch.d.ts +6 -1
- package/dist/watch.js +20 -18
- package/extensions/square-pi.js +100 -112
- package/guides/application-boundary.md +32 -0
- package/guides/change-waits.md +28 -0
- package/guides/host-ledger.md +9 -0
- package/guides/mcp.md +9 -0
- package/guides/operation-cancellation.md +17 -0
- package/package.json +8 -2
- package/skills/square/SKILL.md +5 -7
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { cmdActivity } from '../activity.js';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
2
|
+
import { scanMentionCandidates } from '../mention-parse.js';
|
|
3
|
+
import { validateDoneBody } from '../decisions.js';
|
|
4
|
+
import { formatHardCap, } from '../model.js';
|
|
5
|
+
import { formatRefusal, participantCommandPrefix, participantIdentity, takeoverRecoveryLines, renderRoomChangeText, renderAmbientEvent, withPathOutput, } from '../presentation.js';
|
|
6
|
+
import { hostLedgerForEnv, hasAutomaticDeliveryIdentity, } from '../registry.js';
|
|
7
7
|
import { actId, nowMs } from '../runtime.js';
|
|
8
8
|
import { createSquare, openSquare } from '../square-file-adapter.js';
|
|
9
9
|
import { closeOpenSquare } from '../open-square.js';
|
|
10
|
-
import {
|
|
10
|
+
import { Square } from '../square-wiring.js';
|
|
11
|
+
import { createSquareApplication } from '../square-application.js';
|
|
11
12
|
import { entryPresentation, eventPresentation } from '../views.js';
|
|
12
13
|
import { createDefaultWakeTransport } from '../notifications.js';
|
|
13
14
|
import { style } from '../tty-style.js';
|
|
@@ -85,78 +86,60 @@ export const joinCommand = {
|
|
|
85
86
|
parse: parseJoin,
|
|
86
87
|
async execute(intent, context) {
|
|
87
88
|
const squarePath = requireSquarePath(context);
|
|
88
|
-
const
|
|
89
|
+
const hostLedger = hostLedgerForEnv(context.env);
|
|
90
|
+
const beforeSquare = await openSquare(squarePath, { clock: nowMs, env: context.env, hostLedger });
|
|
89
91
|
const before = await entryPresentation(beforeSquare, intent.name, intent.lastN);
|
|
90
92
|
await closeOpenSquare(beforeSquare);
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
+
const application = createSquareApplication({ cwd: context.cwd, env: context.env, squarePath, participant: intent.name, clock: nowMs, hostLedger, wakeTransport: await createDefaultWakeTransport(hostLedger, nowMs) });
|
|
94
|
+
let joinedName;
|
|
95
|
+
let joinKind;
|
|
93
96
|
try {
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
97
|
+
const result = await application.join({ takeover: intent.kick });
|
|
98
|
+
joinedName = result.participant;
|
|
99
|
+
joinKind = result.kind;
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
if (error instanceof Error && 'code' in error && error.code === 'already_joined') {
|
|
99
103
|
process.stderr.write(formatRefusal(squarePath, [
|
|
100
104
|
`✕ ${participantIdentity(intent.name)} shoos you out of the square`,
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
`${participantCommandPrefix(squarePath, intent.name)} join --kick`,
|
|
105
|
+
' · a same-named participant stands here — the name is taken',
|
|
106
|
+
...takeoverRecoveryLines(squarePath, intent.name),
|
|
104
107
|
], { participantCount: before.participantCount }));
|
|
105
108
|
process.exit(2);
|
|
106
109
|
}
|
|
107
|
-
|
|
108
|
-
const oldBindings = takeoverNeeded ? await lookupParticipant(squarePath, intent.name) : [];
|
|
109
|
-
let participant;
|
|
110
|
-
if (takeoverNeeded) {
|
|
111
|
-
const currentSessions = new Set(sessionIdsFromEnvironment());
|
|
112
|
-
await square.takeover(intent.name);
|
|
113
|
-
for (const binding of oldBindings) {
|
|
114
|
-
if (!currentSessions.has(binding.sessionId)) {
|
|
115
|
-
await recordSessionDone(binding.sessionId, binding.name, binding.squarePath, binding.channel).catch(() => undefined);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
await claimSessionParticipant(squarePath, intent.name, process.env);
|
|
119
|
-
participant = await square.join(intent.name);
|
|
120
|
-
}
|
|
121
|
-
else {
|
|
122
|
-
await claimSessionParticipant(squarePath, intent.name, process.env);
|
|
123
|
-
participant = await square.join(intent.name);
|
|
124
|
-
}
|
|
125
|
-
const joinedName = participant.name;
|
|
126
|
-
const afterSquare = await openSquare(squarePath, { clock: nowMs });
|
|
127
|
-
const after = await entryPresentation(afterSquare, joinedName, intent.lastN);
|
|
128
|
-
await closeOpenSquare(afterSquare);
|
|
129
|
-
await square.reconcileBinding().catch(() => undefined);
|
|
130
|
-
const activities = after.recentActivities.map((event) => renderAmbientEvent(event, joinedName, {
|
|
131
|
-
now: nowMs(),
|
|
132
|
-
preview: intent.lastN === null ? undefined : 200,
|
|
133
|
-
actNumber: event.kind === 'say' ? after.sayNumbers[event.index] : undefined,
|
|
134
|
-
squareState: after.state,
|
|
135
|
-
})).filter(Boolean).join('\n\n');
|
|
136
|
-
const contextText = after.joinContext;
|
|
137
|
-
const fallback = hasAutomaticDeliveryIdentity()
|
|
138
|
-
? []
|
|
139
|
-
: ['', `${participantCommandPrefix(squarePath, joinedName)} catch --idle 30m`, ' the square has no way to call you — keep this catch open and stay within earshot'];
|
|
140
|
-
const scene = after.scene;
|
|
141
|
-
const entryLine = !isRejoin
|
|
142
|
-
? '● you stepped into the square'
|
|
143
|
-
: reconnect && !intent.kick
|
|
144
|
-
? '● you are already in the square'
|
|
145
|
-
: `✓ you banished the original ${participantIdentity(joinedName)} — the name is yours`;
|
|
146
|
-
const output = [
|
|
147
|
-
entryLine,
|
|
148
|
-
'',
|
|
149
|
-
"· carved into the fountain's edge: every word here lands on a real ear — speak when someone needs it.",
|
|
150
|
-
...(reconnect || scene === '' ? [] : ['', scene]),
|
|
151
|
-
...(isRejoin || contextText === '' ? [] : ['', style('dim', 'context'), contextText]),
|
|
152
|
-
...(isRejoin || activities === '' ? [] : ['', style('dim', 'recent activity'), activities]),
|
|
153
|
-
...fallback,
|
|
154
|
-
].join('\n');
|
|
155
|
-
return withPathOutput(squarePath, output, { participantCount: after.participantCount });
|
|
156
|
-
}
|
|
157
|
-
finally {
|
|
158
|
-
await square.close();
|
|
110
|
+
throw error;
|
|
159
111
|
}
|
|
112
|
+
const afterSquare = await openSquare(squarePath, { clock: nowMs, env: context.env, hostLedger });
|
|
113
|
+
const after = await entryPresentation(afterSquare, joinedName, intent.lastN);
|
|
114
|
+
await closeOpenSquare(afterSquare);
|
|
115
|
+
const activities = after.recentActivities.map((event) => renderAmbientEvent(event, joinedName, {
|
|
116
|
+
now: nowMs(),
|
|
117
|
+
preview: intent.lastN === null ? undefined : 200,
|
|
118
|
+
actNumber: event.kind === 'say' ? after.sayNumbers[event.index] : undefined,
|
|
119
|
+
squareState: after.state,
|
|
120
|
+
})).filter(Boolean).join('\n\n');
|
|
121
|
+
const contextText = after.joinContext;
|
|
122
|
+
const fallback = hasAutomaticDeliveryIdentity()
|
|
123
|
+
? []
|
|
124
|
+
: ['', `${participantCommandPrefix(squarePath, joinedName)} catch --idle 30m`, ' the square has no way to call you — keep this catch open and stay within earshot'];
|
|
125
|
+
const scene = after.scene;
|
|
126
|
+
const entryLine = joinKind === 'joined'
|
|
127
|
+
? '● you stepped into the square'
|
|
128
|
+
: joinKind === 'reconnected'
|
|
129
|
+
? '● you are already in the square'
|
|
130
|
+
: `✓ you banished the original ${participantIdentity(joinedName)} — the name is yours`;
|
|
131
|
+
const isRejoin = joinKind !== 'joined';
|
|
132
|
+
const reconnect = joinKind === 'reconnected';
|
|
133
|
+
const output = [
|
|
134
|
+
entryLine,
|
|
135
|
+
'',
|
|
136
|
+
"· carved into the fountain's edge: every word here lands on a real ear — speak when someone needs it.",
|
|
137
|
+
...(reconnect || scene === '' ? [] : ['', scene]),
|
|
138
|
+
...(isRejoin || contextText === '' ? [] : ['', style('dim', 'context'), contextText]),
|
|
139
|
+
...(isRejoin || activities === '' ? [] : ['', style('dim', 'recent activity'), activities]),
|
|
140
|
+
...fallback,
|
|
141
|
+
].join('\n');
|
|
142
|
+
return withPathOutput(squarePath, output, { participantCount: after.participantCount });
|
|
160
143
|
},
|
|
161
144
|
present: (result) => process.stdout.write(result),
|
|
162
145
|
};
|
|
@@ -181,14 +164,11 @@ function parseActivity(argv, context) {
|
|
|
181
164
|
else if (argument === '--no-mention')
|
|
182
165
|
noMention = true;
|
|
183
166
|
else if (argument === '--mention') {
|
|
184
|
-
mentions.push(
|
|
167
|
+
mentions.push(requireValue(argv, index, argument));
|
|
185
168
|
index += 1;
|
|
186
169
|
}
|
|
187
170
|
else if (argument === '--reply') {
|
|
188
|
-
|
|
189
|
-
if (replyIndex === undefined)
|
|
190
|
-
fail('Invalid --reply: expected an activity id like act/12');
|
|
191
|
-
reply = replyIndex;
|
|
171
|
+
reply = requireValue(argv, index, argument);
|
|
192
172
|
index += 1;
|
|
193
173
|
}
|
|
194
174
|
else
|
|
@@ -199,8 +179,6 @@ function parseActivity(argv, context) {
|
|
|
199
179
|
fail('✕ --bell cannot be combined with --mention or --no-mention\nsquare express --help');
|
|
200
180
|
if (noMention && mentions.length > 0)
|
|
201
181
|
fail('✕ --no-mention cannot be combined with --mention\nsquare express --help');
|
|
202
|
-
if (!bell && !noMention && mentions.length === 0)
|
|
203
|
-
fail('✕ express needs --mention <name>, --no-mention, or --bell\nsquare express --help');
|
|
204
182
|
if (bodyArgs.length !== 1) {
|
|
205
183
|
if (bodyArgs.length === 0) {
|
|
206
184
|
if (!process.stdin.isTTY)
|
|
@@ -215,14 +193,18 @@ export const expressCommand = {
|
|
|
215
193
|
async execute(intent, context) {
|
|
216
194
|
const squarePath = requireSquarePath(context);
|
|
217
195
|
const body = await resolveBody(intent.activity);
|
|
218
|
-
|
|
196
|
+
if (intent.reach === undefined && !intent.noMention && intent.mentions.length === 0 && scanMentionCandidates(body).length === 0) {
|
|
197
|
+
fail('✕ express needs --mention <name>, --no-mention, --bell, or an @name in the body\nsquare express --help');
|
|
198
|
+
}
|
|
219
199
|
await cmdActivity(squarePath, intent.name, body, (value) => value, {
|
|
220
200
|
force: intent.force,
|
|
221
201
|
noWait: intent.noWait,
|
|
202
|
+
noMention: intent.noMention,
|
|
222
203
|
mentions: intent.mentions,
|
|
223
204
|
reach: intent.reach,
|
|
224
205
|
reply: intent.reply,
|
|
225
|
-
|
|
206
|
+
cwd: context.cwd,
|
|
207
|
+
env: context.env,
|
|
226
208
|
});
|
|
227
209
|
},
|
|
228
210
|
present: () => { },
|
|
@@ -249,15 +231,12 @@ export const listenCommand = {
|
|
|
249
231
|
async execute(intent, context) {
|
|
250
232
|
const squarePath = requireSquarePath(context);
|
|
251
233
|
const square = await Square.at({ path: squarePath, clock: nowMs });
|
|
252
|
-
const facade = await openParticipant({ path: squarePath, clock: nowMs }, intent.name);
|
|
253
234
|
try {
|
|
254
|
-
const
|
|
255
|
-
const result = await participant.listen(intent.target);
|
|
235
|
+
const result = await createSquareApplication({ cwd: context.cwd, env: context.env, squarePath, participant: intent.name, clock: nowMs }).listen(intent.target);
|
|
256
236
|
const participantCount = (await square.snapshot()).participants.filter((item) => item.state === 'joined').length;
|
|
257
|
-
return listenerPresentation(squarePath,
|
|
237
|
+
return listenerPresentation(squarePath, intent.name, intent.target, 'listen', result.activity, participantCount);
|
|
258
238
|
}
|
|
259
239
|
finally {
|
|
260
|
-
await facade.close();
|
|
261
240
|
await square.close();
|
|
262
241
|
}
|
|
263
242
|
},
|
|
@@ -268,15 +247,12 @@ export const ignoreCommand = {
|
|
|
268
247
|
async execute(intent, context) {
|
|
269
248
|
const squarePath = requireSquarePath(context);
|
|
270
249
|
const square = await Square.at({ path: squarePath, clock: nowMs });
|
|
271
|
-
const facade = await openParticipant({ path: squarePath, clock: nowMs }, intent.name);
|
|
272
250
|
try {
|
|
273
|
-
const
|
|
274
|
-
const result = await participant.ignore(intent.target);
|
|
251
|
+
const result = await createSquareApplication({ cwd: context.cwd, env: context.env, squarePath, participant: intent.name, clock: nowMs }).ignore(intent.target);
|
|
275
252
|
const participantCount = (await square.snapshot()).participants.filter((item) => item.state === 'joined').length;
|
|
276
|
-
return listenerPresentation(squarePath,
|
|
253
|
+
return listenerPresentation(squarePath, intent.name, intent.target, 'ignore', result.activity, participantCount);
|
|
277
254
|
}
|
|
278
255
|
finally {
|
|
279
|
-
await facade.close();
|
|
280
256
|
await square.close();
|
|
281
257
|
}
|
|
282
258
|
},
|
|
@@ -287,10 +263,9 @@ export const listeningCommand = {
|
|
|
287
263
|
async execute(intent, context) {
|
|
288
264
|
const squarePath = requireSquarePath(context);
|
|
289
265
|
const square = await Square.at({ path: squarePath, clock: nowMs });
|
|
290
|
-
const facade = await openParticipant({ path: squarePath, clock: nowMs }, intent.name);
|
|
291
266
|
try {
|
|
292
|
-
const participant =
|
|
293
|
-
const targets = await participant.listening();
|
|
267
|
+
const participant = { name: intent.name };
|
|
268
|
+
const targets = await createSquareApplication({ cwd: context.cwd, env: context.env, squarePath, participant: intent.name, clock: nowMs }).listening();
|
|
294
269
|
const participantCount = (await square.snapshot()).participants.filter((item) => item.state === 'joined').length;
|
|
295
270
|
const body = targets.length === 0
|
|
296
271
|
? `○ ${participantIdentity(participant.name)} is not turned toward anyone`
|
|
@@ -298,29 +273,36 @@ export const listeningCommand = {
|
|
|
298
273
|
return withPathOutput(squarePath, body, { participantCount });
|
|
299
274
|
}
|
|
300
275
|
finally {
|
|
301
|
-
await facade.close();
|
|
302
276
|
await square.close();
|
|
303
277
|
}
|
|
304
278
|
},
|
|
305
279
|
present: (result) => process.stdout.write(result),
|
|
306
280
|
};
|
|
281
|
+
function validateDoneInput(body) {
|
|
282
|
+
try {
|
|
283
|
+
validateDoneBody(body);
|
|
284
|
+
}
|
|
285
|
+
catch (error) {
|
|
286
|
+
fail(`${error instanceof Error ? error.message : String(error)}\nsquare done --help`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
307
289
|
function parseDone(argv, context) {
|
|
308
290
|
if (argv.length > 1)
|
|
309
291
|
usage(context.command);
|
|
292
|
+
if (argv[0] !== '-')
|
|
293
|
+
validateDoneInput(argv[0]);
|
|
310
294
|
return { name: requireParticipant(context.name), body: argv.length === 1 ? argv[0] : undefined };
|
|
311
295
|
}
|
|
312
296
|
export const doneCommand = {
|
|
313
297
|
parse: parseDone,
|
|
314
298
|
async execute(intent, context) {
|
|
315
299
|
const squarePath = requireSquarePath(context);
|
|
316
|
-
const body =
|
|
317
|
-
|
|
318
|
-
const
|
|
319
|
-
const result = await
|
|
320
|
-
await square.reconcileBinding();
|
|
321
|
-
await square.close();
|
|
300
|
+
const body = intent.body === '-' || !process.stdin.isTTY ? await readStdin() : '';
|
|
301
|
+
validateDoneInput(body);
|
|
302
|
+
const application = createSquareApplication({ cwd: context.cwd, env: context.env, squarePath, clock: nowMs, participant: intent.name });
|
|
303
|
+
const result = await application.done(body);
|
|
322
304
|
const name = result.activity.actor;
|
|
323
|
-
const presentation = await openSquare(squarePath, { clock: nowMs });
|
|
305
|
+
const presentation = await openSquare(squarePath, { clock: nowMs, env: context.env, hostLedger: hostLedgerForEnv(context.env) });
|
|
324
306
|
const participantCount = (await entryPresentation(presentation, name).finally(() => closeOpenSquare(presentation))).participantCount;
|
|
325
307
|
return withPathOutput(squarePath, `○ ${participantIdentity(name)} steps out of the square — done · ${result.activity.id} · just now`, { participantCount });
|
|
326
308
|
},
|
|
@@ -335,19 +317,12 @@ export const holdCommand = {
|
|
|
335
317
|
parse: parseHold,
|
|
336
318
|
async execute(intent, context) {
|
|
337
319
|
const squarePath = requireSquarePath(context);
|
|
338
|
-
const
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
const presentation = await eventPresentation(presentationSquare, result.activity.id);
|
|
345
|
-
await closeOpenSquare(presentationSquare);
|
|
346
|
-
return withPathOutput(squarePath, `· ${renderRoomChangeText(presentation.activity)} · ${actId(presentation.activity.index)} · just now`, { participantCount: presentation.participantCount, held: true });
|
|
347
|
-
}
|
|
348
|
-
finally {
|
|
349
|
-
await square.close();
|
|
350
|
-
}
|
|
320
|
+
const application = createSquareApplication({ cwd: context.cwd, env: context.env, squarePath, clock: nowMs, participant: intent.name });
|
|
321
|
+
const result = await application.hold((await resolveBody(intent.body ?? '')).replace(/\r\n/g, '\n').trim());
|
|
322
|
+
const presentationSquare = await openSquare(squarePath, { clock: nowMs, env: context.env, hostLedger: hostLedgerForEnv(context.env) });
|
|
323
|
+
const presentation = await eventPresentation(presentationSquare, result.activity.id);
|
|
324
|
+
await closeOpenSquare(presentationSquare);
|
|
325
|
+
return withPathOutput(squarePath, `· ${renderRoomChangeText(presentation.activity)} · ${actId(presentation.activity.index)} · just now`, { participantCount: presentation.participantCount, held: true });
|
|
351
326
|
},
|
|
352
327
|
present: (result) => process.stdout.write(result),
|
|
353
328
|
};
|
|
@@ -359,19 +334,12 @@ export const resumeCommand = {
|
|
|
359
334
|
},
|
|
360
335
|
async execute(intent, context) {
|
|
361
336
|
const squarePath = requireSquarePath(context);
|
|
362
|
-
const
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
const presentation = await eventPresentation(presentationSquare, result.activity.id);
|
|
369
|
-
await closeOpenSquare(presentationSquare);
|
|
370
|
-
return withPathOutput(squarePath, `✓ ${renderRoomChangeText(presentation.activity)} · ${actId(presentation.activity.index)} · just now`, { participantCount: presentation.participantCount });
|
|
371
|
-
}
|
|
372
|
-
finally {
|
|
373
|
-
await square.close();
|
|
374
|
-
}
|
|
337
|
+
const application = createSquareApplication({ cwd: context.cwd, env: context.env, squarePath, clock: nowMs, participant: intent.name });
|
|
338
|
+
const result = await application.resume();
|
|
339
|
+
const presentationSquare = await openSquare(squarePath, { clock: nowMs, env: context.env, hostLedger: hostLedgerForEnv(context.env) });
|
|
340
|
+
const presentation = await eventPresentation(presentationSquare, result.activity.id);
|
|
341
|
+
await closeOpenSquare(presentationSquare);
|
|
342
|
+
return withPathOutput(squarePath, `✓ ${renderRoomChangeText(presentation.activity)} · ${actId(presentation.activity.index)} · just now`, { participantCount: presentation.participantCount });
|
|
375
343
|
},
|
|
376
344
|
present: (result) => process.stdout.write(result),
|
|
377
345
|
};
|
package/dist/decisions.d.ts
CHANGED
|
@@ -68,7 +68,9 @@ export declare function decideAct(squareState: SquareState, input: {
|
|
|
68
68
|
mentions?: readonly string[];
|
|
69
69
|
reach?: Reach;
|
|
70
70
|
reply?: number;
|
|
71
|
+
noMention?: boolean;
|
|
71
72
|
}): ActDecision;
|
|
73
|
+
export declare function validateDoneBody(body: unknown): void;
|
|
72
74
|
export declare function coreDone(squareState: SquareState, name: string, body: string, now: number): Extract<Act, {
|
|
73
75
|
kind: 'done';
|
|
74
76
|
}>;
|
package/dist/decisions.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
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, publicActs, resolveRosterName, THROTTLE_WINDOW_MS, } from './runtime.js';
|
|
3
|
+
import { UNREAD_BLOCK_GRACE_MS, actId, actStableIndex, foldedState, freshWatchLease, publicActs, resolveRosterName, rosterNames, THROTTLE_WINDOW_MS, } from './runtime.js';
|
|
4
4
|
import { actDelta, directedPeerSays } from './activity-feed.js';
|
|
5
5
|
import { formatActivityId, isIgnored, isListening, listeningTo, MAX_IDENTITY_SET_SIZE, validate } from './square-core.js';
|
|
6
6
|
import { deriveDeliveryModel } from './delivery.js';
|
|
7
7
|
import { compileSearchPattern } from './search.js';
|
|
8
|
+
import { scanMentionCandidates, resolveNameAt } from './mention-parse.js';
|
|
8
9
|
export function resolveKnownName(squareState, name) {
|
|
9
10
|
validateName(name);
|
|
10
11
|
const known = resolveRosterName(squareState, name);
|
|
@@ -124,8 +125,42 @@ export function decideAct(squareState, input) {
|
|
|
124
125
|
mentionNames.push(resolved);
|
|
125
126
|
}
|
|
126
127
|
}
|
|
128
|
+
if (input.noMention !== true) {
|
|
129
|
+
// Body @name tokens resolve against the standing roster at landing time; the stored
|
|
130
|
+
// body never changes, only mention metadata is derived. Explicit --no-mention turns
|
|
131
|
+
// this scan off entirely.
|
|
132
|
+
const unmatched = [];
|
|
133
|
+
for (const candidate of scanMentionCandidates(body)) {
|
|
134
|
+
const resolved = resolveNameAt(body, candidate, joinedNames);
|
|
135
|
+
if (resolved === undefined) {
|
|
136
|
+
unmatched.push(candidate);
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
if (!mentionNames.some((existing) => sameName(existing, resolved))) {
|
|
140
|
+
if (mentionNames.length >= MAX_IDENTITY_SET_SIZE) {
|
|
141
|
+
throw new SquareError('invalid_args', `An activity can mention at most ${MAX_IDENTITY_SET_SIZE} participants`);
|
|
142
|
+
}
|
|
143
|
+
mentionNames.push(resolved);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// A bell already reaches everyone: only there does an unmatched @token stay literal text.
|
|
147
|
+
if (reach !== 'bell' && unmatched.length > 0) {
|
|
148
|
+
const roster = rosterNames(squareState);
|
|
149
|
+
const notStanding = unmatched.filter((candidate) => resolveNameAt(body, candidate, roster) !== undefined);
|
|
150
|
+
const unknown = unmatched.filter((candidate) => !notStanding.includes(candidate));
|
|
151
|
+
const lines = [];
|
|
152
|
+
if (unknown.length > 0) {
|
|
153
|
+
lines.push(`✕ ${unknown.map((candidate) => participantIdentity(candidate.token)).join(', ')} ${unknown.length === 1 ? 'does' : 'do'} not match anyone in this square`);
|
|
154
|
+
}
|
|
155
|
+
if (notStanding.length > 0) {
|
|
156
|
+
lines.push(`✕ ${notStanding.map((candidate) => participantIdentity(candidate.token)).join(', ')} ${notStanding.length === 1 ? 'is' : 'are'} not standing in this square`);
|
|
157
|
+
}
|
|
158
|
+
lines.push(' · wrap literal @text in backticks, or pass --no-mention to land bare');
|
|
159
|
+
throw new SquareError('invalid_args', lines.join('\n'));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
127
162
|
if (reach === 'bell' && mentionNames.length > 0) {
|
|
128
|
-
throw new SquareError('invalid_args', 'A bell cannot be combined with --mention.');
|
|
163
|
+
throw new SquareError('invalid_args', 'A bell cannot be combined with a mention — remove --mention or the @name in the body.');
|
|
129
164
|
}
|
|
130
165
|
const reply = input.reply;
|
|
131
166
|
if (reply !== undefined) {
|
|
@@ -181,9 +216,15 @@ export function decideAct(squareState, input) {
|
|
|
181
216
|
pendingRoomChanges: unreadRoomChanges,
|
|
182
217
|
};
|
|
183
218
|
}
|
|
219
|
+
export function validateDoneBody(body) {
|
|
220
|
+
if (body !== undefined && body !== '') {
|
|
221
|
+
throw new SquareError('invalid_args', 'done only leaves the square and does not accept a message; use express before leaving');
|
|
222
|
+
}
|
|
223
|
+
}
|
|
184
224
|
export function coreDone(squareState, name, body, now) {
|
|
225
|
+
validateDoneBody(body);
|
|
185
226
|
const resolvedName = resolveStandingName(squareState, name);
|
|
186
|
-
const act = { kind: 'done', actor: resolvedName, at: now
|
|
227
|
+
const act = { kind: 'done', actor: resolvedName, at: now };
|
|
187
228
|
requireStanding(squareState, act);
|
|
188
229
|
return act;
|
|
189
230
|
}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { type SquareState } from './model.js';
|
|
2
|
-
import type { HostLedgerPort, PresenceRecord, PresentationEvidenceProjection, SquareArtifactPort, DeliverPendingInput, DeliveryResult, ObserveSquareInput,
|
|
2
|
+
import type { HostLedgerPort, PresenceRecord, PresentationEvidenceProjection, SquareArtifactPort, DeliverPendingInput, DeliveryResult, ObserveSquareInput, SquareObservation } from './ports.js';
|
|
3
3
|
import { deriveDeliveryModel } from './delivery.js';
|
|
4
4
|
import { type WakeAttempt } from './square-projections.js';
|
|
5
5
|
export declare function observeSquare(input: ObserveSquareInput): Promise<SquareObservation>;
|
|
6
|
-
export declare function reconcileBinding(input: ReconcileBindingInput): Promise<import("./host-ledger.js").ReconcileBindingResult>;
|
|
7
6
|
export declare function deliverPending(input: DeliverPendingInput): Promise<DeliveryResult>;
|
|
8
7
|
export declare function selectPendingWakeActivities(state: SquareState, routes: readonly PresenceRecord[], attempts: readonly WakeAttempt[], now: number, graceMs: number, limit: number, delivery?: import("./delivery.js").DeliveryModel, presentations?: readonly PresentationEvidenceProjection[]): number[];
|
|
9
8
|
export declare function sweepPending(input: {
|
|
@@ -39,7 +39,7 @@ export async function observeSquare(input) {
|
|
|
39
39
|
let rows = [];
|
|
40
40
|
if (input.hostLedger !== undefined) {
|
|
41
41
|
try {
|
|
42
|
-
rows = await input.hostLedger.listPresence({ location: input.location,
|
|
42
|
+
rows = await input.hostLedger.listPresence({ location: input.location, now: input.now });
|
|
43
43
|
}
|
|
44
44
|
catch {
|
|
45
45
|
rows = [];
|
|
@@ -55,9 +55,6 @@ export async function observeSquare(input) {
|
|
|
55
55
|
}));
|
|
56
56
|
return { ...(input.location === undefined ? {} : { location: input.location }), version: snapshot.version, state: snapshot.state, pending: delivery.joinedRecipients().map((recipient) => ({ recipient, notifications: delivery.pendingFor(recipient) })), bindings };
|
|
57
57
|
}
|
|
58
|
-
export async function reconcileBinding(input) {
|
|
59
|
-
return input.hostLedger.reconcileBinding({ artifact: input.artifact, scopes: input.scopes, now: input.now });
|
|
60
|
-
}
|
|
61
58
|
export async function deliverPending(input) {
|
|
62
59
|
const observation = await observeSquare({ artifact: input.artifact, hostLedger: input.hostLedger, location: input.location, now: input.now });
|
|
63
60
|
const routes = (observation.state.routes ?? []).map((route) => ({
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare function canonicalFilePath(value: string): Promise<string>;
|
|
2
|
+
export interface VersionObserver<T> {
|
|
3
|
+
readonly generation: number;
|
|
4
|
+
hinted(since: number, timeoutMs: number, signal?: AbortSignal): Promise<boolean>;
|
|
5
|
+
read(signal?: AbortSignal): Promise<T>;
|
|
6
|
+
changed(since: T, timeoutMs: number, signal?: AbortSignal): Promise<boolean>;
|
|
7
|
+
close(): void;
|
|
8
|
+
}
|
|
9
|
+
/** Shared only within this process. Durable state is always read at the storage boundary. */
|
|
10
|
+
export declare function observeFileVersion<T>(filePath: string, kind: string, read: (file: string, signal: AbortSignal) => Promise<T>): Promise<VersionObserver<T>>;
|
|
11
|
+
/** File metadata invalidates a ledger projection; it is not presentation evidence. */
|
|
12
|
+
export declare function observeFileMetadata(file: string): Promise<VersionObserver<string>>;
|