@astrosheep/square 0.3.58 → 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 +170 -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 +21 -19
- package/dist/automatic-session.js +16 -26
- package/dist/boundary-presentation.d.ts +1 -2
- package/dist/boundary-presentation.js +4 -19
- package/dist/catch-decisions.js +1 -1
- package/dist/cli/context.d.ts +2 -0
- package/dist/cli/context.js +17 -33
- 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 +132 -64
- package/dist/cli/program.js +36 -9
- package/dist/cli/registry.js +2 -0
- package/dist/cli/square-commands.d.ts +1 -1
- package/dist/cli/square-commands.js +103 -133
- package/dist/decisions.d.ts +4 -0
- package/dist/decisions.js +82 -34
- 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/list.js +12 -9
- 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 +19 -1
- package/dist/presentation.js +111 -73
- package/dist/presented.js +2 -1
- package/dist/registry.d.ts +8 -4
- package/dist/registry.js +43 -76
- package/dist/routes.d.ts +19 -2
- package/dist/routes.js +48 -5
- package/dist/runtime.d.ts +10 -0
- package/dist/runtime.js +39 -0
- package/dist/square-actions.d.ts +26 -13
- package/dist/square-actions.js +203 -87
- 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 +31 -27
- package/dist/tty-style.d.ts +7 -0
- package/dist/tty-style.js +13 -0
- package/dist/views.d.ts +3 -3
- package/dist/views.js +10 -9
- 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 +43 -28
- package/extensions/square-pi.js +107 -109
- 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 +10 -4
- package/skills/square/SKILL.md +5 -7
package/dist/presentation.js
CHANGED
|
@@ -3,16 +3,18 @@ import fs from 'node:fs';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { participantIdentity } from './participant-identity.js';
|
|
5
5
|
export { participantIdentity } from './participant-identity.js';
|
|
6
|
-
import { audienceIncludes, audienceOf, MAX_IDENTITY_SET_SIZE } from './square-core.js';
|
|
6
|
+
import { audienceIncludes, audienceOf, MAX_IDENTITY_SET_SIZE, replayLandedAudiences } from './square-core.js';
|
|
7
7
|
import { perceiveActivity } from './delivery.js';
|
|
8
|
-
import { actId,
|
|
8
|
+
import { actId, presenceAnchor, publicActs, rosterNames, sayNumberFor, HELD_WAIT_BUDGET_MS } from './runtime.js';
|
|
9
9
|
import { formatDuration, formatRelativeTime, formatTimestamp } from './time.js';
|
|
10
10
|
import { compareParticipantActivity } from './decisions.js';
|
|
11
11
|
import { grepSnippet } from './search.js';
|
|
12
|
+
import { style } from './tty-style.js';
|
|
12
13
|
function headerLine(squarePath, opts = {}) {
|
|
13
14
|
const count = opts.participantCount ?? 0;
|
|
14
15
|
const heldSuffix = opts.held ? ' — a hand is raised' : '';
|
|
15
|
-
|
|
16
|
+
const line = `· ${displayPath(squarePath)} — ${count} in the square${heldSuffix}`;
|
|
17
|
+
return opts.plain === true ? line : style('dim', line);
|
|
16
18
|
}
|
|
17
19
|
export function displayPath(squarePath, cwd = process.cwd()) {
|
|
18
20
|
if (!path.isAbsolute(squarePath))
|
|
@@ -32,6 +34,14 @@ export function displayPath(squarePath, cwd = process.cwd()) {
|
|
|
32
34
|
export function withPathOutput(squarePath, body = '', opts = {}) {
|
|
33
35
|
return [headerLine(squarePath, opts), ...(body === '' ? [] : ['', body])].join('\n') + '\n';
|
|
34
36
|
}
|
|
37
|
+
/** The same header/blank/body layout as withPathOutput, for refusal bodies written to stderr. */
|
|
38
|
+
export function formatRefusal(squarePath, bodyLines, opts = {}) {
|
|
39
|
+
const body = bodyLines.join('\n');
|
|
40
|
+
// A refusal with no known count stays bare rather than claiming "0 in the square".
|
|
41
|
+
if (opts.participantCount === undefined)
|
|
42
|
+
return body === '' ? '' : `${body}\n`;
|
|
43
|
+
return withPathOutput(squarePath, body, { ...opts, plain: true });
|
|
44
|
+
}
|
|
35
45
|
export function quoteShell(value) {
|
|
36
46
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
37
47
|
}
|
|
@@ -43,10 +53,17 @@ export function participantCommandPrefix(squarePath, name) {
|
|
|
43
53
|
}
|
|
44
54
|
/** Blocked actions end with a full copy-pasteable recovery command. */
|
|
45
55
|
export function joinRecoveryCommand(squarePath, name) {
|
|
46
|
-
return
|
|
56
|
+
return `${participantCommandPrefix(squarePath, name)} join`;
|
|
57
|
+
}
|
|
58
|
+
/** Taking a name is an explicit recovery choice, never an automatic retry. */
|
|
59
|
+
export function takeoverRecoveryLines(squarePath, name) {
|
|
60
|
+
return [
|
|
61
|
+
'If this is your name to reclaim, --kick banishes the one standing here so you can step in:',
|
|
62
|
+
`${participantCommandPrefix(squarePath, name)} join --kick`,
|
|
63
|
+
];
|
|
47
64
|
}
|
|
48
65
|
export function participantsRecoveryCommand(squarePath) {
|
|
49
|
-
return
|
|
66
|
+
return `${commandPrefix(squarePath)} participants`;
|
|
50
67
|
}
|
|
51
68
|
function formatAge(ms) {
|
|
52
69
|
if (ms === undefined)
|
|
@@ -68,6 +85,10 @@ function presenceGlyph(participant) {
|
|
|
68
85
|
return '●';
|
|
69
86
|
return '○';
|
|
70
87
|
}
|
|
88
|
+
function styledPresenceGlyph(participant) {
|
|
89
|
+
const glyph = presenceGlyph(participant);
|
|
90
|
+
return glyph === '○' ? style('dim', glyph) : glyph;
|
|
91
|
+
}
|
|
71
92
|
function presenceText(participant, now) {
|
|
72
93
|
if (participant.state === 'done') {
|
|
73
94
|
return participant.lastActiveAt === undefined ? 'stepped out of the square' : `stepped out of the square · ${formatRelativeTime(participant.lastActiveAt, now)}`;
|
|
@@ -86,17 +107,17 @@ export function renderPresenceLines(participants, now, limit = 5) {
|
|
|
86
107
|
.sort(compareParticipantActivity);
|
|
87
108
|
const shown = recent.slice(0, limit);
|
|
88
109
|
if (shown.length === 0)
|
|
89
|
-
return [' ○ nobody nearby'];
|
|
90
|
-
const lines = shown.map((p) => ` ${
|
|
110
|
+
return [style('dim', ' ○ nobody nearby')];
|
|
111
|
+
const lines = shown.map((p) => ` ${styledPresenceGlyph(p)} ${participantIdentity(p.name)}${style('dim', ` · ${presenceText(p, now)}`)}`);
|
|
91
112
|
const remaining = recent.length - shown.length;
|
|
92
113
|
if (remaining > 0)
|
|
93
|
-
lines.push(` ○ …
|
|
114
|
+
lines.push(style('dim', ` ○ … ${remaining} more nearby`));
|
|
94
115
|
return lines;
|
|
95
116
|
}
|
|
96
117
|
const EXPRESS_HINTS = [
|
|
97
118
|
'*asterisks* are your body — *slams table*, *sketches in the air*, *shrugs*',
|
|
98
119
|
"you're standing in a square — words and gestures both land",
|
|
99
|
-
'the square
|
|
120
|
+
'the square runs on conversation — say things that move it, not nods that fill it',
|
|
100
121
|
];
|
|
101
122
|
export function expressHintLine(ownActivityCount) {
|
|
102
123
|
if (ownActivityCount !== 1 && ownActivityCount % 5 !== 0)
|
|
@@ -119,7 +140,7 @@ export function truncateExternalDiagnostic(diagnostic) {
|
|
|
119
140
|
}
|
|
120
141
|
function previewBody(body, maxLen = BODY_PREVIEW_LENGTH) {
|
|
121
142
|
const preview = truncateChars(body, maxLen);
|
|
122
|
-
return preview.remaining === 0 ? preview.text : `${preview.text}\n
|
|
143
|
+
return preview.remaining === 0 ? preview.text : `${preview.text}\n${style('dim', `… ${preview.remaining} more chars`)}`;
|
|
123
144
|
}
|
|
124
145
|
const UNREAD_PREVIEW_CHARS = 120;
|
|
125
146
|
export function previewActivityBody(body) {
|
|
@@ -127,7 +148,7 @@ export function previewActivityBody(body) {
|
|
|
127
148
|
if (compact === '')
|
|
128
149
|
return '(empty)';
|
|
129
150
|
const preview = truncateChars(compact, UNREAD_PREVIEW_CHARS);
|
|
130
|
-
return preview.remaining === 0 ? preview.text : `${preview.text}
|
|
151
|
+
return preview.remaining === 0 ? preview.text : `${preview.text}${style('dim', `… (+${preview.remaining} chars)`)}`;
|
|
131
152
|
}
|
|
132
153
|
export function renderRoomChangeText(event) {
|
|
133
154
|
const actor = event.actor ?? 'someone';
|
|
@@ -166,7 +187,7 @@ export function renderEventCli(event, opts = {}) {
|
|
|
166
187
|
case 'hold':
|
|
167
188
|
return `· ${renderRoomChangeText(event)}`;
|
|
168
189
|
case 'resume':
|
|
169
|
-
return
|
|
190
|
+
return `${style('release', '✓')} ${renderRoomChangeText(event)}`;
|
|
170
191
|
case 'listen':
|
|
171
192
|
case 'ignore':
|
|
172
193
|
return `· ${renderRoomChangeText(event)}`;
|
|
@@ -177,11 +198,12 @@ export function renderEventCli(event, opts = {}) {
|
|
|
177
198
|
? ` · calls your name across the square — ${participantIdentity(mention)}`
|
|
178
199
|
: '';
|
|
179
200
|
const replySuffix = event.reply === undefined ? '' : ` · replies to ${actId(event.reply)}`;
|
|
180
|
-
|
|
201
|
+
const meta = ` #${opts.actNumber ?? 1} · ${actId(event)} · ${formatRelativeTime(event.at, now)}${mentionSuffix}${replySuffix}`;
|
|
202
|
+
return `● ${participantIdentity(event.actor)}${style('dim', meta)}${bodySuffix(body)}`;
|
|
181
203
|
}
|
|
182
204
|
case 'done': {
|
|
183
205
|
const body = renderedBody(event.body, maxBody);
|
|
184
|
-
return
|
|
206
|
+
return `${style('dim', '○')} ${participantIdentity(event.actor)} stepped out of the square — done${style('dim', ` · ${actId(event)} · ${formatRelativeTime(event.at, now)}`)}${bodySuffix(body)}`;
|
|
185
207
|
}
|
|
186
208
|
case 'read':
|
|
187
209
|
return '';
|
|
@@ -190,8 +212,9 @@ export function renderEventCli(event, opts = {}) {
|
|
|
190
212
|
function renderPresenceOnlySay(event, opts = {}) {
|
|
191
213
|
const audience = audienceOf(event);
|
|
192
214
|
const targets = audience.kind === 'bell' ? [] : audience.names;
|
|
215
|
+
const meta = style('dim', ` #${opts.actNumber ?? 1} · ${actId(event)} · ${formatRelativeTime(event.at, opts.now)}`);
|
|
193
216
|
if (targets.length === 0) {
|
|
194
|
-
return `● ${participantIdentity(event.actor)}
|
|
217
|
+
return `● ${participantIdentity(event.actor)}${meta}`;
|
|
195
218
|
}
|
|
196
219
|
const visibleTargets = targets.slice(0, MAX_IDENTITY_SET_SIZE).map((name) => participantIdentity(name));
|
|
197
220
|
const remainingTargets = targets.length - visibleTargets.length;
|
|
@@ -199,7 +222,7 @@ function renderPresenceOnlySay(event, opts = {}) {
|
|
|
199
222
|
...visibleTargets,
|
|
200
223
|
...(remainingTargets === 0 ? [] : [`${remainingTargets} ${remainingTargets === 1 ? 'other' : 'others'}`]),
|
|
201
224
|
].join(' and ')}`;
|
|
202
|
-
return `● ${participantIdentity(event.actor)}
|
|
225
|
+
return `● ${participantIdentity(event.actor)}${meta}\n${style('dim', ` talked to${dest}`)}`;
|
|
203
226
|
}
|
|
204
227
|
export function renderAmbientEvent(event, viewer, opts = {}) {
|
|
205
228
|
if (event.kind !== 'say')
|
|
@@ -216,7 +239,7 @@ export function renderAmbientEvent(event, viewer, opts = {}) {
|
|
|
216
239
|
return renderEventCli(event, opts);
|
|
217
240
|
}
|
|
218
241
|
function draftSavedLines(draftPath) {
|
|
219
|
-
return draftPath === undefined ? [] : [`· draft kept: ${draftPath}`];
|
|
242
|
+
return draftPath === undefined ? [] : [style('dim', `· draft kept: ${draftPath}`)];
|
|
220
243
|
}
|
|
221
244
|
function withDraftInput(command, draftPath) {
|
|
222
245
|
return draftPath === undefined ? command : `${command} < ${quoteShell(draftPath)}`;
|
|
@@ -229,15 +252,15 @@ function renderUnreadSummary(opts) {
|
|
|
229
252
|
...item.previews.slice(-1).map((preview) => {
|
|
230
253
|
const rendered = renderAmbientEvent(preview.act, opts.viewer, { actNumber: preview.number, perception: preview.perception });
|
|
231
254
|
if (rendered === '')
|
|
232
|
-
return ` · ${participantIdentity(item.name)} spoke — ${formatAge(item.latestActivityAgeMs)} ago
|
|
255
|
+
return style('dim', ` · ${participantIdentity(item.name)} spoke — ${formatAge(item.latestActivityAgeMs)} ago`);
|
|
233
256
|
if (preview.perception === 'presence') {
|
|
234
|
-
return ` · ${participantIdentity(item.name)} spoke — ${formatAge(item.latestActivityAgeMs)} ago · ${rendered.replace(/\n/g, ' ')}
|
|
257
|
+
return style('dim', ` · ${participantIdentity(item.name)} spoke — ${formatAge(item.latestActivityAgeMs)} ago · ${rendered.replace(/\n/g, ' ')}`);
|
|
235
258
|
}
|
|
236
|
-
return ` · ${participantIdentity(item.name)} spoke — ${formatAge(item.latestActivityAgeMs)} ago · "${previewActivityBody(preview.act.body)}"
|
|
259
|
+
return style('dim', ` · ${participantIdentity(item.name)} spoke — ${formatAge(item.latestActivityAgeMs)} ago · "${previewActivityBody(preview.act.body)}"`);
|
|
237
260
|
}),
|
|
238
261
|
]),
|
|
239
|
-
...(remainingSummaries === 0 ? [] : [` · ${remainingSummaries} more participants have unread activity`]),
|
|
240
|
-
...opts.roomChanges.map((act) => ` · ${renderRoomChangeText(act)}`),
|
|
262
|
+
...(remainingSummaries === 0 ? [] : [style('dim', ` · ${remainingSummaries} more participants have unread activity`)]),
|
|
263
|
+
...opts.roomChanges.map((act) => style('dim', ` · ${renderRoomChangeText(act)}`)),
|
|
241
264
|
];
|
|
242
265
|
}
|
|
243
266
|
export function renderPendingFeed(history, publicItems, roomChanges, viewer = '', squareState) {
|
|
@@ -261,35 +284,58 @@ export function renderPendingFeed(history, publicItems, roomChanges, viewer = ''
|
|
|
261
284
|
export function renderActivityBlocked(opts) {
|
|
262
285
|
const readNowCommand = `${participantCommandPrefix(opts.squarePath, opts.name)} catch --now`;
|
|
263
286
|
return withPathOutput(opts.squarePath, [
|
|
264
|
-
|
|
287
|
+
`${style('blocked', '✕')} your activity doesn't land — the square moved behind your back`,
|
|
265
288
|
...renderUnreadSummary({ activitySummaries: opts.activitySummaries, roomChanges: opts.unreadRoomChanges, viewer: opts.name }),
|
|
266
289
|
...draftSavedLines(opts.draftPath),
|
|
267
|
-
|
|
268
|
-
' take it in, then
|
|
269
|
-
|
|
270
|
-
'
|
|
290
|
+
`${readNowCommand}`,
|
|
291
|
+
' · take it in, then retry:',
|
|
292
|
+
withDraftInput(opts.retryCommand, opts.draftPath),
|
|
293
|
+
' · optional: express over unread activity with --force:',
|
|
294
|
+
withDraftInput(opts.forceCommand, opts.draftPath),
|
|
271
295
|
].join('\n'), { participantCount: opts.participantCount, held: opts.held });
|
|
272
296
|
}
|
|
297
|
+
/** An indeterminate or already-committed send only points to inspection, never resend. */
|
|
298
|
+
export function renderActivityUncertain(opts) {
|
|
299
|
+
return formatRefusal(opts.squarePath, [
|
|
300
|
+
opts.landedId === undefined
|
|
301
|
+
? '✕ your activity may have landed — check history before sending again'
|
|
302
|
+
: `✕ your activity landed · ${opts.landedId} — the confirmation could not finish`,
|
|
303
|
+
...(opts.detail === undefined ? [] : [` · ${truncateExternalDiagnostic(opts.detail)}`]),
|
|
304
|
+
...draftSavedLines(opts.draftPath),
|
|
305
|
+
' · the draft is a saved copy; sending it again could repeat your activity',
|
|
306
|
+
`${commandPrefix(opts.squarePath)} history${opts.landedId === undefined ? ' --limit 10' : ` --at ${opts.landedId}`} --no-truncate`,
|
|
307
|
+
], opts);
|
|
308
|
+
}
|
|
273
309
|
export function renderExpressWaiting(opts) {
|
|
274
310
|
if (opts.reason === 'throttled') {
|
|
275
|
-
return [
|
|
311
|
+
return [
|
|
312
|
+
'✕ the square is packed',
|
|
313
|
+
` · your activity is waiting · next opening in ${formatDuration(opts.delayMs)}`,
|
|
314
|
+
' · --no-wait saves a draft and returns now',
|
|
315
|
+
].join('\n');
|
|
276
316
|
}
|
|
277
|
-
|
|
317
|
+
const holder = opts.holder === undefined ? 'a hand is raised' : `${participantIdentity(opts.holder)} raised a hand`;
|
|
318
|
+
return [
|
|
319
|
+
`✕ your activity doesn't land — ${holder}`,
|
|
320
|
+
...(opts.holdReason === undefined ? [] : [` · ${opts.holdReason}`]),
|
|
321
|
+
` · your activity is waiting — after ${formatDuration(HELD_WAIT_BUDGET_MS)} it saves a draft and stops`,
|
|
322
|
+
' · --no-wait saves a draft and returns now',
|
|
323
|
+
].join('\n');
|
|
278
324
|
}
|
|
279
325
|
export function renderExpressNoWait(opts) {
|
|
280
|
-
const retryCommand = opts.
|
|
326
|
+
const retryCommand = opts.retryCommand;
|
|
281
327
|
const lines = opts.reason === 'throttled'
|
|
282
328
|
? [
|
|
283
|
-
'✕ the square is packed
|
|
329
|
+
`${style('blocked', '✕')} the square is packed`,
|
|
284
330
|
` · next opening in ${formatDuration(opts.delayMs)}`,
|
|
285
331
|
...draftSavedLines(opts.draftPath),
|
|
286
|
-
|
|
332
|
+
`${withDraftInput(retryCommand, opts.draftPath)}`,
|
|
287
333
|
]
|
|
288
334
|
: [
|
|
289
|
-
|
|
290
|
-
` · ${opts.holdReason ?? 'the square holds its breath'}
|
|
335
|
+
`${style('blocked', '✕')} your activity doesn't land — a hand is raised`,
|
|
336
|
+
style('dim', ` · ${opts.holdReason ?? 'the square holds its breath'}`),
|
|
291
337
|
...draftSavedLines(opts.draftPath),
|
|
292
|
-
|
|
338
|
+
`${withDraftInput(retryCommand, opts.draftPath)}`,
|
|
293
339
|
];
|
|
294
340
|
return withPathOutput(opts.squarePath, lines.join('\n'), { participantCount: opts.participantCount, held: opts.held });
|
|
295
341
|
}
|
|
@@ -302,32 +348,24 @@ export function renderPublicTail(squareState, events, lastN, now, viewer = '') {
|
|
|
302
348
|
.filter(Boolean)
|
|
303
349
|
.join('\n\n');
|
|
304
350
|
}
|
|
305
|
-
function lastPresenceAnchor(squareState, name) {
|
|
306
|
-
|
|
307
|
-
for (let i = squareState.acts.length - 1; i >= 0; i--) {
|
|
308
|
-
const event = squareState.acts[i];
|
|
309
|
-
const index = event.index;
|
|
310
|
-
if (index > cursor)
|
|
311
|
-
continue;
|
|
312
|
-
if (event.kind === 'say' || event.kind === 'done')
|
|
313
|
-
return index;
|
|
314
|
-
}
|
|
315
|
-
return -1;
|
|
351
|
+
function lastPresenceAnchor(squareState, name, landed) {
|
|
352
|
+
return presenceAnchor(squareState, name, landed);
|
|
316
353
|
}
|
|
317
354
|
export function renderPresenceAnchor(names) {
|
|
318
355
|
const visibleNames = names.slice(0, MAX_IDENTITY_SET_SIZE);
|
|
319
356
|
const participants = visibleNames.map((name) => participantIdentity(name)).join(', ');
|
|
320
357
|
const remaining = names.length - visibleNames.length;
|
|
321
358
|
const suffix = remaining === 0 ? '' : ` and ${remaining} more`;
|
|
322
|
-
return names.length === 1 ? `→ ${participants} was here` : `→ ${participants}${suffix} were here
|
|
359
|
+
return style('dim', names.length === 1 ? `→ ${participants} was here` : `→ ${participants}${suffix} were here`);
|
|
323
360
|
}
|
|
324
361
|
export function renderActivitiesView(squareState, visible, lastN, noTruncate, squarePath, viewer = '', mode = 'ambient') {
|
|
325
362
|
const publicVisible = visible.filter((act) => act.kind === 'say' || act.kind === 'done');
|
|
326
363
|
const shown = lastN == null ? publicVisible : publicVisible.slice(-lastN);
|
|
327
364
|
const previewLen = noTruncate ? undefined : BODY_PREVIEW_LENGTH;
|
|
328
365
|
const markers = new Map();
|
|
366
|
+
const landed = replayLandedAudiences(squareState.acts);
|
|
329
367
|
for (const participant of rosterNames(squareState)) {
|
|
330
|
-
const anchor = lastPresenceAnchor(squareState, participant);
|
|
368
|
+
const anchor = lastPresenceAnchor(squareState, participant, landed);
|
|
331
369
|
if (anchor >= 0)
|
|
332
370
|
markers.set(anchor, [...(markers.get(anchor) ?? []), participant]);
|
|
333
371
|
}
|
|
@@ -351,15 +389,13 @@ export function renderActivitiesView(squareState, visible, lastN, noTruncate, sq
|
|
|
351
389
|
if (previewLen !== undefined) {
|
|
352
390
|
const truncated = shown.some((act) => act.kind === 'say' && act.body.length > previewLen && (mode === 'archive' || perceiveActivity(squareState, act, viewer) === 'full'));
|
|
353
391
|
if (truncated)
|
|
354
|
-
chunks.push(
|
|
392
|
+
chunks.push(`${commandPrefix(squarePath)} history --no-truncate`);
|
|
355
393
|
}
|
|
356
394
|
return chunks.join('\n\n');
|
|
357
395
|
}
|
|
358
396
|
const GREP_PREVIEW_CHARS = 160;
|
|
359
397
|
function highlightGrepMatch(text) {
|
|
360
|
-
|
|
361
|
-
return text;
|
|
362
|
-
return `\x1b[38;5;222m\x1b[1m${text}\x1b[0m`;
|
|
398
|
+
return style('match', text);
|
|
363
399
|
}
|
|
364
400
|
export function renderGrepActivitiesView(visible, totalMatches, noTruncate, squarePath, pattern, fixed = false, perception) {
|
|
365
401
|
const publicVisible = visible.filter((act) => act.kind === 'say' || act.kind === 'done');
|
|
@@ -370,19 +406,20 @@ export function renderGrepActivitiesView(visible, totalMatches, noTruncate, squa
|
|
|
370
406
|
let truncated = false;
|
|
371
407
|
for (const act of publicVisible) {
|
|
372
408
|
const rawBody = act.body ?? '';
|
|
409
|
+
const header = `${style('dim', actId(act.index))} · ${act.actor === undefined ? 'unknown' : participantIdentity(act.actor)} · ${style('dim', formatTimestamp(act.at))}`;
|
|
373
410
|
if (perception?.(act) === 'presence') {
|
|
374
|
-
chunks.push(
|
|
411
|
+
chunks.push(header);
|
|
375
412
|
continue;
|
|
376
413
|
}
|
|
377
414
|
if (noTruncate === true) {
|
|
378
415
|
const body = rawBody.split('\n').map((line) => ` ${line}`).join('\n');
|
|
379
|
-
chunks.push(`${
|
|
416
|
+
chunks.push(`${header}\n${body}`);
|
|
380
417
|
continue;
|
|
381
418
|
}
|
|
382
419
|
const snippet = grepSnippet(rawBody, pattern, GREP_PREVIEW_CHARS, fixed);
|
|
383
420
|
if (snippet === undefined) {
|
|
384
421
|
const preview = previewBody(rawBody, GREP_PREVIEW_CHARS);
|
|
385
|
-
chunks.push(`${
|
|
422
|
+
chunks.push(`${header}${preview === '' ? '' : `\n ${preview}`}`);
|
|
386
423
|
continue;
|
|
387
424
|
}
|
|
388
425
|
const clippedBefore = snippet.beforeOmitted > 0;
|
|
@@ -390,25 +427,26 @@ export function renderGrepActivitiesView(visible, totalMatches, noTruncate, squa
|
|
|
390
427
|
truncated ||= clippedBefore || clippedAfter;
|
|
391
428
|
const text = `${clippedBefore ? '… ' : ''}${snippet.before}${highlightGrepMatch(snippet.match)}${snippet.after}${clippedAfter ? ' …' : ''}`;
|
|
392
429
|
const omitted = clippedBefore || clippedAfter
|
|
393
|
-
? `\n · ${snippet.beforeOmitted} chars before · ${snippet.afterOmitted} chars after`
|
|
430
|
+
? `\n${style('dim', ` · ${snippet.beforeOmitted} chars before · ${snippet.afterOmitted} chars after`)}`
|
|
394
431
|
: '';
|
|
395
|
-
chunks.push(`${
|
|
432
|
+
chunks.push(`${header}\n ${text.trim()}${omitted}`);
|
|
396
433
|
}
|
|
397
434
|
if (publicVisible.length === 1) {
|
|
398
|
-
chunks.push(
|
|
435
|
+
chunks.push(`${commandPrefix(squarePath)} history --at ${actId(publicVisible[0].index)} -C 2${truncated ? ' --no-truncate' : ''}`);
|
|
399
436
|
}
|
|
400
437
|
else if (truncated && publicVisible.length > 1) {
|
|
401
|
-
chunks.push(
|
|
438
|
+
chunks.push(`${commandPrefix(squarePath)} history --at ${actId(publicVisible[0].index)} -C 2 --no-truncate`);
|
|
402
439
|
}
|
|
403
440
|
return chunks.join('\n\n');
|
|
404
441
|
}
|
|
405
442
|
function renderActivityLimitBody(opts) {
|
|
406
443
|
const countText = opts.count !== undefined && opts.hardCap !== undefined ? ` (${opts.count}/${opts.hardCap})` : '';
|
|
407
|
-
const doneCommand = `${participantCommandPrefix(opts.squarePath, opts.name)} done
|
|
444
|
+
const doneCommand = `${participantCommandPrefix(opts.squarePath, opts.name)} done`;
|
|
408
445
|
return [
|
|
409
|
-
|
|
446
|
+
`${style('blocked', '✕')} your activity doesn't land — the cap is reached${countText}`,
|
|
410
447
|
...draftSavedLines(opts.draftPath),
|
|
411
|
-
|
|
448
|
+
' · your draft stays unsent; done only steps out',
|
|
449
|
+
doneCommand,
|
|
412
450
|
].join('\n');
|
|
413
451
|
}
|
|
414
452
|
export function renderActivityLimit(opts) {
|
|
@@ -416,17 +454,17 @@ export function renderActivityLimit(opts) {
|
|
|
416
454
|
}
|
|
417
455
|
export function renderWatchAlreadyActive(opts) {
|
|
418
456
|
return [
|
|
419
|
-
'✕ you are already catching
|
|
457
|
+
`${style('blocked', '✕')} you are already catching`,
|
|
420
458
|
` · an active catch is already running for ${participantIdentity(opts.name)}`,
|
|
421
|
-
' ·
|
|
422
|
-
|
|
459
|
+
' · --replace lets a new catch take over',
|
|
460
|
+
`${participantCommandPrefix(opts.squarePath, opts.name)} catch --idle 30m --replace`,
|
|
423
461
|
].join('\n');
|
|
424
462
|
}
|
|
425
463
|
export function renderWatchForceTakeover(_opts) {
|
|
426
464
|
return '✓ your new catch takes over';
|
|
427
465
|
}
|
|
428
466
|
export function renderWatchReplaceMissing(_opts) {
|
|
429
|
-
return '
|
|
467
|
+
return '· no catch stood here — yours takes the spot';
|
|
430
468
|
}
|
|
431
469
|
export function renderWatchReplaced(_opts) {
|
|
432
470
|
return '✕ a newer catch took over';
|
|
@@ -443,26 +481,26 @@ export function renderWatchStatus(opts) {
|
|
|
443
481
|
case 'empty-now': {
|
|
444
482
|
const prefix = participantCommandPrefix(opts.squarePath, opts.name);
|
|
445
483
|
const quiet = opts.status === 'stale' && opts.idleMs !== undefined
|
|
446
|
-
? `○ ${formatDuration(opts.idleMs)} of quiet — nothing new for you`
|
|
447
|
-
: '○ only footsteps in the square — nothing new for you';
|
|
484
|
+
? style('dim', `○ ${formatDuration(opts.idleMs)} of quiet — nothing new for you`)
|
|
485
|
+
: style('dim', '○ only footsteps in the square — nothing new for you');
|
|
448
486
|
return [
|
|
449
487
|
quiet,
|
|
450
488
|
...(opts.showCatchHint === false
|
|
451
489
|
? []
|
|
452
|
-
: [
|
|
490
|
+
: [`${prefix} catch --idle 30m`, ` glance: ${prefix} catch --now`]),
|
|
453
491
|
...presenceLines,
|
|
454
492
|
].join('\n');
|
|
455
493
|
}
|
|
456
494
|
case 'quorum':
|
|
457
|
-
return ['✓ everyone else has left — the square is yours alone
|
|
495
|
+
return [`${style('release', '✓')} everyone else has left — the square is yours alone`, `${participantCommandPrefix(opts.squarePath, opts.name)} done`].join('\n');
|
|
458
496
|
case 'capped':
|
|
459
|
-
return ['✕ nothing left in you — the cap is reached
|
|
497
|
+
return [`${style('blocked', '✕')} nothing left in you — the cap is reached`, `${participantCommandPrefix(opts.squarePath, opts.name)} done`].join('\n');
|
|
460
498
|
}
|
|
461
499
|
}
|
|
462
500
|
function renderRoomChanges(changes) {
|
|
463
501
|
if (changes.length === 0)
|
|
464
502
|
return '';
|
|
465
|
-
return ['▲ while your back was turned
|
|
503
|
+
return [`${style('changed', '▲')} while your back was turned`, ...changes.map((act) => ` · ${renderRoomChangeText(act)}`)].join('\n');
|
|
466
504
|
}
|
|
467
505
|
export function renderDoctorClean() {
|
|
468
506
|
return '✓ no problems found';
|
|
@@ -481,7 +519,7 @@ export function renderWatchOutput(history, publicItems, roomChanges, opts) {
|
|
|
481
519
|
quiet,
|
|
482
520
|
...(opts.showCatchHint === false
|
|
483
521
|
? []
|
|
484
|
-
: [
|
|
522
|
+
: [`${prefix} catch --idle 30m`, ` glance: ${prefix} catch --now`]),
|
|
485
523
|
].join('\n'));
|
|
486
524
|
}
|
|
487
525
|
const publicIndexes = new Set(publicItems.map((item) => item.index));
|
package/dist/presented.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import os from 'node:os';
|
|
2
|
+
import { hostLedgerRoot } from './host-ledger-root.js';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import { canonicalSquarePath } from './registry.js';
|
|
4
5
|
import { createHostLedgerPort } from './host-ledger-file-adapter.js';
|
|
5
6
|
import { formatActivityId } from './square-core.js';
|
|
6
7
|
import { projectPresentationEvidence } from './square-projections.js';
|
|
7
8
|
export function presentedPath(env = process.env) { return env.SQUARE_PRESENTED || path.join(os.homedir(), '.square', 'presented.ndjsonl'); }
|
|
8
|
-
function evidence(env = process.env) { const file = presentedPath(env); return createHostLedgerPort({
|
|
9
|
+
function evidence(env = process.env) { const file = presentedPath(env); return createHostLedgerPort({ rootPath: hostLedgerRoot(env) }); }
|
|
9
10
|
export async function hasPresentedForOwner(sessionId, squarePath, name, actIndex, env = process.env, now = Date.now()) { const resolved = await canonicalSquarePath(squarePath); return (await projectPresentationEvidence({ hostLedger: evidence(env), now, location: resolved, participant: name, sessionId, activity: formatActivityId(actIndex) })).some((row) => row.outcome === 'presented'); }
|
package/dist/registry.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** Machine-local participant discovery cache. */
|
|
2
2
|
import { type StoredAct } from './model.js';
|
|
3
|
-
import type { PresenceRecord } from './host-ledger.js';
|
|
3
|
+
import type { HostLedgerPort, PresenceRecord } from './host-ledger.js';
|
|
4
4
|
export type SessionChannel = 'claude-code' | 'codex' | 'opencode' | 'pi' | 'paseo' | 'unknown';
|
|
5
5
|
export interface RegistryBinding {
|
|
6
6
|
sessionId: string;
|
|
@@ -26,7 +26,7 @@ export declare function registryPath(env?: NodeJS.ProcessEnv): string;
|
|
|
26
26
|
export declare function canonicalSquarePath(squarePath: string): Promise<string>;
|
|
27
27
|
export declare function presenceEpoch(record: PresenceWithEpoch | undefined): number;
|
|
28
28
|
/** Runs `fn` inside the ownership claim critical section, under the same lock as claims and finalize. */
|
|
29
|
-
export declare function withOwnershipClaimLock<T>(env: NodeJS.ProcessEnv, fn: () => Promise<T
|
|
29
|
+
export declare function withOwnershipClaimLock<T>(env: NodeJS.ProcessEnv, fn: () => Promise<T>, signal?: AbortSignal): Promise<T>;
|
|
30
30
|
export declare function recordJoin(sessionId: string, name: string, squarePath: string, options?: RegistryWriteOptions): Promise<void>;
|
|
31
31
|
export declare function recordDone(sessionId: string, name: string, squarePath: string, options?: RegistryWriteOptions): Promise<void>;
|
|
32
32
|
export declare function readActiveBindings(now?: number, env?: NodeJS.ProcessEnv): Promise<RegistryBinding[]>;
|
|
@@ -45,6 +45,7 @@ export type CurrentParticipantBinding = Readonly<{
|
|
|
45
45
|
}>;
|
|
46
46
|
export type OwnershipClaim = {
|
|
47
47
|
readonly status: 'acquired' | 'owned';
|
|
48
|
+
readonly record: PresenceWithEpoch;
|
|
48
49
|
readonly sessionId: string;
|
|
49
50
|
readonly epoch: number;
|
|
50
51
|
} | undefined;
|
|
@@ -63,7 +64,8 @@ export type TakeoverRunResult<T> = {
|
|
|
63
64
|
};
|
|
64
65
|
export declare function readParticipantOwner(squarePath: string, name: string, env?: NodeJS.ProcessEnv): Promise<RegistryBinding | undefined>;
|
|
65
66
|
export declare function sessionOwnsParticipant(squarePath: string, name: string, sessionId: string, env?: NodeJS.ProcessEnv, expectedEpoch?: number): Promise<boolean>;
|
|
66
|
-
export declare function
|
|
67
|
+
export declare function releaseSessionParticipantClaim(squarePath: string, name: string, env: NodeJS.ProcessEnv, claim: OwnershipClaim): Promise<void>;
|
|
68
|
+
export declare function claimSessionParticipant(squarePath: string, name: string, env?: NodeJS.ProcessEnv, signal?: AbortSignal): Promise<OwnershipClaim>;
|
|
67
69
|
/**
|
|
68
70
|
* Fenced takeover: the ownership claim and the lifecycle run as one critical section under the
|
|
69
71
|
* presence-claim lock, so no second takeover can interleave between claim and commit. The claim
|
|
@@ -76,7 +78,7 @@ export declare function claimSessionParticipant(squarePath: string, name: string
|
|
|
76
78
|
export declare function claimSessionTakeover<T>(squarePath: string, name: string, env: NodeJS.ProcessEnv | undefined, opts: {
|
|
77
79
|
readonly expectedEpoch?: number;
|
|
78
80
|
readonly expectedSession?: string;
|
|
79
|
-
} | undefined, lifecycle: (claim: TakeoverClaimToken) => Promise<T
|
|
81
|
+
} | undefined, lifecycle: (claim: TakeoverClaimToken) => Promise<T>, signal?: AbortSignal): Promise<TakeoverRunResult<T>>;
|
|
80
82
|
export declare function releaseSessionParticipant(squarePath: string, name: string, env?: NodeJS.ProcessEnv): Promise<void>;
|
|
81
83
|
export declare function bindCurrentParticipant(squarePath: string, name: string, env?: NodeJS.ProcessEnv): Promise<CurrentParticipantBinding>;
|
|
82
84
|
export declare function unbindCurrentParticipant(squarePath: string, name: string, env?: NodeJS.ProcessEnv): Promise<boolean>;
|
|
@@ -97,4 +99,6 @@ export declare function recordLocalJoin(name: string, squarePath: string, env?:
|
|
|
97
99
|
export declare function recordLocalDone(name: string, squarePath: string, env?: NodeJS.ProcessEnv): Promise<void>;
|
|
98
100
|
export declare function recordSessionJoin(sessionId: string, name: string, squarePath: string, channel: SessionChannel, env?: NodeJS.ProcessEnv): Promise<string>;
|
|
99
101
|
export declare function recordSessionDone(sessionId: string, name: string, squarePath: string, channel: SessionChannel, env?: NodeJS.ProcessEnv): Promise<boolean>;
|
|
102
|
+
/** Bind the host ledger to the caller's captured environment. */
|
|
103
|
+
export declare function hostLedgerForEnv(env?: NodeJS.ProcessEnv): HostLedgerPort;
|
|
100
104
|
export {};
|