@astrosheep/square 0.3.92 → 0.3.94
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/activity-feed.d.ts +3 -5
- package/dist/activity-feed.js +16 -14
- package/dist/artifact.js +2 -2
- package/dist/boundary-presentation.d.ts +2 -2
- package/dist/catch-decisions.d.ts +4 -6
- package/dist/catch-decisions.js +4 -4
- package/dist/claude-delivery-status.js +10 -4
- package/dist/claude-delivery.js +19 -11
- package/dist/claude-mod.js +24 -53
- package/dist/cli/context.d.ts +9 -4
- package/dist/cli/context.js +30 -25
- package/dist/cli/express-presentation.d.ts +3 -1
- package/dist/cli/express-presentation.js +26 -14
- package/dist/cli/json.d.ts +3 -0
- package/dist/cli/json.js +14 -0
- package/dist/cli/mcp-server-command.js +2 -2
- package/dist/cli/observation-commands.d.ts +1 -3
- package/dist/cli/observation-commands.js +43 -56
- package/dist/cli/program.js +2 -2
- package/dist/cli/registry.js +6 -3
- package/dist/cli/square-commands.d.ts +1 -1
- package/dist/cli/square-commands.js +46 -54
- package/dist/codex-hook.d.ts +3 -2
- package/dist/codex-hook.js +13 -4
- package/dist/codex-queue.js +4 -4
- package/dist/decisions.d.ts +5 -11
- package/dist/decisions.js +9 -20
- package/dist/delivery-deferral.js +2 -2
- package/dist/delivery-health.d.ts +0 -1
- package/dist/delivery-health.js +1 -3
- package/dist/delivery-operations.d.ts +2 -2
- package/dist/delivery-operations.js +38 -38
- package/dist/delivery.d.ts +1 -1
- package/dist/delivery.js +1 -1
- package/dist/harness-links.d.ts +1 -10
- package/dist/harness-links.js +144 -109
- package/dist/harness.js +4 -13
- package/dist/help.js +12 -10
- package/dist/inbox.d.ts +9 -22
- package/dist/inbox.js +23 -27
- package/dist/index.d.ts +1 -3
- package/dist/index.js +1 -1
- package/dist/mcp.d.ts +3 -0
- package/dist/mcp.js +23 -24
- package/dist/model.d.ts +8 -15
- package/dist/model.js +4 -7
- package/dist/notifications.d.ts +1 -1
- package/dist/notifications.js +6 -5
- package/dist/operation-presentation.d.ts +5 -4
- package/dist/operation-presentation.js +50 -45
- package/dist/owner.d.ts +11 -0
- package/dist/owner.js +32 -0
- package/dist/paseo-delivery.d.ts +4 -1
- package/dist/paseo-delivery.js +7 -12
- package/dist/paseo-state.d.ts +2 -2
- package/dist/paseo-state.js +6 -4
- package/dist/plugins/opencode/delivery.js +27 -15
- package/dist/ports.d.ts +13 -33
- package/dist/presentation-operations.js +4 -4
- package/dist/presentation.d.ts +5 -8
- package/dist/presentation.js +33 -43
- package/dist/runtime.d.ts +1 -7
- package/dist/runtime.js +0 -14
- package/dist/seats.d.ts +2 -3
- package/dist/seats.js +7 -15
- package/dist/session-lifecycle.d.ts +6 -2
- package/dist/session-lifecycle.js +39 -11
- package/dist/sqlite-square.d.ts +36 -22
- package/dist/sqlite-square.js +138 -161
- package/dist/square-api.d.ts +15 -10
- package/dist/square-api.js +26 -22
- package/dist/square-core.d.ts +0 -5
- package/dist/square-core.js +0 -9
- package/dist/square-domain.d.ts +4 -2
- package/dist/square-domain.js +56 -21
- package/dist/square-facade.d.ts +45 -87
- package/dist/square-index.d.ts +5 -13
- package/dist/square-index.js +13 -38
- package/dist/square-location.d.ts +1 -14
- package/dist/square-location.js +4 -69
- package/dist/square-migration-host-ledger.d.ts +2 -2
- package/dist/square-migration-host-ledger.js +6 -5
- package/dist/square-migration.d.ts +2 -1
- package/dist/square-migration.js +52 -30
- package/dist/square-sqlite.d.ts +6 -2
- package/dist/square-sqlite.js +54 -28
- package/dist/square-tools.d.ts +7 -10
- package/dist/square-tools.js +22 -54
- package/dist/stream.js +4 -5
- package/dist/views.d.ts +21 -10
- package/dist/views.js +38 -38
- package/dist/wakes.d.ts +1 -2
- package/dist/wakes.js +1 -1
- package/guides/application-facade-delivery-evidence.md +1 -1
- package/guides/claude-native.md +18 -9
- package/guides/codex-delivery.md +17 -6
- package/guides/mcp-connection-lifetime.md +1 -1
- package/guides/mcp.md +3 -3
- package/guides/migration.md +142 -151
- package/guides/operation-cancellation.md +1 -1
- package/guides/participant-ownership.md +78 -47
- package/guides/pi-tools.md +33 -4
- package/guides/primitive-history-consolidation.md +1 -1
- package/guides/row-storage-design.md +8 -8
- package/guides/square-api.md +244 -88
- package/package.json +2 -1
- package/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/plugins/codex/.codex-plugin/plugin.json +1 -1
- package/plugins/opencode/delivery.ts +27 -17
- package/plugins/paseo/client/palette.ts +18 -18
- package/plugins/paseo/client/square-message.tsx +17 -23
- package/plugins/pi/square-pi.js +17 -17
- package/dist/square-backend.d.ts +0 -13
- package/dist/square-backend.js +0 -1
- package/dist/square-stdio.d.ts +0 -22
- package/dist/square-stdio.js +0 -235
- package/dist/stdio-server.d.ts +0 -3
- package/dist/stdio-server.js +0 -212
- package/guides/backends.md +0 -193
package/dist/sqlite-square.js
CHANGED
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
import { formatActivityId } from './square-core.js';
|
|
2
|
+
import { assertCurrentSeat } from './square-domain.js';
|
|
3
|
+
import { canonicalPath } from './canonical-path.js';
|
|
2
4
|
import { validateMetadataMatch, validateDeferralDuration, validateDeferralId } from './delivery-deferral.js';
|
|
3
|
-
import { squareLocation
|
|
5
|
+
import { squareLocation } from './square-location.js';
|
|
4
6
|
import crypto from 'node:crypto';
|
|
5
7
|
import { setTimeout as sleep } from 'node:timers/promises';
|
|
6
|
-
import { InternalSquareError, SquareError, isSquareError, nameKey,
|
|
7
|
-
import { coreDone, coreHold, coreIgnore, coreListen, coreListening, coreResume, coreUnlisten, decideAct,
|
|
8
|
+
import { InternalSquareError, SquareError, isSquareError, nameKey, validateName } from './model.js';
|
|
9
|
+
import { coreDone, coreHold, coreIgnore, coreListen, coreListening, coreResume, coreUnlisten, decideAct, decideJoin, resolveKnownName, validateDoneBody } from './decisions.js';
|
|
8
10
|
import { HELD_WAIT_BUDGET_MS, WATCH_HEARTBEAT_MS, inSquareCount, presenceAnchor } from './runtime.js';
|
|
9
11
|
import { catchLimit, decideCatch, observeCatchSelection, selectCatchPage } from './catch-decisions.js';
|
|
10
|
-
import {
|
|
12
|
+
import { expressionContext } from './activity-feed.js';
|
|
11
13
|
import { expressHint } from './hints.js';
|
|
12
|
-
import { asSeatRef, participantAuthority,
|
|
14
|
+
import { asSeatRef, participantAuthority, requireOwner, requireJoinName } from './seats.js';
|
|
13
15
|
import { recoveryObservation, entryPresentation, snapshot, queryStatus, queryParticipants, catchPresence, parseRequiredActivityId, toPublicActivity, publicActivities, selectHistory, NON_READ, HISTORY_DEFAULT_LIMIT, HISTORY_MAX_LIMIT } from './views.js';
|
|
14
16
|
import { sessionInboxCounts } from './inbox.js';
|
|
15
17
|
import { rememberSquarePath, squareIndexPath } from './square-index.js';
|
|
16
|
-
import { createDefaultWakeTransport } from './notifications.js';
|
|
17
18
|
import { deliverPending } from './delivery-operations.js';
|
|
18
19
|
import { participantIdentity } from './participant-identity.js';
|
|
20
|
+
import { joinRecoveryCommand } from './presentation.js';
|
|
19
21
|
import { acquireWatchLease, ownsWatchLease, pulseWatchLease, releaseWatchLease } from './wakes.js';
|
|
20
22
|
import { createArtifactPort, createSquare, diagnoseSquareFile, prepareSquareBuild } from './artifact.js';
|
|
21
23
|
import { migrateSquareFile } from './square-migration.js';
|
|
@@ -65,38 +67,28 @@ export class SqliteSquare {
|
|
|
65
67
|
location;
|
|
66
68
|
artifact;
|
|
67
69
|
clock;
|
|
68
|
-
body;
|
|
69
|
-
wakeAddress;
|
|
70
70
|
wakeTransport;
|
|
71
71
|
indexPath;
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
constructor(location, artifact, clock, body, wakeAddress, wakeTransport, indexPath, automaticDelivery = true, ownsArtifact = true, defaultParticipant) {
|
|
72
|
+
closesArtifact;
|
|
73
|
+
deliveryLocation;
|
|
74
|
+
constructor(location, artifact, clock, wakeTransport, indexPath, closesArtifact = true, deliveryLocation = location) {
|
|
76
75
|
this.location = location;
|
|
77
76
|
this.artifact = artifact;
|
|
78
77
|
this.clock = clock;
|
|
79
|
-
this.body = body;
|
|
80
|
-
this.wakeAddress = wakeAddress;
|
|
81
78
|
this.wakeTransport = wakeTransport;
|
|
82
79
|
this.indexPath = indexPath;
|
|
83
|
-
this.
|
|
84
|
-
this.
|
|
85
|
-
this.defaultParticipant = defaultParticipant;
|
|
80
|
+
this.closesArtifact = closesArtifact;
|
|
81
|
+
this.deliveryLocation = deliveryLocation;
|
|
86
82
|
}
|
|
87
|
-
static async at(input, control
|
|
83
|
+
static async at(input, control) {
|
|
88
84
|
const env = { ...(input.env ?? process.env) }, location = squareLocation({ ...input, env });
|
|
89
85
|
const clock = squareClock(env, input.clock);
|
|
90
|
-
const wakeTransport = input.wakeTransport ?? (automaticDelivery
|
|
91
|
-
? await createDefaultWakeTransport(env).catch(() => undefined) : undefined);
|
|
92
86
|
// A request can abort its own borrowed reads, never the owner's connection lifetime.
|
|
93
87
|
const borrowed = input.resources?.artifact(location);
|
|
94
88
|
const artifact = borrowed ?? createArtifactPort(location, undefined, control?.signal);
|
|
95
89
|
try {
|
|
96
90
|
await artifact.read(facts => facts.header(), control?.signal);
|
|
97
|
-
|
|
98
|
-
await resource.publishIdentity();
|
|
99
|
-
return resource;
|
|
91
|
+
return new SqliteSquare(location, artifact, clock, input.wakeTransport || undefined, squareIndexPath(env), borrowed === undefined, await canonicalPath(location));
|
|
100
92
|
}
|
|
101
93
|
catch (error) {
|
|
102
94
|
if (borrowed === undefined)
|
|
@@ -106,33 +98,33 @@ export class SqliteSquare {
|
|
|
106
98
|
throw error;
|
|
107
99
|
}
|
|
108
100
|
}
|
|
109
|
-
static async build(input, control
|
|
101
|
+
static async build(input, control) {
|
|
110
102
|
checkControl(control);
|
|
111
103
|
const env = { ...(input.env ?? process.env) }, location = squareLocation({ ...input, env });
|
|
112
104
|
await createSquare(location, { hardCap: input.hardCap, throttlePerMinute: input.throttlePerMinute,
|
|
113
105
|
template: input.template, force: input.force ?? false }, input.markdown, control?.signal);
|
|
114
106
|
// Publication already committed; cancellation cannot erase the completed build.
|
|
115
|
-
return SqliteSquare.at({ ...input, path: location, env }
|
|
107
|
+
return SqliteSquare.at({ ...input, path: location, env });
|
|
116
108
|
}
|
|
117
109
|
static inMemory(input) {
|
|
118
110
|
const env = { ...(input.env ?? process.env) };
|
|
119
|
-
return new SqliteSquare(
|
|
111
|
+
return new SqliteSquare(`memory://${crypto.randomUUID()}`, createArtifactPort(':memory:', prepareSquareBuild(input, input.markdown)), squareClock(env, input.clock), input.wakeTransport || undefined);
|
|
120
112
|
}
|
|
121
|
-
/** Discovery is best effort and never grants write authority.
|
|
122
|
-
async
|
|
123
|
-
if (this.
|
|
124
|
-
await rememberSquarePath(this.location,
|
|
113
|
+
/** Discovery is best effort and never grants write authority. */
|
|
114
|
+
async publishSeat(owner) {
|
|
115
|
+
if (this.indexPath !== undefined)
|
|
116
|
+
await rememberSquarePath(this.location, owner, this.indexPath).catch(() => undefined);
|
|
125
117
|
}
|
|
126
118
|
observation(standingCount, held, participant, now = this.clock()) {
|
|
127
119
|
return { location: this.location, standingCount, held, now,
|
|
128
|
-
...(participant === undefined ? {} : { participant })
|
|
120
|
+
...(participant === undefined ? {} : { participant }) };
|
|
129
121
|
}
|
|
130
122
|
async contextObservation(participant, control) {
|
|
131
123
|
return (await this.artifact.read(facts => this.observation(inSquareCount(facts), facts.hold().active, participant), control?.signal)).value;
|
|
132
124
|
}
|
|
133
|
-
handle(
|
|
134
|
-
// The nested handle
|
|
135
|
-
//
|
|
125
|
+
handle(seat) { return new SqliteSquare.ParticipantHandle(seat.participant, this, seat); }
|
|
126
|
+
// The nested handle uses its resource's private dependencies without copying
|
|
127
|
+
// them into a second context.
|
|
136
128
|
static ParticipantHandle = class {
|
|
137
129
|
name;
|
|
138
130
|
owner;
|
|
@@ -150,16 +142,12 @@ export class SqliteSquare {
|
|
|
150
142
|
const control = input === undefined ? operationControl : optionsOrControl;
|
|
151
143
|
let name = this.name, effect = 'unsent';
|
|
152
144
|
let landedId;
|
|
153
|
-
let
|
|
145
|
+
let heldWaitedMs = 0;
|
|
154
146
|
try {
|
|
155
147
|
checkControl(control);
|
|
156
148
|
name = (await square.artifact.read(facts => resolveKnownName(facts, this.name), control?.signal)).value;
|
|
157
|
-
const seat =
|
|
158
|
-
const
|
|
159
|
-
const pendingPublic = directedSuffix(facts, name);
|
|
160
|
-
return { pendingPublic, sayNumbers: facts.sayOrdinals(pendingPublic.map(item => item.index)),
|
|
161
|
-
perceptions: Object.fromEntries(pendingPublic.map(item => [item.index, 'full'])) };
|
|
162
|
-
}, control?.signal)).value;
|
|
149
|
+
const seat = this.seat;
|
|
150
|
+
const context = !options.observe ? undefined : (await square.artifact.read(facts => expressionContext(facts, name), control?.signal)).value;
|
|
163
151
|
while (true) {
|
|
164
152
|
checkControl(control);
|
|
165
153
|
try {
|
|
@@ -200,16 +188,13 @@ export class SqliteSquare {
|
|
|
200
188
|
}, control?.signal)).value;
|
|
201
189
|
landedId = formatActivityId(committed.stored.index);
|
|
202
190
|
effect = 'landed';
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
? await deliverPending({ artifact: square.artifact, transport: square.wakeTransport, location: square.location, activity: committed.stored.index, now })
|
|
206
|
-
.catch(() => ({ attempted: 0, accepted: 0, failed: 0, unknown: 0, notCapable: 1 }))
|
|
207
|
-
: { attempted: 0, accepted: 0, failed: 0, unknown: 0, notCapable: 1 };
|
|
191
|
+
if (square.wakeTransport !== undefined)
|
|
192
|
+
await deliverPending({ artifact: square.artifact, transport: square.wakeTransport, location: square.deliveryLocation, activity: committed.stored.index, now }).catch(() => undefined);
|
|
208
193
|
const { stored, standingCount, held, ownActivityCount, hardCap, ...metadata } = committed;
|
|
209
194
|
const hint = expressHint(ownActivityCount);
|
|
210
|
-
return { activity: toPublicActivity(stored),
|
|
211
|
-
...(hint === undefined ? {} : { hint }), ...(
|
|
212
|
-
...square.observation(standingCount, held, name, now), ownActivityCount, hardCap,
|
|
195
|
+
return { activity: toPublicActivity(stored), ...metadata,
|
|
196
|
+
...(hint === undefined ? {} : { hint }), ...(context === undefined ? {} : { observation: {
|
|
197
|
+
...square.observation(standingCount, held, name, now), ownActivityCount, hardCap, context, participant: name,
|
|
213
198
|
} }) };
|
|
214
199
|
}
|
|
215
200
|
catch (error) {
|
|
@@ -219,7 +204,6 @@ export class SqliteSquare {
|
|
|
219
204
|
throw error;
|
|
220
205
|
if (error.code === 'held' && heldWaitedMs >= HELD_WAIT_BUDGET_MS)
|
|
221
206
|
throw error;
|
|
222
|
-
waited = true;
|
|
223
207
|
control?.onProgress?.({ kind: 'waiting', reason: error.code,
|
|
224
208
|
...(error.code === 'throttled' ? { delayMs: error.facts?.retryAfterMs ?? 1 } : {}),
|
|
225
209
|
...(error.facts?.holder === undefined ? {} : { holder: error.facts.holder }),
|
|
@@ -246,13 +230,13 @@ export class SqliteSquare {
|
|
|
246
230
|
const square = this.owner;
|
|
247
231
|
const name = (await square.artifact.read(facts => resolveKnownName(facts, this.name), control?.signal)).value;
|
|
248
232
|
checkControl(control);
|
|
249
|
-
const seat =
|
|
233
|
+
const seat = this.seat, now = square.clock();
|
|
250
234
|
return (await square.artifact.transactAs(participantAuthority(seat), facts => {
|
|
251
235
|
const act = verb === 'listen' ? coreListen(facts, name, target, now) : verb === 'ignore' ? coreIgnore(facts, name, target, now) : coreUnlisten(facts, name, target, now);
|
|
252
236
|
const activity = act === undefined ? null : toPublicActivity(facts.land(act));
|
|
253
237
|
return { activity, ...(!options.observe ? {} : { observation: {
|
|
254
238
|
...square.observation(inSquareCount(facts), facts.hold().active, name, now), participant: name,
|
|
255
|
-
|
|
239
|
+
target: activity?.target ?? target,
|
|
256
240
|
} }) };
|
|
257
241
|
}, control?.signal)).value;
|
|
258
242
|
}
|
|
@@ -278,15 +262,14 @@ export class SqliteSquare {
|
|
|
278
262
|
const square = this.owner;
|
|
279
263
|
let name = this.name, subject = 'caller';
|
|
280
264
|
let leaseId, seat;
|
|
281
|
-
let waitStarted = false, replaced = false;
|
|
282
265
|
let selection = options;
|
|
283
266
|
const idle = options.idle ?? 0;
|
|
284
|
-
const complete = async (caught,
|
|
267
|
+
const complete = async (caught, reason = 'drained') => {
|
|
285
268
|
const view = caught ?? (await square.artifact.read(facts => observeCatchSelection(facts, name, [], {
|
|
286
269
|
unseenOnly: true, senders: selection.from, ...(selection.mention === true ? { attentionOnly: true } : {}),
|
|
287
270
|
}, square.clock()))).value;
|
|
288
|
-
if (
|
|
289
|
-
|
|
271
|
+
if (reason === 'drained')
|
|
272
|
+
reason = view.terminalStatus ?? reason;
|
|
290
273
|
if (leaseId !== undefined) {
|
|
291
274
|
await releaseWatchLease(square.artifact, seat, leaseId).catch(() => undefined);
|
|
292
275
|
leaseId = undefined;
|
|
@@ -298,13 +281,12 @@ export class SqliteSquare {
|
|
|
298
281
|
}
|
|
299
282
|
catch { /* Optional presence cannot erase the completed selection or receipts. */ }
|
|
300
283
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
consumedThrough: view.consumedThrough, remaining: view.remaining, idleExpired: stop === 'stale',
|
|
304
|
-
...(status === undefined ? {} : { status }), ...(view.hint === undefined ? {} : { hint: view.hint }), ...(!options.observe ? {} : { observation: {
|
|
284
|
+
return { activities: view.activities, remaining: view.remaining, reason,
|
|
285
|
+
...(view.hint === undefined ? {} : { hint: view.hint }), ...(!options.observe ? {} : { observation: {
|
|
305
286
|
...square.observation(view.standingCount, view.held, view.participant, view.now), participant: view.participant,
|
|
306
|
-
|
|
307
|
-
|
|
287
|
+
deliveryAvailable: view.deliveryAvailable,
|
|
288
|
+
ownActivityCount: view.ownActivityCount, hardCap: view.hardCap,
|
|
289
|
+
...(presence === undefined ? { enrichmentUnavailable: true } : { presence }),
|
|
308
290
|
} }) };
|
|
309
291
|
};
|
|
310
292
|
try {
|
|
@@ -319,7 +301,8 @@ export class SqliteSquare {
|
|
|
319
301
|
catchLimit(options.limit);
|
|
320
302
|
const probe = (await square.artifact.read(facts => {
|
|
321
303
|
name = resolveKnownName(facts, this.name);
|
|
322
|
-
seat =
|
|
304
|
+
seat = this.seat;
|
|
305
|
+
assertCurrentSeat(facts, asSeatRef(seat));
|
|
323
306
|
subject = 'filter';
|
|
324
307
|
const from = options.from?.map(target => resolveKnownName(facts, target));
|
|
325
308
|
selection = { ...options, ...(from === undefined ? {} : { from }) };
|
|
@@ -331,7 +314,7 @@ export class SqliteSquare {
|
|
|
331
314
|
}, control?.signal)).value;
|
|
332
315
|
if (idle === 0) {
|
|
333
316
|
const data = probe ?? (await this.catchPage(name, seat, selection, control));
|
|
334
|
-
return await complete(data
|
|
317
|
+
return await complete(data);
|
|
335
318
|
}
|
|
336
319
|
const watch = {
|
|
337
320
|
...(selection.from === undefined ? {} : { participants: [...selection.from] }), ...(options.mention ? { mention: name } : {}),
|
|
@@ -341,9 +324,7 @@ export class SqliteSquare {
|
|
|
341
324
|
if (start.type === 'active')
|
|
342
325
|
return await complete(undefined, 'already-active');
|
|
343
326
|
leaseId = start.leaseId;
|
|
344
|
-
|
|
345
|
-
replaced = start.replaced;
|
|
346
|
-
control?.onProgress?.({ kind: 'catch-started', participant: name, idleMs: idle, replacementRequested: options.replace ?? false, replaced });
|
|
327
|
+
control?.onProgress?.({ kind: 'catch-started', participant: name, idleMs: idle, replacementRequested: options.replace ?? false, replaced: start.replaced });
|
|
347
328
|
let quietSince = square.clock(), wasHeld = false, nextHeartbeat = start.heartbeatAt + WATCH_HEARTBEAT_MS;
|
|
348
329
|
while (true) {
|
|
349
330
|
checkControl(control);
|
|
@@ -368,7 +349,7 @@ export class SqliteSquare {
|
|
|
368
349
|
wasHeld = pulse.type === 'held';
|
|
369
350
|
if (!wasHeld && square.clock() - quietSince >= idle) {
|
|
370
351
|
const owns = await ownsWatchLease(square.artifact, seat, square.clock(), leaseId, control?.signal);
|
|
371
|
-
return await complete(undefined, owns ? '
|
|
352
|
+
return await complete(undefined, owns ? 'idle' : 'replaced');
|
|
372
353
|
}
|
|
373
354
|
const deadline = Math.min(nextHeartbeat, wasHeld ? Infinity : quietSince + idle);
|
|
374
355
|
await square.artifact.changed(baseline, Math.max(0, deadline - square.clock()), control?.signal);
|
|
@@ -386,7 +367,7 @@ export class SqliteSquare {
|
|
|
386
367
|
async deferDelivery(input, control) {
|
|
387
368
|
checkControl(control);
|
|
388
369
|
const match = validateMetadataMatch(input?.match), duration = validateDeferralDuration(input?.durationMs);
|
|
389
|
-
const square = this.owner, seat =
|
|
370
|
+
const square = this.owner, seat = this.seat;
|
|
390
371
|
return (await square.artifact.transactAs(participantAuthority(seat), facts => {
|
|
391
372
|
const now = square.clock(), expiresAt = now + duration, id = crypto.randomUUID();
|
|
392
373
|
const result = { id, expiresAt: new Date(expiresAt).toISOString() };
|
|
@@ -398,7 +379,7 @@ export class SqliteSquare {
|
|
|
398
379
|
checkControl(control);
|
|
399
380
|
validateDeferralId(id);
|
|
400
381
|
const duration = validateDeferralDuration(durationMs), square = this.owner;
|
|
401
|
-
const seat =
|
|
382
|
+
const seat = this.seat;
|
|
402
383
|
return (await square.artifact.transactAs(participantAuthority(seat), facts => {
|
|
403
384
|
const now = square.clock(), expiresAt = now + duration;
|
|
404
385
|
const result = { id, expiresAt: new Date(expiresAt).toISOString() };
|
|
@@ -408,55 +389,55 @@ export class SqliteSquare {
|
|
|
408
389
|
async releaseDeliveryDeferral(id, control) {
|
|
409
390
|
checkControl(control);
|
|
410
391
|
validateDeferralId(id);
|
|
411
|
-
const square = this.owner, seat =
|
|
392
|
+
const square = this.owner, seat = this.seat;
|
|
412
393
|
return (await square.artifact.transactAs(participantAuthority(seat), facts => facts.releaseDeliveryDeferral(asSeatRef(seat), id, square.clock()), control?.signal)).value;
|
|
413
394
|
}
|
|
414
|
-
history(query, control) { return this.owner.history(query, control); }
|
|
415
395
|
async lifecycle(verb, body, options, control) {
|
|
416
396
|
if (verb === 'done')
|
|
417
397
|
validateDoneBody(body);
|
|
418
398
|
const square = this.owner;
|
|
419
399
|
const name = (await square.artifact.read(facts => resolveKnownName(facts, this.name), control?.signal)).value;
|
|
420
|
-
const seat =
|
|
400
|
+
const seat = this.seat, now = square.clock();
|
|
421
401
|
return (await square.artifact.transactAs(participantAuthority(seat), facts => {
|
|
422
402
|
const stored = facts.land(verb === 'done' ? coreDone(facts, name, body ?? '', now) : verb === 'hold' ? coreHold(facts, name, body ?? '', now) : coreResume(facts, name, now));
|
|
423
|
-
return { activity: toPublicActivity(stored), ...(!options.observe ? {} : { observation:
|
|
424
|
-
|
|
425
|
-
} }) };
|
|
403
|
+
return { activity: toPublicActivity(stored), ...(!options.observe ? {} : { observation: square.observation(inSquareCount(facts), facts.hold().active, name, now),
|
|
404
|
+
}) };
|
|
426
405
|
}, control?.signal)).value;
|
|
427
406
|
}
|
|
428
407
|
hold(reason, options = {}, control) { return this.lifecycle('hold', reason, options, control); }
|
|
429
408
|
resume(options = {}, control) { return this.lifecycle('resume', undefined, options, control); }
|
|
430
409
|
done(body, options = {}, control) { return this.lifecycle('done', body, options, control); }
|
|
431
410
|
};
|
|
432
|
-
async participant(
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
const
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
411
|
+
async participant(name, { owner }) {
|
|
412
|
+
validateName(name);
|
|
413
|
+
requireOwner(owner);
|
|
414
|
+
return (await this.artifact.read(facts => {
|
|
415
|
+
const participant = facts.participant(name), seat = facts.seat(name);
|
|
416
|
+
if (!participant?.joined || seat === undefined || seat.owner !== owner || seat.attachment !== 'attached') {
|
|
417
|
+
const reason = participant === undefined ? 'has never stepped into this square'
|
|
418
|
+
: !participant.joined ? 'needs a current attached seat; join first'
|
|
419
|
+
: seat !== undefined && seat.owner !== owner ? 'is not held by this owner; join before writing'
|
|
420
|
+
: seat?.attachment === 'detached' ? 'is away — join again before writing'
|
|
421
|
+
: 'needs a current attached seat; join first';
|
|
422
|
+
throw new SquareError('already_joined', `${participantIdentity(participant?.name ?? name)} ${reason}`, {
|
|
423
|
+
reason: 'needs_seat', join: joinRecoveryCommand(this.location, participant?.name ?? name),
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
return this.handle(seat);
|
|
427
|
+
})).value;
|
|
447
428
|
}
|
|
448
|
-
async join(requested, options
|
|
429
|
+
async join(requested, options, control) {
|
|
449
430
|
let name = requested;
|
|
450
431
|
try {
|
|
451
432
|
checkControl(control);
|
|
452
433
|
name = requireJoinName(requested);
|
|
453
|
-
const last = options
|
|
434
|
+
const last = options?.last ?? HISTORY_DEFAULT_LIMIT;
|
|
454
435
|
if (!Number.isSafeInteger(last) || last < 1 || last > HISTORY_MAX_LIMIT)
|
|
455
436
|
throw new SquareError('invalid_args', 'Join history limit must be between 1 and 100');
|
|
456
|
-
const
|
|
437
|
+
const owner = requireOwner(options?.owner), wakeAddress = options.wakeAddress, now = this.clock();
|
|
457
438
|
const initial = !options.takeover ? undefined : (await this.artifact.read(facts => facts.participant(name)?.joined ? facts.seat(name) : undefined, control?.signal)).value;
|
|
458
|
-
const authority = initial === undefined ? { kind: 'join', participant: name,
|
|
459
|
-
: { kind: 'takeover', participant: initial.participant,
|
|
439
|
+
const authority = initial === undefined ? { kind: 'join', participant: name, owner, wakeAddress }
|
|
440
|
+
: { kind: 'takeover', participant: initial.participant, owner, expectedGeneration: initial.generation, wakeAddress };
|
|
460
441
|
const committed = (await this.artifact.transactAs(authority, facts => {
|
|
461
442
|
const participant = facts.participant(name)?.name ?? name;
|
|
462
443
|
const current = facts.participant(participant), currentSeat = facts.seat(participant);
|
|
@@ -468,10 +449,12 @@ export class SqliteSquare {
|
|
|
468
449
|
kind = 'taken-over';
|
|
469
450
|
}
|
|
470
451
|
else if (current?.joined) {
|
|
471
|
-
if (currentSeat === undefined ||
|
|
472
|
-
throw new SquareError('already_joined', `${participantIdentity(participant)} is held by another
|
|
473
|
-
if (
|
|
474
|
-
facts.
|
|
452
|
+
if (currentSeat === undefined || currentSeat.owner !== owner)
|
|
453
|
+
throw new SquareError('already_joined', `${participantIdentity(participant)} is held by another owner; take it over explicitly`);
|
|
454
|
+
if (currentSeat.attachment === 'detached')
|
|
455
|
+
facts.reattachSeat(asSeatRef(currentSeat), wakeAddress);
|
|
456
|
+
else if (wakeAddress !== undefined && canRefreshWakeAddress(currentSeat.wakeAddress, wakeAddress))
|
|
457
|
+
facts.updateWakeAddress(asSeatRef(currentSeat), wakeAddress);
|
|
475
458
|
kind = 'reconnected';
|
|
476
459
|
}
|
|
477
460
|
if (kind !== 'reconnected') {
|
|
@@ -483,11 +466,11 @@ export class SqliteSquare {
|
|
|
483
466
|
const seat = facts.seat(participant);
|
|
484
467
|
if (seat === undefined)
|
|
485
468
|
throw new SquareError('invalid_args', 'The join activity did not establish its artifact seat.');
|
|
486
|
-
return { participant: this.handle(seat
|
|
487
|
-
|
|
488
|
-
|
|
469
|
+
return { participant: this.handle(seat), activity, kind,
|
|
470
|
+
...(!options.observe ? {} : { observation: { ...this.observation(inSquareCount(facts), facts.hold().active, seat.participant, now),
|
|
471
|
+
deliveryAvailable: seat.wakeAddress !== undefined } }) };
|
|
489
472
|
}, control?.signal)).value;
|
|
490
|
-
await this.
|
|
473
|
+
await this.publishSeat(owner);
|
|
491
474
|
if (!options.observe)
|
|
492
475
|
return committed;
|
|
493
476
|
let entry;
|
|
@@ -501,30 +484,27 @@ export class SqliteSquare {
|
|
|
501
484
|
throw failure(error, { operation: 'join', subject: 'caller', participant: name });
|
|
502
485
|
}
|
|
503
486
|
}
|
|
504
|
-
|
|
487
|
+
/** Restore only a standing seat; a concurrent departure or takeover cannot be revived. */
|
|
488
|
+
async reattach(name, { owner, wakeAddress }, control) {
|
|
505
489
|
checkControl(control);
|
|
506
490
|
validateName(name);
|
|
507
|
-
|
|
508
|
-
const
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
else
|
|
518
|
-
facts.
|
|
519
|
-
|
|
520
|
-
if (seat === undefined)
|
|
521
|
-
throw new SquareError('invalid_args', 'The implicit join did not establish its artifact seat.');
|
|
522
|
-
}
|
|
523
|
-
return { state: decision.state, ...(decision.state === 'done' ? {} : { participant: this.handle(decision.joinedName, seat) }) };
|
|
491
|
+
requireOwner(owner);
|
|
492
|
+
const captured = (await this.artifact.read(facts => facts.seat(name), control?.signal)).value;
|
|
493
|
+
if (captured === undefined || captured.owner !== owner)
|
|
494
|
+
return null;
|
|
495
|
+
const seat = (await this.artifact.transactAs({ kind: 'join', participant: name, owner, wakeAddress }, facts => {
|
|
496
|
+
const current = facts.seat(name);
|
|
497
|
+
if (!facts.participant(name)?.joined || current === undefined || current.generation !== captured.generation || current.owner !== owner)
|
|
498
|
+
return null;
|
|
499
|
+
if (current.attachment === 'detached')
|
|
500
|
+
facts.reattachSeat(asSeatRef(current), wakeAddress);
|
|
501
|
+
else if (wakeAddress !== undefined && canRefreshWakeAddress(current.wakeAddress, wakeAddress))
|
|
502
|
+
facts.updateWakeAddress(asSeatRef(current), wakeAddress);
|
|
503
|
+
return facts.seat(name);
|
|
524
504
|
}, control?.signal)).value;
|
|
525
|
-
if (
|
|
526
|
-
await this.
|
|
527
|
-
return
|
|
505
|
+
if (seat !== null)
|
|
506
|
+
await this.publishSeat(owner);
|
|
507
|
+
return seat === null ? null : this.handle(seat);
|
|
528
508
|
}
|
|
529
509
|
async snapshot(control) { return (await this.artifact.read(facts => snapshot(facts, this.clock()), control?.signal)).value; }
|
|
530
510
|
async history(query = {}, control) {
|
|
@@ -560,73 +540,70 @@ export class SqliteSquare {
|
|
|
560
540
|
}, control?.signal)).value;
|
|
561
541
|
}
|
|
562
542
|
async status(options = {}, control) {
|
|
563
|
-
return (await this.artifact.read(facts => {
|
|
564
|
-
const seats = this.body === undefined ? [] : facts.seatsForBody(this.body);
|
|
565
|
-
const viewer = options.viewer ?? this.defaultParticipant ?? (seats.length === 1 ? seats[0].participant : '');
|
|
566
|
-
return queryStatus(facts, {
|
|
567
|
-
location: this.location, now: this.clock(), automaticDeliveryAvailable: this.automaticDelivery && this.body !== undefined,
|
|
568
|
-
}, viewer, options.observe === true);
|
|
569
|
-
}, control?.signal)).value;
|
|
543
|
+
return (await this.artifact.read(facts => queryStatus(facts, { location: this.location, now: this.clock() }, options.viewer, options.observe === true), control?.signal)).value;
|
|
570
544
|
}
|
|
571
545
|
async participants(options = {}, control) {
|
|
572
546
|
return (await this.artifact.read(facts => queryParticipants(facts, {
|
|
573
|
-
location: this.location, now: this.clock(),
|
|
547
|
+
location: this.location, now: this.clock(),
|
|
574
548
|
}, options.observe === true), control?.signal)).value;
|
|
575
549
|
}
|
|
576
550
|
async recovery(requested, control) {
|
|
577
|
-
const
|
|
578
|
-
|
|
579
|
-
return facts ?? await this.contextObservation(name, control);
|
|
551
|
+
const facts = requested === undefined ? undefined : (await this.artifact.read(facts => recoveryObservation(facts, requested, this.clock()), control?.signal).catch(() => undefined))?.value;
|
|
552
|
+
return facts ?? await this.contextObservation(requested, control);
|
|
580
553
|
}
|
|
581
554
|
stream(options, control) {
|
|
582
555
|
// A path stream follows replacements independently of this connection's inode
|
|
583
556
|
// and lifetime. Only explicit artifact readers borrow an existing port.
|
|
584
557
|
return streamSquare(this.location, options, control);
|
|
585
558
|
}
|
|
586
|
-
async
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
const candidates = (await this.artifact.read(facts => facts.seatsForBody(this.body))).value;
|
|
590
|
-
return candidates.map(seat => this.handle(seat.participant, seat));
|
|
559
|
+
async seatsOf(owner) {
|
|
560
|
+
requireOwner(owner);
|
|
561
|
+
return (await this.artifact.read(facts => facts.seatsForOwner(owner).map(({ participant: name, generation, attachment }) => ({ name, generation, attachment })))).value;
|
|
591
562
|
}
|
|
592
|
-
close() { return this.
|
|
593
|
-
async
|
|
594
|
-
|
|
595
|
-
if (body === undefined)
|
|
596
|
-
return false;
|
|
563
|
+
close() { return this.closesArtifact ? this.artifact.close() : Promise.resolve(); }
|
|
564
|
+
async heldSeat(name, { owner, generation }) {
|
|
565
|
+
requireOwner(owner);
|
|
597
566
|
const seat = (await this.artifact.read(facts => facts.seat(name))).value;
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
567
|
+
return seat?.generation === generation && seat.owner === owner ? seat : undefined;
|
|
568
|
+
}
|
|
569
|
+
async cancelDeliveries(name, identity) {
|
|
570
|
+
const seat = await this.heldSeat(name, identity);
|
|
571
|
+
return seat === undefined ? false : this.artifact.cancelDeliveries(asSeatRef(seat));
|
|
572
|
+
}
|
|
573
|
+
async detach(name, identity) {
|
|
574
|
+
const captured = await this.heldSeat(name, identity);
|
|
575
|
+
if (captured === undefined)
|
|
576
|
+
return null;
|
|
577
|
+
const seat = asSeatRef(captured);
|
|
578
|
+
return (await this.artifact.transactAs({ kind: 'host-cleanup', seat }, facts => facts.detachSeat(seat) ? true : null)).value;
|
|
601
579
|
}
|
|
602
|
-
async
|
|
603
|
-
const captured =
|
|
604
|
-
if (captured === undefined
|
|
605
|
-
|| (expectedGeneration !== undefined && captured.generation !== expectedGeneration))
|
|
580
|
+
async endHeld(name, identity) {
|
|
581
|
+
const captured = await this.heldSeat(name, identity);
|
|
582
|
+
if (captured === undefined)
|
|
606
583
|
return null;
|
|
607
|
-
return (await this.artifact.transactAs(
|
|
584
|
+
return (await this.artifact.transactAs({ kind: 'host-cleanup', seat: asSeatRef(captured) }, facts => {
|
|
608
585
|
const current = facts.seat(name);
|
|
609
|
-
if (current === undefined || current.generation !== captured.generation ||
|
|
586
|
+
if (current === undefined || current.generation !== captured.generation || current.owner !== captured.owner)
|
|
610
587
|
return null;
|
|
611
588
|
return { activity: toPublicActivity(facts.land(coreDone(facts, current.participant, '', this.clock()))) };
|
|
612
589
|
})).value;
|
|
613
590
|
}
|
|
614
591
|
}
|
|
615
|
-
export function diagnoseSquare(input, control) { checkControl(control); return diagnoseSquareFile(
|
|
616
|
-
export function migrateSquare(input, control) { checkControl(control); const env = input.env ?? process.env; return migrateSquareFile(
|
|
592
|
+
export function diagnoseSquare(input, control) { checkControl(control); return diagnoseSquareFile(squareLocation(input)); }
|
|
593
|
+
export function migrateSquare(input, control) { checkControl(control); const env = input.env ?? process.env; return migrateSquareFile(squareLocation(input), control?.signal, env); }
|
|
617
594
|
export function discover(input, options = {}, control) {
|
|
618
595
|
if (input.path !== undefined || (input.env ?? process.env).SQUARE_LOCATION)
|
|
619
|
-
|
|
596
|
+
squareLocation(input);
|
|
620
597
|
const env = { ...(input.env ?? process.env) };
|
|
621
598
|
return discoverSquares(input.cwd ?? process.cwd(), options, control, { clock: squareClock(env, input.clock) });
|
|
622
599
|
}
|
|
623
|
-
export async function inbox(input,
|
|
600
|
+
export async function inbox(input, owner, options = {}, control) {
|
|
624
601
|
checkControl(control);
|
|
602
|
+
requireOwner(owner);
|
|
625
603
|
const env = { ...(input.env ?? process.env) }, limit = options.limit ?? 20;
|
|
626
604
|
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100)
|
|
627
605
|
throw new SquareError('invalid_args', 'Inbox limit must be between 1 and 100');
|
|
628
|
-
const
|
|
629
|
-
const rows = await sessionInboxCounts(query, control?.signal);
|
|
606
|
+
const rows = await sessionInboxCounts({ owner, env }, control?.signal);
|
|
630
607
|
checkControl(control);
|
|
631
608
|
const compare = (left, right) => left < right ? -1 : left > right ? 1 : 0;
|
|
632
609
|
rows.sort((a, b) => compare(a.squarePath, b.squarePath) || compare(nameKey(a.name), nameKey(b.name)) || compare(a.name, b.name));
|
package/dist/square-api.d.ts
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import { SqliteSquare } from './sqlite-square.js';
|
|
2
|
+
import { type Owner } from './model.js';
|
|
3
|
+
import type { OperationControl, Participant, SquareAtInput, SquareBuildInput } from './square-facade.js';
|
|
4
|
+
export type Square = SqliteSquare;
|
|
5
|
+
export declare const Square: {
|
|
6
|
+
at: (input: SquareAtInput, control?: OperationControl) => Promise<SqliteSquare>;
|
|
7
|
+
build: (input: SquareBuildInput, control?: OperationControl) => Promise<SqliteSquare>;
|
|
8
|
+
inMemory: typeof SqliteSquare.inMemory;
|
|
9
|
+
};
|
|
10
|
+
export { requireOwner, requireJoinName } from './seats.js';
|
|
3
11
|
export type * from './square-facade.js';
|
|
4
12
|
export { squareLocation } from './square-location.js';
|
|
13
|
+
export declare function selectOwnSeat(square: Pick<SqliteSquare, 'participant' | 'seatsOf'>, owner: Owner | undefined, name?: string): Promise<Participant>;
|
|
14
|
+
export declare function selectOwnSeat(square: Pick<SqliteSquare, 'participant' | 'seatsOf'>, owner: Owner | undefined, name: string | undefined, options: {
|
|
15
|
+
readonly optional: true;
|
|
16
|
+
}): Promise<Participant | undefined>;
|
|
5
17
|
export { ACTIVITY_METADATA_MAX_BYTES } from './model.js';
|
|
18
|
+
export { localUserOwner, harnessOwner, decodeHarnessOwner } from './owner.js';
|
|
6
19
|
export { squareClock, createSquareResourceOwner, formatActivityId, parseActivityId, MAX_IDENTITY_SET_SIZE, CATCH_DEFAULT_LIMIT, CATCH_MAX_LIMIT, HELD_WAIT_BUDGET_MS, HISTORY_DEFAULT_LIMIT, HISTORY_MAX_LIMIT, DEFAULT_LIST_DEPTH, MAX_LIST_DEPTH, DEFAULT_LIST_LIMIT, MAX_LIST_LIMIT, diagnoseSquare, migrateSquare, discover, inbox } from './sqlite-square.js';
|
|
7
|
-
/** The same structural resource regardless of persistence or transport. */
|
|
8
|
-
export interface Square extends SquareBackend {
|
|
9
|
-
}
|
|
10
|
-
export declare const Square: {
|
|
11
|
-
at(input: SquareAtInput, control?: OperationControl): Promise<Square>;
|
|
12
|
-
build(input: SquareBuildInput, control?: OperationControl): Promise<Square>;
|
|
13
|
-
inMemory(input: Omit<SquareBuildInput, "path" | "resources">): Square;
|
|
14
|
-
};
|