@astrosheep/square 0.3.69 → 0.3.71
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/activity-feed.d.ts +4 -5
- package/dist/activity-feed.js +14 -10
- package/dist/artifact.d.ts +26 -33
- package/dist/artifact.js +346 -317
- package/dist/attention-presentation.js +2 -9
- package/dist/automatic-session.js +6 -6
- package/dist/boundary-presentation.js +3 -3
- package/dist/catch-decisions.d.ts +19 -12
- package/dist/catch-decisions.js +32 -68
- package/dist/claude-delivery.js +7 -7
- package/dist/claude-mod.js +18 -7
- package/dist/cli/catch-presentation.d.ts +7 -0
- package/dist/cli/catch-presentation.js +55 -0
- package/dist/cli/context.d.ts +1 -1
- package/dist/cli/context.js +5 -23
- package/dist/cli/express-presentation.d.ts +10 -0
- package/dist/cli/express-presentation.js +196 -0
- package/dist/cli/maintenance-commands.d.ts +4 -0
- package/dist/cli/maintenance-commands.js +29 -6
- package/dist/cli/observation-commands.d.ts +7 -5
- package/dist/cli/observation-commands.js +275 -262
- package/dist/cli/program.d.ts +1 -1
- package/dist/cli/program.js +43 -56
- package/dist/cli/registry.js +2 -1
- package/dist/cli/square-commands.d.ts +3 -9
- package/dist/cli/square-commands.js +134 -255
- package/dist/codex-queue.js +4 -1
- package/dist/decisions.d.ts +26 -74
- package/dist/decisions.js +190 -335
- package/dist/delivery-health.js +13 -22
- package/dist/delivery-operations.d.ts +0 -11
- package/dist/delivery-operations.js +44 -33
- package/dist/delivery.d.ts +33 -32
- package/dist/delivery.js +70 -81
- package/dist/harness-claude.js +1 -1
- package/dist/harness-codex.js +1 -1
- package/dist/harness-pi.js +1 -1
- package/dist/help.js +6 -1
- package/dist/inbox.d.ts +27 -0
- package/dist/inbox.js +55 -18
- package/dist/index.d.ts +2 -4
- package/dist/index.js +1 -1
- package/dist/list.d.ts +7 -1
- package/dist/list.js +49 -122
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.js +7 -37
- package/dist/model.d.ts +4 -44
- package/dist/model.js +1 -3
- package/dist/notifications.d.ts +1 -1
- package/dist/notifications.js +13 -7
- package/dist/open-square.d.ts +16 -2
- package/dist/open-square.js +40 -1
- package/dist/perception-projection.d.ts +6 -15
- package/dist/perception-projection.js +6 -26
- package/dist/{opencode-delivery.d.ts → plugins/opencode/delivery.d.ts} +1 -1
- package/dist/{opencode-delivery.js → plugins/opencode/delivery.js} +18 -18
- package/dist/{opencode.d.ts → plugins/opencode/index.d.ts} +2 -2
- package/dist/{opencode.js → plugins/opencode/index.js} +9 -9
- package/dist/ports.d.ts +156 -11
- package/dist/presentation-operations.d.ts +1 -1
- package/dist/presentation-operations.js +35 -44
- package/dist/presentation.d.ts +12 -19
- package/dist/presentation.js +33 -54
- package/dist/routes.d.ts +7 -14
- package/dist/routes.js +33 -62
- package/dist/runtime.d.ts +12 -39
- package/dist/runtime.js +33 -164
- package/dist/square-actions.d.ts +11 -21
- package/dist/square-actions.js +171 -271
- package/dist/square-application.d.ts +31 -55
- package/dist/square-application.js +637 -141
- package/dist/square-core.d.ts +3 -94
- package/dist/square-core.js +8 -353
- package/dist/square-domain.d.ts +24 -0
- package/dist/square-domain.js +77 -0
- package/dist/square-facade.d.ts +360 -9
- package/dist/square-migration.d.ts +24 -0
- package/dist/square-migration.js +211 -0
- package/dist/square-projections.d.ts +2 -42
- package/dist/square-projections.js +1 -37
- package/dist/square-sqlite.d.ts +59 -0
- package/dist/square-sqlite.js +702 -0
- package/dist/stream.d.ts +3 -10
- package/dist/stream.js +81 -52
- package/dist/views.d.ts +27 -95
- package/dist/views.js +217 -87
- package/dist/wake-eligibility.d.ts +15 -5
- package/dist/wake-eligibility.js +32 -16
- package/dist/wakes.d.ts +3 -3
- package/dist/wakes.js +31 -27
- package/guides/application-facade-delivery-evidence.md +200 -0
- package/guides/application-facade-design.md +95 -0
- package/guides/change-waits.md +4 -4
- package/guides/migration.md +77 -0
- package/guides/pi-native-delivery.md +1 -1
- package/guides/primitive-catch-consolidation.md +85 -0
- package/guides/primitive-history-consolidation.md +76 -0
- package/guides/primitive-status-consolidation.md +87 -0
- package/guides/primitive-storage-consolidation.md +128 -0
- package/guides/row-runtime-delivery-evidence.md +160 -0
- package/guides/row-storage-design.md +567 -0
- package/package.json +5 -7
- package/{claude-plugin → plugins/claude}/.claude-plugin/plugin.json +1 -1
- package/{codex-plugin → plugins/codex}/.codex-plugin/plugin.json +1 -1
- package/plugins/opencode/delivery.ts +149 -0
- package/plugins/opencode/index.ts +136 -0
- package/plugins/paseo/OVERVIEW.md +14 -0
- package/plugins/paseo/README.md +54 -0
- package/plugins/paseo/client/square-message.tsx +106 -0
- package/plugins/paseo/client/transform.ts +24 -0
- package/plugins/paseo/index.client.tsx +19 -0
- package/plugins/paseo/package.json +19 -0
- package/plugins/paseo/paseo-plugin.json +7 -0
- package/plugins/paseo/shared/message.ts +29 -0
- package/{extensions → plugins/pi}/square-pi.js +8 -5
- package/dist/activity.d.ts +0 -14
- package/dist/activity.js +0 -248
- package/dist/square-file-adapter.d.ts +0 -26
- package/dist/square-file-adapter.js +0 -85
- package/dist/square-state.d.ts +0 -6
- package/dist/square-state.js +0 -138
- package/dist/square-storage.d.ts +0 -14
- package/dist/square-storage.js +0 -188
- package/dist/square-wiring.d.ts +0 -33
- package/dist/square-wiring.js +0 -109
- package/dist/watch.d.ts +0 -8
- package/dist/watch.js +0 -252
- /package/{claude-plugin → plugins/claude}/hooks/hooks.json +0 -0
- /package/{claude-plugin → plugins/claude}/hooks/register.js +0 -0
- /package/{claude-plugin → plugins/claude}/skills/square/SKILL.md +0 -0
- /package/{claude-plugin → plugins/claude}/types.d.ts +0 -0
- /package/{codex-plugin → plugins/codex}/hooks/hooks.json +0 -0
package/dist/delivery-health.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { collectPending } from './delivery.js';
|
|
2
2
|
import { formatActivityId } from './square-core.js';
|
|
3
3
|
import { participantIdentity, truncateChars } from './presentation.js';
|
|
4
4
|
import { formatDuration } from './time.js';
|
|
5
|
-
import {
|
|
6
|
-
import { openSquare } from './square
|
|
5
|
+
import { projectWakeEvidence } from './wake-eligibility.js';
|
|
6
|
+
import { openSquare } from './open-square.js';
|
|
7
7
|
import { closeOpenSquare } from './open-square.js';
|
|
8
|
-
import { pendingDeliveriesFromState } from './views.js';
|
|
9
8
|
import { readWakeReleaseDiagnostics } from './wake-attempts.js';
|
|
10
9
|
const DISPLAY_ORDER = [
|
|
11
10
|
'awaiting',
|
|
@@ -23,21 +22,13 @@ export async function classifyDeliveryHealth(squarePath, opts) {
|
|
|
23
22
|
const env = opts.env ?? process.env;
|
|
24
23
|
const square = await openSquare(squarePath, { clock: () => now, env });
|
|
25
24
|
try {
|
|
26
|
-
const {
|
|
27
|
-
const
|
|
28
|
-
const pending = pendingDeliveriesFromState(state, delivery);
|
|
29
|
-
const evidenceProjection = await projectWakeEvidenceFromState({
|
|
30
|
-
location: squarePath,
|
|
31
|
-
state,
|
|
32
|
-
hostLedger: square.hostLedger,
|
|
33
|
-
now,
|
|
34
|
-
delivery,
|
|
35
|
-
});
|
|
25
|
+
const { scan, routes, pending } = await collectPending(square.artifact);
|
|
26
|
+
const evidenceProjection = await projectWakeEvidence({ location: squarePath, routes, pending, hostLedger: square.hostLedger, now });
|
|
36
27
|
const items = [];
|
|
37
|
-
for (const
|
|
38
|
-
for (const note of
|
|
39
|
-
const ageMs = Math.max(0, now - note.
|
|
40
|
-
const evidence = evidenceProjection.evidence(
|
|
28
|
+
for (const participant of scan.roster) {
|
|
29
|
+
for (const note of pending.filter((head) => head.recipient === participant.name)) {
|
|
30
|
+
const ageMs = Math.max(0, now - note.at);
|
|
31
|
+
const evidence = evidenceProjection.evidence(note.recipient, note.index);
|
|
41
32
|
const kind = evidence.presented
|
|
42
33
|
? 'presented-not-delivered'
|
|
43
34
|
: evidence.terminal?.outcome === 'accepted'
|
|
@@ -50,10 +41,10 @@ export async function classifyDeliveryHealth(squarePath, opts) {
|
|
|
50
41
|
const attempt = evidence.terminal ?? evidence.attempts.at(-1);
|
|
51
42
|
items.push({
|
|
52
43
|
squarePath,
|
|
53
|
-
recipient:
|
|
54
|
-
actIndex: note.
|
|
55
|
-
actor: note.
|
|
56
|
-
at: note.
|
|
44
|
+
recipient: note.recipient,
|
|
45
|
+
actIndex: note.index,
|
|
46
|
+
actor: note.actor,
|
|
47
|
+
at: note.at,
|
|
57
48
|
ageMs,
|
|
58
49
|
route: note.route,
|
|
59
50
|
kind,
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import type { SquareState } from './model.js';
|
|
2
1
|
import type { HostLedgerPort, SquareArtifactPort, DeliverPendingInput, DeliveryResult } from './ports.js';
|
|
3
|
-
import { deriveDeliveryModel } from './delivery.js';
|
|
4
2
|
export declare function deliverPending(input: DeliverPendingInput): Promise<DeliveryResult>;
|
|
5
3
|
export declare function sweepPending(input: {
|
|
6
4
|
readonly artifact: SquareArtifactPort;
|
|
@@ -10,12 +8,3 @@ export declare function sweepPending(input: {
|
|
|
10
8
|
readonly graceMs: number;
|
|
11
9
|
readonly limit: number;
|
|
12
10
|
}): Promise<number[]>;
|
|
13
|
-
export declare function sweepPendingFromState(input: {
|
|
14
|
-
readonly state: SquareState;
|
|
15
|
-
readonly hostLedger: HostLedgerPort;
|
|
16
|
-
readonly location: string;
|
|
17
|
-
readonly now: number;
|
|
18
|
-
readonly graceMs: number;
|
|
19
|
-
readonly limit: number;
|
|
20
|
-
readonly deriveDelivery?: (snapshot: SquareState) => ReturnType<typeof deriveDeliveryModel>;
|
|
21
|
-
}): Promise<number[]>;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { formatActivityId, parseActivityId } from './square-core.js';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { wakeIsEligible } from './wake-eligibility.js';
|
|
2
|
+
import { capturePendingScan, collectPending, pendingFacts, pendingHead, pendingPages, sameJoin } from './delivery.js';
|
|
3
|
+
import { projectWakeEvidence, wakeIsEligible } from './wake-eligibility.js';
|
|
5
4
|
import { canonicalPath } from './canonical-path.js';
|
|
6
5
|
import { retireWakeRouteFromArtifact } from './routes.js';
|
|
7
6
|
import { redactCurrentDiagnostic } from './diagnostic-redaction.js';
|
|
@@ -35,12 +34,20 @@ async function attemptWakeWithin(transport, request, timeoutMs, beforeSend) {
|
|
|
35
34
|
}
|
|
36
35
|
export async function deliverPending(input) {
|
|
37
36
|
const location = await canonicalPath(input.location);
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
const requested = input.activity === undefined ? undefined : typeof input.activity === 'number' ? input.activity : parseActivityId(input.activity);
|
|
38
|
+
if (input.activity !== undefined && requested === undefined)
|
|
39
|
+
return { attempted: 0, accepted: 0, failed: 0, unknown: 0, notCapable: 0 };
|
|
40
|
+
const owners = await input.hostLedger.listPresence({ location, now: input.now ?? Date.now() });
|
|
41
|
+
const { scan, routes, pending } = await collectPending(input.artifact, requested);
|
|
42
|
+
const eligibility = await projectWakeEvidence({ routes, pending, owners, location, hostLedger: input.hostLedger, now: input.now ?? Date.now() });
|
|
41
43
|
const observeCurrentness = async (request, now) => {
|
|
42
|
-
const
|
|
43
|
-
const
|
|
44
|
+
const index = parseActivityId(request.activity);
|
|
45
|
+
const captured = scan.roster.find((participant) => participant.name === request.participant);
|
|
46
|
+
const { value: facts } = await input.artifact.read((facts) => {
|
|
47
|
+
const head = sameJoin(facts, captured) ? pendingHead(facts, request.participant, index) : undefined;
|
|
48
|
+
return { routes: facts.routes(), pending: head === undefined ? [] : [head] };
|
|
49
|
+
});
|
|
50
|
+
const projection = await projectWakeEvidence({ ...facts, bindings: owners, location, hostLedger: input.hostLedger, now });
|
|
44
51
|
return { ...projection.currentness({ ...request, location }), observationAvailable: true };
|
|
45
52
|
};
|
|
46
53
|
let attempted = 0;
|
|
@@ -48,14 +55,10 @@ export async function deliverPending(input) {
|
|
|
48
55
|
let failed = 0;
|
|
49
56
|
let unknown = 0;
|
|
50
57
|
let notCapable = 0;
|
|
51
|
-
for (const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
if (requested === undefined || requested !== notification.item.index)
|
|
56
|
-
continue;
|
|
57
|
-
}
|
|
58
|
-
const evidence = eligibility.evidence(recipient, notification.item.index);
|
|
58
|
+
for (const participant of scan.roster) {
|
|
59
|
+
const recipient = participant.name;
|
|
60
|
+
for (const notification of pending.filter((head) => head.recipient === recipient)) {
|
|
61
|
+
const evidence = eligibility.evidence(recipient, notification.index);
|
|
59
62
|
if (evidence.terminal !== undefined)
|
|
60
63
|
continue;
|
|
61
64
|
const candidates = evidence.attemptableRoutes;
|
|
@@ -66,8 +69,8 @@ export async function deliverPending(input) {
|
|
|
66
69
|
if (candidates.length === 0 && !evidence.presented)
|
|
67
70
|
notCapableForAttention = true;
|
|
68
71
|
for (const route of candidates) {
|
|
69
|
-
const activity = formatActivityId(notification.
|
|
70
|
-
const attention = { squarePath: location, actIndex: notification.
|
|
72
|
+
const activity = formatActivityId(notification.index);
|
|
73
|
+
const attention = { squarePath: location, actIndex: notification.index, recipient };
|
|
71
74
|
const leaseMs = input.timeoutMs ?? 5000;
|
|
72
75
|
const requestRoute = route;
|
|
73
76
|
if (input.transport.probe !== undefined) {
|
|
@@ -102,7 +105,7 @@ export async function deliverPending(input) {
|
|
|
102
105
|
if (claim.status === 'terminal')
|
|
103
106
|
break;
|
|
104
107
|
const { claimToken, attemptN } = claim;
|
|
105
|
-
const request = { location: input.location, participant: recipient, activity, actor: notification.
|
|
108
|
+
const request = { location: input.location, participant: recipient, activity, actor: notification.actor, route: requestRoute };
|
|
106
109
|
const claimRelease = { hostLedger: input.hostLedger, location, participant: recipient, session: route.sessionId, activity, claimToken, routeKind: route.kind, attemptN };
|
|
107
110
|
// One teardown path for every exit after claim acquisition; each call site keeps its own error policy.
|
|
108
111
|
const abandon = async (details, options = {}) => {
|
|
@@ -202,20 +205,28 @@ export async function deliverPending(input) {
|
|
|
202
205
|
return { attempted, accepted, failed, unknown, notCapable };
|
|
203
206
|
}
|
|
204
207
|
export async function sweepPending(input) {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
const
|
|
210
|
-
const
|
|
211
|
-
const
|
|
212
|
-
for (const
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
if (
|
|
217
|
-
|
|
208
|
+
if (input.limit <= 0)
|
|
209
|
+
return [];
|
|
210
|
+
const location = await canonicalPath(input.location);
|
|
211
|
+
const owners = await input.hostLedger.listPresence({ location, now: input.now });
|
|
212
|
+
const { value: scan } = await input.artifact.read(capturePendingScan);
|
|
213
|
+
const selected = [];
|
|
214
|
+
const indexes = new Set();
|
|
215
|
+
for await (const page of pendingPages(input.artifact, scan)) {
|
|
216
|
+
const { value: facts } = await input.artifact.read((facts) => pendingFacts(facts, scan, page));
|
|
217
|
+
const eligibility = await projectWakeEvidence({ ...facts, bindings: owners, location, hostLedger: input.hostLedger, now: input.now });
|
|
218
|
+
for (const head of facts.pending) {
|
|
219
|
+
if (indexes.size >= input.limit && !indexes.has(head.index))
|
|
220
|
+
break;
|
|
221
|
+
if (input.now - head.at > input.graceMs && wakeIsEligible(eligibility.evidence(head.recipient, head.index))) {
|
|
222
|
+
selected.push(head);
|
|
223
|
+
indexes.add(head.index);
|
|
224
|
+
}
|
|
218
225
|
}
|
|
226
|
+
if (indexes.size >= input.limit)
|
|
227
|
+
break;
|
|
219
228
|
}
|
|
220
|
-
|
|
229
|
+
const { value: facts } = await input.artifact.read((facts) => pendingFacts(facts, scan, selected));
|
|
230
|
+
const eligibility = await projectWakeEvidence({ ...facts, bindings: owners, location, hostLedger: input.hostLedger, now: input.now });
|
|
231
|
+
return [...new Set(facts.pending.filter((head) => wakeIsEligible(eligibility.evidence(head.recipient, head.index))).map((head) => head.index))];
|
|
221
232
|
}
|
package/dist/delivery.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import type { WakeOutcome } from './ports.js';
|
|
1
|
+
import type { DirectedNotificationRoute, StoredAct, WatchLease, WakeRouteKind } from './model.js';
|
|
2
|
+
import type { DirectedHead, ParticipantFact, SquareArtifactPort, SquareRead, WakeOutcome } from './ports.js';
|
|
4
3
|
export type { DirectedNotificationRoute } from './model.js';
|
|
5
4
|
export type SayItem = StoredAct & {
|
|
6
5
|
kind: 'say';
|
|
@@ -10,21 +9,13 @@ export interface PlannedNotification {
|
|
|
10
9
|
recipient: string;
|
|
11
10
|
route: DirectedNotificationRoute;
|
|
12
11
|
}
|
|
13
|
-
/**
|
|
14
|
-
* What one adapter reports about a wake it was handed.
|
|
15
|
-
* `gate-rejected` means exactly one thing: the supplied beforeSend gate returned false and nothing was sent.
|
|
16
|
-
* It is not a bin for other not-sent reasons; those stay `failed` with `unavailable: true`.
|
|
17
|
-
*/
|
|
12
|
+
/** gate-rejected means the supplied beforeSend returned false and nothing was sent. */
|
|
18
13
|
export type WakeAdapterResult = Exclude<WakeOutcome, {
|
|
19
14
|
outcome: 'not-capable';
|
|
20
15
|
}> | {
|
|
21
16
|
outcome: 'gate-rejected';
|
|
22
17
|
};
|
|
23
|
-
/**
|
|
24
|
-
* The coordinates of one wake attempt. An adapter that must hand the receiver a
|
|
25
|
-
* stable dedup key derives it from these, so a retry of the same attempt reuses
|
|
26
|
-
* the same key and a later attempt gets a new one.
|
|
27
|
-
*/
|
|
18
|
+
/** One attempt's stable coordinates; retries reuse its dedup key. */
|
|
28
19
|
export interface WakeDispatchContext {
|
|
29
20
|
readonly location: string;
|
|
30
21
|
readonly participant: string;
|
|
@@ -40,24 +31,34 @@ export interface RoutedNotification {
|
|
|
40
31
|
route: DirectedNotificationRoute;
|
|
41
32
|
recipient: string;
|
|
42
33
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
knownParticipant(name: string): string | undefined;
|
|
51
|
-
participants(): readonly string[];
|
|
52
|
-
joinedRecipients(): readonly string[];
|
|
53
|
-
readonly replayedActivityCount: number;
|
|
34
|
+
/** Notification pending is not cursor suffix or historical catch membership. */
|
|
35
|
+
export declare function pendingHead(facts: SquareRead, name: string, index: number): DirectedHead | undefined;
|
|
36
|
+
/** Complete recipient projection, assembled inside its one transaction. */
|
|
37
|
+
export declare function pendingNotifications(facts: SquareRead, name: string): PlannedNotification[];
|
|
38
|
+
export interface PendingScan {
|
|
39
|
+
readonly roster: readonly ParticipantFact[];
|
|
40
|
+
readonly throughIndex: number;
|
|
54
41
|
}
|
|
55
|
-
export declare function
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
42
|
+
export declare function capturePendingScan(facts: SquareRead): PendingScan;
|
|
43
|
+
export declare function sameJoin(facts: SquareRead, captured: ParticipantFact): boolean;
|
|
44
|
+
/** Finite bodyless pass: unrelated revisions never rewind the last examined group. */
|
|
45
|
+
export declare function pendingPages(artifact: SquareArtifactPort, scan: PendingScan, afterIndex?: number): AsyncGenerator<{
|
|
46
|
+
index: number;
|
|
47
|
+
at: number;
|
|
48
|
+
actor: string;
|
|
49
|
+
recipient: string;
|
|
50
|
+
route: "bell" | "mention" | "reply" | "attention";
|
|
51
|
+
}[], void, unknown>;
|
|
52
|
+
/** A pass retains only its captured joins; routes and individual receipts are fresh. */
|
|
53
|
+
export declare function pendingFacts(facts: SquareRead, scan: PendingScan, heads: readonly DirectedHead[]): {
|
|
54
|
+
routes: readonly import("./ports.js").RouteFact[];
|
|
55
|
+
pending: DirectedHead[];
|
|
56
|
+
};
|
|
57
|
+
/** Global discovery alone uses pendingAudience; exact dispatch uses its one landed audience. */
|
|
58
|
+
export declare function collectPending(artifact: SquareArtifactPort, index?: number): Promise<{
|
|
59
|
+
routes: readonly import("./ports.js").RouteFact[];
|
|
60
|
+
pending: DirectedHead[];
|
|
61
|
+
scan: PendingScan;
|
|
62
|
+
}>;
|
|
63
|
+
/** A live catch owns every matching notification, independent of its page size. */
|
|
63
64
|
export declare function leaseOwnsNotification(lease: WatchLease, notification: RoutedNotification): boolean;
|
package/dist/delivery.js
CHANGED
|
@@ -1,90 +1,79 @@
|
|
|
1
|
-
import { findParticipantName, sameName, } from './model.js';
|
|
2
|
-
import { activityAuthors, audienceOf, replayLandedAudiences } from './square-core.js';
|
|
3
|
-
import { derivePerceptionProjection } from './perception-projection.js';
|
|
4
|
-
import { matchesMentionTarget } from './runtime.js';
|
|
5
1
|
import { matchesCatchSelection } from './catch-selection.js';
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
/** Notification pending is not cursor suffix or historical catch membership. */
|
|
3
|
+
export function pendingHead(facts, name, index) {
|
|
4
|
+
const participant = facts.participant(name);
|
|
5
|
+
if (!participant?.joined || index <= (participant.lastJoinIndex ?? -1) || facts.receipt(participant.name, index))
|
|
6
|
+
return undefined;
|
|
7
|
+
return facts.directedAt(participant.name, index);
|
|
8
8
|
}
|
|
9
|
-
/**
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
return perception.isSeen(requestedRecipient, index);
|
|
9
|
+
/** Complete recipient projection, assembled inside its one transaction. */
|
|
10
|
+
export function pendingNotifications(facts, name) {
|
|
11
|
+
const participant = facts.participant(name);
|
|
12
|
+
if (!participant?.joined)
|
|
13
|
+
return [];
|
|
14
|
+
const heads = [];
|
|
15
|
+
let afterIndex = participant.lastJoinIndex ?? -1;
|
|
16
|
+
for (;;) {
|
|
17
|
+
const page = facts.directed(participant.name, { afterIndex, unseenOnly: true }, { edge: 'oldest', limit: 100 });
|
|
18
|
+
heads.push(...page.items);
|
|
19
|
+
if (!page.hasMore)
|
|
20
|
+
break;
|
|
21
|
+
afterIndex = page.items.at(-1).index;
|
|
23
22
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
23
|
+
const routes = new Map(heads.map((head) => [head.index, head.route]));
|
|
24
|
+
return facts.activities(heads.map((head) => head.index)).flatMap((item) => item.kind === 'say'
|
|
25
|
+
? [{ item, recipient: participant.name, route: routes.get(item.index) }] : []);
|
|
26
|
+
}
|
|
27
|
+
export function capturePendingScan(facts) {
|
|
28
|
+
return { roster: facts.roster().filter((participant) => participant.joined), throughIndex: facts.header().lastActIndex };
|
|
29
|
+
}
|
|
30
|
+
export function sameJoin(facts, captured) {
|
|
31
|
+
const current = facts.participant(captured.name);
|
|
32
|
+
return current?.joined === captured.joined && current?.lastJoinIndex === captured.lastJoinIndex;
|
|
33
|
+
}
|
|
34
|
+
/** Finite bodyless pass: unrelated revisions never rewind the last examined group. */
|
|
35
|
+
export async function* pendingPages(artifact, scan, afterIndex = -1) {
|
|
36
|
+
const captured = new Map(scan.roster.map((participant) => [participant.name, participant]));
|
|
37
|
+
while (afterIndex < scan.throughIndex) {
|
|
38
|
+
const { value: page } = await artifact.read((facts) => {
|
|
39
|
+
const page = facts.pendingAudience(afterIndex, scan.throughIndex, 100);
|
|
40
|
+
return { ...page, groups: page.groups.map((group) => ({ ...group, recipients: group.recipients.filter((recipient) => {
|
|
41
|
+
const participant = captured.get(recipient.recipient);
|
|
42
|
+
return participant !== undefined && sameJoin(facts, participant);
|
|
43
|
+
}) })) };
|
|
41
44
|
});
|
|
42
|
-
|
|
43
|
-
|
|
45
|
+
yield page.groups.flatMap((group) => group.recipients.map(({ recipient, route }) => ({
|
|
46
|
+
index: group.index, at: group.at, actor: group.actor, recipient, route,
|
|
47
|
+
})));
|
|
48
|
+
if (!page.hasMore)
|
|
49
|
+
break;
|
|
50
|
+
afterIndex = page.groups.at(-1).index;
|
|
44
51
|
}
|
|
45
|
-
function pendingFor(requestedRecipient) {
|
|
46
|
-
const recipient = findParticipantName(roster, requestedRecipient);
|
|
47
|
-
if (recipient === undefined)
|
|
48
|
-
return [];
|
|
49
|
-
if (pendingByRecipient === undefined) {
|
|
50
|
-
pendingByRecipient = new Map(roster.map((name) => [name, []]));
|
|
51
|
-
const joinedAfter = new Map(roster.map((name) => [name, landed.lastJoinIndex(name)]));
|
|
52
|
-
for (const act of squareState.acts) {
|
|
53
|
-
if (act.kind !== 'say')
|
|
54
|
-
continue;
|
|
55
|
-
for (const planned of plan(act)) {
|
|
56
|
-
const joinedAt = joinedAfter.get(planned.recipient);
|
|
57
|
-
if (joinedAt === undefined || act.index <= joinedAt)
|
|
58
|
-
continue;
|
|
59
|
-
if (isSeen(planned.recipient, act.index))
|
|
60
|
-
continue;
|
|
61
|
-
pendingByRecipient.get(planned.recipient)?.push(planned);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
return [...(pendingByRecipient.get(recipient) ?? [])];
|
|
66
|
-
}
|
|
67
|
-
return {
|
|
68
|
-
plan,
|
|
69
|
-
pendingFor,
|
|
70
|
-
directedTo: perception.directedTo,
|
|
71
|
-
perceive: perception.perceive,
|
|
72
|
-
cursorFor: perception.cursorFor,
|
|
73
|
-
isSeen,
|
|
74
|
-
knownParticipant: (name) => landed.resolveParticipant(name),
|
|
75
|
-
participants: () => landed.participants,
|
|
76
|
-
joinedRecipients: () => roster,
|
|
77
|
-
replayedActivityCount: perception.replayedActivityCount,
|
|
78
|
-
};
|
|
79
52
|
}
|
|
80
|
-
|
|
81
|
-
|
|
53
|
+
/** A pass retains only its captured joins; routes and individual receipts are fresh. */
|
|
54
|
+
export function pendingFacts(facts, scan, heads) {
|
|
55
|
+
const joined = new Set(scan.roster.filter((participant) => sameJoin(facts, participant)).map((participant) => participant.name));
|
|
56
|
+
return { routes: facts.routes(), pending: heads.filter((head) => joined.has(head.recipient)
|
|
57
|
+
&& pendingHead(facts, head.recipient, head.index) !== undefined) };
|
|
58
|
+
}
|
|
59
|
+
/** Global discovery alone uses pendingAudience; exact dispatch uses its one landed audience. */
|
|
60
|
+
export async function collectPending(artifact, index) {
|
|
61
|
+
const { value: initial } = await artifact.read((facts) => ({
|
|
62
|
+
scan: capturePendingScan(facts),
|
|
63
|
+
pending: index === undefined ? [] : facts.audience(index).flatMap(({ recipient }) => {
|
|
64
|
+
const head = pendingHead(facts, recipient, index);
|
|
65
|
+
return head === undefined ? [] : [head];
|
|
66
|
+
}),
|
|
67
|
+
}));
|
|
68
|
+
const pending = [...initial.pending];
|
|
69
|
+
if (index === undefined)
|
|
70
|
+
for await (const page of pendingPages(artifact, initial.scan))
|
|
71
|
+
pending.push(...page);
|
|
72
|
+
const { value } = await artifact.read((facts) => ({ scan: initial.scan, ...pendingFacts(facts, initial.scan, pending) }));
|
|
73
|
+
return value;
|
|
82
74
|
}
|
|
83
|
-
/**
|
|
75
|
+
/** A live catch owns every matching notification, independent of its page size. */
|
|
84
76
|
export function leaseOwnsNotification(lease, notification) {
|
|
85
|
-
return matchesCatchSelection(notification.actor, notification.route === 'bell'
|
|
86
|
-
|
|
87
|
-
: { kind: 'mentions', names: notification.route === 'mention' ? [notification.recipient] : [] }, lease.filter ?? {},
|
|
88
|
-
// A reply reaches the recipient as the author of the activity it answers.
|
|
89
|
-
notification.route === 'reply' ? notification.recipient : undefined);
|
|
77
|
+
return matchesCatchSelection(notification.actor, notification.route === 'bell' ? { kind: 'bell' }
|
|
78
|
+
: { kind: 'mentions', names: notification.route === 'mention' ? [notification.recipient] : [] }, lease.filter ?? {}, notification.route === 'reply' ? notification.recipient : undefined);
|
|
90
79
|
}
|
package/dist/harness-claude.js
CHANGED
|
@@ -31,7 +31,7 @@ export async function installClaudePlugin(homeDir, run = runClaude) {
|
|
|
31
31
|
const marketplaceRoot = claudeMarketplaceRoot(homeDir);
|
|
32
32
|
const staged = stageReplacement(marketplaceRoot, (stage) => {
|
|
33
33
|
const plugin = path.join(stage, 'plugins', SQUARE_IDENTITY.pluginName);
|
|
34
|
-
fs.cpSync(fileURLToPath(new URL('../claude
|
|
34
|
+
fs.cpSync(fileURLToPath(new URL('../plugins/claude/', import.meta.url)), plugin, { recursive: true });
|
|
35
35
|
const skill = path.join(plugin, 'skills', 'square', 'SKILL.md');
|
|
36
36
|
fs.mkdirSync(path.dirname(skill), { recursive: true });
|
|
37
37
|
fs.copyFileSync(path.join(fileURLToPath(new URL('../skills/square/', import.meta.url)), 'SKILL.md'), skill);
|
package/dist/harness-codex.js
CHANGED
|
@@ -85,7 +85,7 @@ export async function installCodexPlugin(homeDir, run = runCodex, codexHomeDir)
|
|
|
85
85
|
const root = activeMarketplaceRoot(homeDir, current, resolvedCodexHome);
|
|
86
86
|
const staged = stageReplacement(root, (stage) => {
|
|
87
87
|
const plugin = path.join(stage, 'plugins', SQUARE_IDENTITY.pluginName);
|
|
88
|
-
fs.cpSync(fileURLToPath(new URL('../codex
|
|
88
|
+
fs.cpSync(fileURLToPath(new URL('../plugins/codex/', import.meta.url)), plugin, { recursive: true });
|
|
89
89
|
const skill = path.join(plugin, 'skills', 'square', 'SKILL.md');
|
|
90
90
|
fs.mkdirSync(path.dirname(skill), { recursive: true });
|
|
91
91
|
fs.copyFileSync(path.join(fileURLToPath(new URL('../skills/square/', import.meta.url)), 'SKILL.md'), skill);
|
package/dist/harness-pi.js
CHANGED
|
@@ -50,7 +50,7 @@ export function doctorPiPackage(homeDir, run = runPi) {
|
|
|
50
50
|
manifest?.version === SQUARE_IDENTITY.packageVersion
|
|
51
51
|
? `✓ Pi package ${SQUARE_IDENTITY.packageVersion} installed at ${root}`
|
|
52
52
|
: `○ Pi package ${SQUARE_IDENTITY.packageVersion} missing at ${root}`,
|
|
53
|
-
Array.isArray(extensions) && extensions.includes('./
|
|
53
|
+
Array.isArray(extensions) && extensions.includes('./plugins/pi/square-pi.js')
|
|
54
54
|
? '✓ Pi Square extension declared'
|
|
55
55
|
: '○ Pi Square extension not declared',
|
|
56
56
|
];
|
package/dist/help.js
CHANGED
|
@@ -74,6 +74,11 @@ const COMMANDS = [
|
|
|
74
74
|
names: ['doctor'], usage: 'doctor', usesSquare: true, group: 'maintenance',
|
|
75
75
|
summary: 'Validate artifact integrity.',
|
|
76
76
|
},
|
|
77
|
+
{
|
|
78
|
+
names: ['migrate'], usage: 'migrate', usesSquare: true, group: 'maintenance',
|
|
79
|
+
summary: 'Convert an existing version 1 square to row storage in place.',
|
|
80
|
+
details: ['It is the only migration command: there is no --check, --backup or --force.', 'A version 2 square is left untouched; an unrelated or unsupported artifact is refused without a write.'],
|
|
81
|
+
},
|
|
77
82
|
];
|
|
78
83
|
function definitionFor(command) {
|
|
79
84
|
return COMMANDS.find((item) => item.names.includes(command));
|
|
@@ -85,7 +90,7 @@ export function renderGlobalHelp() {
|
|
|
85
90
|
const groups = [
|
|
86
91
|
{ key: 'participant', title: 'In the square:', order: ['join', 'express', 'listen', 'ignore', 'listening', 'catch', 'history', 'status', 'hold', 'resume', 'done'] },
|
|
87
92
|
{ key: 'host', title: 'Prepare and manage:', order: ['build', 'list', 'participants'] },
|
|
88
|
-
{ key: 'maintenance', title: 'Setup:', order: ['install', 'uninstall', 'doctor'] },
|
|
93
|
+
{ key: 'maintenance', title: 'Setup:', order: ['install', 'uninstall', 'doctor', 'migrate'] },
|
|
89
94
|
];
|
|
90
95
|
const commandLines = groups.flatMap(({ key, title, order }) => [
|
|
91
96
|
title,
|
package/dist/inbox.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type InboxMembership } from './model.js';
|
|
2
|
+
import type { PresentationProjection, SessionBindingProjection, SquareRead } from './ports.js';
|
|
2
3
|
export interface PendingWaitOptions {
|
|
3
4
|
signal?: AbortSignal;
|
|
4
5
|
/** Local delivery suppression, never persisted as evidence of receipt. */
|
|
@@ -7,6 +8,32 @@ export interface PendingWaitOptions {
|
|
|
7
8
|
skipImmediate?: boolean;
|
|
8
9
|
onChangeArmed?: (armed: boolean) => void;
|
|
9
10
|
}
|
|
11
|
+
export declare function projectPresentation(facts: SquareRead, requested: SessionBindingProjection, now?: number): PresentationProjection;
|
|
12
|
+
export interface NativePendingPreview {
|
|
13
|
+
readonly payload: string;
|
|
14
|
+
readonly clipped: boolean;
|
|
15
|
+
}
|
|
16
|
+
export type NativePendingPreviewInput = {
|
|
17
|
+
readonly location: string;
|
|
18
|
+
readonly sessionId: string;
|
|
19
|
+
readonly participant: string;
|
|
20
|
+
readonly actIndex: number;
|
|
21
|
+
readonly epoch?: number;
|
|
22
|
+
readonly cancelledThrough?: number;
|
|
23
|
+
readonly now: number;
|
|
24
|
+
} & ({
|
|
25
|
+
readonly routeKind: 'claude-native';
|
|
26
|
+
readonly address: {
|
|
27
|
+
readonly endpoint: string;
|
|
28
|
+
};
|
|
29
|
+
} | {
|
|
30
|
+
readonly routeKind: 'opencode-server';
|
|
31
|
+
readonly address: {
|
|
32
|
+
readonly sessionId: string;
|
|
33
|
+
};
|
|
34
|
+
});
|
|
35
|
+
/** First matching ordered route, exact native epoch/address and catch ownership. */
|
|
36
|
+
export declare function nativePendingPreview(facts: SquareRead, input: NativePendingPreviewInput): NativePendingPreview | undefined;
|
|
10
37
|
export declare function sessionInbox(sessionId: string, env?: NodeJS.ProcessEnv, signal?: AbortSignal): Promise<InboxMembership[]>;
|
|
11
38
|
export interface SessionPendingObserver {
|
|
12
39
|
wait(timeoutMs: number, options?: PendingWaitOptions): Promise<InboxMembership[]>;
|