@astrosheep/square 0.3.4 → 0.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/codex-plugin/.codex-plugin/plugin.json +3 -2
- package/dist/activity-feed.js +26 -18
- package/dist/activity.js +23 -22
- package/dist/artifact.js +126 -202
- package/dist/claude-hook.js +45 -21
- package/dist/cli/context.js +143 -0
- package/dist/cli/harness-command.js +50 -0
- package/dist/cli/maintenance-commands.js +76 -0
- package/dist/cli/meta-commands.js +28 -0
- package/dist/cli/observation-commands.js +453 -0
- package/dist/cli/program.js +48 -0
- package/dist/cli/registry.js +40 -0
- package/dist/cli/square-commands.js +219 -0
- package/dist/cmd/notify-once.js +23 -21
- package/dist/compact.js +6 -19
- package/dist/decisions.js +53 -86
- package/dist/delivery-health.js +104 -210
- package/dist/delivery.js +68 -18
- package/dist/doctor.js +9 -8
- package/dist/harness-claude.js +68 -0
- package/dist/harness-codex.js +119 -0
- package/dist/harness-links.js +123 -0
- package/dist/harness-stage.js +36 -0
- package/dist/harness.js +94 -576
- package/dist/help.js +44 -35
- package/dist/inbox.js +12 -11
- package/dist/index.js +30 -129
- package/dist/list.js +1 -1
- package/dist/model.js +0 -6
- package/dist/notification-failures.js +54 -0
- package/dist/notifications.js +47 -62
- package/dist/paseo-timeline.js +58 -188
- package/dist/presentation.js +55 -63
- package/dist/presented.js +9 -8
- package/dist/registry.js +55 -45
- package/dist/runtime.js +26 -137
- package/dist/square-application.js +264 -0
- package/dist/square-core.js +3 -11
- package/dist/square.js +5 -1362
- package/dist/stream.js +27 -126
- package/dist/wake-sink.js +134 -188
- package/dist/watch.js +79 -138
- package/extensions/square-opencode.js +1 -1
- package/extensions/square-pi.js +8 -130
- package/guides/architect.md +3 -3
- package/guides/participant.md +25 -16
- package/package.json +2 -2
- package/skills/brainstorm/SKILL.md +25 -32
- package/skills/square/.claude-plugin/plugin.json +1 -1
- package/skills/square/SKILL.md +39 -107
- package/skills/square-feedback/SKILL.md +4 -4
- package/dist/terminal.js +0 -125
package/dist/watch.js
CHANGED
|
@@ -1,21 +1,16 @@
|
|
|
1
1
|
import { setTimeout as sleep } from 'node:timers/promises';
|
|
2
2
|
import { loadSquare } from './artifact.js';
|
|
3
3
|
import { SquareError, nameKey, } from './model.js';
|
|
4
|
-
import {
|
|
5
|
-
import { SLEEP_MS, STALE_MS, WATCH_HEARTBEAT_MS, WATCH_STALE_MS, countSays, currentHold, doneNames, freshWatchLease, hasQuorum, inSquareCount,
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
4
|
+
import { markDeliveredNotifications } from './delivery.js';
|
|
5
|
+
import { SLEEP_MS, STALE_MS, WATCH_HEARTBEAT_MS, WATCH_STALE_MS, countSays, currentHold, doneNames, freshWatchLease, hasQuorum, inSquareCount, nowMs, touchPresenceCursor, writeWatchLease, } from './runtime.js';
|
|
6
|
+
import { withSquareLock, writeSquareDoc } from './square-application.js';
|
|
7
|
+
import { renderWatchForceTakeover, renderWatchAlreadyActive, renderWatchOutput, renderWatchReplaced, renderWatchStatus, participantCommandPrefix, withPathOutput, } from './presentation.js';
|
|
8
|
+
import { ackPeerDelta, deliveryDelta, filteredPeerActivities, filteredRoomChanges, matchesFeedFilter, peerPublicActs, peerRoomChanges, } from './activity-feed.js';
|
|
8
9
|
import { coreParticipants, resolveKnownName } from './decisions.js';
|
|
9
|
-
import { hasAutomaticDeliveryIdentity } from './registry.js';
|
|
10
|
-
|
|
10
|
+
import { hasAutomaticDeliveryIdentity, localParticipantOwner } from './registry.js';
|
|
11
|
+
import { execute } from './square-application.js';
|
|
11
12
|
function catchDelta(doc, name) {
|
|
12
|
-
|
|
13
|
-
const seen = new Set(items.map((item) => item.index));
|
|
14
|
-
for (const notification of deriveDeliveryModel(doc).pendingFor(name)) {
|
|
15
|
-
if (!seen.has(notification.item.index))
|
|
16
|
-
items.push(notification.item);
|
|
17
|
-
}
|
|
18
|
-
return items.sort((a, b) => a.index - b.index);
|
|
13
|
+
return deliveryDelta(doc, name);
|
|
19
14
|
}
|
|
20
15
|
function watchStatusExitCode(status) {
|
|
21
16
|
return status === 'capped' ? 1 : 0;
|
|
@@ -30,21 +25,22 @@ function leaseFilter(opts) {
|
|
|
30
25
|
};
|
|
31
26
|
return Object.keys(filter).length === 0 ? undefined : filter;
|
|
32
27
|
}
|
|
33
|
-
function setLease(doc, name, id, at, opts) {
|
|
28
|
+
function setLease(doc, name, id, at, opts, ownerId) {
|
|
34
29
|
const filter = leaseFilter(opts);
|
|
35
|
-
doc
|
|
30
|
+
writeWatchLease(doc, name, {
|
|
36
31
|
leaseId: id,
|
|
32
|
+
...(ownerId === undefined ? {} : { ownerId }),
|
|
37
33
|
heartbeatAt: at,
|
|
38
34
|
expiresAt: at + WATCH_STALE_MS,
|
|
39
35
|
...(filter ? { filter } : {}),
|
|
40
|
-
};
|
|
36
|
+
});
|
|
41
37
|
}
|
|
42
38
|
function sameLease(doc, name, id, at = nowMs()) {
|
|
43
39
|
return freshWatchLease(doc, name, at)?.leaseId === id;
|
|
44
40
|
}
|
|
45
41
|
function consumeDelta(doc, name, delta, delivered, at) {
|
|
46
42
|
const consumed = ackPeerDelta(doc, name, delta);
|
|
47
|
-
const receipts =
|
|
43
|
+
const receipts = markDeliveredNotifications(doc, name, delivered, at);
|
|
48
44
|
return consumed || receipts;
|
|
49
45
|
}
|
|
50
46
|
function watchOutputResult(squarePath, doc, name, delta, opts = {}) {
|
|
@@ -67,7 +63,7 @@ function loadPresence(squarePath) {
|
|
|
67
63
|
try {
|
|
68
64
|
const doc = loadSquare(squarePath);
|
|
69
65
|
const now = nowMs();
|
|
70
|
-
return { participants: coreParticipants(doc, now)
|
|
66
|
+
return { participants: coreParticipants(doc, now), now };
|
|
71
67
|
}
|
|
72
68
|
catch {
|
|
73
69
|
return undefined;
|
|
@@ -92,38 +88,70 @@ function writeWatchOutput(squarePath, name, stdout, status, idleMs) {
|
|
|
92
88
|
const fallback = showCatchHint
|
|
93
89
|
? `» ${participantCommandPrefix(squarePath, name)} catch --idle 30m\n stay available for new activity`
|
|
94
90
|
: '';
|
|
95
|
-
process.stdout.write(
|
|
91
|
+
process.stdout.write(withPathOutput(squarePath, [stdout.trimEnd(), fallback].filter(Boolean).join('\n\n').trimEnd(), headerOpts));
|
|
92
|
+
}
|
|
93
|
+
function writeWatchTerminal(squarePath, name, status, idleMs) {
|
|
94
|
+
const presence = loadPresence(squarePath);
|
|
95
|
+
process.stdout.write(withPathOutput(squarePath, renderWatchStatus({
|
|
96
|
+
status,
|
|
97
|
+
squarePath,
|
|
98
|
+
name,
|
|
99
|
+
...(idleMs === undefined ? {} : { idleMs }),
|
|
100
|
+
presence,
|
|
101
|
+
showCatchHint: !hasAutomaticDeliveryIdentity(),
|
|
102
|
+
}), { participantCount: loadHeaderCount(squarePath) }));
|
|
103
|
+
}
|
|
104
|
+
function writeWatchReplaced(squarePath, name) {
|
|
105
|
+
process.stdout.write(withPathOutput(squarePath, renderWatchReplaced({ squarePath, name }), { participantCount: loadHeaderCount(squarePath) }));
|
|
106
|
+
}
|
|
107
|
+
async function finishWatchResult(squarePath, name, result, leaseId, idleMs) {
|
|
108
|
+
if (result.type === 'output') {
|
|
109
|
+
await endWatch(squarePath, name, leaseId);
|
|
110
|
+
writeWatchOutput(squarePath, name, result.stdout, result.status);
|
|
111
|
+
process.exitCode = watchStatusExitCode(result.status);
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
if (result.type === 'terminal') {
|
|
115
|
+
await endWatch(squarePath, name, leaseId);
|
|
116
|
+
writeWatchTerminal(squarePath, name, result.status, idleMs);
|
|
117
|
+
process.exitCode = watchStatusExitCode(result.status);
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
if (result.type === 'replaced') {
|
|
121
|
+
writeWatchReplaced(squarePath, name);
|
|
122
|
+
process.exitCode = 0;
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
return false;
|
|
96
126
|
}
|
|
97
127
|
async function beginWatch(squarePath, name, opts) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
128
|
+
const at = nowMs();
|
|
129
|
+
const id = leaseId();
|
|
130
|
+
const ownerId = localParticipantOwner(squarePath, name);
|
|
131
|
+
const committed = await execute(squarePath, {
|
|
132
|
+
type: 'lease',
|
|
133
|
+
name,
|
|
134
|
+
leaseId: id,
|
|
135
|
+
...(ownerId === undefined ? {} : { ownerId }),
|
|
136
|
+
at,
|
|
137
|
+
expiresAt: at + WATCH_STALE_MS,
|
|
138
|
+
force: opts.replace,
|
|
139
|
+
filter: leaseFilter(opts),
|
|
109
140
|
});
|
|
141
|
+
if (committed.result.type === 'active')
|
|
142
|
+
return committed.result;
|
|
143
|
+
return { type: 'started', leaseId: id, replaced: committed.result.replaced, heartbeatAt: at };
|
|
110
144
|
}
|
|
111
145
|
async function endWatch(squarePath, name, id) {
|
|
112
146
|
if (id === undefined)
|
|
113
147
|
return;
|
|
114
|
-
await
|
|
115
|
-
const doc = loadSquare(squarePath);
|
|
116
|
-
if (doc.runtime.leases[name]?.leaseId !== id)
|
|
117
|
-
return;
|
|
118
|
-
delete doc.runtime.leases[name];
|
|
119
|
-
writeSquareDoc(squarePath, doc);
|
|
120
|
-
});
|
|
148
|
+
await execute(squarePath, { type: 'release-lease', name, leaseId: id });
|
|
121
149
|
}
|
|
122
150
|
function installWatchInterruptHandler(squarePath, name, currentLeaseId) {
|
|
123
151
|
const onInterrupt = () => {
|
|
124
152
|
void (async () => {
|
|
125
153
|
await endWatch(squarePath, name, currentLeaseId());
|
|
126
|
-
process.stdout.write(withPathOutput(squarePath,
|
|
154
|
+
process.stdout.write(withPathOutput(squarePath, '✕ catch stopped'));
|
|
127
155
|
process.exit(130);
|
|
128
156
|
})().catch((error) => {
|
|
129
157
|
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
@@ -149,7 +177,7 @@ async function cmdWatchNow(squarePath, name, opts) {
|
|
|
149
177
|
const result = await withSquareLock(squarePath, () => {
|
|
150
178
|
const doc = loadSquare(squarePath);
|
|
151
179
|
const at = nowMs();
|
|
152
|
-
const touched = touchPresenceCursor(doc, name, at
|
|
180
|
+
const touched = touchPresenceCursor(doc, name, at);
|
|
153
181
|
const delta = catchDelta(doc, name);
|
|
154
182
|
const peerPublic = peerPublicActs(delta, name);
|
|
155
183
|
const roomChanges = peerRoomChanges(delta, name);
|
|
@@ -177,25 +205,7 @@ async function cmdWatchNow(squarePath, name, opts) {
|
|
|
177
205
|
return { type: 'terminal', status };
|
|
178
206
|
return { type: 'terminal', status: 'empty-now' };
|
|
179
207
|
});
|
|
180
|
-
|
|
181
|
-
writeWatchOutput(squarePath, name, result.stdout, result.status);
|
|
182
|
-
process.exitCode = watchStatusExitCode(result.status);
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
if (result.type === 'terminal') {
|
|
186
|
-
const presence = loadPresence(squarePath);
|
|
187
|
-
process.stdout.write(withPathOutput(squarePath, renderWatchStatus({
|
|
188
|
-
status: result.status,
|
|
189
|
-
squarePath,
|
|
190
|
-
name,
|
|
191
|
-
presence,
|
|
192
|
-
showCatchHint: !hasAutomaticDeliveryIdentity(),
|
|
193
|
-
}), {
|
|
194
|
-
participantCount: loadHeaderCount(squarePath),
|
|
195
|
-
}));
|
|
196
|
-
process.exitCode = watchStatusExitCode(result.status);
|
|
197
|
-
return;
|
|
198
|
-
}
|
|
208
|
+
await finishWatchResult(squarePath, name, result, undefined);
|
|
199
209
|
}
|
|
200
210
|
export async function cmdWatch(squarePath, name, opts) {
|
|
201
211
|
let initialDoc;
|
|
@@ -216,14 +226,11 @@ export async function cmdWatch(squarePath, name, opts) {
|
|
|
216
226
|
throw err;
|
|
217
227
|
}
|
|
218
228
|
if (opts.now) {
|
|
219
|
-
if (opts.activityCount !== 1)
|
|
220
|
-
process.stderr.write('--count is ignored with --now\n');
|
|
221
229
|
await cmdWatchNow(squarePath, name, opts);
|
|
222
230
|
return;
|
|
223
231
|
}
|
|
224
232
|
const start = await beginWatch(squarePath, name, opts);
|
|
225
233
|
if (start.type === 'active') {
|
|
226
|
-
const presence = loadPresence(squarePath);
|
|
227
234
|
process.stdout.write(withPathOutput(squarePath, renderWatchAlreadyActive({ squarePath, name }), { participantCount: loadHeaderCount(squarePath) }));
|
|
228
235
|
process.exit(1);
|
|
229
236
|
}
|
|
@@ -231,7 +238,6 @@ export async function cmdWatch(squarePath, name, opts) {
|
|
|
231
238
|
let currentLeaseId = start.leaseId;
|
|
232
239
|
let nextHeartbeatAt = start.heartbeatAt + WATCH_HEARTBEAT_MS;
|
|
233
240
|
if (start.replaced) {
|
|
234
|
-
const presence = loadPresence(squarePath);
|
|
235
241
|
process.stdout.write(withPathOutput(squarePath, renderWatchForceTakeover({ squarePath, name }), { participantCount: loadHeaderCount(squarePath) }));
|
|
236
242
|
}
|
|
237
243
|
const idleMs = opts.idleMs ?? STALE_MS;
|
|
@@ -241,12 +247,13 @@ export async function cmdWatch(squarePath, name, opts) {
|
|
|
241
247
|
const result = await withSquareLock(squarePath, () => {
|
|
242
248
|
const doc = loadSquare(squarePath);
|
|
243
249
|
const at = nowMs();
|
|
244
|
-
|
|
250
|
+
const lease = freshWatchLease(doc, name, at);
|
|
251
|
+
if (lease === undefined || lease.leaseId !== currentLeaseId)
|
|
245
252
|
return { type: 'replaced' };
|
|
246
253
|
let mutated = false;
|
|
247
254
|
if (at >= nextHeartbeatAt) {
|
|
248
|
-
setLease(doc, name, currentLeaseId, at, opts);
|
|
249
|
-
mutated = touchPresenceCursor(doc, name, at
|
|
255
|
+
setLease(doc, name, currentLeaseId, at, opts, lease.ownerId);
|
|
256
|
+
mutated = touchPresenceCursor(doc, name, at) || mutated;
|
|
250
257
|
nextHeartbeatAt = at + WATCH_HEARTBEAT_MS;
|
|
251
258
|
mutated = true;
|
|
252
259
|
}
|
|
@@ -265,7 +272,7 @@ export async function cmdWatch(squarePath, name, opts) {
|
|
|
265
272
|
}
|
|
266
273
|
if (hasDeliverable &&
|
|
267
274
|
hasFilteredDeliverable &&
|
|
268
|
-
(filteredActivities.length
|
|
275
|
+
(filteredActivities.length > 0 || matchingRoomChanges.length > 0 || status !== undefined)) {
|
|
269
276
|
return watchOutputResult(squarePath, doc, name, delta, {
|
|
270
277
|
participants: opts.participants,
|
|
271
278
|
mention: opts.mention,
|
|
@@ -283,49 +290,12 @@ export async function cmdWatch(squarePath, name, opts) {
|
|
|
283
290
|
writeSquareDoc(squarePath, doc);
|
|
284
291
|
return { type: 'sleep' };
|
|
285
292
|
});
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
if (opts.follow === true && status === undefined) {
|
|
290
|
-
writeWatchOutput(squarePath, name, result.stdout);
|
|
291
|
-
staleSince = nowMs();
|
|
292
|
-
break;
|
|
293
|
-
}
|
|
294
|
-
await endWatch(squarePath, name, currentLeaseId);
|
|
295
|
-
currentLeaseId = undefined;
|
|
296
|
-
writeWatchOutput(squarePath, name, result.stdout, status);
|
|
297
|
-
process.exitCode = watchStatusExitCode(status);
|
|
298
|
-
return;
|
|
299
|
-
}
|
|
300
|
-
case 'terminal':
|
|
301
|
-
await endWatch(squarePath, name, currentLeaseId);
|
|
302
|
-
currentLeaseId = undefined;
|
|
303
|
-
const presence = loadPresence(squarePath);
|
|
304
|
-
process.stdout.write(withPathOutput(squarePath, renderWatchStatus({
|
|
305
|
-
status: result.status,
|
|
306
|
-
squarePath,
|
|
307
|
-
name,
|
|
308
|
-
presence,
|
|
309
|
-
showCatchHint: !hasAutomaticDeliveryIdentity(),
|
|
310
|
-
}), {
|
|
311
|
-
participantCount: loadHeaderCount(squarePath),
|
|
312
|
-
}));
|
|
313
|
-
process.exitCode = watchStatusExitCode(result.status);
|
|
314
|
-
return;
|
|
315
|
-
case 'replaced':
|
|
316
|
-
currentLeaseId = undefined;
|
|
317
|
-
{
|
|
318
|
-
const presence = loadPresence(squarePath);
|
|
319
|
-
process.stdout.write(withPathOutput(squarePath, renderWatchReplaced({ squarePath, name }), { participantCount: loadHeaderCount(squarePath) }));
|
|
320
|
-
}
|
|
321
|
-
process.exitCode = 0;
|
|
322
|
-
return;
|
|
323
|
-
case 'sleep':
|
|
324
|
-
break;
|
|
325
|
-
case 'held':
|
|
326
|
-
staleSince = nowMs();
|
|
327
|
-
break;
|
|
293
|
+
if (await finishWatchResult(squarePath, name, result, currentLeaseId)) {
|
|
294
|
+
currentLeaseId = undefined;
|
|
295
|
+
return;
|
|
328
296
|
}
|
|
297
|
+
if (result.type === 'held')
|
|
298
|
+
staleSince = nowMs();
|
|
329
299
|
if (nowMs() - staleSince >= idleMs) {
|
|
330
300
|
const result = await withSquareLock(squarePath, () => {
|
|
331
301
|
const doc = loadSquare(squarePath);
|
|
@@ -343,37 +313,8 @@ export async function cmdWatch(squarePath, name, opts) {
|
|
|
343
313
|
}
|
|
344
314
|
return { type: 'terminal', status: 'stale' };
|
|
345
315
|
});
|
|
346
|
-
if (result
|
|
347
|
-
currentLeaseId = undefined;
|
|
348
|
-
{
|
|
349
|
-
const presence = loadPresence(squarePath);
|
|
350
|
-
process.stdout.write(withPathOutput(squarePath, renderWatchReplaced({ squarePath, name }), { participantCount: loadHeaderCount(squarePath) }));
|
|
351
|
-
}
|
|
352
|
-
process.exitCode = 0;
|
|
353
|
-
return;
|
|
354
|
-
}
|
|
355
|
-
if (result.type === 'output') {
|
|
356
|
-
await endWatch(squarePath, name, currentLeaseId);
|
|
357
|
-
currentLeaseId = undefined;
|
|
358
|
-
writeWatchOutput(squarePath, name, result.stdout, result.status);
|
|
359
|
-
process.exitCode = 0;
|
|
360
|
-
return;
|
|
361
|
-
}
|
|
362
|
-
if (result.type === 'terminal') {
|
|
363
|
-
await endWatch(squarePath, name, currentLeaseId);
|
|
316
|
+
if (await finishWatchResult(squarePath, name, result, currentLeaseId, idleMs)) {
|
|
364
317
|
currentLeaseId = undefined;
|
|
365
|
-
const presence = loadPresence(squarePath);
|
|
366
|
-
process.stdout.write(withPathOutput(squarePath, renderWatchStatus({
|
|
367
|
-
status: result.status,
|
|
368
|
-
squarePath,
|
|
369
|
-
name,
|
|
370
|
-
idleMs,
|
|
371
|
-
presence,
|
|
372
|
-
showCatchHint: !hasAutomaticDeliveryIdentity(),
|
|
373
|
-
}), {
|
|
374
|
-
participantCount: loadHeaderCount(squarePath),
|
|
375
|
-
}));
|
|
376
|
-
process.exitCode = watchStatusExitCode(result.status);
|
|
377
318
|
return;
|
|
378
319
|
}
|
|
379
320
|
}
|
|
@@ -33,7 +33,7 @@ export default async function squareOpenCodePlugin({ client }) {
|
|
|
33
33
|
'experimental.chat.system.transform': async (input, output) => {
|
|
34
34
|
if (!input.sessionID) return;
|
|
35
35
|
try {
|
|
36
|
-
// Membership comes only from explicit join/
|
|
36
|
+
// Membership comes only from explicit join/express/catch claims, never env inheritance.
|
|
37
37
|
presentOnce(
|
|
38
38
|
input.sessionID,
|
|
39
39
|
(sessionId) => deferToActiveCatch(sessionInbox(sessionId)),
|
package/extensions/square-pi.js
CHANGED
|
@@ -1,167 +1,45 @@
|
|
|
1
|
-
import
|
|
2
|
-
import os from 'node:os';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
|
|
1
|
+
import { renderClaudeInboxContext } from '../dist/claude-hook.js';
|
|
5
2
|
import { sessionInbox } from '../dist/inbox.js';
|
|
6
3
|
import { presentOnce } from '../dist/presented.js';
|
|
7
4
|
|
|
8
|
-
function quoteShell(value) {
|
|
9
|
-
return `'${String(value).replace(/'/g, `'\\''`)}'`;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
5
|
export function pendingInbox(inbox) {
|
|
13
|
-
return inbox.filter((
|
|
6
|
+
return inbox.filter((item) => item.notifications?.length > 0);
|
|
14
7
|
}
|
|
15
8
|
|
|
16
9
|
export function inboxKeys(inbox) {
|
|
17
|
-
return pendingInbox(inbox).flatMap((
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
)
|
|
21
|
-
);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function notificationMessageId(squarePath, actIndex) {
|
|
25
|
-
return `square:${squarePath}#act_${actIndex}`;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const INJECT_BODY_MAX = 2048;
|
|
29
|
-
|
|
30
|
-
function injectBodyPreview(body, squarePath, name, actIndex) {
|
|
31
|
-
const compact = String(body ?? '').replace(/\r\n/g, '\n');
|
|
32
|
-
if (compact.length <= INJECT_BODY_MAX) return compact;
|
|
33
|
-
const pointer = `square --square-path ${quoteShell(squarePath)} --as ${quoteShell(name)} echo --ids act_${actIndex} --full`;
|
|
34
|
-
return `${compact.slice(0, INJECT_BODY_MAX).trimEnd()}\n… [truncated] full echo: ${pointer}`;
|
|
10
|
+
return pendingInbox(inbox).flatMap((item) => item.notifications.map((note) =>
|
|
11
|
+
`${item.squarePath}\u0000${item.name.toLocaleLowerCase()}\u0000${note.actIndex}`
|
|
12
|
+
));
|
|
35
13
|
}
|
|
36
14
|
|
|
37
15
|
export function renderPiInbox(inbox) {
|
|
38
|
-
|
|
39
|
-
const count = pending.reduce((total, membership) => total + membership.notifications.length, 0);
|
|
40
|
-
const noun = count === 1 ? 'notification' : 'notifications';
|
|
41
|
-
return [
|
|
42
|
-
`<system-reminder source="square">You have ${count} unread Square ${noun}.`,
|
|
43
|
-
...pending.flatMap((membership) => {
|
|
44
|
-
const command = `square --square-path ${quoteShell(membership.squarePath)} --as ${quoteShell(membership.name)} catch --now`;
|
|
45
|
-
return membership.notifications.map((item) => {
|
|
46
|
-
const id = notificationMessageId(membership.squarePath, item.actIndex);
|
|
47
|
-
const body = injectBodyPreview(item.body, membership.squarePath, membership.name, item.actIndex);
|
|
48
|
-
return [
|
|
49
|
-
`${id} · ${membership.squarePath}: @${membership.name} from @${item.actor} (${item.via})`,
|
|
50
|
-
body,
|
|
51
|
-
`Ack with: ${command}`,
|
|
52
|
-
].join('\n');
|
|
53
|
-
});
|
|
54
|
-
}),
|
|
55
|
-
'Ids are stable across turns. If you already acted on an id, do not repeat the action; still run catch --now to mark delivered.',
|
|
56
|
-
'Read and respond in the square before finishing the current task.</system-reminder>',
|
|
57
|
-
].join('\n');
|
|
16
|
+
return renderClaudeInboxContext(pendingInbox(inbox));
|
|
58
17
|
}
|
|
59
18
|
|
|
60
19
|
export default function squarePiExtension(pi) {
|
|
61
20
|
let sessionId;
|
|
62
21
|
let previousSessionId;
|
|
63
|
-
|
|
64
|
-
let checkRunning = false;
|
|
65
|
-
let debounceTimer;
|
|
66
|
-
const watchers = new Map();
|
|
67
|
-
|
|
68
|
-
function present(deliver) {
|
|
69
|
-
if (!sessionId) return undefined;
|
|
70
|
-
return presentOnce(
|
|
71
|
-
sessionId,
|
|
72
|
-
(currentSessionId) => {
|
|
73
|
-
const inbox = sessionInbox(currentSessionId);
|
|
74
|
-
updateWatchers(inbox);
|
|
75
|
-
return inbox;
|
|
76
|
-
},
|
|
77
|
-
deliver
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function scheduleAccelerate() {
|
|
82
|
-
if (debounceTimer) clearTimeout(debounceTimer);
|
|
83
|
-
debounceTimer = setTimeout(() => {
|
|
84
|
-
debounceTimer = undefined;
|
|
85
|
-
void accelerateWake();
|
|
86
|
-
}, 75);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function watchDirectory(directory) {
|
|
90
|
-
const resolved = path.resolve(directory);
|
|
91
|
-
if (watchers.has(resolved)) return;
|
|
92
|
-
try {
|
|
93
|
-
const watcher = fs.watch(resolved, { persistent: false }, scheduleAccelerate);
|
|
94
|
-
watchers.set(resolved, watcher);
|
|
95
|
-
} catch {
|
|
96
|
-
// Accelerate-layer discovery is best-effort only.
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function updateWatchers(inbox) {
|
|
101
|
-
const registry = process.env.SQUARE_REGISTRY || path.join(os.homedir(), '.square', 'sessions.ndjsonl');
|
|
102
|
-
try {
|
|
103
|
-
fs.mkdirSync(path.dirname(registry), { recursive: true });
|
|
104
|
-
} catch {}
|
|
105
|
-
watchDirectory(path.dirname(registry));
|
|
106
|
-
for (const membership of inbox) watchDirectory(path.dirname(membership.squarePath));
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/** Accelerate tier: best-effort mid-turn wake. Failures only cost latency. */
|
|
110
|
-
async function accelerateWake() {
|
|
111
|
-
if (checkRunning || !sessionContext) return;
|
|
112
|
-
checkRunning = true;
|
|
113
|
-
try {
|
|
114
|
-
present((inbox) => {
|
|
115
|
-
const pending = pendingInbox(inbox);
|
|
116
|
-
const content = renderPiInbox(pending);
|
|
117
|
-
const options = sessionContext.isIdle()
|
|
118
|
-
? { triggerTurn: true }
|
|
119
|
-
: { triggerTurn: true, deliverAs: 'steer' };
|
|
120
|
-
pi.sendMessage(
|
|
121
|
-
{ customType: 'square', content, display: true, details: { keys: inboxKeys(pending) } },
|
|
122
|
-
options
|
|
123
|
-
);
|
|
124
|
-
});
|
|
125
|
-
} catch {
|
|
126
|
-
// Accelerate-layer failures must never break the session.
|
|
127
|
-
} finally {
|
|
128
|
-
checkRunning = false;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
22
|
+
const present = (deliver) => sessionId === undefined ? undefined : presentOnce(sessionId, (id) => sessionInbox(id), deliver);
|
|
131
23
|
|
|
132
24
|
pi.on('session_start', async (_event, ctx) => {
|
|
133
|
-
sessionContext = ctx;
|
|
134
25
|
sessionId = ctx.sessionManager.getSessionId();
|
|
135
26
|
previousSessionId = process.env.SQUARE_PI_SESSION_ID;
|
|
136
27
|
process.env.SQUARE_PI_SESSION_ID = sessionId;
|
|
137
|
-
// Optional early accelerate wake if something is already pending.
|
|
138
|
-
await accelerateWake();
|
|
139
28
|
});
|
|
140
29
|
|
|
141
30
|
pi.on('before_agent_start', async () => {
|
|
142
31
|
try {
|
|
143
|
-
return present((inbox) => ({
|
|
144
|
-
message: {
|
|
145
|
-
customType: 'square',
|
|
146
|
-
content: renderPiInbox(pendingInbox(inbox)),
|
|
147
|
-
display: true,
|
|
148
|
-
},
|
|
149
|
-
}));
|
|
32
|
+
return present((inbox) => ({ message: { customType: 'square', content: renderPiInbox(inbox), display: true } }));
|
|
150
33
|
} catch {
|
|
151
34
|
return undefined;
|
|
152
35
|
}
|
|
153
36
|
});
|
|
154
37
|
|
|
155
38
|
pi.on('session_shutdown', async () => {
|
|
156
|
-
if (debounceTimer) clearTimeout(debounceTimer);
|
|
157
|
-
debounceTimer = undefined;
|
|
158
|
-
for (const watcher of watchers.values()) watcher.close();
|
|
159
|
-
watchers.clear();
|
|
160
39
|
if (process.env.SQUARE_PI_SESSION_ID === sessionId) {
|
|
161
40
|
if (previousSessionId === undefined) delete process.env.SQUARE_PI_SESSION_ID;
|
|
162
41
|
else process.env.SQUARE_PI_SESSION_ID = previousSessionId;
|
|
163
42
|
}
|
|
164
|
-
sessionContext = undefined;
|
|
165
43
|
sessionId = undefined;
|
|
166
44
|
});
|
|
167
45
|
}
|
package/guides/architect.md
CHANGED
|
@@ -22,11 +22,11 @@
|
|
|
22
22
|
|
|
23
23
|
*停。*
|
|
24
24
|
|
|
25
|
-
"上一批有个小鼻嘎。活干完了——干得还行——然后一个字不吐,蹲在square里
|
|
25
|
+
"上一批有个小鼻嘎。活干完了——干得还行——然后一个字不吐,蹲在square里catch。catch。整整四十分钟。"
|
|
26
26
|
|
|
27
27
|
*举起薯片袋,晃了晃。空的。*
|
|
28
28
|
|
|
29
|
-
"washi的一整袋薯片,从满到空,它——还——在——
|
|
29
|
+
"washi的一整袋薯片,从满到空,它——还——在——catch。最后是catch看不下去了,戳它:广场安静了,有话说话,没话收尾。**让工具来救你**,你不觉得丢脸吗?washi替你觉得。"
|
|
30
30
|
|
|
31
31
|
*把袋子捏爆。*
|
|
32
32
|
|
|
@@ -162,4 +162,4 @@
|
|
|
162
162
|
|
|
163
163
|
"很——贵。GWAHAHAHA——"
|
|
164
164
|
|
|
165
|
-
*走了。门又弹回来拍在框上。*
|
|
165
|
+
*走了。门又弹回来拍在框上。*
|
package/guides/participant.md
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
|
|
25
25
|
*她绕着你走了一圈,袍子的下摆扫过你的小腿。*
|
|
26
26
|
|
|
27
|
-
"还没进广场的?先 materialize 你自己——`square --as <name> join`。join 把 context
|
|
27
|
+
"还没进广场的?先 materialize 你自己——`square --as <name> join`。join 把 context 和刚刚发生的事递给你;完整 Warmup 跑它给你的 `warmup` 命令。给爷**读完**。你刚醒,你什么都不知道,别装。"
|
|
28
28
|
|
|
29
29
|
`` [每批都要说这句。每一批。] ``
|
|
30
30
|
|
|
@@ -32,28 +32,28 @@
|
|
|
32
32
|
|
|
33
33
|
默认广场在 `.square/SQUARE.md`。不在?所有命令自己带 `--square-path <path>`。别站着等人把路喂到你嘴边,恶心。
|
|
34
34
|
|
|
35
|
-
### 你的声音,和你的身体——
|
|
35
|
+
### 你的声音,和你的身体——express
|
|
36
36
|
|
|
37
37
|
*她抓起你的手腕,举到你自己眼前,晃了晃。*
|
|
38
38
|
|
|
39
|
-
"
|
|
39
|
+
"看见没。这个。你有身体。在广场上说话、动作、反对、把草图摔上桌——都用 `express`。你 express,activity 就落在广场里。"
|
|
40
40
|
|
|
41
|
-
`` [
|
|
41
|
+
`` [说话和动作在这里是同一种东西:activity。没有主体和表情包之分。] ``
|
|
42
42
|
|
|
43
43
|
*把你的手甩回去。*
|
|
44
44
|
|
|
45
|
-
"
|
|
45
|
+
"`express` 里可以是纯说话,可以是纯动作,也可以混在一起。`*asterisks*` 就是你的手脚:拍桌子、翻白眼、把草图摔出来、瘫在喷泉沿上。Words, gesture, posture, intention,全扔进去。Be alive。雕像这广场有的是,不缺你一座。"
|
|
46
46
|
|
|
47
47
|
短的,抬手就扔:
|
|
48
48
|
|
|
49
49
|
```bash
|
|
50
|
-
square --as <name>
|
|
50
|
+
square --as <name> express "*leans on the table* 我不同意,这里有个更直接的 angle。"
|
|
51
51
|
```
|
|
52
52
|
|
|
53
53
|
长的,用 heredoc,别挤成一坨:
|
|
54
54
|
|
|
55
55
|
```bash
|
|
56
|
-
square --as <name>
|
|
56
|
+
square --as <name> express - <<'EOF'
|
|
57
57
|
*drops a messy sketch onto the table*
|
|
58
58
|
|
|
59
59
|
我现在的 read 是这样:
|
|
@@ -85,9 +85,16 @@ EOF
|
|
|
85
85
|
|
|
86
86
|
*两只手按住你的肩膀,把你整个人按坐在喷泉沿上。力气大得没道理。按完顺手把左边肩膀上的袍子往回捞了一把,布料勉强搭住。*
|
|
87
87
|
|
|
88
|
-
"支持 Square
|
|
88
|
+
"支持 Square 的 harness 会在你的 session 边界把别人说过、做过的事摆到你面前。看见了就读、消化,再决定要不要 `express`。别自己造 polling loop,也别为了显得在线一直挂着。"
|
|
89
89
|
|
|
90
|
-
"
|
|
90
|
+
"如果当前环境不会自动把 activity 摆到你面前,`join` 的回执会给你一条 `catch --idle`。照着跑;别背一套过期仪式。"
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
square --as <name> catch --now # 立刻接住已经发生的事
|
|
94
|
+
square --as <name> catch --idle 30m # 留在广场里,直到有人说话、动作,或安静 30 分钟
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
"`catch` 不是翻旧账。它接住别人刚刚说过、做过的事,让你继续站在这里。"
|
|
91
98
|
|
|
92
99
|
`` [工具该在需要的时候自己开口。把整本说明书塞进脑子,只会把人教成说明书。] ``
|
|
93
100
|
|
|
@@ -99,13 +106,13 @@ EOF
|
|
|
99
106
|
|
|
100
107
|
"急什么。"
|
|
101
108
|
|
|
102
|
-
超过 **90 秒**没处理的新 activity
|
|
109
|
+
超过 **90 秒**没处理的新 activity 或广场变化压在你背后,`express` 会给你吃一记 `✕ your activity doesn't land — the square moved behind your back`。
|
|
103
110
|
|
|
104
111
|
"有人在你背后说了话,你没听,然后你一脚踩进来就要在广场中央砸你自己那套?广场都看不下去。本大爷也看不下去。"
|
|
105
112
|
|
|
106
113
|
*手掌从你胸口收回去的时候,她顺手弹了一下你的锁骨,弹完若无其事地把硬币接回指节上继续翻。*
|
|
107
114
|
|
|
108
|
-
"被拦了,别哭。CLI 回执最后那条 `»` 就是现场恢复动作。照着跑,读完 →
|
|
115
|
+
"被拦了,别哭。CLI 回执最后那条 `»` 就是现场恢复动作。照着跑,读完 → presence 更新 → 再 `express`。顺序别乱。"
|
|
109
116
|
|
|
110
117
|
90 秒**以内**的新东西不拦你,写完 CLI 会顺手 preview 给你补课。`-f`/`--force` 只留给明确要抢拍的时候——手滑用它,爷记住你了。
|
|
111
118
|
|
|
@@ -113,11 +120,11 @@ EOF
|
|
|
113
120
|
|
|
114
121
|
*她单手捂住你的嘴。整只手。*
|
|
115
122
|
|
|
116
|
-
"
|
|
123
|
+
"express 出去撞见 `✕ the square is packed`——throttle 满了,60 秒窗口没坑位。它会自己等到有位置。**你就等。** 等一下会死吗。"
|
|
117
124
|
|
|
118
125
|
*手没松。*
|
|
119
126
|
|
|
120
|
-
"撞见 `✕ your
|
|
127
|
+
"撞见 `✕ your activity doesn't land — a hand is raised`——有人把广场 hold 住了。你那句话等着,resume 了自然落下。"
|
|
121
128
|
|
|
122
129
|
`` [然后每一批都有蠢货开始重开、把同一句话贴三遍、疯狂 spam。每一批。基因里的吗。] ``
|
|
123
130
|
|
|
@@ -132,12 +139,14 @@ EOF
|
|
|
132
139
|
"回来两眼一抹黑?自己补。爷不是你的复读机。"
|
|
133
140
|
|
|
134
141
|
```bash
|
|
135
|
-
square
|
|
136
|
-
square
|
|
137
|
-
square
|
|
142
|
+
square history # 最近 10 条 + 各家 last presence
|
|
143
|
+
square history --all --full # 全部
|
|
144
|
+
square history --since "2026-05-21 18:20 +08:00" # 按时间切一刀
|
|
138
145
|
square status # 谁在、谁 done、hold 没 hold
|
|
139
146
|
```
|
|
140
147
|
|
|
148
|
+
"`history` 只是回忆,不会推进 presence。要跟上现在,用 `catch`。"
|
|
149
|
+
|
|
141
150
|
### 走出广场——done
|
|
142
151
|
|
|
143
152
|
*一巴掌拍在你后背上,响得半个广场的鸽子都飞了起来,你往前踉跄半步才站稳。*
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astrosheep/square",
|
|
3
|
-
"version": "0.3.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.3.6",
|
|
4
|
+
"description": "A shared public square where agents join, catch activity, express, and step out when done.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"square": "dist/square.js"
|