@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/delivery.js
CHANGED
|
@@ -1,96 +1,68 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { parseActivityId } from './square-core.js';
|
|
1
|
+
import { formatMessageId } from './message.js';
|
|
3
2
|
import { deliveryDeferralExpiry } from './delivery-deferral.js';
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
/**
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
if (head === undefined || item?.kind !== 'say' || seat === undefined || seat.attachment !== 'attached'
|
|
13
|
-
|| deliveryDeferralExpiry(facts, request.seat, item.index, Date.now()) !== undefined)
|
|
14
|
-
return undefined;
|
|
15
|
-
return renderSquareReminder([renderAttentionPreview({
|
|
16
|
-
squarePath: request.location, recipient: request.participant, actIndex: item.index,
|
|
17
|
-
actor: item.actor, route: head.route, body: item.body ?? '(structured activity)', delivery: item.delivery,
|
|
18
|
-
})], [item.delivery]);
|
|
19
|
-
}
|
|
20
|
-
function custody(evidence) {
|
|
21
|
-
return evidence.map(row => ({ purpose: row.kind === 'wake' ? 'dispatch' : 'presentation', outcome: row.outcome, expiresAt: row.expiresAt }));
|
|
22
|
-
}
|
|
23
|
-
/** Presentation custody blocks a wake, independently of native wake acceptance. */
|
|
24
|
-
export function presentationBlocksWake(evidence, now) {
|
|
25
|
-
return presentationBlocksDispatch(custody(evidence), now);
|
|
26
|
-
}
|
|
27
|
-
/** Preselection only: an expired wake dispatch is uncertain, never safe automatic replay. */
|
|
28
|
-
export function automaticWakeEligible(evidence, now) {
|
|
29
|
-
return automaticDispatchEligible(custody(evidence), now);
|
|
3
|
+
export { pendingHeadAt, pendingMessageHeads, pendingAddresses, presentationBlocksWake, automaticWakeEligible } from './channel-delivery.js';
|
|
4
|
+
import { pendingHeadAt, pendingMessageHeads } from './channel-delivery.js';
|
|
5
|
+
/** Upper enrichment: the reason this recipient was reached, when an Agora say owns the message. */
|
|
6
|
+
function enriched(facts, head) {
|
|
7
|
+
const relation = facts.activityByMessage(head.index);
|
|
8
|
+
return { index: head.index, at: head.at, actor: head.from, recipient: head.recipient,
|
|
9
|
+
...(head.generation === undefined ? {} : { generation: head.generation }),
|
|
10
|
+
...(relation === undefined ? {} : { activityIndex: relation.index, route: facts.directedAt(head.recipient, relation.index)?.route }) };
|
|
30
11
|
}
|
|
31
12
|
/** Notification pending is not cursor suffix or historical catch membership. */
|
|
32
|
-
export function pendingHead(facts, name, index) {
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
return undefined;
|
|
36
|
-
return facts.directedAt(participant.name, index);
|
|
13
|
+
export function pendingHead(facts, name, index, now = Date.now()) {
|
|
14
|
+
const head = pendingHeadAt(facts.channel, name, index, now);
|
|
15
|
+
return head === undefined ? undefined : enriched(facts, head);
|
|
37
16
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
17
|
+
export function nativePendingHead(facts, input) {
|
|
18
|
+
const seat = facts.channel.endpoint(input.participant);
|
|
19
|
+
if (seat === undefined || seat.attachment !== 'attached' || seat.generation !== input.generation
|
|
20
|
+
|| input.owner !== undefined && seat.owner !== input.owner
|
|
21
|
+
|| !sameWakeAddress(seat.wakeAddress, input.route))
|
|
22
|
+
return undefined;
|
|
23
|
+
const cancelledThrough = facts.channel.cancelledThrough(seat.participant, seat.generation);
|
|
24
|
+
if (input.index <= Math.max(input.cancelledThrough ?? -1, cancelledThrough ?? -1))
|
|
25
|
+
return undefined;
|
|
26
|
+
if (deliveryDeferralExpiry(facts.channel, seat, input.index, input.now) !== undefined)
|
|
27
|
+
return undefined;
|
|
28
|
+
return pendingHead(facts, seat.participant, input.index, input.now);
|
|
44
29
|
}
|
|
45
|
-
/** Still-pending directed
|
|
46
|
-
export function pendingHeads(facts, name) {
|
|
47
|
-
|
|
48
|
-
if (!participant?.joined)
|
|
49
|
-
return [];
|
|
50
|
-
const heads = [];
|
|
51
|
-
let afterIndex = participant.lastJoinIndex ?? -1;
|
|
52
|
-
for (;;) {
|
|
53
|
-
const page = facts.directed(participant.name, { afterIndex, unseenOnly: true }, { edge: 'oldest', limit: 100 });
|
|
54
|
-
heads.push(...page.items);
|
|
55
|
-
if (!page.hasMore)
|
|
56
|
-
break;
|
|
57
|
-
afterIndex = page.items.at(-1).index;
|
|
58
|
-
}
|
|
59
|
-
return heads;
|
|
30
|
+
/** Still-pending directed messages for one recipient, oldest first. Bodies belong to presentation. */
|
|
31
|
+
export function pendingHeads(facts, name, now = Date.now(), suppress = true) {
|
|
32
|
+
return pendingMessageHeads(facts.channel, name, now, suppress).map(head => enriched(facts, head));
|
|
60
33
|
}
|
|
61
34
|
export function capturePendingScan(facts) {
|
|
62
|
-
return { roster: facts.
|
|
35
|
+
return { roster: facts.channel.endpoints().filter(endpoint => endpoint.attachment === 'attached').map(endpoint => ({ name: endpoint.participant, generation: endpoint.generation })),
|
|
36
|
+
throughIndex: facts.channel.header().lastMessageIndex };
|
|
63
37
|
}
|
|
64
38
|
export function sameJoin(facts, captured) {
|
|
65
|
-
const current = facts.
|
|
66
|
-
return current?.
|
|
39
|
+
const current = facts.channel.endpoint(captured.name);
|
|
40
|
+
return current?.attachment === 'attached' && current.generation === captured.generation;
|
|
67
41
|
}
|
|
68
|
-
/** Finite bodyless pass: unrelated revisions never rewind the last examined
|
|
42
|
+
/** Finite bodyless pass: unrelated revisions never rewind the last examined message. */
|
|
69
43
|
export async function* pendingPages(artifact, scan, afterIndex = -1) {
|
|
70
44
|
const captured = new Map(scan.roster.map((participant) => [participant.name, participant]));
|
|
71
|
-
|
|
45
|
+
let cursor = afterIndex;
|
|
46
|
+
while (cursor < scan.throughIndex) {
|
|
72
47
|
const { value: page } = await artifact.read((facts) => {
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}) })) };
|
|
48
|
+
const items = facts.channel.endpoints().filter(endpoint => endpoint.attachment === 'attached' && captured.has(endpoint.participant)).flatMap(endpoint => facts.channel.directed(endpoint.participant, { afterIndex: cursor, throughIndex: scan.throughIndex, unseenOnly: true, currentGeneration: true }, { edge: 'oldest', limit: 100 }).items
|
|
49
|
+
.filter(() => sameJoin(facts, captured.get(endpoint.participant)))
|
|
50
|
+
.map(head => enriched(facts, head)));
|
|
51
|
+
return { items, next: items.at(-1)?.index, done: items.length === 0 };
|
|
78
52
|
});
|
|
79
|
-
yield page.
|
|
80
|
-
|
|
81
|
-
})));
|
|
82
|
-
if (!page.hasMore)
|
|
53
|
+
yield page.items;
|
|
54
|
+
if (page.done || page.next === undefined)
|
|
83
55
|
break;
|
|
84
|
-
|
|
56
|
+
cursor = page.next;
|
|
85
57
|
}
|
|
86
58
|
}
|
|
87
59
|
/** A pass retains only its captured joins; routes and individual receipts are fresh. */
|
|
88
60
|
export function pendingFacts(facts, scan, heads) {
|
|
89
61
|
const joined = new Set(scan.roster.filter((participant) => sameJoin(facts, participant)).map((participant) => participant.name));
|
|
90
62
|
return { pending: heads.filter((head) => joined.has(head.recipient)
|
|
91
|
-
&&
|
|
63
|
+
&& pendingHeadAt(facts.channel, head.recipient, head.index) !== undefined) };
|
|
92
64
|
}
|
|
93
|
-
/** Global discovery alone sweeps
|
|
65
|
+
/** Global discovery alone sweeps every endpoint; exact dispatch uses pendingAddresses. */
|
|
94
66
|
export async function collectPending(artifact) {
|
|
95
67
|
const { value: scan } = await artifact.read(capturePendingScan);
|
|
96
68
|
const pending = [];
|
|
@@ -99,8 +71,10 @@ export async function collectPending(artifact) {
|
|
|
99
71
|
const { value } = await artifact.read((facts) => ({ scan, ...pendingFacts(facts, scan, pending) }));
|
|
100
72
|
return value;
|
|
101
73
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
74
|
+
function sameWakeAddress(left, right) {
|
|
75
|
+
if (left === undefined || right === undefined || left.kind !== right.kind)
|
|
76
|
+
return false;
|
|
77
|
+
const keys = Object.keys(left.address);
|
|
78
|
+
return keys.length === Object.keys(right.address).length && keys.every(key => left.address[key] === right.address[key]);
|
|
106
79
|
}
|
|
80
|
+
export { formatMessageId };
|
package/dist/help.js
CHANGED
|
@@ -89,11 +89,6 @@ const COMMANDS = [
|
|
|
89
89
|
names: ['doctor'], usage: 'doctor', usesSquare: true, group: 'maintenance',
|
|
90
90
|
summary: 'Validate artifact integrity.',
|
|
91
91
|
},
|
|
92
|
-
{
|
|
93
|
-
names: ['migrate'], usage: 'migrate', usesSquare: true, group: 'maintenance',
|
|
94
|
-
summary: 'Convert a supported older square to the current format in place.',
|
|
95
|
-
details: ['For the selected .square artifact only. Stop sessions using this artifact before conversion. Back up the stopped artifact, migrate it, then run square --location <path> doctor before starting updated clients.', 'Versions 2–7 migrate explicitly to format 8. Format 7 migration deletes hold/resume activities and their keyed rows, clears replies to those activities, and preserves surviving activity ids with gaps. Format 6 attachment and opaque-owner conversion are preserved; versions 4/5 first gain attached seats, and 4 also gains empty deferrals. Version 1, unrelated or unsupported artifacts refuse without a write. Names whose legacy owner and routes agree may share an owner; ambiguous names stay standing without custody. Rejoin with the same owner after migration to republish discovery. There is no --check, --backup or --force.'],
|
|
96
|
-
},
|
|
97
92
|
];
|
|
98
93
|
function definitionFor(command) {
|
|
99
94
|
return COMMANDS.find((item) => item.names.includes(command));
|
|
@@ -105,7 +100,7 @@ export function renderGlobalHelp() {
|
|
|
105
100
|
const groups = [
|
|
106
101
|
{ key: 'participant', title: 'In the square:', order: ['join', 'express', 'listen', 'unlisten', 'ignore', 'listening', 'catch', 'history', 'status', 'done'] },
|
|
107
102
|
{ key: 'host', title: 'Prepare and manage:', order: ['build', 'list', 'participants'] },
|
|
108
|
-
{ key: 'maintenance', title: 'Setup:', order: ['install', 'uninstall', 'doctor'
|
|
103
|
+
{ key: 'maintenance', title: 'Setup:', order: ['install', 'uninstall', 'doctor'] },
|
|
109
104
|
];
|
|
110
105
|
const commandLines = groups.flatMap(({ key, title, order }) => [
|
|
111
106
|
title,
|
package/dist/http-delivery.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { WakeRoute } from './
|
|
2
|
-
import type { WakeOutcome, WakeRequest, WakeTransportPort } from './
|
|
1
|
+
import type { WakeRoute } from './channel-values.js';
|
|
2
|
+
import type { WakeOutcome, WakeRequest, WakeTransportPort } from './channel-transport.js';
|
|
3
3
|
export interface HttpWakeTransportOptions {
|
|
4
4
|
fetch?: typeof fetch;
|
|
5
5
|
env?: NodeJS.ProcessEnv;
|
package/dist/http-delivery.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { wakePreview } from './delivery.js';
|
|
2
1
|
/** Configuration only: probing never contacts the receiver. */
|
|
3
2
|
function configuration(route, env) {
|
|
4
3
|
if (route.kind !== 'http')
|
|
@@ -53,12 +52,11 @@ export class HttpWakeTransport {
|
|
|
53
52
|
});
|
|
54
53
|
const send = async () => {
|
|
55
54
|
try {
|
|
56
|
-
const preview = request.
|
|
57
|
-
: (await request.artifact.read(facts => wakePreview(facts, request), controller.signal)).value;
|
|
55
|
+
const preview = request.presentation?.payload;
|
|
58
56
|
if (preview === undefined)
|
|
59
57
|
return { outcome: 'not-capable', diagnostic: 'No pending Square activity to preview.' };
|
|
60
58
|
const body = JSON.stringify({
|
|
61
|
-
location: request.location, participant: request.participant,
|
|
59
|
+
location: request.location, participant: request.participant, message: request.message, actor: request.actor,
|
|
62
60
|
...(request.attemptN === undefined ? {} : { attemptN: request.attemptN }), preview,
|
|
63
61
|
});
|
|
64
62
|
if (controller.signal.aborted || Date.now() >= deadline)
|
package/dist/inbox.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type Owner, type LocatedSeat, type PendingMembership } from './model.js';
|
|
2
2
|
import type { SquareRead } from './ports.js';
|
|
3
|
+
import { type NativePendingInput } from './delivery.js';
|
|
3
4
|
export interface PendingWaitOptions {
|
|
4
5
|
signal?: AbortSignal;
|
|
5
6
|
/** Local delivery suppression, never persisted as evidence of receipt. */
|
|
@@ -8,8 +9,7 @@ export interface PendingWaitOptions {
|
|
|
8
9
|
skipImmediate?: boolean;
|
|
9
10
|
onChangeArmed?: (armed: boolean) => void;
|
|
10
11
|
}
|
|
11
|
-
export declare function pendingKey(membership: LocatedSeat,
|
|
12
|
-
export declare function pendingAtBoundary(inbox: readonly PendingMembership[]): PendingMembership[];
|
|
12
|
+
export declare function pendingKey(membership: LocatedSeat, messageIndex: number): string;
|
|
13
13
|
/** Revalidate a supplied batch immediately before the boundary's independent claim/send checks. */
|
|
14
14
|
export declare function projectBoundaryEligibility(inbox: readonly PendingMembership[]): Promise<PendingMembership[]>;
|
|
15
15
|
export interface SessionBindingCandidate {
|
|
@@ -31,22 +31,14 @@ export interface NativePendingPreview {
|
|
|
31
31
|
readonly payload: string;
|
|
32
32
|
readonly clipped: boolean;
|
|
33
33
|
}
|
|
34
|
-
export
|
|
35
|
-
readonly
|
|
36
|
-
readonly
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
readonly routeKind: 'claude-native';
|
|
43
|
-
readonly address: Readonly<Record<string, string>>;
|
|
44
|
-
} | {
|
|
45
|
-
readonly routeKind: 'opencode-server';
|
|
46
|
-
readonly address: Readonly<Record<string, string>>;
|
|
47
|
-
});
|
|
48
|
-
/** Current-generation native attention, owned by its exact wake address and catch state. */
|
|
49
|
-
export declare function nativePendingPreview(facts: SquareRead, input: NativePendingPreviewInput): NativePendingPreview | undefined;
|
|
34
|
+
export interface NativePendingSource {
|
|
35
|
+
readonly text: string;
|
|
36
|
+
readonly clipped: boolean;
|
|
37
|
+
}
|
|
38
|
+
/** One current eligibility + source projection read; senders use text, ACK coverage uses clipped. */
|
|
39
|
+
export declare function nativePendingSource(facts: SquareRead, input: NativePendingInput): NativePendingSource | undefined;
|
|
40
|
+
/** The sender-facing fixed reminder over the shared source projection. */
|
|
41
|
+
export declare function nativePendingPreview(facts: SquareRead, input: NativePendingInput): NativePendingPreview | undefined;
|
|
50
42
|
export declare function sessionInbox(owner: Owner, env?: NodeJS.ProcessEnv, signal?: AbortSignal): Promise<PendingMembership[]>;
|
|
51
43
|
export interface SessionPendingObserver {
|
|
52
44
|
/** Read committed revisions before reusing content; filesystem hints are only for waiting. */
|
package/dist/inbox.js
CHANGED
|
@@ -1,35 +1,27 @@
|
|
|
1
1
|
import { createArtifactPort, observeSquareDeliveryRevision, observeSquareRevision } from './artifact.js';
|
|
2
2
|
import { indexedSquareEntries, observeSquareIndex } from './square-index.js';
|
|
3
3
|
import { canonicalPath } from './canonical-path.js';
|
|
4
|
-
import {
|
|
4
|
+
import { formatMessageId } from './message.js';
|
|
5
5
|
import { freshWatchLease, WATCH_STALE_MS } from './runtime.js';
|
|
6
|
-
import {
|
|
6
|
+
import { nativePendingHead, pendingHeads } from './delivery.js';
|
|
7
7
|
import { deliveryDeferralExpiry } from './delivery-deferral.js';
|
|
8
|
-
import {
|
|
9
|
-
export function pendingKey(membership,
|
|
10
|
-
return membership.location + '\0' + membership.seat.participant.toLocaleLowerCase() + '\0' +
|
|
11
|
-
}
|
|
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);
|
|
8
|
+
import { projectMessageNotification, renderSquareReminder } from './attention-presentation.js';
|
|
9
|
+
export function pendingKey(membership, messageIndex) {
|
|
10
|
+
return membership.location + '\0' + membership.seat.participant.toLocaleLowerCase() + '\0' + messageIndex;
|
|
19
11
|
}
|
|
20
12
|
function eligibleNotifications(facts, membership, now = Date.now()) {
|
|
21
13
|
const seat = facts.seat(membership.seat.participant);
|
|
22
14
|
if (seat === undefined || seat.attachment !== 'attached' || seat.generation !== membership.seat.generation
|
|
23
15
|
|| seat.owner !== membership.seat.owner)
|
|
24
16
|
return { notifications: [] };
|
|
25
|
-
const cancelledThrough = facts.cancelledThrough(seat.participant, seat.generation);
|
|
17
|
+
const cancelledThrough = facts.channel.cancelledThrough(seat.participant, seat.generation);
|
|
26
18
|
let expiry = Infinity;
|
|
27
19
|
const notifications = membership.notifications.filter(notification => {
|
|
28
|
-
if (cancelledThrough !== undefined && notification.
|
|
29
|
-
|| facts.deliveryEvidence(seat.participant, seat.generation,
|
|
20
|
+
if (cancelledThrough !== undefined && notification.messageIndex <= cancelledThrough
|
|
21
|
+
|| facts.deliveryEvidence(seat.participant, seat.generation, formatMessageId(notification.messageIndex))
|
|
30
22
|
.some(row => row.outcome !== 'failed' && row.outcome !== 'released'))
|
|
31
23
|
return false;
|
|
32
|
-
const deferredUntil = deliveryDeferralExpiry(facts, seat, notification.
|
|
24
|
+
const deferredUntil = deliveryDeferralExpiry(facts.channel, seat, notification.messageIndex, now);
|
|
33
25
|
if (deferredUntil === undefined)
|
|
34
26
|
return true;
|
|
35
27
|
expiry = Math.min(expiry, deferredUntil);
|
|
@@ -40,7 +32,7 @@ function eligibleNotifications(facts, membership, now = Date.now()) {
|
|
|
40
32
|
/** Revalidate a supplied batch immediately before the boundary's independent claim/send checks. */
|
|
41
33
|
export async function projectBoundaryEligibility(inbox) {
|
|
42
34
|
const grouped = new Map();
|
|
43
|
-
for (const membership of
|
|
35
|
+
for (const membership of inbox.filter(candidate => candidate.notifications.length > 0)) {
|
|
44
36
|
const entries = grouped.get(membership.location) ?? [];
|
|
45
37
|
entries.push(membership);
|
|
46
38
|
grouped.set(membership.location, entries);
|
|
@@ -88,9 +80,10 @@ function groupCandidates(candidates) {
|
|
|
88
80
|
}
|
|
89
81
|
function locatedSeats(facts, location, seats) {
|
|
90
82
|
return seats.flatMap(seat => {
|
|
91
|
-
const
|
|
83
|
+
const participant = facts.participant(seat.participant);
|
|
84
|
+
const index = participant?.lastJoinIndex;
|
|
92
85
|
const joined = index === undefined ? undefined : facts.activity(index);
|
|
93
|
-
return joined?.kind
|
|
86
|
+
return joined?.kind === 'join' ? [{ location, seat, joinedAt: joined.at }] : [{ location, seat, joinedAt: 0 }];
|
|
94
87
|
});
|
|
95
88
|
}
|
|
96
89
|
/** Lifecycle reads resolve seats and join times, never pending activity bodies. */
|
|
@@ -120,7 +113,7 @@ export async function sessionInboxCounts(input, signal) {
|
|
|
120
113
|
const artifact = createArtifactPort(location, undefined, signal);
|
|
121
114
|
try {
|
|
122
115
|
rows.push(...(await artifact.read(facts => locatedSeats(facts, location, owners.flatMap(owner => facts.seatsForOwner(owner).filter(seat => seat.attachment === 'attached'))).map(({ seat }) => ({
|
|
123
|
-
name: seat.participant, squarePath: location, pending: facts.directedCount(seat.participant, { unseenOnly: true }),
|
|
116
|
+
name: seat.participant, squarePath: location, pending: facts.directedCount(seat.participant, { unseenOnly: true, currentGeneration: true }),
|
|
124
117
|
})), signal)).value);
|
|
125
118
|
}
|
|
126
119
|
catch (error) {
|
|
@@ -147,19 +140,24 @@ async function projectPending(candidates, cache, signal) {
|
|
|
147
140
|
let cached = cache?.get(location);
|
|
148
141
|
if (cached?.coordinate !== coordinate || cached.version !== header.revision) {
|
|
149
142
|
cached = { coordinate, version: header.revision, value: locatedSeats(facts, location, seats).map(membership => {
|
|
150
|
-
const heads = pendingHeads(facts, membership.seat.participant);
|
|
151
|
-
const
|
|
152
|
-
return { ...membership, notifications:
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
143
|
+
const heads = pendingHeads(facts, membership.seat.participant, Date.now(), false);
|
|
144
|
+
const messages = new Map(facts.channel.messages(heads.map(head => head.index)).map(item => [item.index, item]));
|
|
145
|
+
return { ...membership, notifications: heads.flatMap(head => {
|
|
146
|
+
const item = messages.get(head.index);
|
|
147
|
+
if (item === undefined)
|
|
148
|
+
return [];
|
|
149
|
+
return [{ messageIndex: item.index, ...(head.activityIndex === undefined ? {} : { actIndex: head.activityIndex }),
|
|
150
|
+
actor: item.from, at: item.at, body: item.body, ...(item.preview === undefined ? {} : { preview: item.preview }), ...(head.route === undefined ? {} : { route: head.route }) }];
|
|
151
|
+
}) };
|
|
156
152
|
}) };
|
|
157
153
|
}
|
|
158
154
|
// Cache conversation content, never leases or delivery eligibility: evidence can change
|
|
159
155
|
// without a conversation revision and must not force body hydration or self-wake retries.
|
|
160
156
|
return { cached, pending: cached.value.map(membership => {
|
|
161
157
|
const catchLease = freshWatchLease(facts, membership.seat.participant);
|
|
162
|
-
|
|
158
|
+
// Cached conversation content, but claim coverage and deferrals are re-read on every projection.
|
|
159
|
+
const observed = { ...membership, notifications: membership.notifications.filter(note => !facts.channel.suppressed(membership.seat.participant, membership.seat.generation, note.messageIndex, Date.now())) };
|
|
160
|
+
return { ...observed, ...eligibleNotifications(facts, observed),
|
|
163
161
|
...(catchLease === undefined ? {} : { catchLease }) };
|
|
164
162
|
}) };
|
|
165
163
|
}, signal);
|
|
@@ -178,30 +176,23 @@ async function projectPending(candidates, cache, signal) {
|
|
|
178
176
|
}
|
|
179
177
|
return pending;
|
|
180
178
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
}
|
|
186
|
-
/** Current-generation native attention, owned by its exact wake address and catch state. */
|
|
187
|
-
export function nativePendingPreview(facts, input) {
|
|
188
|
-
const seat = facts.seat(input.participant);
|
|
189
|
-
if (seat === undefined || seat.attachment !== 'attached' || seat.generation !== input.generation
|
|
190
|
-
|| !sameWakeAddress(seat.wakeAddress, input.routeKind, input.address))
|
|
179
|
+
/** One current eligibility + source projection read; senders use text, ACK coverage uses clipped. */
|
|
180
|
+
export function nativePendingSource(facts, input) {
|
|
181
|
+
const head = nativePendingHead(facts, input);
|
|
182
|
+
if (head === undefined)
|
|
191
183
|
return undefined;
|
|
192
|
-
const
|
|
193
|
-
if (
|
|
184
|
+
const item = facts.channel.message(input.index);
|
|
185
|
+
if (item === undefined)
|
|
194
186
|
return undefined;
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
clipped: attentionBodyIsClipped(item.body ?? '(structured activity)', item.delivery) };
|
|
187
|
+
const projected = projectMessageNotification({ squarePath: input.location, recipient: input.participant,
|
|
188
|
+
actor: item.from, body: item.body, ...(item.preview === undefined ? {} : { preview: item.preview }),
|
|
189
|
+
...(head.activityIndex === undefined || head.route === undefined ? {} : { agora: { actIndex: head.activityIndex, route: head.route } }) });
|
|
190
|
+
return { text: projected.text, clipped: projected.clipped };
|
|
191
|
+
}
|
|
192
|
+
/** The sender-facing fixed reminder over the shared source projection. */
|
|
193
|
+
export function nativePendingPreview(facts, input) {
|
|
194
|
+
const source = nativePendingSource(facts, input);
|
|
195
|
+
return source === undefined ? undefined : { payload: renderSquareReminder([source.text]), clipped: source.clipped };
|
|
205
196
|
}
|
|
206
197
|
export async function sessionInbox(owner, env = process.env, signal) {
|
|
207
198
|
return projectPending(await projectSessionCandidates({ owner, env }), undefined, signal);
|
|
@@ -252,9 +243,9 @@ export async function observeSessionPending(owner, suppliedEnv = process.env) {
|
|
|
252
243
|
});
|
|
253
244
|
}
|
|
254
245
|
function eligibleInbox(inbox, excludeKeys) {
|
|
255
|
-
const eligible =
|
|
246
|
+
const eligible = inbox.filter(membership => membership.notifications.length > 0);
|
|
256
247
|
return excludeKeys === undefined ? eligible : eligible.flatMap((membership) => {
|
|
257
|
-
const notifications = membership.notifications.filter((notification) => !excludeKeys.has(pendingKey(membership, notification.
|
|
248
|
+
const notifications = membership.notifications.filter((notification) => !excludeKeys.has(pendingKey(membership, notification.messageIndex)));
|
|
258
249
|
return notifications.length === 0 ? [] : [{ ...membership, notifications }];
|
|
259
250
|
});
|
|
260
251
|
}
|
|
@@ -320,7 +311,7 @@ export async function observeSessionPending(owner, suppliedEnv = process.env) {
|
|
|
320
311
|
}
|
|
321
312
|
const inbox = await currentInbox(candidates, signal);
|
|
322
313
|
const pending = eligibleInbox(inbox, options.excludeKeys);
|
|
323
|
-
const eligibleKeys = JSON.stringify(pending.flatMap((membership) => membership.notifications.map((note) => pendingKey(membership, note.
|
|
314
|
+
const eligibleKeys = JSON.stringify(pending.flatMap((membership) => membership.notifications.map((note) => pendingKey(membership, note.messageIndex))).sort());
|
|
324
315
|
if (previousEligible !== undefined && previousEligible !== eligibleKeys)
|
|
325
316
|
canReturn = true;
|
|
326
317
|
previousEligible = eligibleKeys;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { Square, squareLocation, squareClock, createSquareResourceOwner, discover, inbox, diagnoseSquare
|
|
1
|
+
export { Square, squareLocation, squareClock, createSquareResourceOwner, discover, inbox, diagnoseSquare } from './square-api.js';
|
|
2
2
|
export { SquareError, ACTIVITY_METADATA_MAX_BYTES } from './model.js';
|
|
3
3
|
export { selectOwnSeat, requireOwner, localUserOwner, harnessOwner, decodeHarnessOwner } from './square-api.js';
|
|
4
4
|
export type { WakeTransportPort, WakeRequest, WakeOutcome } from './ports.js';
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { Square, squareLocation, squareClock, createSquareResourceOwner, discover, inbox, diagnoseSquare
|
|
1
|
+
export { Square, squareLocation, squareClock, createSquareResourceOwner, discover, inbox, diagnoseSquare } from './square-api.js';
|
|
2
2
|
export { SquareError, ACTIVITY_METADATA_MAX_BYTES } from './model.js';
|
|
3
3
|
export { selectOwnSeat, requireOwner, localUserOwner, harnessOwner, decodeHarnessOwner } from './square-api.js';
|
package/dist/mcp.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
2
|
import { type Owner } from './square-api.js';
|
|
3
3
|
import type { SquareAtInput } from './square-facade.js';
|
|
4
|
+
import type { WakeAddress } from './model.js';
|
|
4
5
|
/** The server owns its artifact connection lifetime, so callers cannot lend one. */
|
|
5
6
|
export interface SquareMcpOptions extends Omit<SquareAtInput, 'resources'> {
|
|
6
7
|
readonly owner?: Owner;
|
|
7
|
-
readonly wakeAddress?:
|
|
8
|
+
readonly wakeAddress?: WakeAddress;
|
|
8
9
|
}
|
|
9
10
|
export type SquareMcpServer = McpServer;
|
|
10
11
|
export declare function createSquareMcpServer(options: SquareMcpOptions): SquareMcpServer;
|
package/dist/mcp.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
2
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
-
import { SQUARE_TOOLS, SQUARE_TOOL_INSTRUCTIONS,
|
|
3
|
+
import { SQUARE_TOOLS, SQUARE_TOOL_INSTRUCTIONS, createSquareToolRunner, squareToolsEnabled } from './square-tools.js';
|
|
4
4
|
import { operationEnv } from './participant-identity.js';
|
|
5
5
|
import { codexWakeAddress } from './codex-address.js';
|
|
6
6
|
import { readClaudeMcpCall } from './claude-mcp-context.js';
|
|
@@ -12,7 +12,7 @@ export function createSquareMcpServer(options) {
|
|
|
12
12
|
const baseEnv = { ...(resourceOptions.env ?? process.env) };
|
|
13
13
|
const toolsEnabled = squareToolsEnabled(baseEnv);
|
|
14
14
|
const resources = createSquareResourceOwner();
|
|
15
|
-
const
|
|
15
|
+
const invoke = createSquareToolRunner();
|
|
16
16
|
// Calls to the configured square borrow one server-owned connection.
|
|
17
17
|
const inFlight = new Set();
|
|
18
18
|
const server = new McpServer({ name: 'square', version: SQUARE_IDENTITY.packageVersion }, {
|
|
@@ -21,77 +21,47 @@ export function createSquareMcpServer(options) {
|
|
|
21
21
|
if (toolsEnabled)
|
|
22
22
|
for (const tool of SQUARE_TOOLS) {
|
|
23
23
|
server.registerTool(tool.name, { description: tool.description, inputSchema: tool.inputSchema }, (args, extra) => {
|
|
24
|
-
const call = (async () => {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
: typeof opencodeSessionId === 'string' && opencodeSessionId.trim()
|
|
57
|
-
? operationEnv('opencode', opencodeSessionId, baseEnv)
|
|
58
|
-
: baseEnv;
|
|
59
|
-
square = await Square.at({
|
|
24
|
+
const call = invoke(tool, args, { signal: extra.signal }, async () => {
|
|
25
|
+
const threadId = extra._meta?.threadId;
|
|
26
|
+
const opencodeSessionId = extra._meta?.['ai.opencode/sessionID'];
|
|
27
|
+
// Claude's plugin endpoint requires the mod's call-local coordinate, never startup identity.
|
|
28
|
+
const claude = baseEnv.SQUARE_CLAUDE_MCP_CONTEXT === '1'
|
|
29
|
+
? await readClaudeMcpCall(extra._meta?.['claudecode/toolUseId'], baseEnv)
|
|
30
|
+
: undefined;
|
|
31
|
+
const owner = claude !== undefined
|
|
32
|
+
? harnessOwner({ channel: 'claude-code', session: claude.sessionId, ...(claude.agentId === undefined ? {} : { agent: claude.agentId }) })
|
|
33
|
+
: typeof threadId === 'string' && threadId.trim()
|
|
34
|
+
? harnessOwner({ channel: 'codex', session: threadId })
|
|
35
|
+
: typeof opencodeSessionId === 'string' && opencodeSessionId.trim()
|
|
36
|
+
? harnessOwner({ channel: 'opencode', session: opencodeSessionId })
|
|
37
|
+
: configuredOwner;
|
|
38
|
+
const env = claude !== undefined
|
|
39
|
+
? { ...operationEnv('claude', claude.sessionId, baseEnv),
|
|
40
|
+
// An agent loop ID is not a session ID.
|
|
41
|
+
CLAUDE_CODE_CHILD_SESSION: claude.agentId === undefined ? '' : '1' }
|
|
42
|
+
: typeof threadId === 'string' && threadId.trim()
|
|
43
|
+
? operationEnv('codex', threadId, baseEnv)
|
|
44
|
+
: typeof opencodeSessionId === 'string' && opencodeSessionId.trim()
|
|
45
|
+
? operationEnv('opencode', opencodeSessionId, baseEnv)
|
|
46
|
+
: baseEnv;
|
|
47
|
+
const callWake = wakeAddress !== undefined && (owner === configuredOwner
|
|
48
|
+
|| wakeAddress.kind === 'paseo' && wakeAddress.address.agentId === baseEnv.PASEO_AGENT_ID
|
|
49
|
+
|| wakeAddress.kind === 'claude-native' && wakeAddress.address.sessionId === claude?.sessionId
|
|
50
|
+
|| wakeAddress.kind === 'opencode-server' && wakeAddress.address.sessionId === opencodeSessionId)
|
|
51
|
+
? wakeAddress : undefined;
|
|
52
|
+
return {
|
|
53
|
+
identity: { owner, wakeAddress: claude === undefined && typeof threadId === 'string' && threadId.trim()
|
|
54
|
+
? codexWakeAddress(threadId, env) : callWake },
|
|
55
|
+
open: (location, control) => Square.at({
|
|
60
56
|
...resourceOptions, env, cwd: claude?.cwd ?? resourceOptions.cwd,
|
|
61
57
|
path: location ?? options.path,
|
|
62
|
-
//
|
|
58
|
+
// Overrides and Claude calls own their connections.
|
|
63
59
|
resources: location === undefined && claude === undefined ? resources : undefined,
|
|
64
|
-
},
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
? wakeAddress : undefined;
|
|
70
|
-
let text;
|
|
71
|
-
try {
|
|
72
|
-
expressAttempted = tool.name === 'express';
|
|
73
|
-
text = await tool.run(square, toolArgs, { signal: extra.signal }, {
|
|
74
|
-
owner, wakeAddress: claude === undefined && typeof threadId === 'string' && threadId.trim()
|
|
75
|
-
? codexWakeAddress(threadId, env) : callWake,
|
|
76
|
-
});
|
|
77
|
-
drafts.consume(draftToken);
|
|
78
|
-
}
|
|
79
|
-
finally {
|
|
80
|
-
await square.close();
|
|
81
|
-
}
|
|
82
|
-
return { content: [{ type: 'text', text }] };
|
|
83
|
-
}
|
|
84
|
-
catch (error) {
|
|
85
|
-
const failure = squareToolFailure(error, extra.signal);
|
|
86
|
-
const participant = toolArgs.as;
|
|
87
|
-
const draft = tool.name === 'express' ? drafts.retain(toolArgs, owner, draftToken, failure, expressAttempted) : undefined;
|
|
88
|
-
return { isError: true, content: [{ type: 'text', text: squareToolFailureText(failure, {
|
|
89
|
-
location: square?.location ?? options.path,
|
|
90
|
-
participant: typeof participant === 'string' ? participant : undefined,
|
|
91
|
-
draft,
|
|
92
|
-
}) }] };
|
|
93
|
-
}
|
|
94
|
-
})();
|
|
60
|
+
}, control),
|
|
61
|
+
};
|
|
62
|
+
}, options.path).then(({ text, isError }) => ({
|
|
63
|
+
...(isError ? { isError } : {}), content: [{ type: 'text', text }],
|
|
64
|
+
}));
|
|
95
65
|
inFlight.add(call);
|
|
96
66
|
return call.finally(() => { inFlight.delete(call); });
|
|
97
67
|
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type MessageId = `msg/${number}`;
|
|
2
|
+
/** Opaque endpoint custody token, allocated when an endpoint is established. */
|
|
3
|
+
export type EndpointGeneration = string;
|
|
4
|
+
export declare function formatMessageId(index: number): MessageId;
|
|
5
|
+
export declare function parseMessageId(value: string): number | undefined;
|
|
6
|
+
export declare function newEndpointGeneration(): EndpointGeneration;
|
|
7
|
+
export declare function validateEndpointGeneration(value: unknown): EndpointGeneration;
|