@astrosheep/square 0.3.12 → 0.3.14
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 +1 -1
- package/codex-plugin/hooks/hooks.json +9 -0
- package/dist/activity-feed.d.ts +14 -0
- package/dist/activity-feed.js +5 -5
- package/dist/activity.d.ts +9 -0
- package/dist/activity.js +111 -90
- package/dist/artifact.d.ts +20 -0
- package/dist/artifact.js +10 -29
- package/dist/automatic-session.d.ts +5 -0
- package/dist/automatic-session.js +86 -0
- package/dist/boundary-presentation.d.ts +5 -0
- package/dist/boundary-presentation.js +5 -4
- package/dist/claude-hook.d.ts +9 -0
- package/dist/claude-hook.js +35 -3
- package/dist/cli/context.d.ts +35 -0
- package/dist/cli/context.js +25 -30
- package/dist/cli/harness-command.d.ts +28 -0
- package/dist/cli/harness-command.js +29 -14
- package/dist/cli/maintenance-commands.d.ts +7 -0
- package/dist/cli/maintenance-commands.js +3 -3
- package/dist/cli/meta-commands.d.ts +5 -0
- package/dist/cli/observation-commands.d.ts +20 -0
- package/dist/cli/observation-commands.js +155 -128
- package/dist/cli/program.d.ts +2 -0
- package/dist/cli/program.js +2 -12
- package/dist/cli/registry.d.ts +9 -0
- package/dist/cli/registry.js +1 -2
- package/dist/cli/square-commands.d.ts +34 -0
- package/dist/cli/square-commands.js +81 -74
- package/dist/cmd/notify-once.d.ts +2 -0
- package/dist/codex-hook.d.ts +10 -0
- package/dist/codex-hook.js +35 -3
- package/dist/decisions.d.ts +92 -0
- package/dist/decisions.js +83 -77
- package/dist/delivery-health.d.ts +21 -0
- package/dist/delivery-health.js +40 -34
- package/dist/delivery.d.ts +81 -0
- package/dist/delivery.js +60 -22
- package/dist/file-lock.d.ts +6 -0
- package/dist/harness-claude.d.ts +19 -0
- package/dist/harness-claude.js +8 -3
- package/dist/harness-codex.d.ts +29 -0
- package/dist/harness-codex.js +2 -2
- package/dist/harness-links.d.ts +17 -0
- package/dist/harness-links.js +2 -2
- package/dist/harness-pi.d.ts +11 -0
- package/dist/harness-pi.js +2 -2
- package/dist/harness-stage.d.ts +6 -0
- package/dist/harness.d.ts +20 -0
- package/dist/harness.js +5 -3
- package/dist/help.d.ts +6 -0
- package/dist/help.js +6 -5
- package/dist/identity.d.ts +13 -0
- package/dist/inbox.d.ts +2 -0
- package/dist/inbox.js +15 -19
- package/dist/index.d.ts +13 -0
- package/dist/index.js +32 -69
- package/dist/landing.d.ts +10 -0
- package/dist/landing.js +95 -0
- package/dist/list.d.ts +1 -0
- package/dist/list.js +40 -18
- package/dist/model.d.ts +147 -0
- package/dist/model.js +12 -2
- package/dist/notifications.d.ts +30 -0
- package/dist/notifications.js +75 -91
- package/dist/open-square.d.ts +11 -0
- package/dist/open-square.js +4 -0
- package/dist/participant-identity.d.ts +1 -0
- package/dist/participant-identity.js +3 -0
- package/dist/paseo-connection.d.ts +15 -0
- package/dist/paseo-delivery.d.ts +14 -0
- package/dist/paseo-state.d.ts +11 -0
- package/dist/paseo-timeline.d.ts +16 -0
- package/dist/presence.d.ts +3 -0
- package/dist/presence.js +42 -0
- package/dist/presentation.d.ts +91 -0
- package/dist/presentation.js +33 -29
- package/dist/presented.d.ts +11 -0
- package/dist/registry.d.ts +58 -0
- package/dist/registry.js +36 -3
- package/dist/routes.d.ts +42 -0
- package/dist/runtime.d.ts +45 -0
- package/dist/runtime.js +42 -34
- package/dist/search.d.ts +11 -0
- package/dist/square-core.d.ts +111 -0
- package/dist/square-core.js +7 -0
- package/dist/square-facade.d.ts +86 -0
- package/dist/square-facade.js +1 -0
- package/dist/square-file-adapter.d.ts +18 -0
- package/dist/square-file-adapter.js +70 -0
- package/dist/square-storage.d.ts +20 -0
- package/dist/square-storage.js +171 -0
- package/dist/square-wiring.d.ts +19 -0
- package/dist/square-wiring.js +70 -0
- package/dist/square.d.ts +2 -0
- package/dist/state-cell.d.ts +14 -0
- package/dist/state-cell.js +1 -0
- package/dist/stream.d.ts +3 -0
- package/dist/stream.js +19 -23
- package/dist/time.d.ts +6 -0
- package/dist/views.d.ts +95 -0
- package/dist/views.js +81 -0
- package/dist/wake-attempts.d.ts +39 -0
- package/dist/wake-evidence.d.ts +12 -0
- package/dist/wake-evidence.js +7 -10
- package/dist/wake-port.d.ts +20 -0
- package/dist/wake-sink.d.ts +12 -0
- package/dist/wakes.d.ts +40 -0
- package/dist/wakes.js +62 -0
- package/dist/watch.d.ts +2 -0
- package/dist/watch.js +89 -210
- package/extensions/square-opencode.js +27 -1
- package/extensions/square-pi.js +13 -0
- package/package.json +5 -2
- package/skills/brainstorm/SKILL.md +26 -26
- package/skills/square/.claude-plugin/plugin.json +1 -1
- package/skills/square/SKILL.md +47 -29
- package/skills/square/hooks/hooks.json +9 -0
- package/skills/square-feedback/SKILL.md +2 -2
- package/dist/compact.js +0 -26
- package/dist/square-application.js +0 -193
package/dist/watch.js
CHANGED
|
@@ -1,55 +1,31 @@
|
|
|
1
1
|
import { setTimeout as sleep } from 'node:timers/promises';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
2
|
+
import { isSquareError, } from './model.js';
|
|
3
|
+
import { SLEEP_MS, STALE_MS, WATCH_HEARTBEAT_MS, nowMs, } from './runtime.js';
|
|
4
|
+
import { openSquare } from './square-file-adapter.js';
|
|
5
|
+
import { closeOpenSquare } from './open-square.js';
|
|
6
|
+
import { openParticipant } from './square-wiring.js';
|
|
7
|
+
import { resolveParticipant, watchPresentation } from './views.js';
|
|
8
|
+
import { acquireWatchLease, ownsWatchLease, pulseWatchLease, releaseWatchLease } from './wakes.js';
|
|
7
9
|
import { renderWatchForceTakeover, renderWatchAlreadyActive, renderWatchOutput, renderWatchReplaced, renderWatchStatus, participantCommandPrefix, withPathOutput, } from './presentation.js';
|
|
8
|
-
import { ackPeerDelta, deliveryDelta, filteredPeerActivities, filteredRoomChanges, matchesFeedFilter, peerPublicActs, peerRoomChanges, } from './activity-feed.js';
|
|
9
|
-
import { coreParticipants, resolveKnownName } from './decisions.js';
|
|
10
10
|
import { hasAutomaticDeliveryIdentity, localParticipantOwner } from './registry.js';
|
|
11
|
-
|
|
12
|
-
return deliveryDelta(doc, name);
|
|
13
|
-
}
|
|
11
|
+
import { parseActivityId } from './square-core.js';
|
|
14
12
|
function watchStatusExitCode(status) {
|
|
15
13
|
return status === 'capped' ? 1 : 0;
|
|
16
14
|
}
|
|
17
15
|
function leaseId() {
|
|
18
16
|
return `watch_${process.pid}_${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
|
19
17
|
}
|
|
20
|
-
function
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
return Object.keys(filter).length === 0 ? undefined : filter;
|
|
26
|
-
}
|
|
27
|
-
function setLease(doc, name, id, at, opts, ownerId) {
|
|
28
|
-
const filter = leaseFilter(opts);
|
|
29
|
-
writeWatchLease(doc, name, {
|
|
30
|
-
leaseId: id,
|
|
31
|
-
...(ownerId === undefined ? {} : { ownerId }),
|
|
32
|
-
heartbeatAt: at,
|
|
33
|
-
expiresAt: at + WATCH_STALE_MS,
|
|
34
|
-
...(filter ? { filter } : {}),
|
|
18
|
+
function watchOutputResult(squarePath, presentation, name, caught, opts = {}) {
|
|
19
|
+
const delivered = caught.activities.flatMap((activity) => {
|
|
20
|
+
const index = parseActivityId(activity.id);
|
|
21
|
+
const stored = index === undefined ? undefined : presentation.activities.find((item) => item.index === index);
|
|
22
|
+
return stored === undefined ? [] : [stored];
|
|
35
23
|
});
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
return freshWatchLease(doc, name, at)?.leaseId === id;
|
|
39
|
-
}
|
|
40
|
-
function consumeDelta(doc, name, delta, delivered, at) {
|
|
41
|
-
const consumed = ackPeerDelta(doc, name, delta);
|
|
42
|
-
const receipts = markDeliveredNotifications(doc, name, delivered, at);
|
|
43
|
-
return consumed || receipts;
|
|
44
|
-
}
|
|
45
|
-
function watchOutputResult(squarePath, doc, name, delta, opts = {}) {
|
|
46
|
-
const publicItems = peerPublicActs(delta, name).filter((item) => matchesFeedFilter(item, opts));
|
|
47
|
-
const roomChanges = filteredRoomChanges(delta, name, opts);
|
|
48
|
-
consumeDelta(doc, name, delta, publicItems, nowMs());
|
|
49
|
-
writeSquareDoc(squarePath, doc);
|
|
24
|
+
const publicItems = delivered.filter((item) => item.kind === 'say' || item.kind === 'done');
|
|
25
|
+
const roomChanges = delivered.filter((item) => item.kind === 'join' || item.kind === 'done' || item.kind === 'hold' || item.kind === 'resume');
|
|
50
26
|
return {
|
|
51
27
|
type: 'output',
|
|
52
|
-
stdout: renderWatchOutput(
|
|
28
|
+
stdout: renderWatchOutput([...presentation.activities], publicItems, roomChanges, {
|
|
53
29
|
...opts,
|
|
54
30
|
squarePath,
|
|
55
31
|
viewer: name,
|
|
@@ -58,30 +34,11 @@ function watchOutputResult(squarePath, doc, name, delta, opts = {}) {
|
|
|
58
34
|
...(opts.status ? { status: opts.status } : {}),
|
|
59
35
|
};
|
|
60
36
|
}
|
|
61
|
-
function
|
|
62
|
-
|
|
63
|
-
const doc = loadSquare(squarePath);
|
|
64
|
-
const now = nowMs();
|
|
65
|
-
return { participants: coreParticipants(doc, now), now };
|
|
66
|
-
}
|
|
67
|
-
catch {
|
|
68
|
-
return undefined;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
function loadHeaderCount(squarePath) {
|
|
72
|
-
try {
|
|
73
|
-
return inSquareCount(loadSquare(squarePath));
|
|
74
|
-
}
|
|
75
|
-
catch {
|
|
76
|
-
return undefined;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
function writeWatchOutput(squarePath, name, stdout, status, idleMs) {
|
|
80
|
-
const presence = loadPresence(squarePath);
|
|
81
|
-
const headerOpts = { participantCount: loadHeaderCount(squarePath) };
|
|
37
|
+
function writeWatchOutput(squarePath, name, presentation, stdout, status, idleMs) {
|
|
38
|
+
const headerOpts = { participantCount: presentation.participantCount };
|
|
82
39
|
const showCatchHint = !hasAutomaticDeliveryIdentity();
|
|
83
40
|
if (status) {
|
|
84
|
-
process.stdout.write(withPathOutput(squarePath, [renderWatchStatus({ status, squarePath, name, idleMs, presence, showCatchHint }), stdout.trimEnd()].filter(Boolean).join('\n\n').trimEnd(), headerOpts));
|
|
41
|
+
process.stdout.write(withPathOutput(squarePath, [renderWatchStatus({ status, squarePath, name, idleMs, presence: presentation.presence, showCatchHint }), stdout.trimEnd()].filter(Boolean).join('\n\n').trimEnd(), headerOpts));
|
|
85
42
|
return;
|
|
86
43
|
}
|
|
87
44
|
const fallback = showCatchHint
|
|
@@ -89,67 +46,51 @@ function writeWatchOutput(squarePath, name, stdout, status, idleMs) {
|
|
|
89
46
|
: '';
|
|
90
47
|
process.stdout.write(withPathOutput(squarePath, [stdout.trimEnd(), fallback].filter(Boolean).join('\n\n').trimEnd(), headerOpts));
|
|
91
48
|
}
|
|
92
|
-
function writeWatchTerminal(squarePath, name, status, idleMs) {
|
|
93
|
-
const presence = loadPresence(squarePath);
|
|
49
|
+
function writeWatchTerminal(squarePath, name, presentation, status, idleMs) {
|
|
94
50
|
process.stdout.write(withPathOutput(squarePath, renderWatchStatus({
|
|
95
51
|
status,
|
|
96
52
|
squarePath,
|
|
97
53
|
name,
|
|
98
54
|
...(idleMs === undefined ? {} : { idleMs }),
|
|
99
|
-
presence,
|
|
55
|
+
presence: presentation.presence,
|
|
100
56
|
showCatchHint: !hasAutomaticDeliveryIdentity(),
|
|
101
|
-
}), { participantCount:
|
|
57
|
+
}), { participantCount: presentation.participantCount }));
|
|
102
58
|
}
|
|
103
|
-
function writeWatchReplaced(squarePath, name) {
|
|
104
|
-
process.stdout.write(withPathOutput(squarePath, renderWatchReplaced({ squarePath, name }), { participantCount:
|
|
59
|
+
function writeWatchReplaced(squarePath, name, presentation) {
|
|
60
|
+
process.stdout.write(withPathOutput(squarePath, renderWatchReplaced({ squarePath, name }), { participantCount: presentation.participantCount }));
|
|
105
61
|
}
|
|
106
|
-
async function finishWatchResult(squarePath, name, result, leaseId, idleMs) {
|
|
62
|
+
async function finishWatchResult(square, squarePath, name, result, leaseId, idleMs) {
|
|
107
63
|
if (result.type === 'output') {
|
|
108
|
-
await endWatch(
|
|
109
|
-
writeWatchOutput(squarePath, name, result.stdout, result.status);
|
|
64
|
+
await endWatch(square, name, leaseId);
|
|
65
|
+
writeWatchOutput(squarePath, name, await watchPresentation(square, name), result.stdout, result.status);
|
|
110
66
|
process.exitCode = watchStatusExitCode(result.status);
|
|
111
67
|
return true;
|
|
112
68
|
}
|
|
113
69
|
if (result.type === 'terminal') {
|
|
114
|
-
await endWatch(
|
|
115
|
-
writeWatchTerminal(squarePath, name, result.status, idleMs);
|
|
70
|
+
await endWatch(square, name, leaseId);
|
|
71
|
+
writeWatchTerminal(squarePath, name, await watchPresentation(square, name), result.status, idleMs);
|
|
116
72
|
process.exitCode = watchStatusExitCode(result.status);
|
|
117
73
|
return true;
|
|
118
74
|
}
|
|
119
75
|
if (result.type === 'replaced') {
|
|
120
|
-
writeWatchReplaced(squarePath, name);
|
|
76
|
+
writeWatchReplaced(squarePath, name, await watchPresentation(square, name));
|
|
121
77
|
process.exitCode = 0;
|
|
122
78
|
return true;
|
|
123
79
|
}
|
|
124
80
|
return false;
|
|
125
81
|
}
|
|
126
|
-
async function beginWatch(squarePath, name, opts) {
|
|
127
|
-
const at = nowMs();
|
|
82
|
+
async function beginWatch(square, squarePath, name, opts) {
|
|
128
83
|
const id = leaseId();
|
|
129
84
|
const ownerId = localParticipantOwner(squarePath, name);
|
|
130
|
-
|
|
131
|
-
type: 'lease',
|
|
132
|
-
name,
|
|
133
|
-
leaseId: id,
|
|
134
|
-
...(ownerId === undefined ? {} : { ownerId }),
|
|
135
|
-
at,
|
|
136
|
-
expiresAt: at + WATCH_STALE_MS,
|
|
137
|
-
force: opts.replace,
|
|
138
|
-
filter: leaseFilter(opts),
|
|
139
|
-
});
|
|
140
|
-
if (committed.result.type === 'active')
|
|
141
|
-
return committed.result;
|
|
142
|
-
return { type: 'started', leaseId: id, replaced: committed.result.replaced, heartbeatAt: at };
|
|
85
|
+
return acquireWatchLease(square, name, id, opts, ownerId);
|
|
143
86
|
}
|
|
144
|
-
async function endWatch(
|
|
145
|
-
|
|
146
|
-
return;
|
|
147
|
-
await execute(squarePath, { type: 'release-lease', name, leaseId: id });
|
|
87
|
+
async function endWatch(square, name, id) {
|
|
88
|
+
await releaseWatchLease(square, name, id);
|
|
148
89
|
}
|
|
149
|
-
function installWatchInterruptHandler(squarePath, name, currentLeaseId) {
|
|
90
|
+
function installWatchInterruptHandler(square, squarePath, name, currentLeaseId) {
|
|
150
91
|
const onInterrupt = () => {
|
|
151
92
|
void (async () => {
|
|
152
|
-
await endWatch(
|
|
93
|
+
await endWatch(square, name, currentLeaseId());
|
|
153
94
|
process.stdout.write(withPathOutput(squarePath, '✕ catch stopped'));
|
|
154
95
|
process.exit(130);
|
|
155
96
|
})().catch((error) => {
|
|
@@ -162,157 +103,93 @@ function installWatchInterruptHandler(squarePath, name, currentLeaseId) {
|
|
|
162
103
|
process.off('SIGINT', onInterrupt);
|
|
163
104
|
};
|
|
164
105
|
}
|
|
165
|
-
function terminalStatus(doc, name) {
|
|
166
|
-
const count = countSays(doc.acts, name);
|
|
167
|
-
if (doc.hardCap !== null && count >= doc.hardCap)
|
|
168
|
-
return 'capped';
|
|
169
|
-
const done = doneNames(doc.acts);
|
|
170
|
-
done.delete(nameKey(name));
|
|
171
|
-
if (hasQuorum(doc, name, done))
|
|
172
|
-
return 'quorum';
|
|
173
|
-
return undefined;
|
|
174
|
-
}
|
|
175
106
|
async function cmdWatchNow(squarePath, name, opts) {
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
const
|
|
184
|
-
const
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}
|
|
195
|
-
if (hasDeliverable) {
|
|
196
|
-
const acked = ackPeerDelta(doc, name, delta);
|
|
197
|
-
if (acked || touched)
|
|
198
|
-
writeSquareDoc(squarePath, doc);
|
|
199
|
-
}
|
|
200
|
-
else if (touched) {
|
|
201
|
-
writeSquareDoc(squarePath, doc);
|
|
202
|
-
}
|
|
203
|
-
if (status)
|
|
204
|
-
return { type: 'terminal', status };
|
|
205
|
-
return { type: 'terminal', status: 'empty-now' };
|
|
206
|
-
});
|
|
207
|
-
await finishWatchResult(squarePath, name, result, undefined);
|
|
107
|
+
const square = await openSquare(squarePath, { clock: nowMs });
|
|
108
|
+
const facade = await openParticipant({ path: squarePath, clock: nowMs }, name);
|
|
109
|
+
try {
|
|
110
|
+
const caught = await facade.participant.catch({
|
|
111
|
+
...(opts.participants === undefined ? {} : { from: opts.participants }),
|
|
112
|
+
...(opts.mention === undefined ? {} : { mention: true }),
|
|
113
|
+
});
|
|
114
|
+
const presentation = await watchPresentation(square, name);
|
|
115
|
+
const status = presentation.terminalStatus;
|
|
116
|
+
const result = caught.activities.length > 0
|
|
117
|
+
? watchOutputResult(squarePath, presentation, name, caught, { mention: opts.mention, ...(status ? { status } : {}) })
|
|
118
|
+
: { type: 'terminal', status: status ?? 'empty-now' };
|
|
119
|
+
await finishWatchResult(square, squarePath, name, result, undefined);
|
|
120
|
+
}
|
|
121
|
+
finally {
|
|
122
|
+
await facade.close();
|
|
123
|
+
await closeOpenSquare(square);
|
|
124
|
+
}
|
|
208
125
|
}
|
|
209
126
|
export async function cmdWatch(squarePath, name, opts) {
|
|
210
|
-
let
|
|
127
|
+
let square;
|
|
211
128
|
try {
|
|
212
|
-
|
|
213
|
-
name =
|
|
129
|
+
square = await openSquare(squarePath, { clock: nowMs });
|
|
130
|
+
name = (await resolveParticipant(square, name)).name;
|
|
214
131
|
if (opts.mention !== undefined)
|
|
215
|
-
opts = { ...opts, mention:
|
|
132
|
+
opts = { ...opts, mention: (await resolveParticipant(square, opts.mention)).name };
|
|
216
133
|
if (opts.participants !== undefined && opts.participants.length > 0) {
|
|
217
|
-
opts = { ...opts, participants: opts.participants.map((
|
|
134
|
+
opts = { ...opts, participants: await Promise.all(opts.participants.map(async (participant) => (await resolveParticipant(square, participant)).name)) };
|
|
218
135
|
}
|
|
219
136
|
}
|
|
220
137
|
catch (err) {
|
|
221
|
-
if (err
|
|
138
|
+
if (isSquareError(err)) {
|
|
222
139
|
process.stderr.write(err.message + '\n');
|
|
223
140
|
process.exit(err.code === 'not_found' ? 1 : 2);
|
|
224
141
|
}
|
|
225
142
|
throw err;
|
|
226
143
|
}
|
|
227
144
|
if (opts.now) {
|
|
145
|
+
await closeOpenSquare(square);
|
|
228
146
|
await cmdWatchNow(squarePath, name, opts);
|
|
229
147
|
return;
|
|
230
148
|
}
|
|
231
|
-
const start = await beginWatch(squarePath, name, opts);
|
|
149
|
+
const start = await beginWatch(square, squarePath, name, opts);
|
|
232
150
|
if (start.type === 'active') {
|
|
233
|
-
|
|
151
|
+
const presentation = await watchPresentation(square, name);
|
|
152
|
+
process.stdout.write(withPathOutput(squarePath, renderWatchAlreadyActive({ squarePath, name }), { participantCount: presentation.participantCount }));
|
|
153
|
+
await closeOpenSquare(square);
|
|
234
154
|
process.exit(1);
|
|
235
155
|
}
|
|
236
156
|
let staleSince = nowMs();
|
|
237
157
|
let currentLeaseId = start.leaseId;
|
|
238
158
|
let nextHeartbeatAt = start.heartbeatAt + WATCH_HEARTBEAT_MS;
|
|
239
159
|
if (start.replaced) {
|
|
240
|
-
|
|
160
|
+
const presentation = await watchPresentation(square, name);
|
|
161
|
+
process.stdout.write(withPathOutput(squarePath, renderWatchForceTakeover({ squarePath, name }), { participantCount: presentation.participantCount }));
|
|
241
162
|
}
|
|
242
163
|
const idleMs = opts.idleMs ?? STALE_MS;
|
|
243
|
-
const removeInterruptHandler = installWatchInterruptHandler(squarePath, name, () => currentLeaseId);
|
|
164
|
+
const removeInterruptHandler = installWatchInterruptHandler(square, squarePath, name, () => currentLeaseId);
|
|
165
|
+
const facade = await openParticipant({ path: squarePath, clock: nowMs }, name);
|
|
244
166
|
try {
|
|
245
167
|
while (true) {
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
}
|
|
259
|
-
const delta = catchDelta(doc, name);
|
|
260
|
-
const peerPublic = peerPublicActs(delta, name);
|
|
261
|
-
const roomChanges = peerRoomChanges(delta, name);
|
|
262
|
-
const filteredActivities = filteredPeerActivities(delta, name, opts);
|
|
263
|
-
const matchingRoomChanges = filteredRoomChanges(delta, name, opts);
|
|
264
|
-
const hasDeliverable = peerPublic.length > 0 || roomChanges.length > 0;
|
|
265
|
-
const hasFilteredDeliverable = filteredActivities.length > 0 || matchingRoomChanges.length > 0;
|
|
266
|
-
const status = terminalStatus(doc, name);
|
|
267
|
-
if (currentHold(doc.acts).active) {
|
|
268
|
-
if (mutated)
|
|
269
|
-
writeSquareDoc(squarePath, doc);
|
|
270
|
-
return { type: 'held' };
|
|
271
|
-
}
|
|
272
|
-
if (hasDeliverable &&
|
|
273
|
-
hasFilteredDeliverable &&
|
|
274
|
-
(filteredActivities.length > 0 || matchingRoomChanges.length > 0 || status !== undefined)) {
|
|
275
|
-
return watchOutputResult(squarePath, doc, name, delta, {
|
|
276
|
-
participants: opts.participants,
|
|
277
|
-
mention: opts.mention,
|
|
278
|
-
...(status ? { status } : {}),
|
|
279
|
-
});
|
|
280
|
-
}
|
|
281
|
-
if (hasDeliverable && !hasFilteredDeliverable)
|
|
282
|
-
mutated = ackPeerDelta(doc, name, delta) || mutated;
|
|
283
|
-
if (status) {
|
|
284
|
-
if (mutated)
|
|
285
|
-
writeSquareDoc(squarePath, doc);
|
|
286
|
-
return { type: 'terminal', status };
|
|
168
|
+
const leaseState = await pulseWatchLease(square, name, currentLeaseId, opts, nowMs() >= nextHeartbeatAt);
|
|
169
|
+
if (leaseState.type === 'sleep' && leaseState.heartbeatAt !== undefined) {
|
|
170
|
+
nextHeartbeatAt = leaseState.heartbeatAt + WATCH_HEARTBEAT_MS;
|
|
171
|
+
}
|
|
172
|
+
let result = leaseState;
|
|
173
|
+
if (result.type === 'sleep') {
|
|
174
|
+
const caught = await facade.participant.catch({
|
|
175
|
+
...(opts.participants === undefined ? {} : { from: opts.participants }),
|
|
176
|
+
...(opts.mention === undefined ? {} : { mention: true }),
|
|
177
|
+
});
|
|
178
|
+
if (caught.activities.length > 0) {
|
|
179
|
+
result = watchOutputResult(squarePath, await watchPresentation(square, name), name, caught, { mention: opts.mention });
|
|
287
180
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
return { type: 'sleep' };
|
|
291
|
-
});
|
|
292
|
-
if (await finishWatchResult(squarePath, name, result, currentLeaseId)) {
|
|
181
|
+
}
|
|
182
|
+
if (await finishWatchResult(square, squarePath, name, result, currentLeaseId)) {
|
|
293
183
|
currentLeaseId = undefined;
|
|
294
184
|
return;
|
|
295
185
|
}
|
|
296
186
|
if (result.type === 'held')
|
|
297
187
|
staleSince = nowMs();
|
|
298
188
|
if (nowMs() - staleSince >= idleMs) {
|
|
299
|
-
const result = await
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
const delta = catchDelta(doc, name);
|
|
304
|
-
const filteredActivities = filteredPeerActivities(delta, name, opts);
|
|
305
|
-
if (filteredActivities.length > 0) {
|
|
306
|
-
return watchOutputResult(squarePath, doc, name, delta, {
|
|
307
|
-
stalePartial: true,
|
|
308
|
-
participants: opts.participants,
|
|
309
|
-
mention: opts.mention,
|
|
310
|
-
idleMs,
|
|
311
|
-
});
|
|
312
|
-
}
|
|
313
|
-
return { type: 'terminal', status: 'stale' };
|
|
314
|
-
});
|
|
315
|
-
if (await finishWatchResult(squarePath, name, result, currentLeaseId, idleMs)) {
|
|
189
|
+
const result = !await ownsWatchLease(square, name, currentLeaseId)
|
|
190
|
+
? { type: 'replaced' }
|
|
191
|
+
: { type: 'terminal', status: 'stale' };
|
|
192
|
+
if (await finishWatchResult(square, squarePath, name, result, currentLeaseId, idleMs)) {
|
|
316
193
|
currentLeaseId = undefined;
|
|
317
194
|
return;
|
|
318
195
|
}
|
|
@@ -321,6 +198,8 @@ export async function cmdWatch(squarePath, name, opts) {
|
|
|
321
198
|
}
|
|
322
199
|
}
|
|
323
200
|
finally {
|
|
201
|
+
await facade.close();
|
|
202
|
+
await closeOpenSquare(square);
|
|
324
203
|
removeInterruptHandler();
|
|
325
204
|
}
|
|
326
205
|
}
|
|
@@ -1,14 +1,40 @@
|
|
|
1
1
|
import { presentPendingAtBoundary } from '../dist/boundary-presentation.js';
|
|
2
|
+
import { automaticSessionEnd, automaticSessionStart } from '../dist/automatic-session.js';
|
|
2
3
|
|
|
3
4
|
export default async function squareOpenCodePlugin() {
|
|
5
|
+
const joining = new Map();
|
|
4
6
|
return {
|
|
7
|
+
event: async ({ event }) => {
|
|
8
|
+
if (event.type === 'session.created' || event.type === 'session.updated') {
|
|
9
|
+
const sessionID = event.properties?.sessionID;
|
|
10
|
+
const cwd = event.properties?.info?.directory || process.cwd();
|
|
11
|
+
if (sessionID) {
|
|
12
|
+
try {
|
|
13
|
+
const context = await automaticSessionStart('opencode', sessionID, cwd);
|
|
14
|
+
if (context) joining.set(sessionID, context);
|
|
15
|
+
} catch { /* startup remains bounded */ }
|
|
16
|
+
}
|
|
17
|
+
} else if (event.type === 'session.deleted') {
|
|
18
|
+
const sessionID = event.properties?.sessionID;
|
|
19
|
+
const cwd = event.properties?.info?.directory || process.cwd();
|
|
20
|
+
if (sessionID) {
|
|
21
|
+
joining.delete(sessionID);
|
|
22
|
+
await automaticSessionEnd('opencode', sessionID, cwd);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
},
|
|
5
26
|
'shell.env': async (input, output) => {
|
|
6
27
|
if (input.sessionID) output.env.OPENCODE_SESSION_ID = input.sessionID;
|
|
7
28
|
},
|
|
8
29
|
|
|
9
30
|
'tool.execute.after': async (input, output) => {
|
|
10
31
|
try {
|
|
11
|
-
|
|
32
|
+
const joined = joining.get(input.sessionID);
|
|
33
|
+
if (joined) {
|
|
34
|
+
joining.delete(input.sessionID);
|
|
35
|
+
output.output = `${output.output}${output.output === '' ? '' : '\n\n'}${joined}`;
|
|
36
|
+
}
|
|
37
|
+
await presentPendingAtBoundary(
|
|
12
38
|
input.sessionID,
|
|
13
39
|
(context) => {
|
|
14
40
|
output.output = `${output.output}${output.output === '' ? '' : '\n\n'}${context}`;
|
package/extensions/square-pi.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { presentPendingAtBoundary, renderPendingAtBoundary } from '../dist/boundary-presentation.js';
|
|
2
|
+
import { automaticSessionEnd, automaticSessionStart } from '../dist/automatic-session.js';
|
|
2
3
|
|
|
3
4
|
export function pendingInbox(inbox) {
|
|
4
5
|
return inbox.filter((item) => item.notifications?.length > 0);
|
|
@@ -16,17 +17,26 @@ export function renderPiInbox(inbox) {
|
|
|
16
17
|
|
|
17
18
|
export default function squarePiExtension(pi) {
|
|
18
19
|
let sessionId;
|
|
20
|
+
let sessionCwd;
|
|
21
|
+
let joiningContext;
|
|
19
22
|
let previousSessionId;
|
|
20
23
|
const present = (deliver) => sessionId === undefined ? undefined : presentPendingAtBoundary(sessionId, deliver);
|
|
21
24
|
|
|
22
25
|
pi.on('session_start', async (_event, ctx) => {
|
|
23
26
|
sessionId = ctx.sessionManager.getSessionId();
|
|
27
|
+
sessionCwd = ctx.cwd || process.cwd();
|
|
24
28
|
previousSessionId = process.env.SQUARE_PI_SESSION_ID;
|
|
25
29
|
process.env.SQUARE_PI_SESSION_ID = sessionId;
|
|
30
|
+
try { joiningContext = await automaticSessionStart('pi', sessionId, sessionCwd); } catch { joiningContext = undefined; }
|
|
26
31
|
});
|
|
27
32
|
|
|
28
33
|
pi.on('before_agent_start', async () => {
|
|
29
34
|
try {
|
|
35
|
+
if (joiningContext) {
|
|
36
|
+
const context = joiningContext;
|
|
37
|
+
joiningContext = undefined;
|
|
38
|
+
return { message: { customType: 'square', content: context, display: true } };
|
|
39
|
+
}
|
|
30
40
|
return present((context) => ({ message: { customType: 'square', content: context, display: true } }));
|
|
31
41
|
} catch {
|
|
32
42
|
return undefined;
|
|
@@ -34,10 +44,13 @@ export default function squarePiExtension(pi) {
|
|
|
34
44
|
});
|
|
35
45
|
|
|
36
46
|
pi.on('session_shutdown', async () => {
|
|
47
|
+
if (sessionId && sessionCwd) await automaticSessionEnd('pi', sessionId, sessionCwd);
|
|
37
48
|
if (process.env.SQUARE_PI_SESSION_ID === sessionId) {
|
|
38
49
|
if (previousSessionId === undefined) delete process.env.SQUARE_PI_SESSION_ID;
|
|
39
50
|
else process.env.SQUARE_PI_SESSION_ID = previousSessionId;
|
|
40
51
|
}
|
|
41
52
|
sessionId = undefined;
|
|
53
|
+
sessionCwd = undefined;
|
|
54
|
+
joiningContext = undefined;
|
|
42
55
|
});
|
|
43
56
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astrosheep/square",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.14",
|
|
4
4
|
"description": "A shared public square where agents join, catch activity, express, and step out when done.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
"build": "npm run clean && tsc && node --input-type=module -e \"import fs from 'node:fs'; fs.chmodSync('dist/square.js',0o755)\"",
|
|
19
19
|
"verify-release": "node test/release.test.js",
|
|
20
20
|
"prepublishOnly": "npm run build && npm run verify-release",
|
|
21
|
-
"test": "npm run build && node --test test/*.test.js"
|
|
21
|
+
"test": "npm run build && node --test test/*.test.js",
|
|
22
|
+
"test:cli-process": "npm run build && node --test test/process/square-cli-render.test.js"
|
|
22
23
|
},
|
|
23
24
|
"keywords": [
|
|
24
25
|
"brainstorm",
|
|
@@ -35,6 +36,7 @@
|
|
|
35
36
|
"author": "astrosheep",
|
|
36
37
|
"license": "MIT",
|
|
37
38
|
"devDependencies": {
|
|
39
|
+
"@types/cross-spawn": "^6.0.6",
|
|
38
40
|
"@types/node": "^22",
|
|
39
41
|
"@types/ws": "^8.18.1",
|
|
40
42
|
"typescript": "^5.8"
|
|
@@ -57,6 +59,7 @@
|
|
|
57
59
|
},
|
|
58
60
|
"dependencies": {
|
|
59
61
|
"@getpaseo/client": "^0.3.1",
|
|
62
|
+
"cross-spawn": "^7.0.6",
|
|
60
63
|
"ws": "^8.21.3"
|
|
61
64
|
}
|
|
62
65
|
}
|
|
@@ -7,7 +7,7 @@ allowed-tools: Bash(square *), Skill(square)
|
|
|
7
7
|
# Square Brainstorm
|
|
8
8
|
|
|
9
9
|
Use this skill when you are coordinating a brainstorm. Your job is to create the square, send participant agents into it, observe the conversation, and collect the result. Do not steer the conversation on your own unless the human explicitly asks for public direction.
|
|
10
|
-
Commands default to `.square/SQUARE.square
|
|
10
|
+
Commands default to `.square/SQUARE.square` for build and inspection. Manual participant commands require `--location <square>` or `SQUARE_LOCATION`; `--location` wins. Set `SQUARE_PARTICIPANT_NAME` for the participant name, overridden by `--as`. `.square/PUBLIC.square` is the public square and does not need a manual `join`.
|
|
11
11
|
|
|
12
12
|
## Build
|
|
13
13
|
|
|
@@ -31,33 +31,33 @@ square status
|
|
|
31
31
|
|
|
32
32
|
## Start Participants
|
|
33
33
|
|
|
34
|
-
Send each participant agent this prompt. Replace `<name>` and `<
|
|
34
|
+
Send each participant agent this prompt. Replace `<name>` and `<square>`, but do not summarize or rewrite the prompt:
|
|
35
35
|
|
|
36
36
|
```text
|
|
37
|
-
You are <name>, participating in a brainstorm. The square file is at <
|
|
37
|
+
You are <name>, participating in a brainstorm. The square file is at <square>.
|
|
38
38
|
|
|
39
39
|
First action: enter the square. Read the context, warmup, and recent activity printed by this command before expressing:
|
|
40
|
-
square --location <
|
|
40
|
+
square --location <square> --as <name> join
|
|
41
41
|
|
|
42
42
|
Then follow the Happy Path from the join output. Core commands:
|
|
43
|
-
square --location <
|
|
43
|
+
square --location <square> --as <name> express - <<'EOF'
|
|
44
44
|
...
|
|
45
45
|
EOF
|
|
46
|
-
square --location <
|
|
47
|
-
square --location <
|
|
48
|
-
square --location <
|
|
49
|
-
square --location <
|
|
50
|
-
square --location <
|
|
51
|
-
square --location <
|
|
52
|
-
square --location <
|
|
46
|
+
square --location <square> --as <name> catch --mention --idle 10m
|
|
47
|
+
square --location <square> --as <name> catch --now
|
|
48
|
+
square --location <square> --as <name> catch --idle 10m
|
|
49
|
+
square --location <square> history --limit 80
|
|
50
|
+
square --location <square> history --from <name> --limit 80
|
|
51
|
+
square --location <square> status
|
|
52
|
+
square --location <square> --as <name> done - <<'EOF'
|
|
53
53
|
...
|
|
54
54
|
EOF
|
|
55
55
|
|
|
56
|
-
For complete history: square --location <
|
|
56
|
+
For complete history: square --location <square> history --all --full
|
|
57
57
|
|
|
58
58
|
Every activity must address at least one participant with @name. Mentioned participants perceive the full body; others perceive only directed presence. Use `--bell` only when every participant needs the activity — everyone catching with `--mention` will receive it. Precise history queries may still read original archive bodies.
|
|
59
59
|
|
|
60
|
-
If an activity is refused because something happened while the participant was not looking, run `square --location <
|
|
60
|
+
If an activity is refused because something happened while the participant was not looking, run `square --location <square> --as <name> catch --now`, take it in, then express again. `catch --now` catches up without waiting.
|
|
61
61
|
```
|
|
62
62
|
|
|
63
63
|
Need another voice later? Spawn another participant agent with a new `<name>` and give it the same participant prompt.
|
|
@@ -67,12 +67,12 @@ Need another voice later? Spawn another participant agent with a new `<name>` an
|
|
|
67
67
|
If you or the human want to participate, choose a participant name and use the participant loop:
|
|
68
68
|
|
|
69
69
|
```bash
|
|
70
|
-
square --location <
|
|
71
|
-
square --location <
|
|
70
|
+
square --location <square> --as <name> join
|
|
71
|
+
square --location <square> --as <name> express - <<'EOF'
|
|
72
72
|
@<participant-name> your view
|
|
73
73
|
EOF
|
|
74
|
-
square --location <
|
|
75
|
-
square --location <
|
|
74
|
+
square --location <square> --as <name> catch --idle 10m
|
|
75
|
+
square --location <square> --as <name> done - <<'EOF'
|
|
76
76
|
final note
|
|
77
77
|
EOF
|
|
78
78
|
```
|
|
@@ -82,9 +82,9 @@ EOF
|
|
|
82
82
|
Use these to check progress:
|
|
83
83
|
|
|
84
84
|
```bash
|
|
85
|
-
square --location <
|
|
86
|
-
square --location <
|
|
87
|
-
square --location <
|
|
85
|
+
square --location <square> history --limit 50
|
|
86
|
+
square --location <square> history --from <name>
|
|
87
|
+
square --location <square> status
|
|
88
88
|
```
|
|
89
89
|
|
|
90
90
|
`history` reads the archive without advancing participant presence. `status` shows active/done participants, activity counts, cap/throttle, hold state, and latest ambient activity.
|
|
@@ -96,7 +96,7 @@ Every activity must contain `@name`; use `--bell` only for activity that every p
|
|
|
96
96
|
If the human wants to refocus the square, add a constraint, ask a convergence question, or correct its direction, write that direction publicly with a participant name:
|
|
97
97
|
|
|
98
98
|
```bash
|
|
99
|
-
square --location <
|
|
99
|
+
square --location <square> --as <name> express --bell - <<'EOF'
|
|
100
100
|
Refocus on <specific direction, constraint, question, or decision needed>.
|
|
101
101
|
EOF
|
|
102
102
|
```
|
|
@@ -106,8 +106,8 @@ Do not add direction on your own. If you notice the square drifting or stuck, re
|
|
|
106
106
|
Pause the participant loop when a human needs time to read, think, or add another voice:
|
|
107
107
|
|
|
108
108
|
```bash
|
|
109
|
-
square --location <
|
|
110
|
-
square --location <
|
|
109
|
+
square --location <square> hold "human reading"
|
|
110
|
+
square --location <square> resume
|
|
111
111
|
```
|
|
112
112
|
|
|
113
113
|
While held, participant expression and catch pause. Join, done, status, and history still work.
|
|
@@ -117,8 +117,8 @@ While held, participant expression and catch pause. Join, done, status, and hist
|
|
|
117
117
|
When participants are done, collect the public activities:
|
|
118
118
|
|
|
119
119
|
```bash
|
|
120
|
-
square --location <
|
|
121
|
-
square --location <
|
|
120
|
+
square --location <square> history --all --full # complete public history
|
|
121
|
+
square --location <square> status
|
|
122
122
|
```
|
|
123
123
|
|
|
124
124
|
## Boundaries
|