@astrosheep/square 0.3.57 → 0.3.61
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/codex-plugin/.codex-plugin/plugin.json +1 -1
- package/dist/activity.js +16 -14
- package/dist/attention-presentation.js +19 -19
- package/dist/automatic-session.js +2 -2
- package/dist/catch-decisions.js +1 -1
- package/dist/cli/context.js +16 -16
- package/dist/cli/observation-commands.js +85 -45
- package/dist/cli/program.js +33 -9
- package/dist/cli/square-commands.js +21 -19
- package/dist/decisions.d.ts +2 -0
- package/dist/decisions.js +38 -31
- package/dist/delivery-health.d.ts +3 -2
- package/dist/delivery-health.js +79 -47
- package/dist/delivery-operations.js +50 -8
- package/dist/diagnostic-redaction.d.ts +2 -0
- package/dist/diagnostic-redaction.js +40 -0
- package/dist/help.js +1 -1
- package/dist/host-ledger-file-adapter.js +2 -2
- package/dist/host-ledger.d.ts +9 -2
- package/dist/list.js +12 -9
- package/dist/notifications.js +28 -11
- package/dist/ports.d.ts +5 -1
- package/dist/presentation.d.ts +7 -0
- package/dist/presentation.js +75 -67
- package/dist/registry.js +2 -2
- package/dist/runtime.d.ts +8 -0
- package/dist/runtime.js +37 -0
- package/dist/square-actions.js +4 -4
- package/dist/stream.js +1 -1
- package/dist/tty-style.d.ts +7 -0
- package/dist/tty-style.js +13 -0
- package/dist/views.js +6 -5
- package/dist/wake-attempts.d.ts +18 -0
- package/dist/wake-attempts.js +38 -11
- package/dist/watch.js +24 -11
- package/extensions/square-pi.js +16 -6
- package/package.json +3 -3
package/dist/list.js
CHANGED
|
@@ -5,6 +5,7 @@ import { closeOpenSquare } from './open-square.js';
|
|
|
5
5
|
import { listPresentation } from './views.js';
|
|
6
6
|
import { formatRelativeTime } from './time.js';
|
|
7
7
|
import { participantIdentity, quoteShell } from './presentation.js';
|
|
8
|
+
import { style } from './tty-style.js';
|
|
8
9
|
const DEFAULT_LIST_DEPTH = 4;
|
|
9
10
|
const MAX_LIST_DEPTH = 16;
|
|
10
11
|
const DEFAULT_LIST_LIMIT = 20;
|
|
@@ -84,35 +85,37 @@ function renderSquareList(items, stopped, depth, limit, after) {
|
|
|
84
85
|
const page = after === undefined ? items : items.filter((item) => item.path.localeCompare(after) > 0);
|
|
85
86
|
const shownItems = page.slice(0, limit);
|
|
86
87
|
if (items.length === 0) {
|
|
87
|
-
return [`(no squares found)`, ...(stopped ? [`○
|
|
88
|
+
return [`(no squares found)`, ...(stopped ? [style('dim', `○ the walk stopped early — deeper squares may still be hiding`)] : [])].join('\n') + '\n';
|
|
88
89
|
}
|
|
89
90
|
const now = Date.now();
|
|
90
91
|
const lines = ['squares'];
|
|
91
92
|
for (const item of shownItems) {
|
|
92
|
-
|
|
93
|
+
const glyph = item.activities > 0 ? '●' : style('dim', '○');
|
|
94
|
+
lines.push(`${glyph} ${characterPreview(item.path)}${style('dim', ` · ${formatRelativeTime(item.lastActiveAt, now)} · ${item.participants.length} in the square · ${item.activities} ${item.activities === 1 ? 'activity' : 'activities'}`)}`);
|
|
93
95
|
const shownContext = item.context.slice(0, CONTEXT_PREVIEW_LINES);
|
|
94
96
|
if (shownContext.length === 0) {
|
|
95
|
-
lines.push('
|
|
97
|
+
lines.push(` ${style('dim', 'context')} · (none)`);
|
|
96
98
|
}
|
|
97
99
|
else {
|
|
98
|
-
shownContext.forEach((line, index) => lines.push(` ${index === 0 ? 'context' : ' '} · ${characterPreview(line)}`));
|
|
100
|
+
shownContext.forEach((line, index) => lines.push(` ${index === 0 ? style('dim', 'context') : ' '} · ${characterPreview(line)}`));
|
|
99
101
|
const hiddenContext = item.context.length - shownContext.length;
|
|
100
102
|
if (hiddenContext > 0)
|
|
101
|
-
lines.push(` · … ${hiddenContext} more ${hiddenContext === 1 ? 'line' : 'lines'}`);
|
|
103
|
+
lines.push(style('dim', ` · … ${hiddenContext} more ${hiddenContext === 1 ? 'line' : 'lines'}`));
|
|
102
104
|
}
|
|
103
105
|
const shownParticipants = item.participants.slice(0, PARTICIPANT_PREVIEW_COUNT);
|
|
104
106
|
const hiddenParticipants = item.participants.length - shownParticipants.length;
|
|
105
|
-
|
|
107
|
+
const participantNames = shownParticipants.length === 0 ? 'nobody' : shownParticipants.map((participant) => participantIdentity(characterPreview(participant))).join(' · ');
|
|
108
|
+
lines.push(` ${style('dim', 'participants')} · ${participantNames}${hiddenParticipants > 0 ? style('dim', ` · … ${hiddenParticipants} more`) : ''}`);
|
|
106
109
|
}
|
|
107
110
|
const hiddenItems = page.length - shownItems.length;
|
|
108
111
|
if (hiddenItems > 0) {
|
|
109
112
|
const cursor = shownItems.at(-1)?.path;
|
|
110
|
-
lines.push(stopped ? ' … more squares' : ` … ${hiddenItems} more ${hiddenItems === 1 ? 'square' : 'squares'}`);
|
|
113
|
+
lines.push(style('dim', stopped ? ' … more squares' : ` … ${hiddenItems} more ${hiddenItems === 1 ? 'square' : 'squares'}`));
|
|
111
114
|
if (cursor !== undefined)
|
|
112
|
-
lines.push(
|
|
115
|
+
lines.push(`square list --depth ${depth} --limit ${limit} --after ${quoteShell(cursor)}`);
|
|
113
116
|
}
|
|
114
117
|
if (stopped)
|
|
115
|
-
lines.push(`○
|
|
118
|
+
lines.push(style('dim', `○ the walk stopped early — deeper squares may still be hiding`));
|
|
116
119
|
return lines.join('\n') + '\n';
|
|
117
120
|
}
|
|
118
121
|
function parseListOptions(args, usage) {
|
package/dist/notifications.js
CHANGED
|
@@ -102,28 +102,28 @@ export async function createDefaultWakeTransport(hostLedger, clock, env = proces
|
|
|
102
102
|
const adapters = await defaultWakeAdapters();
|
|
103
103
|
return createWakeTransport(env.SQUARE_DISABLE_PASEO_WAKE === '1' ? adapters.filter((adapter) => adapter.kind !== 'paseo') : adapters, hostLedger, clock);
|
|
104
104
|
}
|
|
105
|
-
async function
|
|
105
|
+
async function wakeRequestCurrentness(request, hostLedger, now) {
|
|
106
106
|
const activity = parseActivityId(request.activity);
|
|
107
107
|
if (activity === undefined)
|
|
108
|
-
return false;
|
|
108
|
+
return { current: false, activityPending: false, sessionBound: false, routePublished: false, observationAvailable: true };
|
|
109
109
|
let square;
|
|
110
110
|
try {
|
|
111
111
|
square = await openSquare(request.location, { hostLedger });
|
|
112
112
|
const current = await observeSquare({ artifact: square.artifact, hostLedger, location: request.location, now });
|
|
113
|
-
const
|
|
113
|
+
const activityPending = current.pending.some((entry) => nameKey(entry.recipient) === nameKey(request.participant)
|
|
114
114
|
&& entry.notifications.some((notification) => notification.item.index === activity));
|
|
115
|
-
const
|
|
115
|
+
const sessionBound = current.bindings.some((binding) => nameKey(binding.participant) === nameKey(request.route.participant)
|
|
116
116
|
&& binding.sessionId === request.route.sessionId
|
|
117
117
|
&& binding.location === request.route.location);
|
|
118
|
-
const
|
|
118
|
+
const routePublished = (current.state.routes ?? []).some((route) => route.location === request.route.location
|
|
119
119
|
&& nameKey(route.participant) === nameKey(request.route.participant)
|
|
120
120
|
&& route.sessionId === request.route.sessionId
|
|
121
121
|
&& route.kind === request.route.kind
|
|
122
122
|
&& JSON.stringify(route.address) === JSON.stringify(request.route.address));
|
|
123
|
-
return
|
|
123
|
+
return { current: activityPending && sessionBound && routePublished, activityPending, sessionBound, routePublished, observationAvailable: true };
|
|
124
124
|
}
|
|
125
125
|
catch {
|
|
126
|
-
return false;
|
|
126
|
+
return { current: false, activityPending: false, sessionBound: false, routePublished: false, observationAvailable: false };
|
|
127
127
|
}
|
|
128
128
|
finally {
|
|
129
129
|
if (square !== undefined)
|
|
@@ -151,15 +151,32 @@ export function createWakeTransport(adapters, hostLedger, clock) {
|
|
|
151
151
|
if (adapter === undefined)
|
|
152
152
|
return { outcome: 'not-capable', diagnostic: `no adapter for ${request.route.kind}` };
|
|
153
153
|
try {
|
|
154
|
-
|
|
154
|
+
let revalidation;
|
|
155
|
+
const result = await adapter.dispatch(request.route.address, renderWakePayload(request), async () => {
|
|
156
|
+
if (!(await (beforeSend ?? (async () => true))()))
|
|
157
|
+
return false;
|
|
158
|
+
revalidation = await wakeRequestCurrentness(request, hostLedger, clock());
|
|
159
|
+
return revalidation.current;
|
|
160
|
+
}, timeoutMs);
|
|
155
161
|
if (result.outcome === 'accepted')
|
|
156
162
|
return { outcome: 'accepted' };
|
|
157
163
|
if (result.outcome === 'failed')
|
|
158
|
-
return { outcome: 'failed', message: result.message };
|
|
164
|
+
return { outcome: 'failed', ...(result.signature === undefined ? {} : { signature: result.signature }), message: result.message, ...(result.diagnostic === undefined ? {} : { diagnostic: result.diagnostic }) };
|
|
159
165
|
if (result.outcome === 'unavailable')
|
|
160
|
-
return { outcome: 'failed', message: result.message, unavailable: true, ...(result.retainRoute === true ? { retainRoute: true } : {}), ...(result.routeStale === true ? { routeStale: true } : {}) };
|
|
166
|
+
return { outcome: 'failed', signature: result.signature, message: result.message, ...(result.diagnostic === undefined ? {} : { diagnostic: result.diagnostic }), unavailable: true, ...(result.retainRoute === true ? { retainRoute: true } : {}), ...(result.routeStale === true ? { routeStale: true } : {}) };
|
|
161
167
|
if (result.outcome === 'unknown')
|
|
162
|
-
return { outcome: 'unknown',
|
|
168
|
+
return { outcome: 'unknown', ...(result.signature === undefined ? {} : { signature: result.signature }), ...(result.message === undefined ? {} : { message: result.message }), ...(result.diagnostic === undefined ? {} : { diagnostic: result.diagnostic }) };
|
|
169
|
+
if (result.outcome === 'cancelled' && revalidation !== undefined && !revalidation.current) {
|
|
170
|
+
return {
|
|
171
|
+
outcome: 'failed',
|
|
172
|
+
signature: 'pre_send_revalidation_failed',
|
|
173
|
+
message: revalidation.observationAvailable
|
|
174
|
+
? 'Wake was not sent because current attention, session binding, or route no longer matches.'
|
|
175
|
+
: 'Wake was not sent because current Square delivery state could not be verified.',
|
|
176
|
+
diagnostic: revalidation,
|
|
177
|
+
unavailable: true,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
163
180
|
return { outcome: 'unknown', diagnostic: 'wake dispatch cancelled' };
|
|
164
181
|
}
|
|
165
182
|
catch (error) {
|
package/dist/ports.d.ts
CHANGED
|
@@ -69,6 +69,8 @@ export type WakeOutcome = {
|
|
|
69
69
|
} | {
|
|
70
70
|
readonly outcome: 'failed';
|
|
71
71
|
readonly message?: string;
|
|
72
|
+
readonly signature?: string;
|
|
73
|
+
readonly diagnostic?: unknown;
|
|
72
74
|
readonly attemptN?: number;
|
|
73
75
|
readonly unavailable?: boolean;
|
|
74
76
|
readonly retainRoute?: boolean;
|
|
@@ -78,7 +80,9 @@ export type WakeOutcome = {
|
|
|
78
80
|
readonly diagnostic?: string;
|
|
79
81
|
} | {
|
|
80
82
|
readonly outcome: 'unknown';
|
|
81
|
-
readonly
|
|
83
|
+
readonly signature?: string;
|
|
84
|
+
readonly message?: string;
|
|
85
|
+
readonly diagnostic?: unknown;
|
|
82
86
|
readonly attemptN?: number;
|
|
83
87
|
};
|
|
84
88
|
export interface SquareObservation {
|
package/dist/presentation.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ export type WatchStatus = 'stale' | 'empty-now' | 'quorum' | 'capped';
|
|
|
6
6
|
interface HeaderOptions {
|
|
7
7
|
participantCount?: number;
|
|
8
8
|
held?: boolean;
|
|
9
|
+
/** Refusal bodies go to stderr, which stays literal even when it is a TTY. */
|
|
10
|
+
plain?: boolean;
|
|
9
11
|
}
|
|
10
12
|
interface ParticipantOutputOptions {
|
|
11
13
|
squarePath: string;
|
|
@@ -46,6 +48,11 @@ interface WatchStatusOptions extends ParticipantOutputOptions {
|
|
|
46
48
|
}
|
|
47
49
|
export declare function displayPath(squarePath: string, cwd?: string): string;
|
|
48
50
|
export declare function withPathOutput(squarePath: string, body?: string, opts?: HeaderOptions): string;
|
|
51
|
+
/** The same header/blank/body layout as withPathOutput, for refusal bodies written to stderr. */
|
|
52
|
+
export declare function formatRefusal(squarePath: string, bodyLines: string[], opts?: {
|
|
53
|
+
participantCount?: number;
|
|
54
|
+
held?: boolean;
|
|
55
|
+
}): string;
|
|
49
56
|
export declare function quoteShell(value: string): string;
|
|
50
57
|
export declare function commandPrefix(squarePath: string): string;
|
|
51
58
|
export declare function participantCommandPrefix(squarePath: string, name: string): string;
|
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 } 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,10 @@ 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`;
|
|
47
57
|
}
|
|
48
58
|
export function participantsRecoveryCommand(squarePath) {
|
|
49
|
-
return
|
|
59
|
+
return `${commandPrefix(squarePath)} participants`;
|
|
50
60
|
}
|
|
51
61
|
function formatAge(ms) {
|
|
52
62
|
if (ms === undefined)
|
|
@@ -68,6 +78,10 @@ function presenceGlyph(participant) {
|
|
|
68
78
|
return '●';
|
|
69
79
|
return '○';
|
|
70
80
|
}
|
|
81
|
+
function styledPresenceGlyph(participant) {
|
|
82
|
+
const glyph = presenceGlyph(participant);
|
|
83
|
+
return glyph === '○' ? style('dim', glyph) : glyph;
|
|
84
|
+
}
|
|
71
85
|
function presenceText(participant, now) {
|
|
72
86
|
if (participant.state === 'done') {
|
|
73
87
|
return participant.lastActiveAt === undefined ? 'stepped out of the square' : `stepped out of the square · ${formatRelativeTime(participant.lastActiveAt, now)}`;
|
|
@@ -86,17 +100,17 @@ export function renderPresenceLines(participants, now, limit = 5) {
|
|
|
86
100
|
.sort(compareParticipantActivity);
|
|
87
101
|
const shown = recent.slice(0, limit);
|
|
88
102
|
if (shown.length === 0)
|
|
89
|
-
return [' ○ nobody nearby'];
|
|
90
|
-
const lines = shown.map((p) => ` ${
|
|
103
|
+
return [style('dim', ' ○ nobody nearby')];
|
|
104
|
+
const lines = shown.map((p) => ` ${styledPresenceGlyph(p)} ${participantIdentity(p.name)}${style('dim', ` · ${presenceText(p, now)}`)}`);
|
|
91
105
|
const remaining = recent.length - shown.length;
|
|
92
106
|
if (remaining > 0)
|
|
93
|
-
lines.push(` ○ …
|
|
107
|
+
lines.push(style('dim', ` ○ … ${remaining} more nearby`));
|
|
94
108
|
return lines;
|
|
95
109
|
}
|
|
96
110
|
const EXPRESS_HINTS = [
|
|
97
111
|
'*asterisks* are your body — *slams table*, *sketches in the air*, *shrugs*',
|
|
98
112
|
"you're standing in a square — words and gestures both land",
|
|
99
|
-
'the square
|
|
113
|
+
'the square runs on conversation — say things that move it, not nods that fill it',
|
|
100
114
|
];
|
|
101
115
|
export function expressHintLine(ownActivityCount) {
|
|
102
116
|
if (ownActivityCount !== 1 && ownActivityCount % 5 !== 0)
|
|
@@ -119,7 +133,7 @@ export function truncateExternalDiagnostic(diagnostic) {
|
|
|
119
133
|
}
|
|
120
134
|
function previewBody(body, maxLen = BODY_PREVIEW_LENGTH) {
|
|
121
135
|
const preview = truncateChars(body, maxLen);
|
|
122
|
-
return preview.remaining === 0 ? preview.text : `${preview.text}\n
|
|
136
|
+
return preview.remaining === 0 ? preview.text : `${preview.text}\n${style('dim', `… ${preview.remaining} more chars`)}`;
|
|
123
137
|
}
|
|
124
138
|
const UNREAD_PREVIEW_CHARS = 120;
|
|
125
139
|
export function previewActivityBody(body) {
|
|
@@ -127,7 +141,7 @@ export function previewActivityBody(body) {
|
|
|
127
141
|
if (compact === '')
|
|
128
142
|
return '(empty)';
|
|
129
143
|
const preview = truncateChars(compact, UNREAD_PREVIEW_CHARS);
|
|
130
|
-
return preview.remaining === 0 ? preview.text : `${preview.text}
|
|
144
|
+
return preview.remaining === 0 ? preview.text : `${preview.text}${style('dim', `… (+${preview.remaining} chars)`)}`;
|
|
131
145
|
}
|
|
132
146
|
export function renderRoomChangeText(event) {
|
|
133
147
|
const actor = event.actor ?? 'someone';
|
|
@@ -166,7 +180,7 @@ export function renderEventCli(event, opts = {}) {
|
|
|
166
180
|
case 'hold':
|
|
167
181
|
return `· ${renderRoomChangeText(event)}`;
|
|
168
182
|
case 'resume':
|
|
169
|
-
return
|
|
183
|
+
return `${style('release', '✓')} ${renderRoomChangeText(event)}`;
|
|
170
184
|
case 'listen':
|
|
171
185
|
case 'ignore':
|
|
172
186
|
return `· ${renderRoomChangeText(event)}`;
|
|
@@ -177,11 +191,12 @@ export function renderEventCli(event, opts = {}) {
|
|
|
177
191
|
? ` · calls your name across the square — ${participantIdentity(mention)}`
|
|
178
192
|
: '';
|
|
179
193
|
const replySuffix = event.reply === undefined ? '' : ` · replies to ${actId(event.reply)}`;
|
|
180
|
-
|
|
194
|
+
const meta = ` #${opts.actNumber ?? 1} · ${actId(event)} · ${formatRelativeTime(event.at, now)}${mentionSuffix}${replySuffix}`;
|
|
195
|
+
return `● ${participantIdentity(event.actor)}${style('dim', meta)}${bodySuffix(body)}`;
|
|
181
196
|
}
|
|
182
197
|
case 'done': {
|
|
183
198
|
const body = renderedBody(event.body, maxBody);
|
|
184
|
-
return
|
|
199
|
+
return `${style('dim', '○')} ${participantIdentity(event.actor)} stepped out of the square — done${style('dim', ` · ${actId(event)} · ${formatRelativeTime(event.at, now)}`)}${bodySuffix(body)}`;
|
|
185
200
|
}
|
|
186
201
|
case 'read':
|
|
187
202
|
return '';
|
|
@@ -190,8 +205,9 @@ export function renderEventCli(event, opts = {}) {
|
|
|
190
205
|
function renderPresenceOnlySay(event, opts = {}) {
|
|
191
206
|
const audience = audienceOf(event);
|
|
192
207
|
const targets = audience.kind === 'bell' ? [] : audience.names;
|
|
208
|
+
const meta = style('dim', ` #${opts.actNumber ?? 1} · ${actId(event)} · ${formatRelativeTime(event.at, opts.now)}`);
|
|
193
209
|
if (targets.length === 0) {
|
|
194
|
-
return `● ${participantIdentity(event.actor)}
|
|
210
|
+
return `● ${participantIdentity(event.actor)}${meta}`;
|
|
195
211
|
}
|
|
196
212
|
const visibleTargets = targets.slice(0, MAX_IDENTITY_SET_SIZE).map((name) => participantIdentity(name));
|
|
197
213
|
const remainingTargets = targets.length - visibleTargets.length;
|
|
@@ -199,7 +215,7 @@ function renderPresenceOnlySay(event, opts = {}) {
|
|
|
199
215
|
...visibleTargets,
|
|
200
216
|
...(remainingTargets === 0 ? [] : [`${remainingTargets} ${remainingTargets === 1 ? 'other' : 'others'}`]),
|
|
201
217
|
].join(' and ')}`;
|
|
202
|
-
return `● ${participantIdentity(event.actor)}
|
|
218
|
+
return `● ${participantIdentity(event.actor)}${meta}\n${style('dim', ` talked to${dest}`)}`;
|
|
203
219
|
}
|
|
204
220
|
export function renderAmbientEvent(event, viewer, opts = {}) {
|
|
205
221
|
if (event.kind !== 'say')
|
|
@@ -216,7 +232,7 @@ export function renderAmbientEvent(event, viewer, opts = {}) {
|
|
|
216
232
|
return renderEventCli(event, opts);
|
|
217
233
|
}
|
|
218
234
|
function draftSavedLines(draftPath) {
|
|
219
|
-
return draftPath === undefined ? [] : [`· draft kept: ${draftPath}`];
|
|
235
|
+
return draftPath === undefined ? [] : [style('dim', `· draft kept: ${draftPath}`)];
|
|
220
236
|
}
|
|
221
237
|
function withDraftInput(command, draftPath) {
|
|
222
238
|
return draftPath === undefined ? command : `${command} < ${quoteShell(draftPath)}`;
|
|
@@ -229,15 +245,15 @@ function renderUnreadSummary(opts) {
|
|
|
229
245
|
...item.previews.slice(-1).map((preview) => {
|
|
230
246
|
const rendered = renderAmbientEvent(preview.act, opts.viewer, { actNumber: preview.number, perception: preview.perception });
|
|
231
247
|
if (rendered === '')
|
|
232
|
-
return ` · ${participantIdentity(item.name)} spoke — ${formatAge(item.latestActivityAgeMs)} ago
|
|
248
|
+
return style('dim', ` · ${participantIdentity(item.name)} spoke — ${formatAge(item.latestActivityAgeMs)} ago`);
|
|
233
249
|
if (preview.perception === 'presence') {
|
|
234
|
-
return ` · ${participantIdentity(item.name)} spoke — ${formatAge(item.latestActivityAgeMs)} ago · ${rendered.replace(/\n/g, ' ')}
|
|
250
|
+
return style('dim', ` · ${participantIdentity(item.name)} spoke — ${formatAge(item.latestActivityAgeMs)} ago · ${rendered.replace(/\n/g, ' ')}`);
|
|
235
251
|
}
|
|
236
|
-
return ` · ${participantIdentity(item.name)} spoke — ${formatAge(item.latestActivityAgeMs)} ago · "${previewActivityBody(preview.act.body)}"
|
|
252
|
+
return style('dim', ` · ${participantIdentity(item.name)} spoke — ${formatAge(item.latestActivityAgeMs)} ago · "${previewActivityBody(preview.act.body)}"`);
|
|
237
253
|
}),
|
|
238
254
|
]),
|
|
239
|
-
...(remainingSummaries === 0 ? [] : [` · ${remainingSummaries} more participants have unread activity`]),
|
|
240
|
-
...opts.roomChanges.map((act) => ` · ${renderRoomChangeText(act)}`),
|
|
255
|
+
...(remainingSummaries === 0 ? [] : [style('dim', ` · ${remainingSummaries} more participants have unread activity`)]),
|
|
256
|
+
...opts.roomChanges.map((act) => style('dim', ` · ${renderRoomChangeText(act)}`)),
|
|
241
257
|
];
|
|
242
258
|
}
|
|
243
259
|
export function renderPendingFeed(history, publicItems, roomChanges, viewer = '', squareState) {
|
|
@@ -261,12 +277,12 @@ export function renderPendingFeed(history, publicItems, roomChanges, viewer = ''
|
|
|
261
277
|
export function renderActivityBlocked(opts) {
|
|
262
278
|
const readNowCommand = `${participantCommandPrefix(opts.squarePath, opts.name)} catch --now`;
|
|
263
279
|
return withPathOutput(opts.squarePath, [
|
|
264
|
-
|
|
280
|
+
`${style('blocked', '✕')} your activity doesn't land — the square moved behind your back`,
|
|
265
281
|
...renderUnreadSummary({ activitySummaries: opts.activitySummaries, roomChanges: opts.unreadRoomChanges, viewer: opts.name }),
|
|
266
282
|
...draftSavedLines(opts.draftPath),
|
|
267
|
-
|
|
283
|
+
`${readNowCommand}`,
|
|
268
284
|
' take it in, then express again',
|
|
269
|
-
|
|
285
|
+
`${withDraftInput(opts.forceCommand, opts.draftPath)}`,
|
|
270
286
|
' only if you truly mean to express over unread activity',
|
|
271
287
|
].join('\n'), { participantCount: opts.participantCount, held: opts.held });
|
|
272
288
|
}
|
|
@@ -280,16 +296,16 @@ export function renderExpressNoWait(opts) {
|
|
|
280
296
|
const retryCommand = opts.forceCommand;
|
|
281
297
|
const lines = opts.reason === 'throttled'
|
|
282
298
|
? [
|
|
283
|
-
'✕ the square is packed
|
|
299
|
+
`${style('blocked', '✕')} the square is packed`,
|
|
284
300
|
` · next opening in ${formatDuration(opts.delayMs)}`,
|
|
285
301
|
...draftSavedLines(opts.draftPath),
|
|
286
|
-
|
|
302
|
+
`${withDraftInput(retryCommand, opts.draftPath)}`,
|
|
287
303
|
]
|
|
288
304
|
: [
|
|
289
|
-
|
|
290
|
-
` · ${opts.holdReason ?? 'the square holds its breath'}
|
|
305
|
+
`${style('blocked', '✕')} your activity doesn't land — a hand is raised`,
|
|
306
|
+
style('dim', ` · ${opts.holdReason ?? 'the square holds its breath'}`),
|
|
291
307
|
...draftSavedLines(opts.draftPath),
|
|
292
|
-
|
|
308
|
+
`${withDraftInput(retryCommand, opts.draftPath)}`,
|
|
293
309
|
];
|
|
294
310
|
return withPathOutput(opts.squarePath, lines.join('\n'), { participantCount: opts.participantCount, held: opts.held });
|
|
295
311
|
}
|
|
@@ -302,32 +318,24 @@ export function renderPublicTail(squareState, events, lastN, now, viewer = '') {
|
|
|
302
318
|
.filter(Boolean)
|
|
303
319
|
.join('\n\n');
|
|
304
320
|
}
|
|
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;
|
|
321
|
+
function lastPresenceAnchor(squareState, name, landed) {
|
|
322
|
+
return presenceAnchor(squareState, name, landed);
|
|
316
323
|
}
|
|
317
324
|
export function renderPresenceAnchor(names) {
|
|
318
325
|
const visibleNames = names.slice(0, MAX_IDENTITY_SET_SIZE);
|
|
319
326
|
const participants = visibleNames.map((name) => participantIdentity(name)).join(', ');
|
|
320
327
|
const remaining = names.length - visibleNames.length;
|
|
321
328
|
const suffix = remaining === 0 ? '' : ` and ${remaining} more`;
|
|
322
|
-
return names.length === 1 ? `→ ${participants} was here` : `→ ${participants}${suffix} were here
|
|
329
|
+
return style('dim', names.length === 1 ? `→ ${participants} was here` : `→ ${participants}${suffix} were here`);
|
|
323
330
|
}
|
|
324
331
|
export function renderActivitiesView(squareState, visible, lastN, noTruncate, squarePath, viewer = '', mode = 'ambient') {
|
|
325
332
|
const publicVisible = visible.filter((act) => act.kind === 'say' || act.kind === 'done');
|
|
326
333
|
const shown = lastN == null ? publicVisible : publicVisible.slice(-lastN);
|
|
327
334
|
const previewLen = noTruncate ? undefined : BODY_PREVIEW_LENGTH;
|
|
328
335
|
const markers = new Map();
|
|
336
|
+
const landed = replayLandedAudiences(squareState.acts);
|
|
329
337
|
for (const participant of rosterNames(squareState)) {
|
|
330
|
-
const anchor = lastPresenceAnchor(squareState, participant);
|
|
338
|
+
const anchor = lastPresenceAnchor(squareState, participant, landed);
|
|
331
339
|
if (anchor >= 0)
|
|
332
340
|
markers.set(anchor, [...(markers.get(anchor) ?? []), participant]);
|
|
333
341
|
}
|
|
@@ -351,15 +359,13 @@ export function renderActivitiesView(squareState, visible, lastN, noTruncate, sq
|
|
|
351
359
|
if (previewLen !== undefined) {
|
|
352
360
|
const truncated = shown.some((act) => act.kind === 'say' && act.body.length > previewLen && (mode === 'archive' || perceiveActivity(squareState, act, viewer) === 'full'));
|
|
353
361
|
if (truncated)
|
|
354
|
-
chunks.push(
|
|
362
|
+
chunks.push(`${commandPrefix(squarePath)} history --no-truncate`);
|
|
355
363
|
}
|
|
356
364
|
return chunks.join('\n\n');
|
|
357
365
|
}
|
|
358
366
|
const GREP_PREVIEW_CHARS = 160;
|
|
359
367
|
function highlightGrepMatch(text) {
|
|
360
|
-
|
|
361
|
-
return text;
|
|
362
|
-
return `\x1b[38;5;222m\x1b[1m${text}\x1b[0m`;
|
|
368
|
+
return style('match', text);
|
|
363
369
|
}
|
|
364
370
|
export function renderGrepActivitiesView(visible, totalMatches, noTruncate, squarePath, pattern, fixed = false, perception) {
|
|
365
371
|
const publicVisible = visible.filter((act) => act.kind === 'say' || act.kind === 'done');
|
|
@@ -370,19 +376,20 @@ export function renderGrepActivitiesView(visible, totalMatches, noTruncate, squa
|
|
|
370
376
|
let truncated = false;
|
|
371
377
|
for (const act of publicVisible) {
|
|
372
378
|
const rawBody = act.body ?? '';
|
|
379
|
+
const header = `${style('dim', actId(act.index))} · ${act.actor === undefined ? 'unknown' : participantIdentity(act.actor)} · ${style('dim', formatTimestamp(act.at))}`;
|
|
373
380
|
if (perception?.(act) === 'presence') {
|
|
374
|
-
chunks.push(
|
|
381
|
+
chunks.push(header);
|
|
375
382
|
continue;
|
|
376
383
|
}
|
|
377
384
|
if (noTruncate === true) {
|
|
378
385
|
const body = rawBody.split('\n').map((line) => ` ${line}`).join('\n');
|
|
379
|
-
chunks.push(`${
|
|
386
|
+
chunks.push(`${header}\n${body}`);
|
|
380
387
|
continue;
|
|
381
388
|
}
|
|
382
389
|
const snippet = grepSnippet(rawBody, pattern, GREP_PREVIEW_CHARS, fixed);
|
|
383
390
|
if (snippet === undefined) {
|
|
384
391
|
const preview = previewBody(rawBody, GREP_PREVIEW_CHARS);
|
|
385
|
-
chunks.push(`${
|
|
392
|
+
chunks.push(`${header}${preview === '' ? '' : `\n ${preview}`}`);
|
|
386
393
|
continue;
|
|
387
394
|
}
|
|
388
395
|
const clippedBefore = snippet.beforeOmitted > 0;
|
|
@@ -390,15 +397,15 @@ export function renderGrepActivitiesView(visible, totalMatches, noTruncate, squa
|
|
|
390
397
|
truncated ||= clippedBefore || clippedAfter;
|
|
391
398
|
const text = `${clippedBefore ? '… ' : ''}${snippet.before}${highlightGrepMatch(snippet.match)}${snippet.after}${clippedAfter ? ' …' : ''}`;
|
|
392
399
|
const omitted = clippedBefore || clippedAfter
|
|
393
|
-
? `\n · ${snippet.beforeOmitted} chars before · ${snippet.afterOmitted} chars after`
|
|
400
|
+
? `\n${style('dim', ` · ${snippet.beforeOmitted} chars before · ${snippet.afterOmitted} chars after`)}`
|
|
394
401
|
: '';
|
|
395
|
-
chunks.push(`${
|
|
402
|
+
chunks.push(`${header}\n ${text.trim()}${omitted}`);
|
|
396
403
|
}
|
|
397
404
|
if (publicVisible.length === 1) {
|
|
398
|
-
chunks.push(
|
|
405
|
+
chunks.push(`${commandPrefix(squarePath)} history --at ${actId(publicVisible[0].index)} -C 2${truncated ? ' --no-truncate' : ''}`);
|
|
399
406
|
}
|
|
400
407
|
else if (truncated && publicVisible.length > 1) {
|
|
401
|
-
chunks.push(
|
|
408
|
+
chunks.push(`${commandPrefix(squarePath)} history --at ${actId(publicVisible[0].index)} -C 2 --no-truncate`);
|
|
402
409
|
}
|
|
403
410
|
return chunks.join('\n\n');
|
|
404
411
|
}
|
|
@@ -406,9 +413,10 @@ function renderActivityLimitBody(opts) {
|
|
|
406
413
|
const countText = opts.count !== undefined && opts.hardCap !== undefined ? ` (${opts.count}/${opts.hardCap})` : '';
|
|
407
414
|
const doneCommand = `${participantCommandPrefix(opts.squarePath, opts.name)} done -`;
|
|
408
415
|
return [
|
|
409
|
-
|
|
416
|
+
`${style('blocked', '✕')} your activity doesn't land — the cap is reached${countText}`,
|
|
410
417
|
...draftSavedLines(opts.draftPath),
|
|
411
|
-
|
|
418
|
+
`${withDraftInput(doneCommand, opts.draftPath)}`,
|
|
419
|
+
' your draft becomes your final note',
|
|
412
420
|
].join('\n');
|
|
413
421
|
}
|
|
414
422
|
export function renderActivityLimit(opts) {
|
|
@@ -416,17 +424,17 @@ export function renderActivityLimit(opts) {
|
|
|
416
424
|
}
|
|
417
425
|
export function renderWatchAlreadyActive(opts) {
|
|
418
426
|
return [
|
|
419
|
-
'✕ you are already catching
|
|
427
|
+
`${style('blocked', '✕')} you are already catching`,
|
|
420
428
|
` · an active catch is already running for ${participantIdentity(opts.name)}`,
|
|
421
|
-
' ·
|
|
422
|
-
|
|
429
|
+
' · --replace lets a new catch take over',
|
|
430
|
+
`${participantCommandPrefix(opts.squarePath, opts.name)} catch --idle 30m --replace`,
|
|
423
431
|
].join('\n');
|
|
424
432
|
}
|
|
425
433
|
export function renderWatchForceTakeover(_opts) {
|
|
426
434
|
return '✓ your new catch takes over';
|
|
427
435
|
}
|
|
428
436
|
export function renderWatchReplaceMissing(_opts) {
|
|
429
|
-
return '
|
|
437
|
+
return '· no catch stood here — yours takes the spot';
|
|
430
438
|
}
|
|
431
439
|
export function renderWatchReplaced(_opts) {
|
|
432
440
|
return '✕ a newer catch took over';
|
|
@@ -443,26 +451,26 @@ export function renderWatchStatus(opts) {
|
|
|
443
451
|
case 'empty-now': {
|
|
444
452
|
const prefix = participantCommandPrefix(opts.squarePath, opts.name);
|
|
445
453
|
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';
|
|
454
|
+
? style('dim', `○ ${formatDuration(opts.idleMs)} of quiet — nothing new for you`)
|
|
455
|
+
: style('dim', '○ only footsteps in the square — nothing new for you');
|
|
448
456
|
return [
|
|
449
457
|
quiet,
|
|
450
458
|
...(opts.showCatchHint === false
|
|
451
459
|
? []
|
|
452
|
-
: [
|
|
460
|
+
: [`${prefix} catch --idle 30m`, ` glance: ${prefix} catch --now`]),
|
|
453
461
|
...presenceLines,
|
|
454
462
|
].join('\n');
|
|
455
463
|
}
|
|
456
464
|
case 'quorum':
|
|
457
|
-
return ['✓ everyone else has left — the square is yours alone
|
|
465
|
+
return [`${style('release', '✓')} everyone else has left — the square is yours alone`, `${participantCommandPrefix(opts.squarePath, opts.name)} done -`].join('\n');
|
|
458
466
|
case 'capped':
|
|
459
|
-
return ['✕ nothing left in you — the cap is reached
|
|
467
|
+
return [`${style('blocked', '✕')} nothing left in you — the cap is reached`, `${participantCommandPrefix(opts.squarePath, opts.name)} done -`].join('\n');
|
|
460
468
|
}
|
|
461
469
|
}
|
|
462
470
|
function renderRoomChanges(changes) {
|
|
463
471
|
if (changes.length === 0)
|
|
464
472
|
return '';
|
|
465
|
-
return ['▲ while your back was turned
|
|
473
|
+
return [`${style('changed', '▲')} while your back was turned`, ...changes.map((act) => ` · ${renderRoomChangeText(act)}`)].join('\n');
|
|
466
474
|
}
|
|
467
475
|
export function renderDoctorClean() {
|
|
468
476
|
return '✓ no problems found';
|
|
@@ -481,7 +489,7 @@ export function renderWatchOutput(history, publicItems, roomChanges, opts) {
|
|
|
481
489
|
quiet,
|
|
482
490
|
...(opts.showCatchHint === false
|
|
483
491
|
? []
|
|
484
|
-
: [
|
|
492
|
+
: [`${prefix} catch --idle 30m`, ` glance: ${prefix} catch --now`]),
|
|
485
493
|
].join('\n'));
|
|
486
494
|
}
|
|
487
495
|
const publicIndexes = new Set(publicItems.map((item) => item.index));
|
package/dist/registry.js
CHANGED
|
@@ -101,7 +101,7 @@ export async function claimSessionParticipant(squarePath, name, env = process.en
|
|
|
101
101
|
epoch: 1,
|
|
102
102
|
}, 'local');
|
|
103
103
|
if (result.status === 'busy')
|
|
104
|
-
throw new SquareError('already_joined',
|
|
104
|
+
throw new SquareError('already_joined', `✕ ${name} already stands here — another session holds the name`);
|
|
105
105
|
if (result.status === 'degraded')
|
|
106
106
|
throw result.error;
|
|
107
107
|
const epoch = presenceEpoch(result.record) || (result.status === 'acquired' ? 1 : 0);
|
|
@@ -203,7 +203,7 @@ export async function releaseSessionParticipant(squarePath, name, env = process.
|
|
|
203
203
|
export async function bindCurrentParticipant(squarePath, name, env = process.env) { if (squareAssignedParticipantName(env) !== name)
|
|
204
204
|
throw new SquareError('invalid_args', `The current session is not assigned ${name}`); const sessionId = await localParticipantOwner(squarePath, name, env); if (sessionId !== undefined)
|
|
205
205
|
return { created: false, sessionId }; if ((await lookupParticipant(squarePath, name, Date.now(), env)).at(0) !== undefined)
|
|
206
|
-
throw new SquareError('already_joined',
|
|
206
|
+
throw new SquareError('already_joined', `✕ ${name} already stands here — another session holds the name`); await recordLocalJoin(name, squarePath, env); const currentSessionId = await localParticipantOwner(squarePath, name, env); if (currentSessionId === undefined)
|
|
207
207
|
throw new Error(`Current participant binding did not commit for ${name}`); return { created: true, sessionId: currentSessionId }; }
|
|
208
208
|
export async function unbindCurrentParticipant(squarePath, name, env = process.env) { const identities = new Set(localSessionIdentities(env).map((identity) => identity.sessionId)); const current = (await lookupParticipant(squarePath, name, Date.now(), env)).filter((binding) => identities.has(binding.sessionId)); for (const binding of current)
|
|
209
209
|
await recordSessionDone(binding.sessionId, binding.name, binding.squarePath, binding.channel, env); return current.length > 0; }
|
package/dist/runtime.d.ts
CHANGED
|
@@ -38,6 +38,14 @@ export declare function observationFor(squareState: SquareState, name: string, i
|
|
|
38
38
|
export declare function recordObservation(squareState: SquareState, name: string, index: number, state: ObservationState, at?: number): boolean;
|
|
39
39
|
export declare function readCursor(squareState: SquareState, name: string, landed?: LandedAudienceReplay): number;
|
|
40
40
|
export declare function latestActIndex(acts: StoredAct[]): number;
|
|
41
|
+
/**
|
|
42
|
+
* "Was here" is a claim of presence, so it rides on evidence: the participant's own acts
|
|
43
|
+
* and seen receipts after their latest join. Anything else — in particular acts merely
|
|
44
|
+
* not addressed to them — says nothing about where they actually were.
|
|
45
|
+
*/
|
|
46
|
+
export declare function presenceEvidenceCursor(squareState: SquareState, name: string, landed?: LandedAudienceReplay): number;
|
|
47
|
+
/** The last public act the participant was verifiably present for, or -1 when there is no evidence. */
|
|
48
|
+
export declare function presenceAnchor(squareState: SquareState, name: string, landed?: LandedAudienceReplay): number;
|
|
41
49
|
export declare function freshWatchLease(squareState: SquareState, name: string, at?: number): WatchLease | undefined;
|
|
42
50
|
export declare function watchLease(squareState: SquareState, name: string): WatchLease | undefined;
|
|
43
51
|
export declare function writeWatchLease(squareState: SquareState, name: string, lease: WatchLease): void;
|