@astrosheep/square 0.3.29 → 0.3.31
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/hooks/hooks.json +0 -3
- package/claude-plugin/skills/square/SKILL.md +8 -7
- package/codex-plugin/.codex-plugin/plugin.json +1 -1
- package/dist/activity.js +7 -3
- package/dist/artifact.js +7 -50
- package/dist/automatic-session.js +31 -14
- package/dist/boundary-presentation.d.ts +1 -1
- package/dist/boundary-presentation.js +58 -14
- package/dist/catch-decisions.d.ts +17 -0
- package/dist/catch-decisions.js +53 -0
- package/dist/claude-hook.d.ts +1 -1
- package/dist/cli/context.js +10 -2
- package/dist/cli/observation-commands.d.ts +5 -1
- package/dist/cli/observation-commands.js +68 -33
- package/dist/cli/square-commands.js +17 -10
- package/dist/codex-hook.d.ts +1 -1
- package/dist/decisions.js +2 -2
- package/dist/delivery-health.d.ts +1 -1
- package/dist/delivery-operations.d.ts +25 -0
- package/dist/delivery-operations.js +124 -0
- package/dist/help.js +1 -1
- package/dist/host-ledger-file-adapter.d.ts +34 -0
- package/dist/host-ledger-file-adapter.js +165 -0
- package/dist/host-ledger.d.ts +160 -0
- package/dist/host-ledger.js +1 -0
- package/dist/inbox.d.ts +2 -2
- package/dist/inbox.js +22 -14
- package/dist/index.d.ts +4 -1
- package/dist/index.js +1 -0
- package/dist/landing.d.ts +18 -14
- package/dist/landing.js +29 -113
- package/dist/model.d.ts +6 -17
- package/dist/notifications.d.ts +6 -10
- package/dist/notifications.js +71 -192
- package/dist/open-square.d.ts +4 -4
- package/dist/open-square.js +1 -1
- package/dist/ports.d.ts +127 -0
- package/dist/ports.js +1 -0
- package/dist/presence.d.ts +1 -2
- package/dist/presence.js +13 -46
- package/dist/presentation-operations.d.ts +3 -0
- package/dist/presentation-operations.js +51 -0
- package/dist/presentation.d.ts +2 -2
- package/dist/presentation.js +11 -7
- package/dist/presented.d.ts +1 -12
- package/dist/presented.js +6 -75
- package/dist/registry.d.ts +10 -10
- package/dist/registry.js +48 -113
- package/dist/routes.d.ts +6 -33
- package/dist/routes.js +6 -170
- package/dist/runtime.d.ts +1 -1
- package/dist/runtime.js +3 -4
- package/dist/square-actions.d.ts +32 -0
- package/dist/square-actions.js +167 -0
- package/dist/square-facade.d.ts +7 -7
- package/dist/square-file-adapter.d.ts +4 -3
- package/dist/square-file-adapter.js +22 -5
- package/dist/square-projections.d.ts +68 -0
- package/dist/square-projections.js +87 -0
- package/dist/square-storage.d.ts +2 -2
- package/dist/square-storage.js +14 -9
- package/dist/square-wiring.d.ts +3 -3
- package/dist/square-wiring.js +44 -29
- package/dist/views.d.ts +8 -2
- package/dist/views.js +28 -24
- package/dist/wake-attempts.d.ts +29 -22
- package/dist/wake-attempts.js +36 -119
- package/dist/wake-evidence.d.ts +6 -18
- package/dist/wake-evidence.js +17 -80
- package/dist/wakes.d.ts +2 -16
- package/dist/wakes.js +7 -27
- package/dist/watch.js +2 -3
- package/extensions/square-pi.js +7 -2
- package/package.json +1 -1
- package/skills/brainstorm/SKILL.md +2 -2
- package/skills/square/SKILL.md +8 -7
|
@@ -4,7 +4,7 @@ import { sessionInbox } from '../inbox.js';
|
|
|
4
4
|
import { sweepPendingNotifications } from '../notifications.js';
|
|
5
5
|
import { cmdListSquares } from '../list.js';
|
|
6
6
|
import { parseActivityId, sameName } from '../model.js';
|
|
7
|
-
import { commandPrefix, participantCommandPrefix, participantIdentity, renderGrepActivitiesView, renderEventCli, renderAmbientEvent, renderPresenceAnchor, withPathOutput, } from '../presentation.js';
|
|
7
|
+
import { commandPrefix, participantCommandPrefix, participantIdentity, renderGrepActivitiesView, renderEventCli, renderAmbientEvent, renderPresenceAnchor, withPathOutput, quoteShell, } from '../presentation.js';
|
|
8
8
|
import { actId, nowMs } from '../runtime.js';
|
|
9
9
|
import { cmdStream, cmdStreamNdjson } from '../stream.js';
|
|
10
10
|
import { formatRelativeTime, formatTimestamp, parseTimeOrRelative } from '../time.js';
|
|
@@ -114,20 +114,39 @@ function parseTimestamp(value, flag) {
|
|
|
114
114
|
fail(`Invalid ${flag} timestamp: ${value}`);
|
|
115
115
|
return timestamp;
|
|
116
116
|
}
|
|
117
|
+
function historyContinuationArgs(argv) {
|
|
118
|
+
const result = [];
|
|
119
|
+
for (let index = 0; index < argv.length; index++) {
|
|
120
|
+
const flag = argv[index];
|
|
121
|
+
if (flag === '--limit' || flag === '--before' || flag === '--after') {
|
|
122
|
+
index += 1;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
result.push(flag);
|
|
126
|
+
if (flag === '--from' || flag === '--since' || flag === '--at' || flag === '-B' || flag === '-A' || flag === '-C' || flag === '--mention' || flag === '--grep' || flag === '--fixed' || flag === '--order' || flag === '--format') {
|
|
127
|
+
const value = argv[index + 1];
|
|
128
|
+
if (value !== undefined) {
|
|
129
|
+
result.push(value);
|
|
130
|
+
index += 1;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return result;
|
|
135
|
+
}
|
|
117
136
|
function parseHistory(argv, context) {
|
|
118
137
|
const squarePath = requireSquarePath(context);
|
|
119
138
|
const viewer = context.name;
|
|
120
139
|
let lastN = 10;
|
|
121
140
|
let lastNExplicit = false;
|
|
122
|
-
let before;
|
|
123
141
|
let after;
|
|
124
142
|
let afterIndex;
|
|
143
|
+
let beforeIndex;
|
|
125
144
|
const atIndexes = [];
|
|
126
145
|
let beforeContext;
|
|
127
146
|
let afterContext;
|
|
128
147
|
let mention;
|
|
129
148
|
let pending = false;
|
|
130
|
-
let
|
|
149
|
+
let noTruncate = false;
|
|
131
150
|
let grep;
|
|
132
151
|
let fixed;
|
|
133
152
|
let order;
|
|
@@ -148,18 +167,10 @@ function parseHistory(argv, context) {
|
|
|
148
167
|
lastNExplicit = true;
|
|
149
168
|
index += 1;
|
|
150
169
|
}
|
|
151
|
-
else if (flag === '--all') {
|
|
152
|
-
lastN = null;
|
|
153
|
-
lastNExplicit = true;
|
|
154
|
-
}
|
|
155
170
|
else if (flag === '--from') {
|
|
156
171
|
participants.push(...parseNameList(requireValue(argv, index, flag), flag));
|
|
157
172
|
index += 1;
|
|
158
173
|
}
|
|
159
|
-
else if (flag === '--until') {
|
|
160
|
-
before = parseTimestamp(requireValue(argv, index, flag), flag);
|
|
161
|
-
index += 1;
|
|
162
|
-
}
|
|
163
174
|
else if (flag === '--since') {
|
|
164
175
|
after = parseTimestamp(requireValue(argv, index, flag), flag);
|
|
165
176
|
index += 1;
|
|
@@ -168,6 +179,10 @@ function parseHistory(argv, context) {
|
|
|
168
179
|
afterIndex = parseActRef(requireValue(argv, index, flag), flag);
|
|
169
180
|
index += 1;
|
|
170
181
|
}
|
|
182
|
+
else if (flag === '--before') {
|
|
183
|
+
beforeIndex = parseActRef(requireValue(argv, index, flag), flag);
|
|
184
|
+
index += 1;
|
|
185
|
+
}
|
|
171
186
|
else if (flag === '--at') {
|
|
172
187
|
const values = requireValue(argv, index, flag).split(',');
|
|
173
188
|
if (values.some((value) => value === ''))
|
|
@@ -189,8 +204,8 @@ function parseHistory(argv, context) {
|
|
|
189
204
|
afterContext = context;
|
|
190
205
|
index += 1;
|
|
191
206
|
}
|
|
192
|
-
else if (flag === '--
|
|
193
|
-
|
|
207
|
+
else if (flag === '--no-truncate')
|
|
208
|
+
noTruncate = true;
|
|
194
209
|
else if (flag === '--mention') {
|
|
195
210
|
mention = requireValue(argv, index, flag);
|
|
196
211
|
index += 1;
|
|
@@ -227,29 +242,37 @@ function parseHistory(argv, context) {
|
|
|
227
242
|
fail('--grep and --fixed cannot be combined.');
|
|
228
243
|
if (grep === '' || fixed === '')
|
|
229
244
|
fail('--grep and --fixed require non-empty text.');
|
|
245
|
+
if (beforeIndex !== undefined && afterIndex !== undefined)
|
|
246
|
+
fail('--before and --after cannot be combined.');
|
|
230
247
|
if (!lastNExplicit && (atIndexes.length > 0 || pending))
|
|
231
248
|
lastN = null;
|
|
232
249
|
return {
|
|
233
250
|
lastN,
|
|
234
251
|
participants,
|
|
235
|
-
before,
|
|
236
252
|
after,
|
|
237
253
|
afterIndex,
|
|
254
|
+
beforeIndex,
|
|
238
255
|
atIndexes: atIndexes.length === 0 ? undefined : atIndexes,
|
|
239
256
|
beforeContext,
|
|
240
257
|
afterContext,
|
|
241
258
|
mention,
|
|
242
259
|
pending,
|
|
243
260
|
viewer,
|
|
244
|
-
|
|
261
|
+
noTruncate,
|
|
245
262
|
grep,
|
|
246
263
|
fixed,
|
|
247
264
|
order,
|
|
248
265
|
format,
|
|
249
266
|
json,
|
|
267
|
+
continuationArgs: historyContinuationArgs(argv),
|
|
250
268
|
};
|
|
251
269
|
}
|
|
252
|
-
function
|
|
270
|
+
function historyContinuationCommand(options, squarePath, direction, index) {
|
|
271
|
+
const prefix = options.viewer === undefined ? commandPrefix(squarePath) : participantCommandPrefix(squarePath, options.viewer);
|
|
272
|
+
const args = [...(options.continuationArgs ?? []), direction, actId(index), '--limit', String(options.lastN ?? 10)];
|
|
273
|
+
return `${prefix} history ${args.map((arg) => arg.startsWith('-') || /^act\/\d+$/.test(arg) || /^\d+$/.test(arg) ? arg : quoteShell(arg)).join(' ')}`;
|
|
274
|
+
}
|
|
275
|
+
function renderFields(sayNumbers, item, fields, perception) {
|
|
253
276
|
return fields.map((field) => {
|
|
254
277
|
switch (field) {
|
|
255
278
|
case 'id': return actId(item.index);
|
|
@@ -258,14 +281,14 @@ function renderFields(sayNumbers, item, fields) {
|
|
|
258
281
|
case 'ts':
|
|
259
282
|
case 'at': return formatTimestamp(item.at);
|
|
260
283
|
case 'kind': return item.kind;
|
|
261
|
-
case 'body': return 'body' in item && typeof item.body === 'string' ? item.body.replace(/\s+/g, ' ').trim() : '';
|
|
284
|
+
case 'body': return perception === 'presence' ? '' : 'body' in item && typeof item.body === 'string' ? item.body.replace(/\s+/g, ' ').trim() : '';
|
|
262
285
|
case 'number': return item.kind === 'say' ? String(sayNumbers[item.index]) : '';
|
|
263
286
|
case 'reply': return item.kind === 'say' && item.reply !== undefined ? actId(item.reply) : '';
|
|
264
287
|
default: return '';
|
|
265
288
|
}
|
|
266
289
|
}).join('\t');
|
|
267
290
|
}
|
|
268
|
-
function jsonLine(sayNumbers, item) {
|
|
291
|
+
function jsonLine(sayNumbers, item, perception) {
|
|
269
292
|
const act = item;
|
|
270
293
|
return JSON.stringify({
|
|
271
294
|
id: actId(item.index),
|
|
@@ -274,15 +297,15 @@ function jsonLine(sayNumbers, item) {
|
|
|
274
297
|
author: act.actor ?? null,
|
|
275
298
|
at: act.at,
|
|
276
299
|
ts: formatTimestamp(act.at),
|
|
277
|
-
body: 'body' in act && typeof act.body === 'string' ? act.body : '',
|
|
300
|
+
body: perception === 'presence' ? '' : 'body' in act && typeof act.body === 'string' ? act.body : '',
|
|
278
301
|
number: act.kind === 'say' ? sayNumbers[act.index] : null,
|
|
279
302
|
reach: act.kind === 'say' ? act.reach ?? null : null,
|
|
280
303
|
reply: act.kind === 'say' && act.reply !== undefined ? actId(act.reply) : null,
|
|
281
304
|
});
|
|
282
305
|
}
|
|
283
|
-
function renderHistoryProjection(projection, visible,
|
|
306
|
+
function renderHistoryProjection(projection, visible, noTruncate, squarePath, viewer, mode) {
|
|
284
307
|
const shown = visible.filter((activity) => activity.kind === 'say' || activity.kind === 'done');
|
|
285
|
-
const preview =
|
|
308
|
+
const preview = noTruncate ? undefined : 200;
|
|
286
309
|
const chunks = [];
|
|
287
310
|
for (const activity of shown) {
|
|
288
311
|
const options = {
|
|
@@ -302,7 +325,7 @@ function renderHistoryProjection(projection, visible, full, squarePath, viewer,
|
|
|
302
325
|
if (chunks.length === 0)
|
|
303
326
|
return 'latest\n ○ no public activity in this view';
|
|
304
327
|
if (preview !== undefined && shown.some((activity) => activity.kind === 'say' && activity.body.length > preview && (mode === 'archive' || activity.perception === 'full'))) {
|
|
305
|
-
chunks.push(`» ${commandPrefix(squarePath)} history --
|
|
328
|
+
chunks.push(`» ${commandPrefix(squarePath)} history --no-truncate`);
|
|
306
329
|
}
|
|
307
330
|
return chunks.join('\n\n');
|
|
308
331
|
}
|
|
@@ -312,29 +335,41 @@ export const historyCommand = {
|
|
|
312
335
|
const squarePath = requireSquarePath(context);
|
|
313
336
|
const square = await openSquare(squarePath, { clock: nowMs });
|
|
314
337
|
try {
|
|
315
|
-
|
|
338
|
+
// Keep the projection chronological; pagination chooses a stable edge,
|
|
339
|
+
// then --order only changes how the selected page is displayed.
|
|
340
|
+
const projection = await historyPresentation(square, { ...options, order: 'asc' });
|
|
316
341
|
let events = [...projection.activities];
|
|
317
342
|
const searching = options.grep !== undefined || options.fixed !== undefined;
|
|
318
343
|
const totalMatches = searching ? events.length : 0;
|
|
319
344
|
if (options.lastN != null) {
|
|
320
|
-
events = options.
|
|
345
|
+
events = options.afterIndex !== undefined
|
|
321
346
|
? events.slice(0, options.lastN)
|
|
322
347
|
: events.slice(-options.lastN);
|
|
323
348
|
}
|
|
349
|
+
if (options.order === 'desc')
|
|
350
|
+
events.reverse();
|
|
324
351
|
if (options.json)
|
|
325
|
-
return events.map((item) => jsonLine(projection.sayNumbers, item)).join('\n') + (events.length > 0 ? '\n' : '');
|
|
352
|
+
return events.map((item) => jsonLine(projection.sayNumbers, item, item.perception)).join('\n') + (events.length > 0 ? '\n' : '');
|
|
326
353
|
if (options.format !== undefined && options.format.length > 0) {
|
|
327
|
-
return events.map((item) => renderFields(projection.sayNumbers, item, options.format)).join('\n') + (events.length > 0 ? '\n' : '');
|
|
354
|
+
return events.map((item) => renderFields(projection.sayNumbers, item, options.format, item.perception)).join('\n') + (events.length > 0 ? '\n' : '');
|
|
328
355
|
}
|
|
329
356
|
const pattern = options.grep ?? options.fixed;
|
|
330
357
|
const anonymous = options.viewer === undefined;
|
|
331
|
-
const archive =
|
|
332
|
-
|| (options.lastN == null && options.full === true)
|
|
333
|
-
|| anonymous;
|
|
358
|
+
const archive = anonymous;
|
|
334
359
|
const output = pattern === undefined || pattern === ''
|
|
335
|
-
? renderHistoryProjection(projection, events, options.
|
|
336
|
-
: renderGrepActivitiesView(events, totalMatches, options.
|
|
337
|
-
|
|
360
|
+
? renderHistoryProjection(projection, events, options.noTruncate === true, squarePath, options.viewer ?? '', archive ? 'archive' : 'ambient')
|
|
361
|
+
: renderGrepActivitiesView(events, totalMatches, options.noTruncate, squarePath, pattern, options.fixed !== undefined, (item) => projection.activities.find((candidate) => candidate.index === item.index)?.perception ?? 'full');
|
|
362
|
+
const publicEvents = events.filter((item) => item.kind === 'say' || item.kind === 'done');
|
|
363
|
+
const allPublic = projection.activities.filter((item) => item.kind === 'say' || item.kind === 'done');
|
|
364
|
+
const pageMin = publicEvents.length === 0 ? undefined : Math.min(...publicEvents.map((item) => item.index));
|
|
365
|
+
const pageMax = publicEvents.length === 0 ? undefined : Math.max(...publicEvents.map((item) => item.index));
|
|
366
|
+
const hasMore = options.lastN != null && publicEvents.length > 0 && (options.afterIndex !== undefined
|
|
367
|
+
? allPublic.some((item) => item.index > (pageMax ?? options.afterIndex))
|
|
368
|
+
: allPublic.some((item) => item.index < (pageMin ?? Infinity)));
|
|
369
|
+
const cursorDirection = options.afterIndex !== undefined ? '--after' : '--before';
|
|
370
|
+
const cursorIndex = cursorDirection === '--after' ? Math.max(...publicEvents.map((item) => item.index)) : Math.min(...publicEvents.map((item) => item.index));
|
|
371
|
+
const continuation = hasMore ? `\n\n» ${historyContinuationCommand(options, squarePath, cursorDirection, cursorIndex)}` : '';
|
|
372
|
+
return withPathOutput(squarePath, output + continuation, { participantCount: projection.participantCount });
|
|
338
373
|
}
|
|
339
374
|
finally {
|
|
340
375
|
await closeOpenSquare(square);
|
|
@@ -428,7 +463,7 @@ export const statusCommand = {
|
|
|
428
463
|
: [` ${visible.replace(/\n/g, '\n ')}`];
|
|
429
464
|
if (visible.includes('more chars') && result.latestAct !== undefined) {
|
|
430
465
|
const prefix = context.name === undefined ? commandPrefix(squarePath) : participantCommandPrefix(squarePath, context.name);
|
|
431
|
-
latest.push(`» ${prefix} history --at ${actId(result.latestAct)} -C 2 --
|
|
466
|
+
latest.push(`» ${prefix} history --at ${actId(result.latestAct)} -C 2 --no-truncate`);
|
|
432
467
|
}
|
|
433
468
|
const output = [
|
|
434
469
|
`${result.activeCount} active · ${result.doneCount} done · cap ${cap} · throttle ${result.throttlePerMinute === undefined ? 'none' : `${result.throttlePerMinute}/min`}`,
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { cmdActivity } from '../activity.js';
|
|
2
2
|
import { formatActivityId, formatHardCap, parseActivityId, } from '../model.js';
|
|
3
3
|
import { participantCommandPrefix, participantIdentity, renderEventCli, renderAmbientEvent, withPathOutput, } from '../presentation.js';
|
|
4
|
-
import { hasAutomaticDeliveryIdentity,
|
|
5
|
-
import {
|
|
4
|
+
import { hasAutomaticDeliveryIdentity, } from '../registry.js';
|
|
5
|
+
import { createHostLedgerPort } from '../host-ledger-file-adapter.js';
|
|
6
|
+
import { projectLocalParticipantBinding, sessionIdsFromEnvironment } from '../square-projections.js';
|
|
7
|
+
import { sweepPendingNotifications } from '../notifications.js';
|
|
6
8
|
import { nowMs } from '../runtime.js';
|
|
7
9
|
import { createSquare, openSquare } from '../square-file-adapter.js';
|
|
8
10
|
import { closeOpenSquare } from '../open-square.js';
|
|
@@ -84,13 +86,18 @@ export const joinCommand = {
|
|
|
84
86
|
const beforeSquare = await openSquare(squarePath, { clock: nowMs });
|
|
85
87
|
const before = await entryPresentation(beforeSquare, intent.name, intent.lastN);
|
|
86
88
|
await closeOpenSquare(beforeSquare);
|
|
87
|
-
const square = await Square.at({ path: squarePath, clock: nowMs
|
|
89
|
+
const square = await Square.at({ path: squarePath, clock: nowMs });
|
|
88
90
|
try {
|
|
91
|
+
const reconnect = before.joined
|
|
92
|
+
&& await projectLocalParticipantBinding({
|
|
93
|
+
hostLedger: createHostLedgerPort(),
|
|
94
|
+
location: squarePath,
|
|
95
|
+
participant: intent.name,
|
|
96
|
+
sessionIds: sessionIdsFromEnvironment(),
|
|
97
|
+
}) !== undefined;
|
|
89
98
|
const participant = await square.join(intent.name);
|
|
90
99
|
const joinedName = participant.name;
|
|
91
100
|
const isRejoin = before.joined;
|
|
92
|
-
const reconnect = isRejoin
|
|
93
|
-
&& await localParticipantOwner(squarePath, joinedName) !== undefined;
|
|
94
101
|
if (isRejoin && !intent.kick && !reconnect) {
|
|
95
102
|
fail([
|
|
96
103
|
`✕ ${participantIdentity(joinedName)} shoos you out of the square`,
|
|
@@ -102,7 +109,7 @@ export const joinCommand = {
|
|
|
102
109
|
const afterSquare = await openSquare(squarePath, { clock: nowMs });
|
|
103
110
|
const after = await entryPresentation(afterSquare, joinedName, intent.lastN);
|
|
104
111
|
await closeOpenSquare(afterSquare);
|
|
105
|
-
await
|
|
112
|
+
await square.reconcileBinding();
|
|
106
113
|
await sweepPendingNotifications(squarePath);
|
|
107
114
|
const activities = after.recentActivities.map((event) => renderAmbientEvent(event, joinedName, {
|
|
108
115
|
now: nowMs(),
|
|
@@ -275,12 +282,12 @@ export const doneCommand = {
|
|
|
275
282
|
async execute(intent, context) {
|
|
276
283
|
const squarePath = requireSquarePath(context);
|
|
277
284
|
const body = (await resolveBody(intent.body ?? (process.stdin.isTTY ? '' : '-'))).replace(/\r\n/g, '\n').trim();
|
|
278
|
-
const square = await Square.at({ path: squarePath, clock: nowMs
|
|
285
|
+
const square = await Square.at({ path: squarePath, clock: nowMs });
|
|
279
286
|
const participant = await square.join(intent.name);
|
|
280
287
|
const result = await participant.done(body);
|
|
288
|
+
await square.reconcileBinding();
|
|
281
289
|
await square.close();
|
|
282
290
|
const name = result.activity.actor;
|
|
283
|
-
await recordLocalDone(name, squarePath);
|
|
284
291
|
const presentation = await openSquare(squarePath, { clock: nowMs });
|
|
285
292
|
const participantCount = (await entryPresentation(presentation, name).finally(() => closeOpenSquare(presentation))).participantCount;
|
|
286
293
|
return withPathOutput(squarePath, `○ ${participantIdentity(name)} steps out of the square — done · just now`, { participantCount });
|
|
@@ -296,7 +303,7 @@ export const holdCommand = {
|
|
|
296
303
|
parse: parseHold,
|
|
297
304
|
async execute(intent, context) {
|
|
298
305
|
const squarePath = requireSquarePath(context);
|
|
299
|
-
const square = await Square.at({ path: squarePath, clock: nowMs
|
|
306
|
+
const square = await Square.at({ path: squarePath, clock: nowMs });
|
|
300
307
|
try {
|
|
301
308
|
const participant = await square.join(intent.name);
|
|
302
309
|
const result = await participant.hold((await resolveBody(intent.body ?? '')).replace(/\r\n/g, '\n').trim());
|
|
@@ -320,7 +327,7 @@ export const resumeCommand = {
|
|
|
320
327
|
},
|
|
321
328
|
async execute(intent, context) {
|
|
322
329
|
const squarePath = requireSquarePath(context);
|
|
323
|
-
const square = await Square.at({ path: squarePath, clock: nowMs
|
|
330
|
+
const square = await Square.at({ path: squarePath, clock: nowMs });
|
|
324
331
|
try {
|
|
325
332
|
const participant = await square.join(intent.name);
|
|
326
333
|
const result = await participant.resume();
|
package/dist/codex-hook.d.ts
CHANGED
|
@@ -5,6 +5,6 @@ export interface CodexHookInput {
|
|
|
5
5
|
cwd?: unknown;
|
|
6
6
|
source?: unknown;
|
|
7
7
|
}
|
|
8
|
-
export declare function codexHookResponse(input: CodexHookInput, lookup?: (sessionId: string) => Promise<InboxMembership[]> | InboxMembership[], env?: NodeJS.ProcessEnv): Promise<object | undefined>;
|
|
8
|
+
export declare function codexHookResponse(input: CodexHookInput, lookup?: (sessionId: string, env?: NodeJS.ProcessEnv) => Promise<InboxMembership[]> | InboxMembership[], env?: NodeJS.ProcessEnv): Promise<object | undefined>;
|
|
9
9
|
export declare function runCodexHook(inputText: string, env?: NodeJS.ProcessEnv): Promise<string>;
|
|
10
10
|
export declare function runCodexHookAsync(inputText: string, env?: NodeJS.ProcessEnv): Promise<string>;
|
package/dist/decisions.js
CHANGED
|
@@ -274,11 +274,11 @@ export function coreActivities(squareState, opts, suppliedDelivery) {
|
|
|
274
274
|
}
|
|
275
275
|
if (opts.afterIndex != null)
|
|
276
276
|
acts = acts.filter((act) => act.index > opts.afterIndex);
|
|
277
|
+
if (opts.beforeIndex != null)
|
|
278
|
+
acts = acts.filter((act) => act.index < opts.beforeIndex);
|
|
277
279
|
if (canonicalParticipants.length > 0) {
|
|
278
280
|
acts = acts.filter((act) => act.actor !== undefined && canonicalParticipants.some((participant) => sameName(participant, act.actor)));
|
|
279
281
|
}
|
|
280
|
-
if (opts.before != null)
|
|
281
|
-
acts = acts.filter((act) => act.at < opts.before);
|
|
282
282
|
if (opts.after != null)
|
|
283
283
|
acts = acts.filter((act) => act.at > opts.after);
|
|
284
284
|
if (opts.mention != null) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type DirectedNotificationRoute } from './delivery.js';
|
|
2
|
-
import type { WakeAttempt } from './
|
|
2
|
+
import type { WakeAttempt } from './square-projections.js';
|
|
3
3
|
export type DeliveryHealthKind = 'awaiting' | 'wake-accepted' | 'wake-unknown' | 'presented-not-delivered' | 'unreachable';
|
|
4
4
|
export interface DeliveryHealthItem {
|
|
5
5
|
squarePath: string;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type SquareState } from './model.js';
|
|
2
|
+
import type { HostLedgerPort, PresenceRecord, SquareArtifactPort, DeliverPendingInput, DeliveryResult, ObserveSquareInput, ReconcileBindingInput, SquareObservation } from './ports.js';
|
|
3
|
+
import { deriveDeliveryModel } from './delivery.js';
|
|
4
|
+
import { type WakeAttempt } from './square-projections.js';
|
|
5
|
+
export declare function observeSquare(input: ObserveSquareInput): Promise<SquareObservation>;
|
|
6
|
+
export declare function reconcileBinding(input: ReconcileBindingInput): Promise<import("./host-ledger.js").ReconcileBindingResult>;
|
|
7
|
+
export declare function deliverPending(input: DeliverPendingInput): Promise<DeliveryResult>;
|
|
8
|
+
export declare function selectPendingWakeActivities(state: SquareState, routes: readonly PresenceRecord[], attempts: readonly WakeAttempt[], now: number, graceMs: number, limit: number, delivery?: import("./delivery.js").DeliveryModel): number[];
|
|
9
|
+
export declare function sweepPending(input: {
|
|
10
|
+
readonly artifact: SquareArtifactPort;
|
|
11
|
+
readonly hostLedger: HostLedgerPort;
|
|
12
|
+
readonly location: string;
|
|
13
|
+
readonly now: number;
|
|
14
|
+
readonly graceMs: number;
|
|
15
|
+
readonly limit: number;
|
|
16
|
+
}): Promise<number[]>;
|
|
17
|
+
export declare function sweepPendingFromState(input: {
|
|
18
|
+
readonly state: SquareState;
|
|
19
|
+
readonly hostLedger: HostLedgerPort;
|
|
20
|
+
readonly location: string;
|
|
21
|
+
readonly now: number;
|
|
22
|
+
readonly graceMs: number;
|
|
23
|
+
readonly limit: number;
|
|
24
|
+
readonly deriveDelivery?: (snapshot: SquareState) => ReturnType<typeof deriveDeliveryModel>;
|
|
25
|
+
}): Promise<number[]>;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { formatActivityId, parseActivityId } from './square-core.js';
|
|
3
|
+
import { deriveDeliveryModel } from './delivery.js';
|
|
4
|
+
import { isWakeRouteAttemptable } from './square-projections.js';
|
|
5
|
+
export async function observeSquare(input) {
|
|
6
|
+
const snapshot = await input.artifact.read();
|
|
7
|
+
const delivery = deriveDeliveryModel(snapshot.state);
|
|
8
|
+
const rows = input.hostLedger === undefined ? [] : await input.hostLedger.listPresence({ location: input.location, scopes: ['user', 'local'], now: input.now });
|
|
9
|
+
const bindings = rows.map((record) => ({
|
|
10
|
+
location: record.location,
|
|
11
|
+
participant: record.participant,
|
|
12
|
+
sessionId: record.session,
|
|
13
|
+
channel: record.channel,
|
|
14
|
+
...(record.route === undefined ? {} : { route: { location: record.location, participant: record.participant, sessionId: record.session, channel: record.channel, kind: record.route.kind, address: { ...record.route.address }, updatedAt: record.updatedAt ?? 0 } }),
|
|
15
|
+
updatedAt: record.updatedAt ?? 0,
|
|
16
|
+
}));
|
|
17
|
+
return { ...(input.location === undefined ? {} : { location: input.location }), version: snapshot.version, state: snapshot.state, pending: delivery.joinedRecipients().map((recipient) => ({ recipient, notifications: delivery.pendingFor(recipient) })), bindings };
|
|
18
|
+
}
|
|
19
|
+
export async function reconcileBinding(input) {
|
|
20
|
+
return input.hostLedger.reconcileBinding({ artifact: input.artifact, scopes: input.scopes, now: input.now });
|
|
21
|
+
}
|
|
22
|
+
export async function deliverPending(input) {
|
|
23
|
+
const observation = await observeSquare({ artifact: input.artifact, hostLedger: input.hostLedger, location: input.location, now: input.now });
|
|
24
|
+
const routes = (await input.hostLedger.listPresence({ location: input.location, scopes: ['user'], now: input.now })).filter((binding) => binding.route !== undefined);
|
|
25
|
+
let attempted = 0;
|
|
26
|
+
let accepted = 0;
|
|
27
|
+
let failed = 0;
|
|
28
|
+
let unknown = 0;
|
|
29
|
+
for (const membership of observation.pending) {
|
|
30
|
+
for (const notification of membership.notifications) {
|
|
31
|
+
const candidates = routes.filter((route) => route.participant.toLocaleLowerCase() === membership.recipient.toLocaleLowerCase());
|
|
32
|
+
if (input.activity !== undefined) {
|
|
33
|
+
const requested = typeof input.activity === 'number' ? input.activity : parseActivityId(input.activity);
|
|
34
|
+
if (requested === undefined || requested !== notification.item.index)
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
for (const route of candidates) {
|
|
38
|
+
const activity = formatActivityId(notification.item.index);
|
|
39
|
+
const attention = { squarePath: input.location, actIndex: notification.item.index, recipient: membership.recipient };
|
|
40
|
+
const leaseMs = input.timeoutMs ?? 5000;
|
|
41
|
+
let leaseId = randomUUID();
|
|
42
|
+
let lease = await input.hostLedger.claimWakeDispatch({ attention, leaseId, leaseMs, session: route.session, at: input.now });
|
|
43
|
+
if (lease.type === 'ambiguous') {
|
|
44
|
+
await input.hostLedger.appendEvidence({ location: input.location, participant: membership.recipient, session: route.session, activity, kind: 'wake', outcome: 'unknown', routeKind: lease.lease.routeKind ?? route.route.kind, attemptN: lease.lease.attemptN ?? 1, signature: 'worker_interrupted_during_dispatch', message: 'The notification worker ended after dispatch began; transport acceptance is unknown.', at: input.now });
|
|
45
|
+
await input.hostLedger.releaseWakeDispatch({ attention, leaseId: lease.lease.leaseId, session: route.session, at: input.now });
|
|
46
|
+
leaseId = randomUUID();
|
|
47
|
+
lease = await input.hostLedger.claimWakeDispatch({ attention, leaseId, leaseMs, session: route.session, at: input.now });
|
|
48
|
+
}
|
|
49
|
+
if (lease.type !== 'acquired')
|
|
50
|
+
continue;
|
|
51
|
+
const attempts = await input.hostLedger.listWakeAttempts({ attention, session: route.session, now: input.now });
|
|
52
|
+
const attemptN = attempts.reduce((highest, record) => Math.max(highest, record.attemptN ?? 0), 0) + 1;
|
|
53
|
+
const request = { location: input.location, participant: membership.recipient, activity, route: { location: route.location, participant: route.participant, sessionId: route.session, channel: route.channel, kind: route.route.kind, address: { ...route.route.address }, updatedAt: route.updatedAt ?? 0 } };
|
|
54
|
+
let outcome;
|
|
55
|
+
const claim = await input.hostLedger.claimEvidence({ location: input.location, participant: membership.recipient, session: route.session, activity, kind: 'wake', leaseMs, now: input.now });
|
|
56
|
+
if (claim.status !== 'acquired') {
|
|
57
|
+
await input.hostLedger.releaseWakeDispatch({ attention, leaseId, session: route.session, at: input.now });
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
attempted += 1;
|
|
61
|
+
const dispatching = await input.hostLedger.transitionWakeDispatch({ attention, leaseId, phase: 'dispatching', leaseMs, routeKind: route.route.kind, attemptN, session: route.session, at: input.now });
|
|
62
|
+
if (!dispatching) {
|
|
63
|
+
await input.hostLedger.releaseWakeDispatch({ attention, leaseId, session: route.session, at: input.now });
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
outcome = await Promise.race([input.transport.attempt(request, leaseMs), new Promise((resolve) => setTimeout(() => resolve({ outcome: 'unknown', diagnostic: 'transport timeout' }), leaseMs))]);
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
outcome = { outcome: 'unknown', diagnostic: error instanceof Error ? error.message : String(error) };
|
|
71
|
+
}
|
|
72
|
+
if (outcome.outcome === 'failed' && outcome.unavailable) {
|
|
73
|
+
await input.transport.invalidate?.(request);
|
|
74
|
+
await input.hostLedger.releaseEvidence({ location: input.location, participant: membership.recipient, session: route.session, activity, kind: 'wake', now: input.now });
|
|
75
|
+
await input.hostLedger.releaseWakeDispatch({ attention, leaseId, session: route.session, at: input.now });
|
|
76
|
+
failed += 1;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
await input.hostLedger.appendEvidence({ location: input.location, participant: membership.recipient, session: route.session, activity, kind: 'wake', outcome: outcome.outcome, routeKind: route.route.kind, attemptN: outcome.attemptN ?? attemptN, ...(outcome.outcome === 'accepted' && outcome.signature === undefined ? {} : outcome.outcome === 'accepted' ? { signature: outcome.signature } : outcome.outcome === 'failed' ? { message: outcome.message } : { diagnostic: outcome.diagnostic }), at: input.now });
|
|
80
|
+
await input.hostLedger.releaseWakeDispatch({ attention, leaseId, session: route.session, at: input.now });
|
|
81
|
+
if (outcome.outcome === 'accepted')
|
|
82
|
+
accepted += 1;
|
|
83
|
+
else if (outcome.outcome === 'failed')
|
|
84
|
+
failed += 1;
|
|
85
|
+
else
|
|
86
|
+
unknown += 1;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return { attempted, accepted, failed, unknown };
|
|
91
|
+
}
|
|
92
|
+
export function selectPendingWakeActivities(state, routes, attempts, now, graceMs, limit, delivery = deriveDeliveryModel(state)) {
|
|
93
|
+
const selected = new Set();
|
|
94
|
+
for (const membership of delivery.joinedRecipients()) {
|
|
95
|
+
for (const notification of delivery.pendingFor(membership)) {
|
|
96
|
+
if (now - notification.item.at <= graceMs)
|
|
97
|
+
continue;
|
|
98
|
+
const eligible = routes.some((binding) => {
|
|
99
|
+
if (binding.route === undefined || binding.participant.toLocaleLowerCase() !== membership.toLocaleLowerCase())
|
|
100
|
+
return false;
|
|
101
|
+
const matching = attempts.filter((attempt) => attempt.session === binding.session && attempt.attention.squarePath === binding.location && attempt.attention.recipient.toLocaleLowerCase() === membership.toLocaleLowerCase() && attempt.attention.actIndex === notification.item.index);
|
|
102
|
+
return isWakeRouteAttemptable({ kind: binding.route.kind, updatedAt: binding.updatedAt ?? 0 }, matching);
|
|
103
|
+
});
|
|
104
|
+
if (eligible)
|
|
105
|
+
selected.add(notification.item.index);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return [...selected].sort((left, right) => left - right).slice(0, Math.max(0, limit));
|
|
109
|
+
}
|
|
110
|
+
export async function sweepPending(input) {
|
|
111
|
+
const { state } = await input.artifact.read();
|
|
112
|
+
return sweepPendingFromState({ ...input, state });
|
|
113
|
+
}
|
|
114
|
+
export async function sweepPendingFromState(input) {
|
|
115
|
+
const [bindings, records] = await Promise.all([input.hostLedger.listPresence({ location: input.location, scopes: ['user'], now: input.now }), input.hostLedger.listWakeAttempts({ now: input.now })]);
|
|
116
|
+
const attempts = records.flatMap((record) => {
|
|
117
|
+
const index = parseActivityId(record.activity);
|
|
118
|
+
if (index === undefined || record.routeKind === undefined || typeof record.attemptN !== 'number')
|
|
119
|
+
return [];
|
|
120
|
+
return [{ at: record.at ?? input.now, attention: { squarePath: record.location, actIndex: index, recipient: record.participant }, routeKind: record.routeKind, outcome: record.outcome, attemptN: record.attemptN, ...(record.session === undefined ? {} : { session: record.session }) }];
|
|
121
|
+
});
|
|
122
|
+
const delivery = input.deriveDelivery?.(input.state) ?? deriveDeliveryModel(input.state);
|
|
123
|
+
return selectPendingWakeActivities(input.state, bindings, attempts, input.now, input.graceMs, input.limit, delivery);
|
|
124
|
+
}
|
package/dist/help.js
CHANGED
|
@@ -43,7 +43,7 @@ const COMMANDS = [
|
|
|
43
43
|
{
|
|
44
44
|
names: ['history'], usage: '[--as <name>] history [filters] [output]', usesSquare: true, group: 'participant',
|
|
45
45
|
summary: 'Read or search the archive without changing what you have caught.',
|
|
46
|
-
details: ['Filters:', ' --from <names> Match activities from participants.', ' --since <time
|
|
46
|
+
details: ['Filters:', ' --from <names> Match activities from participants.', ' --since <time> Match activities after a time.', ' --grep <regex> | --fixed <s> Search activity ids, participants, and original bodies.', ' --mention <name> Match direct attention for a participant.', ' --pending Match attention waiting for --as <name>.', ' --at <ids> Center on comma-separated activity ids; may repeat.', ' -B, -A, -C <N> Set non-negative context around every --at coordinate.', ' --before <id> Read the page immediately before an activity.', ' --after <id> Read the page immediately after an activity.', '', 'Results:', ' --limit <N> Page size (default 10).', ' --order <asc|desc> Set display order (default oldest first).', '', 'Output:', ' --no-truncate --json --format <fields>', ' Bodies are previews by default; --no-truncate expands them. --as keeps participant perception.'],
|
|
47
47
|
},
|
|
48
48
|
{ names: ['status'], usage: '[--as <name>] status', usesSquare: true, group: 'participant', summary: 'Show who is present and what happened most recently.' },
|
|
49
49
|
{ names: ['participants'], usage: 'participants', usesSquare: true, group: 'host', summary: 'Show the full participant roster and current states.' },
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { HostLedgerPort, HostLedgerScope, PresenceRecord, PresenceKey, PresenceLookup, PresenceResult, EvidenceRecord, EvidenceClaim, EvidenceRelease, EvidenceLookup, EvidenceGc, ClaimResult, ReconcileBindingInput, ReconcileBindingResult, WakeDispatchClaim, WakeDispatchClaimInput, WakeDispatchReleaseInput, WakeDispatchTransitionInput, WakeAttemptLookup } from './host-ledger.js';
|
|
2
|
+
export interface HostLedgerFileAdapterOptions {
|
|
3
|
+
userPath?: string;
|
|
4
|
+
localPath?: string;
|
|
5
|
+
writableScope?: HostLedgerScope;
|
|
6
|
+
readableScopes?: readonly HostLedgerScope[];
|
|
7
|
+
claimsPath?: string;
|
|
8
|
+
now?: () => number;
|
|
9
|
+
}
|
|
10
|
+
export declare class FileHostLedgerPort implements HostLedgerPort {
|
|
11
|
+
private readonly user;
|
|
12
|
+
private readonly local;
|
|
13
|
+
private readonly writable;
|
|
14
|
+
private readonly readable;
|
|
15
|
+
private readonly claims;
|
|
16
|
+
private readonly clock;
|
|
17
|
+
constructor(o?: HostLedgerFileAdapterOptions);
|
|
18
|
+
private file;
|
|
19
|
+
ensurePresence(i: PresenceRecord, scope?: HostLedgerScope): Promise<PresenceResult>;
|
|
20
|
+
removePresence(i: PresenceKey): Promise<void>;
|
|
21
|
+
listPresence(i?: PresenceLookup): Promise<readonly PresenceRecord[]>;
|
|
22
|
+
claimWakeDispatch(i: WakeDispatchClaimInput): Promise<WakeDispatchClaim>;
|
|
23
|
+
transitionWakeDispatch(i: WakeDispatchTransitionInput): Promise<boolean>;
|
|
24
|
+
releaseWakeDispatch(i: WakeDispatchReleaseInput): Promise<void>;
|
|
25
|
+
listWakeAttempts(i?: WakeAttemptLookup): Promise<readonly EvidenceRecord[]>;
|
|
26
|
+
appendWakeAttempt(i: EvidenceRecord): Promise<void>;
|
|
27
|
+
claimEvidence(i: EvidenceClaim): Promise<ClaimResult>;
|
|
28
|
+
releaseEvidence(i: EvidenceRelease): Promise<void>;
|
|
29
|
+
appendEvidence(i: EvidenceRecord): Promise<void>;
|
|
30
|
+
listEvidence(i?: EvidenceLookup): Promise<readonly EvidenceRecord[]>;
|
|
31
|
+
gcEvidence(i: EvidenceGc): Promise<void>;
|
|
32
|
+
reconcileBinding(i?: ReconcileBindingInput): Promise<ReconcileBindingResult>;
|
|
33
|
+
}
|
|
34
|
+
export declare function createHostLedgerPort(o?: HostLedgerFileAdapterOptions): HostLedgerPort;
|