@astrosheep/square 0.3.85 → 0.3.87
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/dist/artifact.d.ts +6 -1
- package/dist/artifact.js +79 -10
- package/dist/boundary-presentation.d.ts +5 -4
- package/dist/boundary-presentation.js +21 -25
- package/dist/catch-decisions.js +2 -1
- package/dist/claude-delivery-status.d.ts +8 -2
- package/dist/claude-delivery-status.js +26 -10
- package/dist/claude-delivery.d.ts +8 -4
- package/dist/claude-delivery.js +114 -104
- package/dist/claude-mcp-context.js +2 -2
- package/dist/claude-mod.js +119 -105
- package/dist/cli/context.d.ts +3 -1
- package/dist/cli/context.js +25 -1
- package/dist/cli/express-presentation.d.ts +3 -1
- package/dist/cli/express-presentation.js +15 -12
- package/dist/cli/mcp-server-command.js +2 -2
- package/dist/cli/observation-commands.js +2 -1
- package/dist/cli/registry.js +0 -2
- package/dist/cli/square-commands.d.ts +4 -2
- package/dist/cli/square-commands.js +21 -6
- package/dist/codex-hook.d.ts +3 -3
- package/dist/codex-hook.js +4 -4
- package/dist/codex-queue.d.ts +5 -6
- package/dist/codex-queue.js +9 -5
- package/dist/decisions.d.ts +3 -2
- package/dist/decisions.js +18 -6
- package/dist/delivery-health.d.ts +32 -2
- package/dist/delivery-health.js +108 -45
- package/dist/delivery-operations.d.ts +1 -2
- package/dist/delivery-operations.js +155 -196
- package/dist/delivery.d.ts +14 -23
- package/dist/delivery.js +48 -18
- package/dist/file-lock.d.ts +8 -1
- package/dist/file-lock.js +2 -0
- package/dist/help.js +12 -14
- package/dist/hints.js +2 -3
- package/dist/inbox.d.ts +35 -9
- package/dist/inbox.js +228 -144
- package/dist/index.d.ts +4 -4
- package/dist/index.js +1 -3
- package/dist/list.js +9 -4
- package/dist/mcp.js +12 -1
- package/dist/model.d.ts +39 -5
- package/dist/model.js +84 -0
- package/dist/notifications.d.ts +8 -6
- package/dist/notifications.js +56 -143
- package/dist/operation-presentation.js +12 -14
- package/dist/participant-identity.d.ts +0 -3
- package/dist/participant-identity.js +0 -31
- package/dist/paseo-delivery.d.ts +5 -6
- package/dist/paseo-delivery.js +13 -8
- package/dist/paseo.d.ts +1 -1
- package/dist/paseo.js +1 -1
- package/dist/plugins/opencode/delivery.d.ts +8 -3
- package/dist/plugins/opencode/delivery.js +187 -132
- package/dist/plugins/opencode/index.js +12 -38
- package/dist/ports.d.ts +112 -40
- package/dist/presentation-operations.d.ts +1 -1
- package/dist/presentation-operations.js +51 -35
- package/dist/presentation.d.ts +0 -1
- package/dist/presentation.js +18 -13
- package/dist/seats.d.ts +7 -0
- package/dist/seats.js +27 -0
- package/dist/session-lifecycle.d.ts +2 -2
- package/dist/session-lifecycle.js +10 -12
- package/dist/sqlite-square.d.ts +53 -0
- package/dist/sqlite-square.js +602 -0
- package/dist/square-api.d.ts +12 -41
- package/dist/square-api.js +26 -474
- package/dist/square-backend.d.ts +13 -0
- package/dist/square-core.d.ts +3 -2
- package/dist/square-domain.d.ts +12 -5
- package/dist/square-domain.js +163 -7
- package/dist/square-facade.d.ts +16 -10
- package/dist/square-index.d.ts +24 -0
- package/dist/square-index.js +131 -0
- package/dist/square-legacy-framing.d.ts +9 -0
- package/dist/square-legacy-framing.js +56 -0
- package/dist/square-location.d.ts +15 -0
- package/dist/square-location.js +76 -0
- package/dist/square-migration-host-ledger.d.ts +26 -0
- package/dist/square-migration-host-ledger.js +168 -0
- package/dist/square-migration.d.ts +5 -5
- package/dist/square-migration.js +373 -71
- package/dist/square-sqlite.d.ts +9 -9
- package/dist/square-sqlite.js +329 -79
- package/dist/square-stdio.d.ts +22 -0
- package/dist/square-stdio.js +222 -0
- package/dist/square-tools.js +30 -3
- package/dist/stdio-server.d.ts +3 -0
- package/dist/stdio-server.js +192 -0
- package/dist/stream.d.ts +3 -2
- package/dist/stream.js +9 -10
- package/dist/views.d.ts +9 -14
- package/dist/views.js +50 -70
- package/dist/wakes.d.ts +6 -5
- package/dist/wakes.js +20 -17
- package/guides/application-facade-delivery-evidence.md +1 -1
- package/guides/backends.md +180 -0
- package/guides/claude-native.md +9 -9
- package/guides/mcp.md +1 -1
- package/guides/migration.md +107 -67
- package/guides/row-storage-design.md +1 -1
- package/guides/square-api.md +69 -30
- package/package.json +1 -1
- package/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/plugins/claude/hooks/register.js +1 -1
- package/plugins/claude/skills/square/SKILL.md +7 -7
- package/plugins/claude/types.d.ts +1 -1
- package/plugins/codex/.codex-plugin/plugin.json +1 -1
- package/plugins/opencode/delivery.ts +170 -118
- package/plugins/opencode/index.ts +12 -30
- package/plugins/pi/square-pi.js +58 -20
- package/skills/square/SKILL.md +7 -7
- package/dist/cli/ledger-command.d.ts +0 -12
- package/dist/cli/ledger-command.js +0 -72
- package/dist/host-ledger-file-adapter.d.ts +0 -47
- package/dist/host-ledger-file-adapter.js +0 -384
- package/dist/host-ledger-migration.d.ts +0 -27
- package/dist/host-ledger-migration.js +0 -264
- package/dist/host-ledger-root.d.ts +0 -2
- package/dist/host-ledger-root.js +0 -9
- package/dist/host-ledger-sqlite.d.ts +0 -65
- package/dist/host-ledger-sqlite.js +0 -368
- package/dist/host-ledger.d.ts +0 -195
- package/dist/ledger-maintenance.d.ts +0 -30
- package/dist/ledger-maintenance.js +0 -110
- package/dist/open-square.d.ts +0 -36
- package/dist/open-square.js +0 -81
- package/dist/participant-host.d.ts +0 -22
- package/dist/participant-host.js +0 -92
- package/dist/registry.d.ts +0 -55
- package/dist/registry.js +0 -123
- package/dist/routes.d.ts +0 -39
- package/dist/routes.js +0 -99
- package/dist/square-actions.d.ts +0 -56
- package/dist/square-actions.js +0 -353
- package/dist/square-projections.d.ts +0 -31
- package/dist/square-projections.js +0 -64
- package/dist/wake-attempts.d.ts +0 -10
- package/dist/wake-attempts.js +0 -39
- package/dist/wake-eligibility.d.ts +0 -53
- package/dist/wake-eligibility.js +0 -123
- package/dist/wake-evidence.d.ts +0 -33
- package/dist/wake-evidence.js +0 -32
- package/guides/host-ledger.md +0 -58
- package/guides/indexed-host-ledger-performance.md +0 -39
- /package/dist/{host-ledger.js → square-backend.js} +0 -0
package/dist/inbox.js
CHANGED
|
@@ -1,78 +1,198 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { createArtifactPort, observeSquareRevision } from './artifact.js';
|
|
3
|
-
import {
|
|
1
|
+
import { sameBody } from './model.js';
|
|
2
|
+
import { createArtifactPort, observeSquareDeliveryRevision, observeSquareRevision } from './artifact.js';
|
|
3
|
+
import { indexedSquareEntries, observeSquareIndex } from './square-index.js';
|
|
4
4
|
import { canonicalPath } from './canonical-path.js';
|
|
5
|
-
import {
|
|
5
|
+
import { formatActivityId, parseActivityId } from './square-core.js';
|
|
6
6
|
import { freshWatchLease, WATCH_STALE_MS } from './runtime.js';
|
|
7
7
|
import { pendingHead, pendingNotifications, leaseOwnsNotification } from './delivery.js';
|
|
8
8
|
import { attentionBodyIsClipped, renderAttentionPreview, renderSquareReminder } from './attention-presentation.js';
|
|
9
|
-
function
|
|
10
|
-
return
|
|
9
|
+
export function pendingKey(membership, actIndex) {
|
|
10
|
+
return membership.location + '\0' + membership.seat.participant.toLocaleLowerCase() + '\0' + actIndex;
|
|
11
11
|
}
|
|
12
|
-
function
|
|
13
|
-
return
|
|
12
|
+
export function pendingAtBoundary(inbox) {
|
|
13
|
+
return inbox.map((membership) => {
|
|
14
|
+
const lease = membership.catchLease;
|
|
15
|
+
if (lease === undefined)
|
|
16
|
+
return membership;
|
|
17
|
+
return { ...membership, notifications: membership.notifications.filter((notification) => !leaseOwnsNotification(lease, { ...notification, recipient: membership.seat.participant })) };
|
|
18
|
+
}).filter((membership) => membership.notifications.length > 0);
|
|
14
19
|
}
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
if (
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
return { binding, joined: true, notifications, ...(catchLease === undefined ? {} : { catchLease }) };
|
|
20
|
+
function eligibleNotifications(facts, membership) {
|
|
21
|
+
const seat = facts.seat(membership.seat.participant);
|
|
22
|
+
if (seat === undefined || seat.generation !== membership.seat.generation
|
|
23
|
+
|| !sameBody(seat.body, membership.seat.body))
|
|
24
|
+
return [];
|
|
25
|
+
const cancelledThrough = facts.cancelledThrough(seat.participant, seat.generation);
|
|
26
|
+
return membership.notifications.filter(notification => (cancelledThrough === undefined || notification.actIndex > cancelledThrough)
|
|
27
|
+
&& !facts.deliveryEvidence(seat.participant, seat.generation, formatActivityId(notification.actIndex))
|
|
28
|
+
.some(row => row.kind === 'presentation' && (row.outcome === 'presented' || row.outcome === 'clipped')));
|
|
25
29
|
}
|
|
26
|
-
/**
|
|
27
|
-
export function
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
30
|
+
/** Revalidate a supplied batch immediately before the boundary's independent claim/send checks. */
|
|
31
|
+
export async function projectBoundaryEligibility(inbox) {
|
|
32
|
+
const grouped = new Map();
|
|
33
|
+
for (const membership of pendingAtBoundary(inbox)) {
|
|
34
|
+
const entries = grouped.get(membership.location) ?? [];
|
|
35
|
+
entries.push(membership);
|
|
36
|
+
grouped.set(membership.location, entries);
|
|
37
|
+
}
|
|
38
|
+
const pending = [];
|
|
39
|
+
for (const [location, memberships] of grouped) {
|
|
40
|
+
const artifact = createArtifactPort(location);
|
|
41
|
+
try {
|
|
42
|
+
const { value } = await artifact.read(facts => memberships.flatMap(membership => {
|
|
43
|
+
const notifications = eligibleNotifications(facts, membership);
|
|
44
|
+
return notifications.length === 0 ? [] : [{ ...membership, notifications }];
|
|
45
|
+
}));
|
|
46
|
+
pending.push(...value);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// An unreadable or stale candidate cannot authorize boundary presentation.
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
await artifact.close();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return pending;
|
|
42
56
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
57
|
+
/** HOME is a candidate path index; current seats and delivery evidence live in the artifact. */
|
|
58
|
+
export async function projectSessionCandidates(input) {
|
|
59
|
+
const location = input.location === undefined ? undefined : await canonicalPath(input.location);
|
|
60
|
+
const entries = await indexedSquareEntries({ body: input.body, session: input.sessionId, channel: input.channel, agent: input.agent }, input.env ?? process.env);
|
|
61
|
+
const candidates = new Map();
|
|
62
|
+
for (const entry of entries) {
|
|
63
|
+
if (location !== undefined && location !== entry.location)
|
|
64
|
+
continue;
|
|
65
|
+
const key = JSON.stringify([entry.location, entry.body.channel, entry.body.session, entry.body.agent ?? null]);
|
|
66
|
+
candidates.set(key, { location: entry.location, body: entry.body });
|
|
67
|
+
}
|
|
68
|
+
return [...candidates.values()];
|
|
69
|
+
}
|
|
70
|
+
function groupCandidates(candidates) {
|
|
71
|
+
const grouped = new Map();
|
|
72
|
+
for (const { location, body } of candidates) {
|
|
73
|
+
const bodies = grouped.get(location) ?? [];
|
|
74
|
+
bodies.push(body);
|
|
75
|
+
grouped.set(location, bodies);
|
|
53
76
|
}
|
|
54
|
-
|
|
55
|
-
|
|
77
|
+
return grouped;
|
|
78
|
+
}
|
|
79
|
+
function locatedSeats(facts, location, seats) {
|
|
80
|
+
return seats.flatMap(seat => {
|
|
81
|
+
const index = parseActivityId(seat.generation);
|
|
82
|
+
const joined = index === undefined ? undefined : facts.activity(index);
|
|
83
|
+
return joined?.kind !== 'join' ? [] : [{ location, seat, joinedAt: joined.at }];
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
/** Lifecycle reads resolve seats and join times, never pending activity bodies. */
|
|
87
|
+
export async function projectSessionSeats(input, signal) {
|
|
88
|
+
const seats = [];
|
|
89
|
+
for (const [location, bodies] of groupCandidates(await projectSessionCandidates(input))) {
|
|
90
|
+
signal?.throwIfAborted();
|
|
91
|
+
const artifact = createArtifactPort(location, undefined, signal);
|
|
92
|
+
try {
|
|
93
|
+
seats.push(...(await artifact.read(facts => locatedSeats(facts, location, bodies.flatMap(body => facts.seatsForBody(body))), signal)).value);
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
if (signal?.aborted)
|
|
97
|
+
throw signal.reason ?? error;
|
|
98
|
+
}
|
|
99
|
+
finally {
|
|
100
|
+
await artifact.close();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return seats;
|
|
104
|
+
}
|
|
105
|
+
/** Public count boundary: one coherent read per artifact, without pending body hydration. */
|
|
106
|
+
export async function sessionInboxCounts(input, signal) {
|
|
107
|
+
const rows = [];
|
|
108
|
+
for (const [location, bodies] of groupCandidates(await projectSessionCandidates(input))) {
|
|
109
|
+
signal?.throwIfAborted();
|
|
110
|
+
const artifact = createArtifactPort(location, undefined, signal);
|
|
111
|
+
try {
|
|
112
|
+
rows.push(...(await artifact.read(facts => locatedSeats(facts, location, bodies.flatMap(body => facts.seatsForBody(body))).map(({ seat }) => ({
|
|
113
|
+
name: seat.participant, squarePath: location, pending: facts.directedCount(seat.participant, { unseenOnly: true }),
|
|
114
|
+
})), signal)).value);
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
if (signal?.aborted)
|
|
118
|
+
throw signal.reason ?? error;
|
|
119
|
+
}
|
|
120
|
+
finally {
|
|
121
|
+
await artifact.close();
|
|
122
|
+
}
|
|
56
123
|
}
|
|
124
|
+
return rows;
|
|
57
125
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
for (const
|
|
62
|
-
|
|
63
|
-
|
|
126
|
+
/** One authoritative read per canonical artifact, even when discovery names several bodies. */
|
|
127
|
+
async function projectPending(candidates, cache, signal) {
|
|
128
|
+
const pending = [];
|
|
129
|
+
for (const [location, bodies] of groupCandidates(candidates)) {
|
|
130
|
+
signal?.throwIfAborted();
|
|
131
|
+
const artifact = createArtifactPort(location, undefined, signal);
|
|
64
132
|
try {
|
|
65
|
-
const { value:
|
|
66
|
-
|
|
67
|
-
|
|
133
|
+
const { value: current } = await artifact.read(facts => {
|
|
134
|
+
const seats = bodies.flatMap(body => facts.seatsForBody(body));
|
|
135
|
+
const coordinate = JSON.stringify([bodies, seats.map(seat => [seat.participant, seat.body, seat.generation])]);
|
|
136
|
+
const header = facts.header();
|
|
137
|
+
let cached = cache?.get(location);
|
|
138
|
+
if (cached?.coordinate !== coordinate || cached.version !== header.revision) {
|
|
139
|
+
cached = { coordinate, version: header.revision, value: locatedSeats(facts, location, seats).map(membership => ({
|
|
140
|
+
...membership, notifications: pendingNotifications(facts, membership.seat.participant).map(({ item, route }) => ({
|
|
141
|
+
actIndex: item.index, actor: item.actor, at: item.at, body: item.body ?? '(structured activity)', route,
|
|
142
|
+
})),
|
|
143
|
+
})) };
|
|
144
|
+
}
|
|
145
|
+
// Cache conversation content, never leases or delivery eligibility: evidence can change
|
|
146
|
+
// without a conversation revision and must not force body hydration or self-wake retries.
|
|
147
|
+
return { cached, pending: cached.value.map(membership => {
|
|
148
|
+
const catchLease = freshWatchLease(facts, membership.seat.participant);
|
|
149
|
+
return { ...membership, notifications: eligibleNotifications(facts, membership),
|
|
150
|
+
...(catchLease === undefined ? {} : { catchLease }) };
|
|
151
|
+
}) };
|
|
152
|
+
}, signal);
|
|
153
|
+
cache?.set(location, current.cached);
|
|
154
|
+
pending.push(...current.pending);
|
|
68
155
|
}
|
|
69
156
|
catch (error) {
|
|
157
|
+
cache?.delete(location);
|
|
70
158
|
if (signal?.aborted)
|
|
71
159
|
throw signal.reason ?? error;
|
|
72
|
-
//
|
|
160
|
+
// Unreadable candidates remain watch targets, but never authorize cached content.
|
|
161
|
+
}
|
|
162
|
+
finally {
|
|
163
|
+
await artifact.close();
|
|
73
164
|
}
|
|
74
165
|
}
|
|
75
|
-
return
|
|
166
|
+
return pending;
|
|
167
|
+
}
|
|
168
|
+
function sameWakeAddress(address, kind, requested) {
|
|
169
|
+
if (address?.kind !== kind)
|
|
170
|
+
return false;
|
|
171
|
+
return Object.entries(requested).every(([key, value]) => address.address[key] === value);
|
|
172
|
+
}
|
|
173
|
+
/** First matching current seat, exact body/generation/address and catch ownership. */
|
|
174
|
+
export function nativePendingPreview(facts, input) {
|
|
175
|
+
const seat = facts.seat(input.participant);
|
|
176
|
+
if (seat === undefined || seat.body.session !== input.sessionId
|
|
177
|
+
|| !sameWakeAddress(seat.wakeAddress, input.routeKind, input.address))
|
|
178
|
+
return undefined;
|
|
179
|
+
const cancelledThrough = facts.cancelledThrough(seat.participant, seat.generation);
|
|
180
|
+
if (input.actIndex <= Math.max(input.cancelledThrough ?? -1, cancelledThrough ?? -1))
|
|
181
|
+
return undefined;
|
|
182
|
+
const head = pendingHead(facts, seat.participant, input.actIndex);
|
|
183
|
+
const lease = freshWatchLease(facts, input.participant, input.now);
|
|
184
|
+
if (!head || (lease !== undefined && leaseOwnsNotification(lease, head)))
|
|
185
|
+
return undefined;
|
|
186
|
+
const item = facts.activity(input.actIndex);
|
|
187
|
+
if (item?.kind !== 'say')
|
|
188
|
+
return undefined;
|
|
189
|
+
return { payload: renderSquareReminder([renderAttentionPreview({ squarePath: input.location, recipient: input.participant, actIndex: item.index,
|
|
190
|
+
actor: item.actor, route: head.route, body: item.body ?? '(structured activity)' })]), clipped: attentionBodyIsClipped(item.body ?? '(structured activity)') };
|
|
191
|
+
}
|
|
192
|
+
export async function sessionInbox(identity, env = process.env, signal) {
|
|
193
|
+
const query = 'allAgents' in identity ? { channel: identity.channel, sessionId: identity.session, env }
|
|
194
|
+
: { body: identity, env };
|
|
195
|
+
return projectPending(await projectSessionCandidates(query), undefined, signal);
|
|
76
196
|
}
|
|
77
197
|
async function capture(observer, kind, signal) {
|
|
78
198
|
const generation = observer.generation;
|
|
@@ -103,34 +223,27 @@ async function capture(observer, kind, signal) {
|
|
|
103
223
|
}
|
|
104
224
|
}
|
|
105
225
|
/** Session-scoped projection over process-shared storage change detectors. */
|
|
106
|
-
export async function observeSessionPending(
|
|
226
|
+
export async function observeSessionPending(identity, suppliedEnv = process.env) {
|
|
107
227
|
const env = { ...suppliedEnv };
|
|
108
|
-
const
|
|
109
|
-
|
|
228
|
+
const query = 'allAgents' in identity ? { channel: identity.channel, sessionId: identity.session, env }
|
|
229
|
+
: { body: identity, env };
|
|
230
|
+
const index = await observeSquareIndex(env);
|
|
110
231
|
const artifacts = new Map();
|
|
232
|
+
const cache = new Map();
|
|
111
233
|
const lifetime = new AbortController();
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const cache = artifacts.get(binding.location)?.inbox;
|
|
115
|
-
const name = nameKey(binding.participant);
|
|
116
|
-
const coordinate = bindingCoordinate(binding);
|
|
117
|
-
let cached = cache?.get(name);
|
|
118
|
-
if (cached === undefined || cached.version !== version || cached.binding !== coordinate) {
|
|
119
|
-
cached = { ...await readInboxMembership(binding, signal), binding: coordinate };
|
|
120
|
-
cache?.set(name, cached);
|
|
121
|
-
}
|
|
122
|
-
const membership = cached.value;
|
|
123
|
-
if (membership === undefined)
|
|
124
|
-
return undefined;
|
|
125
|
-
const { catchLease, ...rest } = membership;
|
|
234
|
+
async function currentInbox(candidates, signal) {
|
|
235
|
+
const inbox = await projectPending(candidates, cache, signal);
|
|
126
236
|
const now = Date.now();
|
|
127
|
-
return
|
|
128
|
-
|
|
237
|
+
return inbox.map((membership) => {
|
|
238
|
+
const { catchLease, ...rest } = membership;
|
|
239
|
+
return catchLease !== undefined && catchLease.expiresAt > now && now - catchLease.heartbeatAt <= WATCH_STALE_MS
|
|
240
|
+
? membership : rest;
|
|
241
|
+
});
|
|
129
242
|
}
|
|
130
|
-
|
|
131
|
-
const eligible =
|
|
243
|
+
function eligibleInbox(inbox, excludeKeys) {
|
|
244
|
+
const eligible = pendingAtBoundary(inbox);
|
|
132
245
|
return excludeKeys === undefined ? eligible : eligible.flatMap((membership) => {
|
|
133
|
-
const notifications = membership.notifications.filter((notification) => !excludeKeys.has(
|
|
246
|
+
const notifications = membership.notifications.filter((notification) => !excludeKeys.has(pendingKey(membership, notification.actIndex)));
|
|
134
247
|
return notifications.length === 0 ? [] : [{ ...membership, notifications }];
|
|
135
248
|
});
|
|
136
249
|
}
|
|
@@ -138,34 +251,8 @@ export async function observeSessionPending(sessionId, suppliedEnv = process.env
|
|
|
138
251
|
async read(options = {}) {
|
|
139
252
|
const signal = AbortSignal.any([lifetime.signal, ...(options.signal === undefined ? [] : [options.signal])]);
|
|
140
253
|
signal.throwIfAborted();
|
|
141
|
-
const
|
|
142
|
-
const
|
|
143
|
-
const inbox = [];
|
|
144
|
-
for (const binding of bindings) {
|
|
145
|
-
signal.throwIfAborted();
|
|
146
|
-
try {
|
|
147
|
-
let version = revisions.get(binding.location);
|
|
148
|
-
if (version === undefined) {
|
|
149
|
-
const artifact = createArtifactPort(binding.location);
|
|
150
|
-
try {
|
|
151
|
-
version = await artifact.revision(signal);
|
|
152
|
-
}
|
|
153
|
-
finally {
|
|
154
|
-
await artifact.close();
|
|
155
|
-
}
|
|
156
|
-
revisions.set(binding.location, version);
|
|
157
|
-
}
|
|
158
|
-
const membership = await membershipAt(binding, version, signal);
|
|
159
|
-
if (membership !== undefined)
|
|
160
|
-
inbox.push(membership);
|
|
161
|
-
}
|
|
162
|
-
catch (error) {
|
|
163
|
-
if (signal.aborted)
|
|
164
|
-
throw signal.reason ?? error;
|
|
165
|
-
// An unreadable current revision must never authorize cached content.
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
const pending = await eligibleInbox(inbox, options.excludeKeys);
|
|
254
|
+
const inbox = await currentInbox(await projectSessionCandidates(query), signal);
|
|
255
|
+
const pending = eligibleInbox(inbox, options.excludeKeys);
|
|
169
256
|
signal.throwIfAborted();
|
|
170
257
|
return pending;
|
|
171
258
|
},
|
|
@@ -178,55 +265,51 @@ export async function observeSessionPending(sessionId, suppliedEnv = process.env
|
|
|
178
265
|
let canReturn = !options.skipImmediate;
|
|
179
266
|
let previousEligible;
|
|
180
267
|
let armed = false;
|
|
268
|
+
let candidates = [];
|
|
269
|
+
let candidatePresence;
|
|
270
|
+
let discoveredCandidates = false;
|
|
181
271
|
try {
|
|
182
272
|
while (!signal.aborted) {
|
|
183
|
-
// Subscribe
|
|
184
|
-
|
|
185
|
-
const
|
|
186
|
-
const
|
|
273
|
+
// Subscribe to the candidate index before discovering artifacts; a concurrent join can
|
|
274
|
+
// publish its path without touching the artifact revision.
|
|
275
|
+
const sources = [await capture(index, 'index', signal)];
|
|
276
|
+
const presence = sources[0];
|
|
277
|
+
const currentPresence = presence.value;
|
|
278
|
+
if (!discoveredCandidates || currentPresence !== undefined && !Object.is(currentPresence, candidatePresence)) {
|
|
279
|
+
candidates = await projectSessionCandidates(query);
|
|
280
|
+
candidatePresence = currentPresence;
|
|
281
|
+
discoveredCandidates = true;
|
|
282
|
+
}
|
|
283
|
+
const paths = new Set(candidates.map((candidate) => candidate.location));
|
|
187
284
|
for (const [file, entry] of artifacts) {
|
|
188
285
|
if (!paths.has(file)) {
|
|
189
286
|
entry.observer.close();
|
|
287
|
+
entry.delivery.close();
|
|
190
288
|
artifacts.delete(file);
|
|
289
|
+
cache.delete(file);
|
|
191
290
|
}
|
|
192
291
|
}
|
|
193
292
|
for (const file of paths) {
|
|
194
293
|
if (artifacts.has(file))
|
|
195
294
|
continue;
|
|
196
295
|
const observer = await observeSquareRevision(file);
|
|
296
|
+
const delivery = await observeSquareDeliveryRevision(file);
|
|
197
297
|
if (signal.aborted) {
|
|
198
298
|
observer.close();
|
|
299
|
+
delivery.close();
|
|
199
300
|
return [];
|
|
200
301
|
}
|
|
201
|
-
artifacts.set(file, { observer,
|
|
302
|
+
artifacts.set(file, { observer, delivery });
|
|
202
303
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
sources.push(
|
|
207
|
-
|
|
208
|
-
const names = new Set(bindings.filter((binding) => binding.location === file).map((binding) => nameKey(binding.participant)));
|
|
209
|
-
for (const name of entry.inbox.keys())
|
|
210
|
-
if (!names.has(name))
|
|
211
|
-
entry.inbox.delete(name);
|
|
212
|
-
}
|
|
213
|
-
const inbox = [];
|
|
214
|
-
for (const binding of bindings) {
|
|
215
|
-
if (signal.aborted)
|
|
216
|
-
return [];
|
|
217
|
-
try {
|
|
218
|
-
const membership = await membershipAt(binding, revisions.get(binding.location), signal);
|
|
219
|
-
if (membership !== undefined)
|
|
220
|
-
inbox.push(membership);
|
|
221
|
-
}
|
|
222
|
-
catch (error) {
|
|
223
|
-
if (signal.aborted)
|
|
224
|
-
throw signal.reason ?? error;
|
|
225
|
-
// Unreadable projections are retried, never cached as current.
|
|
226
|
-
}
|
|
304
|
+
// Capture both revision baselines before selecting seats and pending facts. A takeover
|
|
305
|
+
// during an empty selection must remain visible to the already armed observers.
|
|
306
|
+
for (const entry of artifacts.values()) {
|
|
307
|
+
sources.push(await capture(entry.observer, 'artifact', signal));
|
|
308
|
+
sources.push(await capture(entry.delivery, 'delivery', signal));
|
|
227
309
|
}
|
|
228
|
-
const
|
|
229
|
-
const
|
|
310
|
+
const inbox = await currentInbox(candidates, signal);
|
|
311
|
+
const pending = eligibleInbox(inbox, options.excludeKeys);
|
|
312
|
+
const eligibleKeys = JSON.stringify(pending.flatMap((membership) => membership.notifications.map((note) => pendingKey(membership, note.actIndex))).sort());
|
|
230
313
|
if (previousEligible !== undefined && previousEligible !== eligibleKeys)
|
|
231
314
|
canReturn = true;
|
|
232
315
|
previousEligible = eligibleKeys;
|
|
@@ -239,7 +322,7 @@ export async function observeSessionPending(sessionId, suppliedEnv = process.env
|
|
|
239
322
|
return [];
|
|
240
323
|
// Eligibility may change at lease expiry without any storage write.
|
|
241
324
|
const leaseExpiry = Math.min(Infinity, ...inbox.flatMap((membership) => membership.catchLease === undefined ? [] : [Math.min(membership.catchLease.expiresAt, membership.catchLease.heartbeatAt + WATCH_STALE_MS + 1)]));
|
|
242
|
-
const nextExpiry =
|
|
325
|
+
const nextExpiry = leaseExpiry;
|
|
243
326
|
const duration = Math.min(remaining, Math.max(1, nextExpiry - Date.now()));
|
|
244
327
|
const cancelled = new AbortController();
|
|
245
328
|
const waitSignal = AbortSignal.any([signal, cancelled.signal]);
|
|
@@ -259,8 +342,7 @@ export async function observeSessionPending(sessionId, suppliedEnv = process.env
|
|
|
259
342
|
}
|
|
260
343
|
try {
|
|
261
344
|
const change = await Promise.race(waits);
|
|
262
|
-
|
|
263
|
-
if (change !== 'evidence')
|
|
345
|
+
if (change !== 'delivery')
|
|
264
346
|
canReturn = true;
|
|
265
347
|
}
|
|
266
348
|
finally {
|
|
@@ -286,11 +368,13 @@ export async function observeSessionPending(sessionId, suppliedEnv = process.env
|
|
|
286
368
|
if (lifetime.signal.aborted)
|
|
287
369
|
return;
|
|
288
370
|
lifetime.abort();
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
for (const entry of artifacts.values())
|
|
371
|
+
index.close();
|
|
372
|
+
for (const entry of artifacts.values()) {
|
|
292
373
|
entry.observer.close();
|
|
374
|
+
entry.delivery.close();
|
|
375
|
+
}
|
|
293
376
|
artifacts.clear();
|
|
377
|
+
cache.clear();
|
|
294
378
|
},
|
|
295
379
|
};
|
|
296
380
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export { Square, squareLocation, squareClock, createSquareResourceOwner, discover, inbox, diagnoseSquare, migrateSquare } from './square-api.js';
|
|
2
|
-
export { SquareError } from './model.js';
|
|
3
|
-
export {
|
|
2
|
+
export { SquareError, ACTIVITY_METADATA_MAX_BYTES } from './model.js';
|
|
3
|
+
export type { WakeTransportPort, WakeRequest, WakeOutcome } from './ports.js';
|
|
4
|
+
export type { WakeTransports } from './notifications.js';
|
|
4
5
|
export { createDefaultWakeTransport } from './notifications.js';
|
|
5
|
-
export { createHostLedgerPort, FileHostLedgerPort } from './host-ledger-file-adapter.js';
|
|
6
|
-
export type { HostLedgerPort, PresenceRecord, EvidenceRecord } from './host-ledger.js';
|
|
7
6
|
export type { PresentationSinkPort } from './ports.js';
|
|
7
|
+
export type * from './square-backend.js';
|
|
8
8
|
export type * from './square-facade.js';
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
1
|
export { Square, squareLocation, squareClock, createSquareResourceOwner, discover, inbox, diagnoseSquare, migrateSquare } from './square-api.js';
|
|
2
|
-
export { SquareError } from './model.js';
|
|
3
|
-
export { squareAssignedParticipantName } from './registry.js';
|
|
2
|
+
export { SquareError, ACTIVITY_METADATA_MAX_BYTES } from './model.js';
|
|
4
3
|
export { createDefaultWakeTransport } from './notifications.js';
|
|
5
|
-
export { createHostLedgerPort, FileHostLedgerPort } from './host-ledger-file-adapter.js';
|
package/dist/list.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import {
|
|
4
|
-
import { closeOpenSquare } from './open-square.js';
|
|
3
|
+
import { createArtifactPort } from './artifact.js';
|
|
5
4
|
import { listPresentation } from './views.js';
|
|
6
5
|
import { SquareError } from './model.js';
|
|
7
6
|
export const DEFAULT_LIST_DEPTH = 4;
|
|
@@ -16,8 +15,14 @@ async function readSquareListItem(filePath, root, options, control) {
|
|
|
16
15
|
try {
|
|
17
16
|
control?.signal?.throwIfAborted();
|
|
18
17
|
const stat = await fs.promises.stat(filePath);
|
|
19
|
-
const
|
|
20
|
-
|
|
18
|
+
const artifact = createArtifactPort(filePath, undefined, control?.signal);
|
|
19
|
+
let projection;
|
|
20
|
+
try {
|
|
21
|
+
projection = (await artifact.read(listPresentation, control?.signal)).value;
|
|
22
|
+
}
|
|
23
|
+
finally {
|
|
24
|
+
await artifact.close();
|
|
25
|
+
}
|
|
21
26
|
return {
|
|
22
27
|
path: path.relative(root, filePath) || path.basename(filePath),
|
|
23
28
|
lastActiveAt: stat.mtimeMs,
|
package/dist/mcp.js
CHANGED
|
@@ -25,6 +25,14 @@ export function createSquareMcpServer(options) {
|
|
|
25
25
|
const claude = baseEnv.SQUARE_CLAUDE_MCP_CONTEXT === '1'
|
|
26
26
|
? await readClaudeMcpCall(extra._meta?.['claudecode/toolUseId'], baseEnv)
|
|
27
27
|
: undefined;
|
|
28
|
+
const body = claude !== undefined
|
|
29
|
+
? { channel: 'claude-code', session: claude.sessionId,
|
|
30
|
+
...(claude.agentId === undefined ? {} : { agent: claude.agentId }) }
|
|
31
|
+
: typeof threadId === 'string' && threadId.trim()
|
|
32
|
+
? { channel: 'codex', session: threadId }
|
|
33
|
+
: typeof opencodeSessionId === 'string' && opencodeSessionId.trim()
|
|
34
|
+
? { channel: 'opencode', session: opencodeSessionId }
|
|
35
|
+
: options.body;
|
|
28
36
|
const env = claude !== undefined
|
|
29
37
|
? { ...operationEnv('claude', claude.sessionId, baseEnv),
|
|
30
38
|
// Preserve Claude's session + child flag semantics; an agent loop ID is not a session ID.
|
|
@@ -35,7 +43,10 @@ export function createSquareMcpServer(options) {
|
|
|
35
43
|
? operationEnv('opencode', opencodeSessionId, baseEnv)
|
|
36
44
|
: baseEnv;
|
|
37
45
|
square = await Square.at({
|
|
38
|
-
...options, env, cwd: claude?.cwd ?? options.cwd,
|
|
46
|
+
...options, env, body, cwd: claude?.cwd ?? options.cwd,
|
|
47
|
+
// A call-local Codex thread uses its native queue even when managed by Paseo.
|
|
48
|
+
wakeAddress: claude === undefined && typeof threadId === 'string' && threadId.trim()
|
|
49
|
+
? { kind: 'codex-queue', address: { threadId } } : options.wakeAddress,
|
|
39
50
|
path: location ?? options.path,
|
|
40
51
|
participant: participant ?? options.participant,
|
|
41
52
|
// The server owner is bound to its configured square; overrides close after each call.
|
package/dist/model.d.ts
CHANGED
|
@@ -3,6 +3,37 @@ export { formatActivityId, parseActivityId } from './square-core.js';
|
|
|
3
3
|
export type { Act, ActivityId, Audience, Reach } from './square-core.js';
|
|
4
4
|
export declare const WAKE_ROUTE_KINDS: readonly ["opencode-server", "codex-queue", "claude-native", "paseo"];
|
|
5
5
|
export type WakeRouteKind = typeof WAKE_ROUTE_KINDS[number];
|
|
6
|
+
export type JsonValue = null | boolean | number | string | readonly JsonValue[] | {
|
|
7
|
+
readonly [key: string]: JsonValue;
|
|
8
|
+
};
|
|
9
|
+
export type ActivityMetadata = Readonly<Record<string, JsonValue>>;
|
|
10
|
+
export declare const ACTIVITY_METADATA_MAX_BYTES: number;
|
|
11
|
+
export declare const ACTIVITY_METADATA_MAX_DEPTH = 256;
|
|
12
|
+
/** Validate the bounded, lossless JSON object carried by a say. */
|
|
13
|
+
export declare function validateActivityMetadata(value: unknown): ActivityMetadata | undefined;
|
|
14
|
+
export declare function activityMetadataHasContent(metadata: ActivityMetadata | undefined): boolean;
|
|
15
|
+
export interface Body {
|
|
16
|
+
readonly channel: string;
|
|
17
|
+
readonly session: string;
|
|
18
|
+
readonly agent?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface WakeAddress {
|
|
21
|
+
readonly kind: WakeRouteKind;
|
|
22
|
+
readonly address: Readonly<Record<string, string>>;
|
|
23
|
+
}
|
|
24
|
+
/** Artifact-owned custody binding. Generation is the join activity that established it. */
|
|
25
|
+
export interface Seat {
|
|
26
|
+
readonly participant: string;
|
|
27
|
+
readonly body: Body;
|
|
28
|
+
readonly generation: ActivityId;
|
|
29
|
+
readonly wakeAddress?: WakeAddress;
|
|
30
|
+
}
|
|
31
|
+
export interface SeatWatchLease {
|
|
32
|
+
readonly generation: ActivityId;
|
|
33
|
+
readonly lease: WatchLease;
|
|
34
|
+
}
|
|
35
|
+
export declare function sameBody(left: Body, right: Body): boolean;
|
|
36
|
+
export declare function validateBody(body: Body): void;
|
|
6
37
|
export declare function isWakeRouteKind(value: unknown): value is WakeRouteKind;
|
|
7
38
|
export interface WakeRoute {
|
|
8
39
|
/** Ownership generation, when the receiver publishes a fenced route. */
|
|
@@ -80,11 +111,14 @@ export interface InboxNotification {
|
|
|
80
111
|
route: DirectedNotificationRoute;
|
|
81
112
|
body: string;
|
|
82
113
|
}
|
|
83
|
-
export interface
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
114
|
+
export interface LocatedSeat {
|
|
115
|
+
readonly location: string;
|
|
116
|
+
readonly seat: Seat;
|
|
117
|
+
readonly joinedAt: number;
|
|
118
|
+
}
|
|
119
|
+
export interface PendingMembership extends LocatedSeat {
|
|
120
|
+
readonly notifications: InboxNotification[];
|
|
121
|
+
readonly catchLease?: WatchLease;
|
|
88
122
|
}
|
|
89
123
|
export interface WatchOptions {
|
|
90
124
|
id?: ActivityId;
|