@astrosheep/square 0.3.106 → 0.4.1
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 +6 -12
- package/dist/artifact.js +43 -344
- package/dist/attention-presentation.d.ts +13 -7
- package/dist/attention-presentation.js +29 -68
- package/dist/boundary-presentation.d.ts +3 -16
- package/dist/boundary-presentation.js +29 -109
- package/dist/card-presentation.d.ts +1 -0
- package/dist/card-presentation.js +2 -0
- package/dist/catch-decisions.d.ts +2 -2
- package/dist/catch-decisions.js +7 -3
- package/dist/catch-selection.d.ts +8 -0
- package/dist/catch-selection.js +5 -0
- package/dist/claude-delivery.d.ts +14 -12
- package/dist/claude-delivery.js +42 -142
- package/dist/claude-mcp-context.js +2 -2
- package/dist/claude-mod.js +28 -140
- package/dist/claude-transport.d.ts +5 -0
- package/dist/claude-transport.js +34 -0
- package/dist/cli/context.d.ts +2 -2
- package/dist/cli/context.js +7 -10
- package/dist/cli/maintenance-commands.d.ts +0 -4
- package/dist/cli/maintenance-commands.js +2 -29
- package/dist/cli/observation-commands.d.ts +2 -0
- package/dist/cli/observation-commands.js +12 -2
- package/dist/cli/program.js +1 -1
- package/dist/cli/registry.js +4 -3
- package/dist/codex-address.d.ts +2 -2
- package/dist/codex-boundary-state.d.ts +1 -5
- package/dist/codex-boundary-state.js +0 -4
- package/dist/codex-hook.d.ts +7 -10
- package/dist/codex-hook.js +69 -33
- package/dist/codex-queue.d.ts +10 -11
- package/dist/codex-queue.js +32 -34
- package/dist/codex-watch.d.ts +2 -0
- package/dist/codex-watch.js +100 -0
- package/dist/decisions.js +3 -3
- package/dist/delivery-deferral.d.ts +1 -19
- package/dist/delivery-deferral.js +1 -29
- package/dist/delivery-frame.d.ts +7 -0
- package/dist/delivery-frame.js +11 -0
- package/dist/delivery-ledger-sqlite.d.ts +97 -0
- package/dist/delivery-ledger-sqlite.js +496 -0
- package/dist/delivery-ledger.d.ts +196 -0
- package/dist/delivery-ledger.js +268 -0
- package/dist/delivery-lifetime.d.ts +6 -0
- package/dist/delivery-lifetime.js +45 -0
- package/dist/harness-links.js +61 -7
- package/dist/harness.js +12 -17
- package/dist/help.js +3 -6
- package/dist/http-delivery.d.ts +5 -9
- package/dist/http-delivery.js +20 -79
- package/dist/inbox.d.ts +6 -57
- package/dist/inbox.js +24 -380
- package/dist/index.d.ts +7 -4
- package/dist/index.js +5 -2
- 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 +15 -91
- package/dist/model.js +2 -140
- package/dist/neutral.d.ts +72 -0
- package/dist/neutral.js +27 -0
- package/dist/notifications.d.ts +24 -26
- package/dist/notifications.js +39 -143
- package/dist/operation-control.d.ts +4 -0
- package/dist/operation-control.js +20 -0
- 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/packages/agent-delivery/src/claude-native.d.ts +2 -0
- package/dist/packages/agent-delivery/src/claude-native.js +5 -0
- package/dist/packages/agent-delivery/src/codex-boundary.d.ts +11 -6
- package/dist/packages/agent-delivery/src/codex-boundary.js +31 -40
- package/dist/packages/agent-delivery/src/codex-hook.d.ts +17 -12
- package/dist/packages/agent-delivery/src/codex-hook.js +26 -39
- package/dist/packages/agent-delivery/src/codex.d.ts +3 -0
- package/dist/packages/agent-delivery/src/codex.js +41 -4
- package/dist/packages/agent-delivery/src/index.d.ts +3 -1
- package/dist/packages/agent-delivery/src/index.js +1 -1
- package/dist/packages/agent-delivery/src/paseo.js +11 -2
- package/dist/packages/agent-delivery/src/types.d.ts +1 -0
- package/dist/paseo-delivery.d.ts +5 -5
- package/dist/paseo-delivery.js +19 -99
- package/dist/paseo.d.ts +1 -1
- package/dist/paseo.js +1 -1
- package/dist/plugins/opencode/index.d.ts +5 -3
- package/dist/plugins/opencode/index.js +99 -21
- package/dist/plugins/opencode-shared/delivery.d.ts +23 -0
- package/dist/plugins/opencode-shared/delivery.js +109 -0
- package/dist/plugins/opencode-v1/index.d.ts +28 -0
- package/dist/plugins/opencode-v1/index.js +41 -0
- package/dist/plugins/opencode-v1/runtime.d.ts +56 -0
- package/dist/plugins/opencode-v1/runtime.js +152 -0
- package/dist/plugins/opencode-v1/transport.d.ts +13 -0
- package/dist/plugins/opencode-v1/transport.js +50 -0
- package/dist/plugins/paseo/shared/card-presentation.d.ts +10 -0
- package/dist/plugins/paseo/shared/card-presentation.js +7 -0
- package/dist/ports.d.ts +33 -214
- 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/session-lifecycle.d.ts +8 -7
- package/dist/session-lifecycle.js +40 -42
- package/dist/sqlite-framing.d.ts +8 -0
- package/dist/sqlite-framing.js +31 -0
- package/dist/sqlite-square.d.ts +16 -26
- package/dist/sqlite-square.js +199 -262
- 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 +5 -8
- package/dist/square-core.d.ts +0 -1
- package/dist/square-domain.d.ts +12 -10
- package/dist/square-domain.js +105 -223
- package/dist/square-facade.d.ts +25 -76
- package/dist/square-location.d.ts +6 -1
- package/dist/square-location.js +1 -1
- package/dist/square-post.d.ts +53 -0
- package/dist/square-post.js +4 -0
- package/dist/square-posting.d.ts +13 -0
- package/dist/square-posting.js +174 -0
- package/dist/square-producer-sqlite.d.ts +11 -0
- package/dist/square-producer-sqlite.js +210 -0
- package/dist/square-receive.d.ts +2 -3
- package/dist/square-sqlite.d.ts +33 -42
- package/dist/square-sqlite.js +236 -768
- package/dist/square-tools.d.ts +8 -0
- package/dist/square-tools.js +39 -0
- package/dist/square-values.d.ts +27 -0
- package/dist/square-values.js +90 -0
- package/dist/views.d.ts +6 -6
- package/dist/views.js +14 -9
- package/dist/wake-sink.d.ts +4 -11
- package/dist/wake-sink.js +8 -32
- package/dist/wake-transports.d.ts +4 -0
- package/dist/wake-transports.js +11 -0
- package/examples/symmetric-domains.mjs +29 -249
- package/guides/change-waits.md +13 -53
- package/guides/claude-native.md +19 -172
- package/guides/codex-delivery.md +55 -78
- package/guides/delivery-primitives.md +48 -207
- package/guides/operation-cancellation.md +2 -5
- package/guides/participant-ownership.md +25 -89
- package/guides/pi-native-delivery.md +19 -56
- package/guides/pi-tools.md +6 -8
- package/guides/row-runtime-delivery-evidence.md +2 -0
- package/guides/row-storage-design.md +19 -44
- package/guides/square-api.md +113 -538
- package/package.json +8 -8
- package/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/plugins/claude/hooks/hooks.json +1 -1
- package/plugins/claude/{hooks/register.js → mods/square.js} +4 -7
- package/plugins/claude/types.d.ts +1 -1
- package/plugins/codex/.codex-plugin/plugin.json +1 -1
- package/plugins/codex/hooks/hooks.json +11 -23
- package/plugins/opencode/index.ts +91 -23
- package/plugins/opencode-shared/delivery.ts +91 -0
- package/plugins/opencode-v1/index.ts +36 -0
- package/plugins/opencode-v1/runtime.ts +134 -0
- package/plugins/opencode-v1/transport.ts +41 -0
- package/plugins/paseo/OVERVIEW.md +5 -5
- package/plugins/paseo/README.md +7 -6
- package/plugins/paseo/client/markdown.tsx +5 -5
- package/plugins/paseo/client/square-message.tsx +28 -24
- package/plugins/paseo/client/transform.ts +8 -5
- package/plugins/paseo/shared/card-presentation.ts +16 -0
- package/plugins/paseo/shared/message.ts +6 -11
- package/plugins/pi/square-pi.js +167 -451
- package/dist/channel.d.ts +0 -74
- package/dist/channel.js +0 -92
- package/dist/claude-delivery-status.d.ts +0 -13
- package/dist/claude-delivery-status.js +0 -46
- package/dist/cmd/notify-once.d.ts +0 -2
- package/dist/cmd/notify-once.js +0 -29
- package/dist/delivery-attempts.d.ts +0 -52
- package/dist/delivery-attempts.js +0 -73
- package/dist/delivery-effects.d.ts +0 -139
- package/dist/delivery-effects.js +0 -163
- package/dist/delivery-health.d.ts +0 -51
- package/dist/delivery-health.js +0 -173
- package/dist/delivery-operations.d.ts +0 -9
- package/dist/delivery-operations.js +0 -168
- package/dist/delivery-primitives.d.ts +0 -5
- package/dist/delivery-primitives.js +0 -3
- package/dist/delivery.d.ts +0 -45
- package/dist/delivery.js +0 -106
- package/dist/plugins/opencode/delivery.d.ts +0 -19
- package/dist/plugins/opencode/delivery.js +0 -238
- package/dist/presentation-operations.d.ts +0 -18
- package/dist/presentation-operations.js +0 -86
- package/dist/square-index.d.ts +0 -16
- package/dist/square-index.js +0 -106
- 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/plugins/opencode/delivery.ts +0 -211
package/dist/inbox.js
CHANGED
|
@@ -1,391 +1,35 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { createArtifactPort } from './artifact.js';
|
|
3
|
+
import { discoverSquares, MAX_LIST_LIMIT } from './list.js';
|
|
3
4
|
import { canonicalPath } from './canonical-path.js';
|
|
4
|
-
|
|
5
|
-
import { freshWatchLease, WATCH_STALE_MS } from './runtime.js';
|
|
6
|
-
import { pendingHead, pendingHeads, leaseOwnsNotification } from './delivery.js';
|
|
7
|
-
import { deliveryDeferralExpiry } from './delivery-deferral.js';
|
|
8
|
-
import { attentionBodyIsClipped, renderAttentionPreview, renderSquareReminder } from './attention-presentation.js';
|
|
9
|
-
export function pendingKey(membership, actIndex) {
|
|
10
|
-
return membership.location + '\0' + membership.seat.participant.toLocaleLowerCase() + '\0' + actIndex;
|
|
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);
|
|
19
|
-
}
|
|
20
|
-
function eligibleNotifications(facts, membership, now = Date.now()) {
|
|
21
|
-
const seat = facts.seat(membership.seat.participant);
|
|
22
|
-
if (seat === undefined || seat.attachment !== 'attached' || seat.generation !== membership.seat.generation
|
|
23
|
-
|| seat.owner !== membership.seat.owner)
|
|
24
|
-
return { notifications: [] };
|
|
25
|
-
const cancelledThrough = facts.cancelledThrough(seat.participant, seat.generation);
|
|
26
|
-
let expiry = Infinity;
|
|
27
|
-
const notifications = membership.notifications.filter(notification => {
|
|
28
|
-
if (cancelledThrough !== undefined && notification.actIndex <= cancelledThrough
|
|
29
|
-
|| facts.deliveryEvidence(seat.participant, seat.generation, formatActivityId(notification.actIndex))
|
|
30
|
-
.some(row => row.outcome !== 'failed' && row.outcome !== 'released'))
|
|
31
|
-
return false;
|
|
32
|
-
const deferredUntil = deliveryDeferralExpiry(facts, seat, notification.actIndex, now);
|
|
33
|
-
if (deferredUntil === undefined)
|
|
34
|
-
return true;
|
|
35
|
-
expiry = Math.min(expiry, deferredUntil);
|
|
36
|
-
return false;
|
|
37
|
-
});
|
|
38
|
-
return { notifications, ...(expiry === Infinity ? {} : { deliveryDeferralExpiry: expiry }) };
|
|
39
|
-
}
|
|
40
|
-
/** Revalidate a supplied batch immediately before the boundary's independent claim/send checks. */
|
|
41
|
-
export async function projectBoundaryEligibility(inbox) {
|
|
42
|
-
const grouped = new Map();
|
|
43
|
-
for (const membership of pendingAtBoundary(inbox)) {
|
|
44
|
-
const entries = grouped.get(membership.location) ?? [];
|
|
45
|
-
entries.push(membership);
|
|
46
|
-
grouped.set(membership.location, entries);
|
|
47
|
-
}
|
|
48
|
-
const pending = [];
|
|
49
|
-
for (const [location, memberships] of grouped) {
|
|
50
|
-
const artifact = createArtifactPort(location);
|
|
51
|
-
try {
|
|
52
|
-
const { value } = await artifact.read(facts => memberships.flatMap(membership => {
|
|
53
|
-
const eligibility = eligibleNotifications(facts, membership);
|
|
54
|
-
return eligibility.notifications.length === 0 ? [] : [{ ...membership, ...eligibility }];
|
|
55
|
-
}));
|
|
56
|
-
pending.push(...value);
|
|
57
|
-
}
|
|
58
|
-
catch {
|
|
59
|
-
// An unreadable or stale candidate cannot authorize boundary presentation.
|
|
60
|
-
}
|
|
61
|
-
finally {
|
|
62
|
-
await artifact.close();
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
return pending;
|
|
66
|
-
}
|
|
67
|
-
/** HOME is a candidate path index; current seats and delivery evidence live in the artifact. */
|
|
68
|
-
export async function projectSessionCandidates(input) {
|
|
69
|
-
const location = input.location === undefined ? undefined : await canonicalPath(input.location);
|
|
70
|
-
const entries = await indexedSquareEntries({ owner: input.owner }, input.env ?? process.env);
|
|
71
|
-
const candidates = new Map();
|
|
72
|
-
for (const entry of entries) {
|
|
73
|
-
if (location !== undefined && location !== entry.location)
|
|
74
|
-
continue;
|
|
75
|
-
const key = JSON.stringify([entry.location, entry.owner]);
|
|
76
|
-
candidates.set(key, { location: entry.location, owner: entry.owner });
|
|
77
|
-
}
|
|
78
|
-
return [...candidates.values()];
|
|
79
|
-
}
|
|
80
|
-
function groupCandidates(candidates) {
|
|
81
|
-
const grouped = new Map();
|
|
82
|
-
for (const { location, owner } of candidates) {
|
|
83
|
-
const owners = grouped.get(location) ?? [];
|
|
84
|
-
owners.push(owner);
|
|
85
|
-
grouped.set(location, owners);
|
|
86
|
-
}
|
|
87
|
-
return grouped;
|
|
88
|
-
}
|
|
89
|
-
function locatedSeats(facts, location, seats) {
|
|
90
|
-
return seats.flatMap(seat => {
|
|
91
|
-
const index = parseActivityId(seat.generation);
|
|
92
|
-
const joined = index === undefined ? undefined : facts.activity(index);
|
|
93
|
-
return joined?.kind !== 'join' ? [] : [{ location, seat, joinedAt: joined.at }];
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
/** Lifecycle reads resolve seats and join times, never pending activity bodies. */
|
|
97
|
-
export async function projectSessionSeats(input, signal) {
|
|
98
|
-
const seats = [];
|
|
99
|
-
for (const [location, owners] of groupCandidates(await projectSessionCandidates(input))) {
|
|
100
|
-
signal?.throwIfAborted();
|
|
101
|
-
const artifact = createArtifactPort(location, undefined, signal);
|
|
102
|
-
try {
|
|
103
|
-
seats.push(...(await artifact.read(facts => locatedSeats(facts, location, owners.flatMap(owner => facts.seatsForOwner(owner))), signal)).value);
|
|
104
|
-
}
|
|
105
|
-
catch (error) {
|
|
106
|
-
if (signal?.aborted)
|
|
107
|
-
throw signal.reason ?? error;
|
|
108
|
-
}
|
|
109
|
-
finally {
|
|
110
|
-
await artifact.close();
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
return seats;
|
|
114
|
-
}
|
|
115
|
-
/** Public count boundary: one coherent read per artifact, without pending body hydration. */
|
|
5
|
+
/** Filesystem-scoped membership counts. This is a World read, not a delivery scan. */
|
|
116
6
|
export async function sessionInboxCounts(input, signal) {
|
|
7
|
+
const root = path.resolve(input.cwd ?? process.cwd());
|
|
117
8
|
const rows = [];
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
const artifact = createArtifactPort(location, undefined, signal);
|
|
121
|
-
try {
|
|
122
|
-
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 }),
|
|
124
|
-
})), signal)).value);
|
|
125
|
-
}
|
|
126
|
-
catch (error) {
|
|
127
|
-
if (signal?.aborted)
|
|
128
|
-
throw signal.reason ?? error;
|
|
129
|
-
}
|
|
130
|
-
finally {
|
|
131
|
-
await artifact.close();
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
return rows;
|
|
135
|
-
}
|
|
136
|
-
/** One authoritative read per canonical artifact, even when discovery names several bodies. */
|
|
137
|
-
async function projectPending(candidates, cache, signal) {
|
|
138
|
-
const pending = [];
|
|
139
|
-
for (const [location, owners] of groupCandidates(candidates)) {
|
|
9
|
+
let after;
|
|
10
|
+
do {
|
|
140
11
|
signal?.throwIfAborted();
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
const header = facts.header();
|
|
147
|
-
let cached = cache?.get(location);
|
|
148
|
-
if (cached?.coordinate !== coordinate || cached.version !== header.revision) {
|
|
149
|
-
cached = { coordinate, version: header.revision, value: locatedSeats(facts, location, seats).map(membership => {
|
|
150
|
-
const heads = pendingHeads(facts, membership.seat.participant);
|
|
151
|
-
const routes = new Map(heads.map(head => [head.index, head.route]));
|
|
152
|
-
return { ...membership, notifications: facts.activities(heads.map(head => head.index)).flatMap(item => item.kind === 'say'
|
|
153
|
-
? [{ actIndex: item.index, actor: item.actor, at: item.at, body: item.body ?? '(structured activity)',
|
|
154
|
-
route: routes.get(item.index), ...(item.delivery === undefined ? {} : { delivery: item.delivery }) }]
|
|
155
|
-
: []) };
|
|
156
|
-
}) };
|
|
157
|
-
}
|
|
158
|
-
// Cache conversation content, never leases or delivery eligibility: evidence can change
|
|
159
|
-
// without a conversation revision and must not force body hydration or self-wake retries.
|
|
160
|
-
return { cached, pending: cached.value.map(membership => {
|
|
161
|
-
const catchLease = freshWatchLease(facts, membership.seat.participant);
|
|
162
|
-
return { ...membership, ...eligibleNotifications(facts, membership),
|
|
163
|
-
...(catchLease === undefined ? {} : { catchLease }) };
|
|
164
|
-
}) };
|
|
165
|
-
}, signal);
|
|
166
|
-
cache?.set(location, current.cached);
|
|
167
|
-
pending.push(...current.pending);
|
|
168
|
-
}
|
|
169
|
-
catch (error) {
|
|
170
|
-
cache?.delete(location);
|
|
171
|
-
if (signal?.aborted)
|
|
172
|
-
throw signal.reason ?? error;
|
|
173
|
-
// Unreadable candidates remain watch targets, but never authorize cached content.
|
|
174
|
-
}
|
|
175
|
-
finally {
|
|
176
|
-
await artifact.close();
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
return pending;
|
|
180
|
-
}
|
|
181
|
-
function sameWakeAddress(address, kind, requested) {
|
|
182
|
-
if (address?.kind !== kind || Object.keys(address.address).length !== Object.keys(requested).length)
|
|
183
|
-
return false;
|
|
184
|
-
return Object.entries(requested).every(([key, value]) => address.address[key] === value);
|
|
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))
|
|
191
|
-
return undefined;
|
|
192
|
-
const cancelledThrough = facts.cancelledThrough(seat.participant, seat.generation);
|
|
193
|
-
if (input.actIndex <= Math.max(input.cancelledThrough ?? -1, cancelledThrough ?? -1))
|
|
194
|
-
return undefined;
|
|
195
|
-
const head = pendingHead(facts, seat.participant, input.actIndex);
|
|
196
|
-
const lease = freshWatchLease(facts, input.participant, input.now);
|
|
197
|
-
if (!head || (lease !== undefined && leaseOwnsNotification(lease, head)))
|
|
198
|
-
return undefined;
|
|
199
|
-
const item = facts.activity(input.actIndex);
|
|
200
|
-
if (item?.kind !== 'say' || deliveryDeferralExpiry(facts, seat, input.actIndex, input.now) !== undefined)
|
|
201
|
-
return undefined;
|
|
202
|
-
return { payload: renderSquareReminder([renderAttentionPreview({ squarePath: input.location, recipient: input.participant, actIndex: item.index,
|
|
203
|
-
actor: item.actor, route: head.route, body: item.body ?? '(structured activity)', delivery: item.delivery })], [item.delivery]),
|
|
204
|
-
clipped: attentionBodyIsClipped(item.body ?? '(structured activity)', item.delivery) };
|
|
205
|
-
}
|
|
206
|
-
export async function sessionInbox(owner, env = process.env, signal) {
|
|
207
|
-
return projectPending(await projectSessionCandidates({ owner, env }), undefined, signal);
|
|
208
|
-
}
|
|
209
|
-
async function capture(observer, kind, signal) {
|
|
210
|
-
const generation = observer.generation;
|
|
211
|
-
try {
|
|
212
|
-
const value = await observer.read(signal);
|
|
213
|
-
return { kind, value, wait: (timeout, abort) => observer.changed(value, timeout, abort) };
|
|
214
|
-
}
|
|
215
|
-
catch (error) {
|
|
216
|
-
if (signal.aborted)
|
|
217
|
-
throw signal.reason ?? error;
|
|
218
|
-
// Failed revision reads are not changes; only a successful retry wakes projection.
|
|
219
|
-
return { kind, wait: async (timeout, abort) => {
|
|
220
|
-
const deadline = Date.now() + Math.max(0, timeout);
|
|
221
|
-
let since = generation;
|
|
222
|
-
while (await observer.hinted(since, Math.max(0, deadline - Date.now()), abort)) {
|
|
223
|
-
since = observer.generation;
|
|
224
|
-
try {
|
|
225
|
-
await observer.read(abort);
|
|
226
|
-
return true;
|
|
227
|
-
}
|
|
228
|
-
catch (error) {
|
|
229
|
-
if (abort.aborted)
|
|
230
|
-
throw abort.reason ?? error;
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
return false;
|
|
234
|
-
} };
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
/** Session-scoped projection over process-shared storage change detectors. */
|
|
238
|
-
export async function observeSessionPending(owner, suppliedEnv = process.env) {
|
|
239
|
-
const env = { ...suppliedEnv };
|
|
240
|
-
const query = { owner, env };
|
|
241
|
-
const index = await observeSquareIndex(env);
|
|
242
|
-
const artifacts = new Map();
|
|
243
|
-
const cache = new Map();
|
|
244
|
-
const lifetime = new AbortController();
|
|
245
|
-
async function currentInbox(candidates, signal) {
|
|
246
|
-
const inbox = await projectPending(candidates, cache, signal);
|
|
247
|
-
const now = Date.now();
|
|
248
|
-
return inbox.map((membership) => {
|
|
249
|
-
const { catchLease, ...rest } = membership;
|
|
250
|
-
return catchLease !== undefined && catchLease.expiresAt > now && now - catchLease.heartbeatAt <= WATCH_STALE_MS
|
|
251
|
-
? membership : rest;
|
|
252
|
-
});
|
|
253
|
-
}
|
|
254
|
-
function eligibleInbox(inbox, excludeKeys) {
|
|
255
|
-
const eligible = pendingAtBoundary(inbox);
|
|
256
|
-
return excludeKeys === undefined ? eligible : eligible.flatMap((membership) => {
|
|
257
|
-
const notifications = membership.notifications.filter((notification) => !excludeKeys.has(pendingKey(membership, notification.actIndex)));
|
|
258
|
-
return notifications.length === 0 ? [] : [{ ...membership, notifications }];
|
|
259
|
-
});
|
|
260
|
-
}
|
|
261
|
-
return {
|
|
262
|
-
async read(options = {}) {
|
|
263
|
-
const signal = AbortSignal.any([lifetime.signal, ...(options.signal === undefined ? [] : [options.signal])]);
|
|
264
|
-
signal.throwIfAborted();
|
|
265
|
-
const inbox = await currentInbox(await projectSessionCandidates(query), signal);
|
|
266
|
-
const pending = eligibleInbox(inbox, options.excludeKeys);
|
|
267
|
-
signal.throwIfAborted();
|
|
268
|
-
return pending;
|
|
269
|
-
},
|
|
270
|
-
async wait(timeoutMs, options = {}) {
|
|
271
|
-
const deadline = Date.now() + Math.max(0, timeoutMs);
|
|
272
|
-
const expired = new AbortController();
|
|
273
|
-
const timer = Number.isFinite(timeoutMs) && timeoutMs > 0
|
|
274
|
-
? setTimeout(() => expired.abort(), Math.min(timeoutMs, 2_147_483_647)) : undefined;
|
|
275
|
-
const signal = AbortSignal.any([lifetime.signal, expired.signal, ...(options.signal === undefined ? [] : [options.signal])]);
|
|
276
|
-
let canReturn = !options.skipImmediate;
|
|
277
|
-
let previousEligible;
|
|
278
|
-
let armed = false;
|
|
279
|
-
let candidates = [];
|
|
280
|
-
let candidatePresence;
|
|
281
|
-
let discoveredCandidates = false;
|
|
12
|
+
const page = await discoverSquares(root, { limit: MAX_LIST_LIMIT, ...(after === undefined ? {} : { after }) }, { signal });
|
|
13
|
+
for (const item of page.items) {
|
|
14
|
+
signal?.throwIfAborted();
|
|
15
|
+
const location = await canonicalPath(path.resolve(root, item.path));
|
|
16
|
+
const artifact = createArtifactPort(location, undefined, signal);
|
|
282
17
|
try {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
const presence = sources[0];
|
|
288
|
-
const currentPresence = presence.value;
|
|
289
|
-
if (!discoveredCandidates || currentPresence !== undefined && !Object.is(currentPresence, candidatePresence)) {
|
|
290
|
-
candidates = await projectSessionCandidates(query);
|
|
291
|
-
candidatePresence = currentPresence;
|
|
292
|
-
discoveredCandidates = true;
|
|
293
|
-
}
|
|
294
|
-
const paths = new Set(candidates.map((candidate) => candidate.location));
|
|
295
|
-
for (const [file, entry] of artifacts) {
|
|
296
|
-
if (!paths.has(file)) {
|
|
297
|
-
entry.observer.close();
|
|
298
|
-
entry.delivery.close();
|
|
299
|
-
artifacts.delete(file);
|
|
300
|
-
cache.delete(file);
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
for (const file of paths) {
|
|
304
|
-
if (artifacts.has(file))
|
|
305
|
-
continue;
|
|
306
|
-
const observer = await observeSquareRevision(file);
|
|
307
|
-
const delivery = await observeSquareDeliveryRevision(file);
|
|
308
|
-
if (signal.aborted) {
|
|
309
|
-
observer.close();
|
|
310
|
-
delivery.close();
|
|
311
|
-
return [];
|
|
312
|
-
}
|
|
313
|
-
artifacts.set(file, { observer, delivery });
|
|
314
|
-
}
|
|
315
|
-
// Capture both revision baselines before selecting seats and pending facts. A takeover
|
|
316
|
-
// during an empty selection must remain visible to the already armed observers.
|
|
317
|
-
for (const entry of artifacts.values()) {
|
|
318
|
-
sources.push(await capture(entry.observer, 'artifact', signal));
|
|
319
|
-
sources.push(await capture(entry.delivery, 'delivery', signal));
|
|
320
|
-
}
|
|
321
|
-
const inbox = await currentInbox(candidates, signal);
|
|
322
|
-
const pending = eligibleInbox(inbox, options.excludeKeys);
|
|
323
|
-
const eligibleKeys = JSON.stringify(pending.flatMap((membership) => membership.notifications.map((note) => pendingKey(membership, note.actIndex))).sort());
|
|
324
|
-
if (previousEligible !== undefined && previousEligible !== eligibleKeys)
|
|
325
|
-
canReturn = true;
|
|
326
|
-
previousEligible = eligibleKeys;
|
|
327
|
-
if (signal.aborted)
|
|
328
|
-
return [];
|
|
329
|
-
if (canReturn && pending.length > 0)
|
|
330
|
-
return pending;
|
|
331
|
-
const remaining = deadline - Date.now();
|
|
332
|
-
if (remaining <= 0)
|
|
333
|
-
return [];
|
|
334
|
-
// Eligibility may change at lease expiry without any storage write.
|
|
335
|
-
const leaseExpiry = Math.min(Infinity, ...inbox.flatMap((membership) => membership.catchLease === undefined ? [] : [Math.min(membership.catchLease.expiresAt, membership.catchLease.heartbeatAt + WATCH_STALE_MS + 1)]));
|
|
336
|
-
const nextExpiry = Math.min(leaseExpiry, ...inbox.map(membership => membership.deliveryDeferralExpiry ?? Infinity));
|
|
337
|
-
const duration = Math.min(remaining, Math.max(1, nextExpiry - Date.now()));
|
|
338
|
-
const cancelled = new AbortController();
|
|
339
|
-
const waitSignal = AbortSignal.any([signal, cancelled.signal]);
|
|
340
|
-
const waits = sources.map(async (source) => {
|
|
341
|
-
try {
|
|
342
|
-
return await source.wait(duration, waitSignal) ? source.kind : 'deadline';
|
|
343
|
-
}
|
|
344
|
-
catch (error) {
|
|
345
|
-
if (waitSignal.aborted)
|
|
346
|
-
throw error;
|
|
347
|
-
return source.kind;
|
|
348
|
-
}
|
|
349
|
-
});
|
|
350
|
-
if (!armed) {
|
|
351
|
-
armed = true;
|
|
352
|
-
options.onChangeArmed?.(true);
|
|
353
|
-
}
|
|
354
|
-
try {
|
|
355
|
-
const change = await Promise.race(waits);
|
|
356
|
-
if (change !== 'delivery')
|
|
357
|
-
canReturn = true;
|
|
358
|
-
}
|
|
359
|
-
finally {
|
|
360
|
-
cancelled.abort();
|
|
361
|
-
await Promise.allSettled(waits);
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
return [];
|
|
18
|
+
rows.push(...(await artifact.read(facts => facts.seatsForOwner(input.owner)
|
|
19
|
+
.filter(seat => facts.participant(seat.participant)?.joined)
|
|
20
|
+
.map(seat => ({ name: seat.participant, squarePath: location,
|
|
21
|
+
pending: facts.directedCount(seat.participant, { unseenOnly: true, currentGeneration: true }) })), signal)).value);
|
|
365
22
|
}
|
|
366
23
|
catch (error) {
|
|
367
|
-
if (signal
|
|
368
|
-
|
|
369
|
-
|
|
24
|
+
if (signal?.aborted)
|
|
25
|
+
throw signal.reason ?? error;
|
|
26
|
+
// Discovery candidates may disappear or become unreadable before this read.
|
|
370
27
|
}
|
|
371
28
|
finally {
|
|
372
|
-
|
|
373
|
-
clearTimeout(timer);
|
|
374
|
-
if (!armed)
|
|
375
|
-
options.onChangeArmed?.(false);
|
|
376
|
-
}
|
|
377
|
-
},
|
|
378
|
-
close() {
|
|
379
|
-
if (lifetime.signal.aborted)
|
|
380
|
-
return;
|
|
381
|
-
lifetime.abort();
|
|
382
|
-
index.close();
|
|
383
|
-
for (const entry of artifacts.values()) {
|
|
384
|
-
entry.observer.close();
|
|
385
|
-
entry.delivery.close();
|
|
29
|
+
await artifact.close();
|
|
386
30
|
}
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
31
|
+
}
|
|
32
|
+
after = page.next;
|
|
33
|
+
} while (after !== undefined);
|
|
34
|
+
return rows;
|
|
391
35
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
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
|
-
export { selectOwnSeat, requireOwner, localUserOwner, harnessOwner, decodeHarnessOwner } from './square-api.js';
|
|
4
|
-
export
|
|
5
|
-
export
|
|
3
|
+
export { selectOwnSeat, requireOwner, requireJoinName, localUserOwner, harnessOwner, decodeHarnessOwner } from './square-api.js';
|
|
4
|
+
export { postAndDispatch, dispatchEligibleCards } from './notifications.js';
|
|
5
|
+
export { DeliveryLedger, deliveryLedgerLocation } from './delivery-ledger.js';
|
|
6
|
+
export type * from './delivery-ledger.js';
|
|
7
|
+
export { isCardPresentation } from './card-presentation.js';
|
|
8
|
+
export type { CardPresentation } from './card-presentation.js';
|
|
6
9
|
export type * from './square-facade.js';
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
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
|
-
export { selectOwnSeat, requireOwner, localUserOwner, harnessOwner, decodeHarnessOwner } from './square-api.js';
|
|
3
|
+
export { selectOwnSeat, requireOwner, requireJoinName, localUserOwner, harnessOwner, decodeHarnessOwner } from './square-api.js';
|
|
4
|
+
export { postAndDispatch, dispatchEligibleCards } from './notifications.js';
|
|
5
|
+
export { DeliveryLedger, deliveryLedgerLocation } from './delivery-ledger.js';
|
|
6
|
+
export { isCardPresentation } from './card-presentation.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), kind: 'codex-queue' } : 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;
|
package/dist/message.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Message coordinates belong to Channel alone; Agora activity ids are a separate coordinate.
|
|
2
|
+
import crypto from 'node:crypto';
|
|
3
|
+
export function formatMessageId(index) {
|
|
4
|
+
if (!Number.isSafeInteger(index) || index < 0)
|
|
5
|
+
throw new Error(`Invalid message index: ${index}`);
|
|
6
|
+
return `msg/${index}`;
|
|
7
|
+
}
|
|
8
|
+
export function parseMessageId(value) {
|
|
9
|
+
if (value === 'msg/0')
|
|
10
|
+
return 0;
|
|
11
|
+
if (typeof value !== 'string' || !/^msg\/[1-9]\d*$/.test(value))
|
|
12
|
+
return undefined;
|
|
13
|
+
const index = Number(value.slice(4));
|
|
14
|
+
return Number.isSafeInteger(index) ? index : undefined;
|
|
15
|
+
}
|
|
16
|
+
export function newEndpointGeneration() {
|
|
17
|
+
return `endpoint/${crypto.randomUUID()}`;
|
|
18
|
+
}
|
|
19
|
+
export function validateEndpointGeneration(value) {
|
|
20
|
+
if (typeof value !== 'string' || value.length === 0 || value.length > 512 || /[\u0000-\u001f\u007f-\u009f]/u.test(value))
|
|
21
|
+
throw new TypeError('Endpoint generation must be a non-empty opaque token without control characters.');
|
|
22
|
+
return value;
|
|
23
|
+
}
|