@astrosheep/square 0.3.105 → 0.4.0
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.js +1 -1
- package/dist/artifact-store.d.ts +41 -0
- package/dist/artifact-store.js +272 -0
- package/dist/artifact.d.ts +8 -12
- package/dist/artifact.js +66 -343
- package/dist/attention-presentation.d.ts +33 -7
- package/dist/attention-presentation.js +33 -69
- package/dist/boundary-presentation.d.ts +0 -2
- package/dist/boundary-presentation.js +17 -22
- package/dist/catch-decisions.d.ts +2 -2
- package/dist/catch-decisions.js +14 -4
- package/dist/catch-selection.d.ts +8 -0
- package/dist/catch-selection.js +5 -0
- package/dist/channel-artifact.d.ts +10 -0
- package/dist/channel-artifact.js +55 -0
- package/dist/channel-delivery.d.ts +11 -0
- package/dist/channel-delivery.js +51 -0
- package/dist/channel-domain.d.ts +3 -0
- package/dist/channel-domain.js +83 -0
- package/dist/channel-facade.d.ts +76 -0
- package/dist/channel-facade.js +1 -0
- package/dist/channel-internal.d.ts +126 -0
- package/dist/channel-internal.js +440 -0
- package/dist/channel-metadata.d.ts +11 -0
- package/dist/channel-metadata.js +1 -0
- package/dist/channel-ports.d.ts +241 -0
- package/dist/channel-ports.js +1 -0
- package/dist/channel-presentation.d.ts +17 -0
- package/dist/channel-presentation.js +22 -0
- package/dist/channel-sqlite-schema.d.ts +13 -0
- package/dist/channel-sqlite-schema.js +81 -0
- package/dist/channel-transport.d.ts +62 -0
- package/dist/channel-transport.js +1 -0
- package/dist/channel-values.d.ts +27 -0
- package/dist/channel-values.js +90 -0
- package/dist/channel.d.ts +31 -21
- package/dist/channel.js +42 -74
- package/dist/claude-delivery-status.js +1 -1
- package/dist/claude-delivery.d.ts +2 -5
- package/dist/claude-delivery.js +56 -103
- package/dist/claude-mod.js +4 -4
- package/dist/claude-transport.d.ts +5 -0
- package/dist/claude-transport.js +58 -0
- package/dist/cli/context.d.ts +2 -2
- package/dist/cli/context.js +0 -1
- package/dist/cli/maintenance-commands.d.ts +0 -4
- package/dist/cli/maintenance-commands.js +2 -29
- package/dist/cli/program.js +1 -1
- package/dist/cli/registry.js +1 -2
- package/dist/codex-address.d.ts +1 -1
- package/dist/codex-queue.d.ts +2 -2
- package/dist/codex-queue.js +1 -3
- package/dist/decisions.js +3 -3
- package/dist/delivery-deferral.d.ts +9 -18
- package/dist/delivery-deferral.js +4 -4
- package/dist/delivery-health.d.ts +4 -4
- package/dist/delivery-health.js +11 -11
- package/dist/delivery-operations.d.ts +15 -2
- package/dist/delivery-operations.js +60 -30
- package/dist/delivery.d.ts +44 -33
- package/dist/delivery.js +49 -75
- package/dist/help.js +1 -6
- package/dist/http-delivery.d.ts +2 -2
- package/dist/http-delivery.js +2 -4
- package/dist/inbox.d.ts +10 -18
- package/dist/inbox.js +44 -53
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/mcp.d.ts +2 -1
- package/dist/mcp.js +40 -70
- package/dist/message.d.ts +7 -0
- package/dist/message.js +23 -0
- package/dist/model.d.ts +41 -77
- package/dist/model.js +7 -139
- package/dist/neutral.d.ts +72 -0
- package/dist/neutral.js +27 -0
- package/dist/notifications.d.ts +2 -8
- package/dist/notifications.js +15 -50
- package/dist/operation-presentation.d.ts +1 -2
- package/dist/operation-presentation.js +1 -7
- package/dist/owner.d.ts +2 -2
- package/dist/owner.js +1 -1
- package/dist/paseo-delivery.d.ts +1 -1
- package/dist/paseo-delivery.js +2 -4
- package/dist/paseo-timeline.js +1 -0
- package/dist/plugins/opencode/delivery.js +27 -42
- package/dist/ports.d.ts +46 -168
- package/dist/presentation-operations.d.ts +1 -14
- package/dist/presentation-operations.js +16 -16
- package/dist/presentation.d.ts +5 -5
- package/dist/presentation.js +1 -0
- package/dist/runtime.js +2 -2
- package/dist/search.js +1 -1
- package/dist/sqlite-channel.d.ts +15 -0
- package/dist/sqlite-channel.js +600 -0
- package/dist/sqlite-framing.d.ts +8 -0
- package/dist/sqlite-framing.js +31 -0
- package/dist/sqlite-square.d.ts +12 -13
- package/dist/sqlite-square.js +192 -248
- package/dist/sqlite-values.d.ts +21 -0
- package/dist/sqlite-values.js +78 -0
- package/dist/square-api.d.ts +1 -1
- package/dist/square-api.js +1 -1
- package/dist/square-core.d.ts +1 -1
- package/dist/square-domain.d.ts +21 -7
- package/dist/square-domain.js +93 -94
- package/dist/square-facade.d.ts +12 -66
- package/dist/square-index.d.ts +1 -1
- package/dist/square-index.js +1 -1
- package/dist/square-location.d.ts +6 -1
- package/dist/square-location.js +1 -1
- package/dist/square-receive.d.ts +2 -3
- package/dist/square-sqlite.d.ts +45 -44
- package/dist/square-sqlite.js +308 -764
- package/dist/square-tools.d.ts +8 -0
- package/dist/square-tools.js +39 -0
- package/dist/views.d.ts +6 -6
- package/dist/views.js +15 -9
- package/dist/wake-preview.d.ts +7 -0
- package/dist/wake-preview.js +15 -0
- package/dist/wake-sink.d.ts +2 -2
- package/dist/wake-sink.js +1 -1
- package/dist/wake-transports.d.ts +7 -0
- package/dist/wake-transports.js +42 -0
- package/guides/claude-native.md +13 -10
- package/guides/codex-delivery.md +3 -1
- package/guides/participant-ownership.md +1 -1
- package/guides/row-runtime-delivery-evidence.md +2 -0
- package/guides/row-storage-design.md +19 -44
- package/guides/square-api.md +137 -60
- package/package.json +3 -3
- package/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/plugins/claude/types.d.ts +1 -1
- package/plugins/codex/.codex-plugin/plugin.json +1 -1
- package/plugins/opencode/delivery.ts +28 -41
- package/plugins/pi/square-pi.js +28 -51
- package/dist/square-legacy-framing.d.ts +0 -11
- package/dist/square-legacy-framing.js +0 -64
- package/dist/square-migration-host-ledger.d.ts +0 -26
- package/dist/square-migration-host-ledger.js +0 -169
- package/dist/square-migration.d.ts +0 -30
- package/dist/square-migration.js +0 -599
- package/guides/migration.md +0 -156
package/dist/cli/program.js
CHANGED
|
@@ -9,7 +9,7 @@ async function handleSquareError(error, context) {
|
|
|
9
9
|
throw error;
|
|
10
10
|
const detail = error.operation;
|
|
11
11
|
const squarePath = context?.squarePath;
|
|
12
|
-
const recovery =
|
|
12
|
+
const recovery = context === undefined || squarePath === undefined ? undefined
|
|
13
13
|
: await withSquare({ ...context, name: detail?.participant ?? context.name }, (square) => square.recovery(detail?.participant ?? context.name)).catch(() => undefined);
|
|
14
14
|
const name = detail?.participant ?? recovery?.participant ?? context?.name;
|
|
15
15
|
const lines = renderFailureLines(error, { squarePath, name, owner: context?.owner ?? context?.env.SQUARE_OWNER });
|
package/dist/cli/registry.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { buildCommand, doneCommand, expressCommand, ignoreCommand, joinCommand, listenCommand, listeningCommand, unlistenCommand } from './square-commands.js';
|
|
2
|
-
import { doctorCommand
|
|
2
|
+
import { doctorCommand } from './maintenance-commands.js';
|
|
3
3
|
import { harnessCommand, installCommand, uninstallCommand } from './harness-command.js';
|
|
4
4
|
import { helpCommand, versionCommand } from './meta-commands.js';
|
|
5
5
|
import { mcpServerCommand } from './mcp-server-command.js';
|
|
@@ -25,7 +25,6 @@ export const commandRegistry = [
|
|
|
25
25
|
{ names: ['uninstall'], spec: uninstallCommand },
|
|
26
26
|
{ names: ['harness'], spec: harnessCommand },
|
|
27
27
|
{ names: ['doctor'], spec: doctorCommand },
|
|
28
|
-
{ names: ['migrate'], spec: migrateCommand },
|
|
29
28
|
{ names: ['history'], spec: historyCommand },
|
|
30
29
|
{ names: ['status'], spec: statusCommand },
|
|
31
30
|
{ names: ['participants'], spec: participantsCommand },
|
package/dist/codex-address.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type { WakeAddress } from './
|
|
1
|
+
import type { WakeAddress } from './channel-values.js';
|
|
2
2
|
/** Capture the receiver's CLI configuration when its seat is established, not at send time. */
|
|
3
3
|
export declare function codexWakeAddress(threadId: string, env: NodeJS.ProcessEnv): WakeAddress;
|
package/dist/codex-queue.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { type CodexCliAgent, type CodexCliConnectOptions } from './packages/agent-delivery/src/index.js';
|
|
2
2
|
export { sendCodexQueue, CodexQueueSendError } from './packages/agent-delivery/src/codex-queue.js';
|
|
3
3
|
export type { CodexQueueRequest, CodexQueueFailureKind } from './packages/agent-delivery/src/codex-queue.js';
|
|
4
|
-
import type { WakeRequest, WakeOutcome, WakeTransportPort } from './
|
|
5
|
-
import type { WakeRoute } from './
|
|
4
|
+
import type { WakeRequest, WakeOutcome, WakeTransportPort } from './channel-transport.js';
|
|
5
|
+
import type { WakeRoute } from './channel-values.js';
|
|
6
6
|
export interface CodexQueueTransportOptions {
|
|
7
7
|
env?: NodeJS.ProcessEnv;
|
|
8
8
|
connect?: (options: CodexCliConnectOptions) => Promise<CodexCliAgent>;
|
package/dist/codex-queue.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { connect, DeliveryError } from './packages/agent-delivery/src/index.js';
|
|
2
2
|
export { sendCodexQueue, CodexQueueSendError } from './packages/agent-delivery/src/codex-queue.js';
|
|
3
3
|
import { codexBoundaryStore } from './codex-boundary-state.js';
|
|
4
|
-
import { wakePreview } from './delivery.js';
|
|
5
4
|
export class CodexQueueTransport {
|
|
6
5
|
opts;
|
|
7
6
|
constructor(opts = {}) {
|
|
@@ -15,8 +14,7 @@ export class CodexQueueTransport {
|
|
|
15
14
|
return { outcome: 'failed', signature: 'invalid_address', message: 'Codex route has no thread id.' };
|
|
16
15
|
}
|
|
17
16
|
const env = this.opts.env ?? process.env;
|
|
18
|
-
const payload = request.
|
|
19
|
-
: (await request.artifact.read((facts) => wakePreview(facts, request))).value;
|
|
17
|
+
const payload = request.presentation?.payload;
|
|
20
18
|
if (payload === undefined)
|
|
21
19
|
return { outcome: 'not-capable', diagnostic: 'No pending Square activity to preview.' };
|
|
22
20
|
if (!(await beforeSend()))
|
package/dist/decisions.js
CHANGED
|
@@ -51,7 +51,7 @@ export function unreadActivitySummaries(facts, name, now) {
|
|
|
51
51
|
let afterIndex = readCursor(facts, name);
|
|
52
52
|
const summaries = new Map();
|
|
53
53
|
while (true) {
|
|
54
|
-
const page = facts.directed(name, { afterIndex }, { edge: 'oldest', limit: FACT_PAGE_SIZE });
|
|
54
|
+
const page = facts.directed(name, { afterIndex, kinds: ['say'] }, { edge: 'oldest', limit: FACT_PAGE_SIZE });
|
|
55
55
|
for (const item of page.items) {
|
|
56
56
|
const summary = summaries.get(item.actor) ?? { count: 0, latestAt: -Infinity, lastIndex: item.index };
|
|
57
57
|
summary.count++;
|
|
@@ -194,8 +194,8 @@ export function coreParticipants(facts, now) {
|
|
|
194
194
|
activityCount: current.sayCount, lastActiveAt,
|
|
195
195
|
presence: current.done ? 'done' : !current.joined ? 'never-joined' : facts.seat(current.name)?.attachment === 'detached' ? 'away'
|
|
196
196
|
: freshWatchLease(facts, current.name, now) !== undefined ? 'watching' : 'active',
|
|
197
|
-
unreadActivityCount: current.joined ? facts.directedCount(current.name, { afterIndex: readCursor(facts, current.name) }) : 0,
|
|
198
|
-
pendingMentionCount: current.joined ? facts.directedCount(current.name, { afterIndex: current.lastJoinIndex ?? -1, unseenOnly: true }) : 0,
|
|
197
|
+
unreadActivityCount: current.joined ? facts.directedCount(current.name, { kinds: ['say'], afterIndex: readCursor(facts, current.name) }) : 0,
|
|
198
|
+
pendingMentionCount: current.joined ? facts.directedCount(current.name, { kinds: ['say'], afterIndex: current.lastJoinIndex ?? -1, unseenOnly: true }) : 0,
|
|
199
199
|
};
|
|
200
200
|
}).sort(compareParticipantActivity);
|
|
201
201
|
}
|
|
@@ -1,19 +1,10 @@
|
|
|
1
|
-
import
|
|
2
|
-
import type {
|
|
3
|
-
|
|
4
|
-
export
|
|
5
|
-
readonly fields: readonly Readonly<{
|
|
6
|
-
path: readonly string[];
|
|
7
|
-
equals: MetadataScalar;
|
|
8
|
-
}>[];
|
|
9
|
-
}
|
|
10
|
-
export interface DeliveryDeferral {
|
|
11
|
-
readonly id: string;
|
|
12
|
-
readonly expiresAt: string;
|
|
13
|
-
}
|
|
1
|
+
import type { MessageMetadata } from './channel-values.js';
|
|
2
|
+
import type { ChannelRead, EndpointRef } from './channel-ports.js';
|
|
3
|
+
import type { MetadataMatch } from './channel-metadata.js';
|
|
4
|
+
export type { MetadataMatch, MetadataScalar, DeliveryDeferral } from './channel-metadata.js';
|
|
14
5
|
export interface DeliveryDeferralFact {
|
|
15
6
|
readonly participant: string;
|
|
16
|
-
readonly generation:
|
|
7
|
+
readonly generation: string;
|
|
17
8
|
readonly id: string;
|
|
18
9
|
readonly match: MetadataMatch;
|
|
19
10
|
readonly expiresAt: number;
|
|
@@ -22,7 +13,7 @@ export declare const MAX_DELIVERY_DEFERRAL_MS: number;
|
|
|
22
13
|
export declare function validateMetadataMatch(value: unknown): MetadataMatch;
|
|
23
14
|
export declare function validateDeferralDuration(value: unknown): number;
|
|
24
15
|
export declare function validateDeferralId(value: unknown): string;
|
|
25
|
-
export declare function matchesMetadata(metadata:
|
|
26
|
-
export declare function matchingDeferrals(deferrals: readonly DeliveryDeferralFact[], metadata:
|
|
27
|
-
export declare function deliveryDeferralExpiry(facts:
|
|
28
|
-
export declare function earliestMatchingDeferralExpiry(deferrals: readonly DeliveryDeferralFact[], metadata:
|
|
16
|
+
export declare function matchesMetadata(metadata: MessageMetadata | undefined, match: MetadataMatch): boolean;
|
|
17
|
+
export declare function matchingDeferrals(deferrals: readonly DeliveryDeferralFact[], metadata: MessageMetadata | undefined, now: number): readonly DeliveryDeferralFact[];
|
|
18
|
+
export declare function deliveryDeferralExpiry(facts: ChannelRead, captured: EndpointRef, index: number, now: number): number | undefined;
|
|
19
|
+
export declare function earliestMatchingDeferralExpiry(deferrals: readonly DeliveryDeferralFact[], metadata: MessageMetadata | undefined, now: number): number | undefined;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SquareError } from './
|
|
1
|
+
import { SquareError } from './neutral.js';
|
|
2
2
|
export const MAX_DELIVERY_DEFERRAL_MS = 24 * 60 * 60 * 1000;
|
|
3
3
|
export function validateMetadataMatch(value) {
|
|
4
4
|
const fail = () => { throw new SquareError('invalid_args', 'Invalid metadata match.'); };
|
|
@@ -68,14 +68,14 @@ export function matchingDeferrals(deferrals, metadata, now) {
|
|
|
68
68
|
return deferrals.filter((lease) => lease.expiresAt > now && matchesMetadata(metadata, lease.match));
|
|
69
69
|
}
|
|
70
70
|
export function deliveryDeferralExpiry(facts, captured, index, now) {
|
|
71
|
-
const seat = facts.
|
|
71
|
+
const seat = facts.endpoint(captured.participant);
|
|
72
72
|
if (seat === undefined || seat.generation !== captured.generation || seat.owner !== captured.owner)
|
|
73
73
|
return undefined;
|
|
74
74
|
const leases = facts.deliveryDeferrals(seat.participant, seat.generation);
|
|
75
75
|
if (!leases.some(lease => lease.expiresAt > now))
|
|
76
76
|
return undefined;
|
|
77
|
-
const
|
|
78
|
-
return
|
|
77
|
+
const message = facts.message(index);
|
|
78
|
+
return message === undefined ? undefined : earliestMatchingDeferralExpiry(leases, message.metadata, now);
|
|
79
79
|
}
|
|
80
80
|
export function earliestMatchingDeferralExpiry(deferrals, metadata, now) {
|
|
81
81
|
const matches = matchingDeferrals(deferrals, metadata, now);
|
|
@@ -6,7 +6,7 @@ export interface WakeAttempt {
|
|
|
6
6
|
readonly attention: {
|
|
7
7
|
readonly squarePath: string;
|
|
8
8
|
readonly recipient: string;
|
|
9
|
-
readonly
|
|
9
|
+
readonly messageIndex: number;
|
|
10
10
|
};
|
|
11
11
|
readonly routeKind: import('./model.js').WakeRouteKind;
|
|
12
12
|
readonly outcome: 'accepted' | 'unknown' | 'failed';
|
|
@@ -20,7 +20,7 @@ export interface WakeReleaseDiagnostic {
|
|
|
20
20
|
readonly attention: {
|
|
21
21
|
readonly squarePath: string;
|
|
22
22
|
readonly recipient: string;
|
|
23
|
-
readonly
|
|
23
|
+
readonly messageIndex: number;
|
|
24
24
|
};
|
|
25
25
|
readonly routeKind?: import('./model.js').WakeRouteKind;
|
|
26
26
|
readonly attemptN?: number;
|
|
@@ -33,11 +33,11 @@ export type DeliveryHealthKind = 'awaiting' | 'wake-accepted' | 'wake-unknown' |
|
|
|
33
33
|
export interface DeliveryHealthItem {
|
|
34
34
|
squarePath: string;
|
|
35
35
|
recipient: string;
|
|
36
|
-
|
|
36
|
+
messageIndex: number;
|
|
37
37
|
actor: string;
|
|
38
38
|
at: number;
|
|
39
39
|
ageMs: number;
|
|
40
|
-
route
|
|
40
|
+
route?: DirectedNotificationRoute;
|
|
41
41
|
kind: DeliveryHealthKind;
|
|
42
42
|
attempt?: WakeAttempt;
|
|
43
43
|
}
|
package/dist/delivery-health.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { collectPending } from './delivery.js';
|
|
2
|
-
import {
|
|
2
|
+
import { formatMessageId, parseMessageId } from './message.js';
|
|
3
3
|
import { participantIdentity, truncateChars } from './presentation.js';
|
|
4
4
|
import { formatDuration } from './time.js';
|
|
5
5
|
import { InternalSquareError, SquareError } from './model.js';
|
|
@@ -22,7 +22,7 @@ export async function classifyDeliveryHealth(squarePath, opts) {
|
|
|
22
22
|
const { pending } = await collectPending(artifact);
|
|
23
23
|
const current = (await artifact.read((facts) => pending.map((note) => {
|
|
24
24
|
const seat = facts.seat(note.recipient);
|
|
25
|
-
const evidence = seat === undefined ? [] : facts.deliveryEvidence(seat.participant, seat.generation,
|
|
25
|
+
const evidence = seat === undefined ? [] : facts.deliveryEvidence(seat.participant, seat.generation, formatMessageId(note.index));
|
|
26
26
|
return { note, seat, evidence };
|
|
27
27
|
}))).value;
|
|
28
28
|
const items = [];
|
|
@@ -32,7 +32,7 @@ export async function classifyDeliveryHealth(squarePath, opts) {
|
|
|
32
32
|
|| row.outcome === 'dispatching'));
|
|
33
33
|
const terminal = evidence.findLast((row) => row.kind === 'wake' && (row.outcome === 'accepted' || row.outcome === 'unknown'));
|
|
34
34
|
const attemptRow = evidence.findLast((row) => row.kind === 'wake' && (row.outcome === 'accepted' || row.outcome === 'unknown' || row.outcome === 'failed'));
|
|
35
|
-
const attempt = attemptRow === undefined || seat === undefined ? undefined : deliveryAttempt(squarePath, attemptRow, { squarePath, recipient: seat.participant,
|
|
35
|
+
const attempt = attemptRow === undefined || seat === undefined ? undefined : deliveryAttempt(squarePath, attemptRow, { squarePath, recipient: seat.participant, messageIndex: note.index });
|
|
36
36
|
const kind = presentation
|
|
37
37
|
? 'presented-not-delivered'
|
|
38
38
|
: terminal?.outcome === 'accepted'
|
|
@@ -45,7 +45,7 @@ export async function classifyDeliveryHealth(squarePath, opts) {
|
|
|
45
45
|
items.push({
|
|
46
46
|
squarePath,
|
|
47
47
|
recipient: note.recipient,
|
|
48
|
-
|
|
48
|
+
messageIndex: note.index,
|
|
49
49
|
actor: note.actor,
|
|
50
50
|
at: note.at,
|
|
51
51
|
ageMs,
|
|
@@ -83,7 +83,7 @@ export function renderDeliveryHealth(items, releases = [], now = Date.now(), nat
|
|
|
83
83
|
const details = group.slice(0, DETAIL_LIMIT - displayed);
|
|
84
84
|
out.push(...details.map((item) => {
|
|
85
85
|
const evidence = item.attempt?.signature === undefined ? '' : ` · ${displayField(item.attempt.signature)}`;
|
|
86
|
-
return ` · ${
|
|
86
|
+
return ` · ${formatMessageId(item.messageIndex)} → ${displayField(participantIdentity(item.recipient))} from ${displayField(participantIdentity(item.actor))} · ${formatDuration(item.ageMs)}${evidence}`;
|
|
87
87
|
}));
|
|
88
88
|
displayed += details.length;
|
|
89
89
|
}
|
|
@@ -96,7 +96,7 @@ export function renderDeliveryHealth(items, releases = [], now = Date.now(), nat
|
|
|
96
96
|
const observations = [...attempt.nativeObservations ?? []].sort((a, b) => a.observedAt - b.observedAt);
|
|
97
97
|
const last = observations.at(-1);
|
|
98
98
|
const states = observations.map((observation) => `${observation.state}${observation.reason ? ` (${observation.reason})` : ''}`).join(' → ');
|
|
99
|
-
return ` · ${
|
|
99
|
+
return ` · ${formatMessageId(attempt.attention.messageIndex)} → ${displayField(participantIdentity(attempt.attention.recipient))} · ${attempt.outcome} · native ${displayField(states)} · ${formatDuration(Math.max(0, now - last.observedAt))} · message ${displayField(last.messageId)}`;
|
|
100
100
|
}));
|
|
101
101
|
if (nativeAttempts.length > DETAIL_LIMIT)
|
|
102
102
|
out.push(`${DETAIL_LIMIT} of ${nativeAttempts.length} native custody details shown`);
|
|
@@ -115,9 +115,9 @@ function formatRelease(release, now) {
|
|
|
115
115
|
const age = formatDuration(Math.max(0, now - release.at));
|
|
116
116
|
const message = release.message === undefined ? '' : ` — ${displayField(release.message)}`;
|
|
117
117
|
const diagnostic = release.diagnostic === undefined ? '' : ` · ${displayField(JSON.stringify(release.diagnostic))}`;
|
|
118
|
-
return ` · ${
|
|
118
|
+
return ` · ${formatMessageId(release.attention.messageIndex)} → ${displayField(participantIdentity(release.attention.recipient))} · ${route} · ${reason} · ${age}${message}${diagnostic}`;
|
|
119
119
|
}
|
|
120
|
-
function deliveryAttempt(squarePath, row, attention = { squarePath, recipient: row.participant,
|
|
120
|
+
function deliveryAttempt(squarePath, row, attention = { squarePath, recipient: row.participant, messageIndex: parseMessageId(row.message) ?? 0 }) {
|
|
121
121
|
if (row.route?.kind === undefined || row.outcome === 'released' || row.outcome === 'dispatching')
|
|
122
122
|
return undefined;
|
|
123
123
|
return {
|
|
@@ -127,7 +127,7 @@ function deliveryAttempt(squarePath, row, attention = { squarePath, recipient: r
|
|
|
127
127
|
outcome: row.outcome,
|
|
128
128
|
attemptN: row.attemptN,
|
|
129
129
|
...(row.signature === undefined ? {} : { signature: row.signature }),
|
|
130
|
-
...(row.
|
|
130
|
+
...(row.messageText === undefined ? {} : { message: row.messageText }),
|
|
131
131
|
...(row.diagnostic === undefined ? {} : { diagnostic: row.diagnostic }),
|
|
132
132
|
...(row.nativeObservations === undefined ? {} : { nativeObservations: row.nativeObservations }),
|
|
133
133
|
};
|
|
@@ -135,11 +135,11 @@ function deliveryAttempt(squarePath, row, attention = { squarePath, recipient: r
|
|
|
135
135
|
function deliveryRelease(squarePath, row) {
|
|
136
136
|
return {
|
|
137
137
|
at: row.at,
|
|
138
|
-
attention: { squarePath, recipient: row.participant,
|
|
138
|
+
attention: { squarePath, recipient: row.participant, messageIndex: parseMessageId(row.message) ?? 0 },
|
|
139
139
|
routeKind: row.route?.kind,
|
|
140
140
|
attemptN: row.attemptN,
|
|
141
141
|
...(row.signature === undefined ? {} : { signature: row.signature }),
|
|
142
|
-
...(row.
|
|
142
|
+
...(row.messageText === undefined ? {} : { message: row.messageText }),
|
|
143
143
|
...(row.diagnostic === undefined ? {} : { diagnostic: row.diagnostic }),
|
|
144
144
|
};
|
|
145
145
|
}
|
|
@@ -1,9 +1,22 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ChannelArtifact } from './channel-ports.js';
|
|
2
|
+
import type { WakeProjection, WakeProjectionContext, WakeRequest, WakeTransportPort } from './channel-transport.js';
|
|
3
|
+
interface DeliverPendingInput {
|
|
4
|
+
readonly artifact: ChannelArtifact;
|
|
5
|
+
readonly preparePresentation?: WakeProjection;
|
|
6
|
+
readonly transport: WakeTransportPort;
|
|
7
|
+
readonly location: string;
|
|
8
|
+
readonly timeoutMs?: number;
|
|
9
|
+
readonly now?: number;
|
|
10
|
+
readonly message?: string | number;
|
|
11
|
+
}
|
|
12
|
+
/** Standalone Channel projection; final send currentness is still checked by the dispatcher. */
|
|
13
|
+
export declare function channelWakePreview(artifact: ChannelArtifact, request: WakeProjectionContext, signal?: AbortSignal): Promise<NonNullable<WakeRequest['presentation']> | undefined>;
|
|
2
14
|
export declare function deliverPending(input: DeliverPendingInput): Promise<void>;
|
|
3
15
|
export declare function sweepPending(input: {
|
|
4
|
-
readonly artifact:
|
|
16
|
+
readonly artifact: ChannelArtifact;
|
|
5
17
|
readonly location: string;
|
|
6
18
|
readonly now: number;
|
|
7
19
|
readonly graceMs: number;
|
|
8
20
|
readonly limit: number;
|
|
9
21
|
}): Promise<number[]>;
|
|
22
|
+
export {};
|
|
@@ -1,11 +1,29 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { asSeatRef } from './seats.js';
|
|
5
|
-
import { freshWatchLease } from './runtime.js';
|
|
2
|
+
import { formatMessageId, parseMessageId } from './message.js';
|
|
3
|
+
import { pendingAddresses, pendingHeadAt, pendingMessageHeads, presentationBlocksWake } from './channel-delivery.js';
|
|
6
4
|
import { deliveryDeferralExpiry } from './delivery-deferral.js';
|
|
7
|
-
import { leaseOwnsNotification } from './delivery.js';
|
|
8
5
|
import { dispatchEffect } from './delivery-effects.js';
|
|
6
|
+
import { projectChannelNotification, renderSquareReminder } from './channel-presentation.js';
|
|
7
|
+
/** Standalone Channel projection; final send currentness is still checked by the dispatcher. */
|
|
8
|
+
export async function channelWakePreview(artifact, request, signal) {
|
|
9
|
+
const index = parseMessageId(request.message);
|
|
10
|
+
if (index === undefined)
|
|
11
|
+
return undefined;
|
|
12
|
+
return (await artifact.read(facts => {
|
|
13
|
+
const seat = facts.endpoint(request.participant);
|
|
14
|
+
const now = Date.now();
|
|
15
|
+
if (seat === undefined || seat.attachment !== 'attached' || seat.owner !== request.seat.owner
|
|
16
|
+
|| seat.generation !== request.seat.generation || pendingHeadAt(facts, request.participant, index, now) === undefined
|
|
17
|
+
|| deliveryDeferralExpiry(facts, request.seat, index, now) !== undefined)
|
|
18
|
+
return undefined;
|
|
19
|
+
const item = facts.message(index);
|
|
20
|
+
if (item === undefined)
|
|
21
|
+
return undefined;
|
|
22
|
+
const projected = projectChannelNotification({ squarePath: request.location, recipient: request.participant,
|
|
23
|
+
actor: item.from, body: item.body, ...(item.preview === undefined ? {} : { preview: item.preview }) });
|
|
24
|
+
return { payload: renderSquareReminder([projected.text]), coverage: projected.clipped ? 'clipped' : 'full' };
|
|
25
|
+
}, signal)).value;
|
|
26
|
+
}
|
|
9
27
|
function routeFor(location, seat, now) {
|
|
10
28
|
if (seat.attachment !== 'attached' || seat.wakeAddress === undefined)
|
|
11
29
|
return undefined;
|
|
@@ -15,37 +33,34 @@ function routeFor(location, seat, now) {
|
|
|
15
33
|
};
|
|
16
34
|
}
|
|
17
35
|
async function currentness(artifact, request, now) {
|
|
18
|
-
const index =
|
|
36
|
+
const index = parseMessageId(request.message);
|
|
19
37
|
const { value } = await artifact.read((facts) => {
|
|
20
|
-
const currentSeat = facts.
|
|
38
|
+
const currentSeat = facts.endpoint(request.participant);
|
|
21
39
|
const seatCurrent = currentSeat !== undefined && currentSeat.attachment === 'attached' && currentSeat.generation === request.seat.generation
|
|
22
40
|
&& currentSeat.owner === request.seat.owner
|
|
23
41
|
&& currentSeat.wakeAddress?.kind === request.route.kind
|
|
24
42
|
&& Object.keys(currentSeat.wakeAddress.address).length === Object.keys(request.route.address).length
|
|
25
43
|
&& Object.entries(currentSeat.wakeAddress.address).every(([key, val]) => request.route.address[key] === val);
|
|
26
|
-
const head =
|
|
44
|
+
const head = pendingHeadAt(facts, request.participant, index, now);
|
|
27
45
|
const activityPending = seatCurrent && head !== undefined;
|
|
28
46
|
const cancelledThrough = facts.cancelledThrough(request.participant, request.seat.generation);
|
|
29
47
|
const cancelled = cancelledThrough !== undefined && index <= cancelledThrough;
|
|
30
|
-
const evidence = facts.deliveryEvidence(request.participant, request.seat.generation,
|
|
48
|
+
const evidence = facts.deliveryEvidence(request.participant, request.seat.generation, formatMessageId(index));
|
|
31
49
|
const presented = presentationBlocksWake(evidence, now);
|
|
32
|
-
const lease = freshWatchLease(facts, request.participant, now);
|
|
33
|
-
const caught = head !== undefined && lease !== undefined && leaseOwnsNotification(lease, {
|
|
34
|
-
actor: request.actor, route: head.route, recipient: request.participant,
|
|
35
|
-
});
|
|
36
50
|
const deferred = deliveryDeferralExpiry(facts, request.seat, index, now) !== undefined;
|
|
37
|
-
return { current: activityPending && !cancelled && !presented && !
|
|
51
|
+
return { current: activityPending && !cancelled && !presented && !deferred, activityPending, seatCurrent, cancelled, presented };
|
|
38
52
|
});
|
|
39
53
|
return value;
|
|
40
54
|
}
|
|
41
55
|
async function candidates(artifact, requested, now = Date.now()) {
|
|
42
56
|
const { value } = await artifact.read((facts) => {
|
|
43
57
|
const heads = requested === undefined
|
|
44
|
-
? facts.
|
|
45
|
-
:
|
|
46
|
-
return heads.flatMap(
|
|
47
|
-
const seat = facts.
|
|
48
|
-
return seat === undefined || seat.attachment !== 'attached' || deliveryDeferralExpiry(facts, seat, head.index, now) !== undefined
|
|
58
|
+
? facts.endpoints().filter(endpoint => endpoint.attachment === 'attached').flatMap(endpoint => pendingMessageHeads(facts, endpoint.participant, now))
|
|
59
|
+
: pendingAddresses(facts, requested, now);
|
|
60
|
+
return heads.flatMap(head => {
|
|
61
|
+
const seat = facts.endpoint(head.recipient);
|
|
62
|
+
return seat === undefined || seat.attachment !== 'attached' || deliveryDeferralExpiry(facts, seat, head.index, now) !== undefined
|
|
63
|
+
? [] : [{ seat, head }];
|
|
49
64
|
});
|
|
50
65
|
});
|
|
51
66
|
return value;
|
|
@@ -63,9 +78,9 @@ function safeJson(value) {
|
|
|
63
78
|
export async function deliverPending(input) {
|
|
64
79
|
// The resource owner supplies its identity; delivery does not interpret it as a file.
|
|
65
80
|
const location = input.location;
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
if (
|
|
81
|
+
const named = input.message;
|
|
82
|
+
const requested = named === undefined ? undefined : typeof named === 'number' ? named : parseMessageId(named);
|
|
83
|
+
if (named !== undefined && requested === undefined)
|
|
69
84
|
return;
|
|
70
85
|
const pending = await candidates(input.artifact, requested, input.now ?? Date.now());
|
|
71
86
|
// Preserve order within a receiving owner; unrelated receivers need not wait for each other.
|
|
@@ -81,9 +96,8 @@ export async function deliverPending(input) {
|
|
|
81
96
|
if (route === undefined)
|
|
82
97
|
return;
|
|
83
98
|
const request = {
|
|
84
|
-
location, participant: seat.participant,
|
|
85
|
-
seat:
|
|
86
|
-
artifact: input.artifact,
|
|
99
|
+
location, participant: seat.participant, message: formatMessageId(head.index), actor: head.from,
|
|
100
|
+
seat: { participant: seat.participant, owner: seat.owner, generation: seat.generation }, route,
|
|
87
101
|
};
|
|
88
102
|
if (input.transport.probe !== undefined) {
|
|
89
103
|
try {
|
|
@@ -100,7 +114,7 @@ export async function deliverPending(input) {
|
|
|
100
114
|
let snapshot;
|
|
101
115
|
await dispatchEffect({
|
|
102
116
|
async claim() {
|
|
103
|
-
const claim = await input.artifact.claimDelivery({ seat: request.seat,
|
|
117
|
+
const claim = await input.artifact.claimDelivery({ seat: request.seat, message: request.message, kind: 'wake',
|
|
104
118
|
route: seat.wakeAddress, at: input.now ?? Date.now(), leaseMs, claimToken }).catch(() => ({ status: 'stale' }));
|
|
105
119
|
return claim.status === 'acquired' ? { token: claimToken, ordinal: claim.attemptN } : undefined;
|
|
106
120
|
},
|
|
@@ -128,15 +142,31 @@ export async function deliverPending(input) {
|
|
|
128
142
|
};
|
|
129
143
|
else if (completion.reason === 'returned')
|
|
130
144
|
fields = completion.data;
|
|
131
|
-
return input.artifact.completeDelivery({ seat: request.seat,
|
|
145
|
+
return input.artifact.completeDelivery({ seat: request.seat, message: request.message, claimToken: claim.token,
|
|
132
146
|
outcome: completion.outcome, at: Date.now(),
|
|
133
147
|
...(fields.signature === undefined ? {} : { signature: fields.signature }),
|
|
134
|
-
...(fields.message === undefined ? {} : {
|
|
148
|
+
...(fields.message === undefined ? {} : { messageText: fields.message }),
|
|
135
149
|
...(fields.diagnostic === undefined ? {} : { diagnostic: safeJson(fields.diagnostic) }),
|
|
136
150
|
}).catch(() => false);
|
|
137
151
|
},
|
|
138
|
-
}, async ({ claim,
|
|
139
|
-
const
|
|
152
|
+
}, async ({ claim, deadline, beforeSend }) => {
|
|
153
|
+
const { location, participant, message, actor, seat, route } = request;
|
|
154
|
+
const prepared = input.preparePresentation === undefined
|
|
155
|
+
? await channelWakePreview(input.artifact, request)
|
|
156
|
+
: await input.preparePresentation({ location, participant, message, actor, seat, route });
|
|
157
|
+
if (prepared === undefined)
|
|
158
|
+
return { outcome: 'not-capable', data: { diagnostic: 'The pending presentation is no longer current.' } };
|
|
159
|
+
const remaining = deadline - Date.now();
|
|
160
|
+
if (remaining <= 0)
|
|
161
|
+
return { outcome: 'not-capable', data: { diagnostic: 'The presentation budget elapsed before transport.' } };
|
|
162
|
+
const presentation = prepared;
|
|
163
|
+
const preparationSeat = Object.freeze({ ...request.seat });
|
|
164
|
+
const channel = { prepare: (details, signal) => input.artifact.prepareDelivery({
|
|
165
|
+
seat: preparationSeat, claimToken: claim.token, correlation: details.correlation,
|
|
166
|
+
coverage: details.coverage, description: details.description,
|
|
167
|
+
}, signal) };
|
|
168
|
+
const outcome = await input.transport.attempt({ ...request, seat: { ...request.seat }, channel, claimToken: claim.token, attemptN: claim.ordinal,
|
|
169
|
+
...(presentation === undefined ? {} : { presentation }) }, remaining, beforeSend);
|
|
140
170
|
return { outcome: outcome.outcome, data: outcome };
|
|
141
171
|
}, { timeoutMs: leaseMs });
|
|
142
172
|
};
|
package/dist/delivery.d.ts
CHANGED
|
@@ -1,45 +1,56 @@
|
|
|
1
|
-
import type { DirectedNotificationRoute,
|
|
2
|
-
import type {
|
|
1
|
+
import type { DirectedNotificationRoute, Owner, WakeAddress } from './model.js';
|
|
2
|
+
import type { SquareArtifactPort, SquareRead, EndpointGeneration } from './ports.js';
|
|
3
|
+
import { formatMessageId } from './message.js';
|
|
3
4
|
export type { DirectedNotificationRoute } from './model.js';
|
|
4
|
-
/**
|
|
5
|
-
export
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
recipient: string;
|
|
5
|
+
/** One lower pending message plus its upper reason when an Agora say created it. */
|
|
6
|
+
export interface PendingMessage {
|
|
7
|
+
readonly index: number;
|
|
8
|
+
readonly at: number;
|
|
9
|
+
readonly actor: string;
|
|
10
|
+
readonly recipient: string;
|
|
11
|
+
readonly generation?: EndpointGeneration;
|
|
12
|
+
/** Agora activity that shares this message, when one exists. */
|
|
13
|
+
readonly activityIndex?: number;
|
|
14
|
+
readonly route?: DirectedNotificationRoute;
|
|
10
15
|
}
|
|
11
|
-
|
|
12
|
-
export declare function presentationBlocksWake(evidence: readonly DeliveryEvidence[], now: number): boolean;
|
|
13
|
-
/** Preselection only: an expired wake dispatch is uncertain, never safe automatic replay. */
|
|
14
|
-
export declare function automaticWakeEligible(evidence: readonly DeliveryEvidence[], now: number): boolean;
|
|
16
|
+
export { pendingHeadAt, pendingMessageHeads, pendingAddresses, presentationBlocksWake, automaticWakeEligible } from './channel-delivery.js';
|
|
15
17
|
/** Notification pending is not cursor suffix or historical catch membership. */
|
|
16
|
-
export declare function pendingHead(facts: SquareRead, name: string, index: number):
|
|
17
|
-
/**
|
|
18
|
-
export
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
export declare function pendingHead(facts: SquareRead, name: string, index: number, now?: number): PendingMessage | undefined;
|
|
19
|
+
/** Current native eligibility shared by wake selection, preview and ACK. */
|
|
20
|
+
export interface NativePendingInput {
|
|
21
|
+
readonly location: string;
|
|
22
|
+
readonly participant: string;
|
|
23
|
+
readonly generation: EndpointGeneration;
|
|
24
|
+
readonly owner?: Owner;
|
|
25
|
+
readonly index: number;
|
|
26
|
+
readonly route: WakeAddress;
|
|
27
|
+
readonly now: number;
|
|
28
|
+
readonly cancelledThrough?: number;
|
|
29
|
+
}
|
|
30
|
+
export declare function nativePendingHead(facts: SquareRead, input: NativePendingInput): PendingMessage | undefined;
|
|
31
|
+
/** Still-pending directed messages for one recipient, oldest first. Bodies belong to presentation. */
|
|
32
|
+
export declare function pendingHeads(facts: SquareRead, name: string, now?: number, suppress?: boolean): PendingMessage[];
|
|
21
33
|
export interface PendingScan {
|
|
22
|
-
readonly roster: readonly
|
|
34
|
+
readonly roster: readonly {
|
|
35
|
+
readonly name: string;
|
|
36
|
+
readonly generation?: string;
|
|
37
|
+
}[];
|
|
23
38
|
readonly throughIndex: number;
|
|
24
39
|
}
|
|
25
40
|
export declare function capturePendingScan(facts: SquareRead): PendingScan;
|
|
26
|
-
export declare function sameJoin(facts: SquareRead, captured:
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
recipient: string;
|
|
33
|
-
route: import("./attention.js").AudienceRoute;
|
|
34
|
-
}[], void, unknown>;
|
|
41
|
+
export declare function sameJoin(facts: SquareRead, captured: {
|
|
42
|
+
readonly name: string;
|
|
43
|
+
readonly generation?: string;
|
|
44
|
+
}): boolean;
|
|
45
|
+
/** Finite bodyless pass: unrelated revisions never rewind the last examined message. */
|
|
46
|
+
export declare function pendingPages(artifact: SquareArtifactPort, scan: PendingScan, afterIndex?: number): AsyncGenerator<PendingMessage[], void, unknown>;
|
|
35
47
|
/** A pass retains only its captured joins; routes and individual receipts are fresh. */
|
|
36
|
-
export declare function pendingFacts(facts: SquareRead, scan: PendingScan, heads: readonly
|
|
37
|
-
pending:
|
|
48
|
+
export declare function pendingFacts(facts: SquareRead, scan: PendingScan, heads: readonly PendingMessage[]): {
|
|
49
|
+
pending: PendingMessage[];
|
|
38
50
|
};
|
|
39
|
-
/** Global discovery alone sweeps
|
|
51
|
+
/** Global discovery alone sweeps every endpoint; exact dispatch uses pendingAddresses. */
|
|
40
52
|
export declare function collectPending(artifact: SquareArtifactPort): Promise<{
|
|
41
|
-
pending:
|
|
53
|
+
pending: PendingMessage[];
|
|
42
54
|
scan: PendingScan;
|
|
43
55
|
}>;
|
|
44
|
-
|
|
45
|
-
export declare function leaseOwnsNotification(lease: WatchLease, notification: RoutedNotification): boolean;
|
|
56
|
+
export { formatMessageId };
|