@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/sqlite-square.js
CHANGED
|
@@ -1,54 +1,34 @@
|
|
|
1
|
-
import { formatActivityId
|
|
1
|
+
import { formatActivityId } from './square-core.js';
|
|
2
2
|
import { assertCurrentSeat } from './square-domain.js';
|
|
3
3
|
import { canonicalPath } from './canonical-path.js';
|
|
4
|
-
import { activityQueryFilter } from './activity-query.js';
|
|
5
|
-
import { validateMetadataMatch, validateDeferralDuration, validateDeferralId } from './delivery-deferral.js';
|
|
6
4
|
import { squareLocation } from './square-location.js';
|
|
7
5
|
import crypto from 'node:crypto';
|
|
8
6
|
import { setTimeout as sleep } from 'node:timers/promises';
|
|
9
|
-
import {
|
|
7
|
+
import { InternalSquareError, SquareError, isSquareError, nameKey, validateName } from './model.js';
|
|
8
|
+
import { checkControl, failure, squareClock } from './operation-control.js';
|
|
9
|
+
import { validateMetadataMatch } from './delivery-deferral.js';
|
|
10
|
+
import { flushSquarePosts, reconcileSquareDelivery, readSquareSessions, ensureInitialDeliverySession } from './square-posting.js';
|
|
10
11
|
import { attentionChange, coreDone, coreListening, decideAct, decideJoin, resolveKnownName, validateDoneBody } from './decisions.js';
|
|
11
12
|
import { WATCH_HEARTBEAT_MS, inSquareCount, presenceAnchor } from './runtime.js';
|
|
12
13
|
import { catchLimit, decideCatch, observeCatchSelection, selectCatchPage } from './catch-decisions.js';
|
|
13
14
|
import { expressionContext } from './activity-feed.js';
|
|
14
15
|
import { expressHint } from './hints.js';
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import { recoveryObservation, entryPresentation, queryStatus, queryParticipants, catchPresence, parseRequiredActivityId, toPublicActivity, toPerceivedActivity, publicActivities, selectHistory, NON_READ, HISTORY_DEFAULT_LIMIT, HISTORY_MAX_LIMIT } from './views.js';
|
|
16
|
+
import { asSeatRef, participantAuthority, requireJoinName, requireOwner } from './seats.js';
|
|
17
|
+
import { recoveryObservation, entryPresentation, queryStatus, queryParticipants, catchPresence, parseRequiredActivityId, toPublicActivity, toPerceivedActivity, publicActivities, selectHistory, NON_READ, HISTORY_MAX_LIMIT, HISTORY_DEFAULT_LIMIT } from './views.js';
|
|
18
18
|
import { sessionInboxCounts } from './inbox.js';
|
|
19
|
-
import { rememberSquarePath, squareIndexPath } from './square-index.js';
|
|
20
|
-
import { deliverPending } from './delivery-operations.js';
|
|
21
19
|
import { participantIdentity } from './participant-identity.js';
|
|
22
20
|
import { joinRecoveryCommand } from './presentation.js';
|
|
23
21
|
import { acquireWatchLease, ownsWatchLease, pulseWatchLease, releaseWatchLease } from './wakes.js';
|
|
24
22
|
import { createArtifactPort, createSquare, diagnoseSquareFile, prepareSquareBuild } from './artifact.js';
|
|
25
|
-
import {
|
|
23
|
+
import { activityQueryFilter } from './activity-query.js';
|
|
26
24
|
import { discoverSquares } from './list.js';
|
|
27
25
|
import { streamSquare } from './stream.js';
|
|
28
26
|
export { createSquareResourceOwner } from './artifact.js';
|
|
27
|
+
export { squareClock } from './operation-control.js';
|
|
29
28
|
export { formatActivityId, parseActivityId, MAX_IDENTITY_SET_SIZE } from './square-core.js';
|
|
30
29
|
export { CATCH_DEFAULT_LIMIT, CATCH_MAX_LIMIT } from './catch-decisions.js';
|
|
31
30
|
export { HISTORY_DEFAULT_LIMIT, HISTORY_MAX_LIMIT } from './views.js';
|
|
32
31
|
export { DEFAULT_LIST_DEPTH, MAX_LIST_DEPTH, DEFAULT_LIST_LIMIT, MAX_LIST_LIMIT } from './list.js';
|
|
33
|
-
function checkControl(control) {
|
|
34
|
-
if (control?.signal?.aborted)
|
|
35
|
-
throw control.signal.reason ?? new Error('Square operation was cancelled');
|
|
36
|
-
}
|
|
37
|
-
export function squareClock(env, clock) {
|
|
38
|
-
if (clock !== undefined)
|
|
39
|
-
return clock;
|
|
40
|
-
if (env.SQUARE_NOW_MS === undefined)
|
|
41
|
-
return Date.now;
|
|
42
|
-
const value = Number.parseInt(env.SQUARE_NOW_MS, 10);
|
|
43
|
-
if (!Number.isFinite(value))
|
|
44
|
-
throw new SquareError('invalid_args', 'Invalid SQUARE_NOW_MS: expected integer milliseconds.');
|
|
45
|
-
return () => value;
|
|
46
|
-
}
|
|
47
|
-
function failure(error, detail) {
|
|
48
|
-
const normalized = error instanceof Error ? error : new Error(String(error), { cause: error });
|
|
49
|
-
Object.assign(normalized, { operation: detail });
|
|
50
|
-
return normalized;
|
|
51
|
-
}
|
|
52
32
|
function rejectUnsentActivity(operation) {
|
|
53
33
|
try {
|
|
54
34
|
return operation();
|
|
@@ -59,28 +39,25 @@ function rejectUnsentActivity(operation) {
|
|
|
59
39
|
throw error;
|
|
60
40
|
}
|
|
61
41
|
}
|
|
62
|
-
/** A generic managed address must not displace the receiver's native registration on reconnect. */
|
|
63
|
-
function canRefreshWakeAddress(current, proposed) {
|
|
64
|
-
return proposed.kind !== 'paseo' || current === undefined || current.kind === 'paseo';
|
|
65
|
-
}
|
|
66
42
|
/** One concrete local resource, including its entry-resolved dependencies and lifetime. */
|
|
67
43
|
export class SqliteSquare {
|
|
68
44
|
location;
|
|
69
45
|
artifact;
|
|
70
46
|
clock;
|
|
71
|
-
|
|
72
|
-
indexPath;
|
|
47
|
+
env;
|
|
73
48
|
closesArtifact;
|
|
74
49
|
deliveryLocation;
|
|
75
|
-
constructor(location, artifact, clock,
|
|
50
|
+
constructor(location, artifact, clock, env, closesArtifact = true, deliveryLocation = location) {
|
|
76
51
|
this.location = location;
|
|
77
52
|
this.artifact = artifact;
|
|
78
53
|
this.clock = clock;
|
|
79
|
-
this.
|
|
80
|
-
this.indexPath = indexPath;
|
|
54
|
+
this.env = env;
|
|
81
55
|
this.closesArtifact = closesArtifact;
|
|
82
56
|
this.deliveryLocation = deliveryLocation;
|
|
83
57
|
}
|
|
58
|
+
reconcile() { return reconcileSquareDelivery(this.artifact, this.deliveryLocation, this.env, this.clock); }
|
|
59
|
+
// Posting is retryable after commit; a delivery failure cannot make a landed write unsent.
|
|
60
|
+
async flush() { await flushSquarePosts(this.artifact, this.deliveryLocation, this.env, this.clock).catch(() => undefined); }
|
|
84
61
|
static async at(input, control) {
|
|
85
62
|
const env = { ...(input.env ?? process.env) }, location = squareLocation({ ...input, env });
|
|
86
63
|
const clock = squareClock(env, input.clock);
|
|
@@ -89,7 +66,7 @@ export class SqliteSquare {
|
|
|
89
66
|
const artifact = borrowed ?? createArtifactPort(location, undefined, control?.signal);
|
|
90
67
|
try {
|
|
91
68
|
await artifact.read(facts => facts.header(), control?.signal);
|
|
92
|
-
return new SqliteSquare(location, artifact, clock,
|
|
69
|
+
return new SqliteSquare(location, artifact, clock, env, borrowed === undefined, await canonicalPath(location));
|
|
93
70
|
}
|
|
94
71
|
catch (error) {
|
|
95
72
|
if (borrowed === undefined)
|
|
@@ -109,12 +86,64 @@ export class SqliteSquare {
|
|
|
109
86
|
}
|
|
110
87
|
static inMemory(input) {
|
|
111
88
|
const env = { ...(input.env ?? process.env) };
|
|
112
|
-
return new SqliteSquare(`memory://${crypto.randomUUID()}`, createArtifactPort(':memory:', prepareSquareBuild(input, input.markdown)), squareClock(env, input.clock),
|
|
89
|
+
return new SqliteSquare(`memory://${crypto.randomUUID()}`, createArtifactPort(':memory:', prepareSquareBuild(input, input.markdown)), squareClock(env, input.clock), env);
|
|
113
90
|
}
|
|
114
|
-
/**
|
|
115
|
-
async
|
|
116
|
-
|
|
117
|
-
|
|
91
|
+
/** Join establishes membership and local producer custody in one transaction. */
|
|
92
|
+
async joinSeat(name, options, control) {
|
|
93
|
+
checkControl(control);
|
|
94
|
+
const owner = requireOwner(options?.owner), now = this.clock();
|
|
95
|
+
const initial = !options.takeover ? undefined : (await this.artifact.read(facts => facts.participant(name)?.joined ? facts.seat(name) : undefined, control?.signal)).value;
|
|
96
|
+
const authority = initial === undefined ? { kind: 'join', participant: name, owner }
|
|
97
|
+
: { kind: 'takeover', participant: initial.participant, owner, expectedGeneration: initial.generation };
|
|
98
|
+
const committed = (await this.artifact.transactAs(authority, facts => {
|
|
99
|
+
const participant = facts.participant(name)?.name ?? name;
|
|
100
|
+
const current = facts.participant(participant), currentSeat = facts.seat(participant);
|
|
101
|
+
let kind = 'joined', activity = null;
|
|
102
|
+
if (initial !== undefined) {
|
|
103
|
+
if (!current?.joined)
|
|
104
|
+
throw new SquareError('already_done', `${participantIdentity(participant)} is no longer standing here`);
|
|
105
|
+
facts.land(coreDone(facts, participant, '', now));
|
|
106
|
+
kind = 'taken-over';
|
|
107
|
+
}
|
|
108
|
+
else if (current?.joined) {
|
|
109
|
+
if (currentSeat === undefined || currentSeat.owner !== owner)
|
|
110
|
+
throw new SquareError('already_joined', `${participantIdentity(participant)} is held by another owner; take it over explicitly`);
|
|
111
|
+
if (currentSeat.attachment !== 'attached')
|
|
112
|
+
throw new SquareError('already_joined', `${participantIdentity(participant)} is away; resume the host session first`);
|
|
113
|
+
kind = 'reconnected';
|
|
114
|
+
}
|
|
115
|
+
if (kind !== 'reconnected') {
|
|
116
|
+
const joinAct = decideJoin(facts, participant, now);
|
|
117
|
+
if (joinAct === undefined)
|
|
118
|
+
throw new SquareError('already_joined', `${participantIdentity(participant)} could not be joined`);
|
|
119
|
+
activity = toPublicActivity(facts.land(joinAct));
|
|
120
|
+
}
|
|
121
|
+
const seat = facts.seat(participant);
|
|
122
|
+
if (seat === undefined)
|
|
123
|
+
throw new SquareError('invalid_args', 'The join activity did not establish participant custody.');
|
|
124
|
+
return { seat, activity, kind,
|
|
125
|
+
...(!options.observe ? {} : { observation: { ...this.observation(inSquareCount(facts), seat.participant, now),
|
|
126
|
+
deliveryAvailable: false } }) };
|
|
127
|
+
}, control?.signal)).value;
|
|
128
|
+
return committed;
|
|
129
|
+
}
|
|
130
|
+
async requiredSeat(name, { owner }) {
|
|
131
|
+
validateName(name);
|
|
132
|
+
requireOwner(owner);
|
|
133
|
+
return (await this.artifact.read(facts => {
|
|
134
|
+
const participant = facts.participant(name), seat = facts.seat(name);
|
|
135
|
+
if (!participant?.joined || seat === undefined || seat.owner !== owner || seat.attachment !== 'attached') {
|
|
136
|
+
const reason = participant === undefined ? 'has never stepped into this square'
|
|
137
|
+
: !participant.joined ? 'needs to join first'
|
|
138
|
+
: seat !== undefined && seat.owner !== owner ? 'is not held by this owner; join before writing'
|
|
139
|
+
: seat?.attachment === 'detached' ? 'is away; resume the host session first'
|
|
140
|
+
: 'needs a current participant seat; join first';
|
|
141
|
+
throw new SquareError('already_joined', `${participantIdentity(participant?.name ?? name)} ${reason}`, {
|
|
142
|
+
reason: 'needs_seat', join: joinRecoveryCommand(this.location, participant?.name ?? name),
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
return seat;
|
|
146
|
+
})).value;
|
|
118
147
|
}
|
|
119
148
|
observation(standingCount, participant, now = this.clock()) {
|
|
120
149
|
return { location: this.location, standingCount, now,
|
|
@@ -123,6 +152,28 @@ export class SqliteSquare {
|
|
|
123
152
|
async contextObservation(participant, control) {
|
|
124
153
|
return (await this.artifact.read(facts => this.observation(inSquareCount(facts), participant), control?.signal)).value;
|
|
125
154
|
}
|
|
155
|
+
/** Host liveness is a read-only, non-atomic overlay; it never changes membership. */
|
|
156
|
+
async withHostPresence(select, control) {
|
|
157
|
+
const seats = (await this.artifact.read(facts => facts.roster().filter(person => person.joined)
|
|
158
|
+
.flatMap(person => { const seat = facts.seat(person.name); return seat === undefined ? [] : [seat]; }), control?.signal)).value;
|
|
159
|
+
const sessions = await readSquareSessions(seats, this.env);
|
|
160
|
+
const captured = new Map(seats.map(seat => [nameKey(seat.participant), seat]));
|
|
161
|
+
return (await this.artifact.read(facts => {
|
|
162
|
+
const seat = (name) => {
|
|
163
|
+
const current = facts.seat(name);
|
|
164
|
+
if (current === undefined)
|
|
165
|
+
return undefined;
|
|
166
|
+
const before = captured.get(nameKey(current.participant));
|
|
167
|
+
const attached = before?.owner === current.owner && before.generation === current.generation
|
|
168
|
+
&& sessions.get(current.owner)?.state === 'attached';
|
|
169
|
+
return { ...current, attachment: attached ? 'attached' : 'detached' };
|
|
170
|
+
};
|
|
171
|
+
return select(Object.assign({}, facts, { seat }), name => {
|
|
172
|
+
const current = seat(name);
|
|
173
|
+
return current?.attachment === 'attached' && (sessions.get(current.owner)?.routes.length ?? 0) > 0;
|
|
174
|
+
});
|
|
175
|
+
}, control?.signal)).value;
|
|
176
|
+
}
|
|
126
177
|
handle(seat) { return new SqliteSquare.ParticipantHandle(seat.participant, this, seat); }
|
|
127
178
|
// The nested handle uses its resource's private dependencies without copying
|
|
128
179
|
// them into a second context.
|
|
@@ -146,12 +197,14 @@ export class SqliteSquare {
|
|
|
146
197
|
let landedId;
|
|
147
198
|
try {
|
|
148
199
|
checkControl(control);
|
|
200
|
+
await square.reconcile();
|
|
149
201
|
name = (await square.artifact.read(facts => resolveKnownName(facts, this.name), control?.signal)).value;
|
|
150
202
|
const seat = this.seat;
|
|
151
203
|
const context = !options.observe ? undefined : (await square.artifact.read(facts => expressionContext(facts, name), control?.signal)).value;
|
|
152
204
|
while (true) {
|
|
153
205
|
checkControl(control);
|
|
154
206
|
try {
|
|
207
|
+
await square.reconcile();
|
|
155
208
|
effect = 'unknown';
|
|
156
209
|
const now = square.clock();
|
|
157
210
|
const reply = rejectUnsentActivity(() => {
|
|
@@ -179,15 +232,15 @@ export class SqliteSquare {
|
|
|
179
232
|
throw new SquareError('bell_quota', `${participantIdentity(name)} cannot ring the bell yet`, { retryAfterMs: Math.max(1, proposed.nextAt - now) });
|
|
180
233
|
return proposed;
|
|
181
234
|
});
|
|
182
|
-
|
|
235
|
+
const stored = facts.land(decision.act);
|
|
236
|
+
return { stored, standingCount: inSquareCount(facts),
|
|
183
237
|
ownActivityCount: facts.participant(name).sayCount, hardCap: facts.header().hardCap,
|
|
184
238
|
...(decision.unmatchedMentions === undefined ? {} : { unmatchedMentions: decision.unmatchedMentions }),
|
|
185
239
|
...(decision.replyAuthorNotHere === undefined ? {} : { replyAuthorNotHere: decision.replyAuthorNotHere }) };
|
|
186
240
|
}, control?.signal)).value;
|
|
187
241
|
landedId = formatActivityId(committed.stored.index);
|
|
188
242
|
effect = 'landed';
|
|
189
|
-
|
|
190
|
-
await deliverPending({ artifact: square.artifact, transport: square.wakeTransport, location: square.deliveryLocation, activity: committed.stored.index, now }).catch(() => undefined);
|
|
243
|
+
await square.flush();
|
|
191
244
|
const { stored, standingCount, ownActivityCount, hardCap, ...metadata } = committed;
|
|
192
245
|
const hint = expressHint(ownActivityCount);
|
|
193
246
|
return { activity: toPublicActivity(stored), ...metadata,
|
|
@@ -213,10 +266,11 @@ export class SqliteSquare {
|
|
|
213
266
|
}
|
|
214
267
|
async listener(verb, target, options, control) {
|
|
215
268
|
const square = this.owner;
|
|
216
|
-
const name = (await square.artifact.read(facts => resolveKnownName(facts, this.name), control?.signal)).value;
|
|
217
269
|
checkControl(control);
|
|
270
|
+
await square.reconcile();
|
|
271
|
+
const name = (await square.artifact.read(facts => resolveKnownName(facts, this.name), control?.signal)).value;
|
|
218
272
|
const seat = this.seat, now = square.clock();
|
|
219
|
-
|
|
273
|
+
const result = (await square.artifact.transactAs(participantAuthority(seat), facts => {
|
|
220
274
|
const act = attentionChange(facts, verb, name, target, now);
|
|
221
275
|
const activity = act === undefined ? null : toPublicActivity(facts.land(act));
|
|
222
276
|
return { activity, ...(!options.observe ? {} : { observation: {
|
|
@@ -224,6 +278,8 @@ export class SqliteSquare {
|
|
|
224
278
|
target: activity?.target ?? target,
|
|
225
279
|
} }) };
|
|
226
280
|
}, control?.signal)).value;
|
|
281
|
+
await square.flush();
|
|
282
|
+
return result;
|
|
227
283
|
}
|
|
228
284
|
listen(target, options = {}, control) { return this.listener('listen', target, options, control); }
|
|
229
285
|
ignore(target, options = {}, control) { return this.listener('ignore', target, options, control); }
|
|
@@ -237,6 +293,7 @@ export class SqliteSquare {
|
|
|
237
293
|
}
|
|
238
294
|
/** Receipt selection rechecks the lease inside the writer, not at its last heartbeat. */
|
|
239
295
|
async catchPage(name, seat, options, control, leaseId) {
|
|
296
|
+
await this.owner.reconcile();
|
|
240
297
|
return (await this.owner.artifact.transactAs(participantAuthority(seat), facts => {
|
|
241
298
|
if (leaseId !== undefined && facts.lease(name)?.leaseId !== leaseId)
|
|
242
299
|
return null;
|
|
@@ -255,27 +312,35 @@ export class SqliteSquare {
|
|
|
255
312
|
}, square.clock()))).value;
|
|
256
313
|
if (reason === 'drained')
|
|
257
314
|
reason = view.terminalStatus ?? reason;
|
|
315
|
+
await square.flush();
|
|
258
316
|
if (leaseId !== undefined) {
|
|
259
317
|
await releaseWatchLease(square.artifact, seat, leaseId).catch(() => undefined);
|
|
260
318
|
leaseId = undefined;
|
|
261
319
|
}
|
|
262
|
-
let presence;
|
|
320
|
+
let presence, deliveryAvailable = false;
|
|
263
321
|
if (options.observe) {
|
|
264
322
|
try {
|
|
265
|
-
|
|
323
|
+
const observed = await square.withHostPresence((facts, available) => {
|
|
324
|
+
const data = catchPresence(facts, square.clock());
|
|
325
|
+
return { presence: { ...data, participants: data.participants.map(person => ({ ...person, deliveryAvailable: available(person.name) })) },
|
|
326
|
+
deliveryAvailable: available(view.participant) };
|
|
327
|
+
}, control);
|
|
328
|
+
presence = observed.presence;
|
|
329
|
+
deliveryAvailable = observed.deliveryAvailable;
|
|
266
330
|
}
|
|
267
331
|
catch { /* Optional presence cannot erase the completed selection or receipts. */ }
|
|
268
332
|
}
|
|
269
333
|
return { activities: view.activities, remaining: view.remaining, reason,
|
|
270
334
|
...(view.hint === undefined ? {} : { hint: view.hint }), ...(!options.observe ? {} : { observation: {
|
|
271
335
|
...square.observation(view.standingCount, view.participant, view.now), participant: view.participant,
|
|
272
|
-
deliveryAvailable
|
|
336
|
+
deliveryAvailable,
|
|
273
337
|
ownActivityCount: view.ownActivityCount, hardCap: view.hardCap,
|
|
274
338
|
...(presence === undefined ? { enrichmentUnavailable: true } : { presence }),
|
|
275
339
|
} }) };
|
|
276
340
|
};
|
|
277
341
|
try {
|
|
278
342
|
checkControl(control);
|
|
343
|
+
await square.reconcile();
|
|
279
344
|
validateName(name);
|
|
280
345
|
if (!Number.isFinite(idle) || idle < 0)
|
|
281
346
|
throw new SquareError('invalid_args', 'Catch idle duration must be a non-negative number');
|
|
@@ -350,219 +415,107 @@ export class SqliteSquare {
|
|
|
350
415
|
const limit = options.limit === undefined ? 100 : options.limit;
|
|
351
416
|
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100)
|
|
352
417
|
throw new SquareError('invalid_args', 'Pending limit must be a positive integer no greater than 100.');
|
|
353
|
-
|
|
354
|
-
|
|
418
|
+
// Pending follows the shared archive cursor: act/N with before/after.
|
|
419
|
+
const shared = activityQueryFilter(options);
|
|
420
|
+
const filter = { kinds: ['say'], unseenOnly: true,
|
|
421
|
+
...(shared.indexes === undefined ? {} : { indexes: shared.indexes }),
|
|
422
|
+
...(shared.actors === undefined ? {} : { senders: shared.actors }),
|
|
423
|
+
...(shared.metadataMatch === undefined ? {} : { metadataMatch: shared.metadataMatch }),
|
|
424
|
+
...(shared.search === undefined ? {} : { search: shared.search }),
|
|
425
|
+
...(shared.afterIndex === undefined ? {} : { afterIndex: shared.afterIndex }),
|
|
426
|
+
...(shared.beforeIndex === undefined ? {} : { beforeIndex: shared.beforeIndex }) };
|
|
427
|
+
return (await this.owner.artifact.read(facts => {
|
|
355
428
|
assertCurrentSeat(facts, asSeatRef(this.seat));
|
|
356
|
-
const filter = { ...query, senders: actors, unseenOnly: true };
|
|
357
429
|
const heads = facts.directed(this.name, filter, { edge: 'oldest', limit });
|
|
358
|
-
|
|
359
|
-
.
|
|
360
|
-
return { activities, remaining: facts.directedCount(this.name, filter) - activities.length };
|
|
430
|
+
return { activities: facts.activities(heads.items.map(head => head.index)).map(activity => toPerceivedActivity(facts, activity, this.name)),
|
|
431
|
+
remaining: facts.directedCount(this.name, filter) - heads.items.length };
|
|
361
432
|
}, control?.signal)).value;
|
|
362
433
|
}
|
|
363
|
-
|
|
434
|
+
ack(input, control) {
|
|
435
|
+
const square = this.owner;
|
|
364
436
|
checkControl(control);
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
437
|
+
return square.artifact.transactAs(participantAuthority(this.seat), facts => {
|
|
438
|
+
if (!Array.isArray(input)) {
|
|
439
|
+
const exact = input;
|
|
440
|
+
if (exact?.future !== 'exact')
|
|
441
|
+
throw new SquareError('invalid_args', 'Ack requires an exact future predicate.');
|
|
442
|
+
validateName(exact.from);
|
|
443
|
+
return { consumed: facts.ackMatching(asSeatRef(this.seat), { from: exact.from, match: validateMetadataMatch(exact.match), future: 'exact' }, square.clock()) };
|
|
444
|
+
}
|
|
445
|
+
const indexes = [];
|
|
446
|
+
for (const id of input) {
|
|
447
|
+
const activity = facts.activity(parseRequiredActivityId(id));
|
|
448
|
+
if (activity?.kind !== 'say')
|
|
449
|
+
throw new SquareError('invalid_args', `${id} is not addressed to you.`, { reason: 'not_addressed', activityId: id });
|
|
450
|
+
const head = facts.directed(this.name, { indexes: [activity.index], kinds: ['say'] }, { edge: 'oldest', limit: 1 }).items[0];
|
|
451
|
+
if (head === undefined)
|
|
452
|
+
throw new SquareError('invalid_args', `${id} is not addressed to you.`, { reason: 'not_addressed', activityId: id });
|
|
453
|
+
indexes.push(activity.index);
|
|
374
454
|
}
|
|
375
455
|
return { consumed: facts.markSeen(this.name, indexes, square.clock()) };
|
|
376
|
-
}, control?.signal)
|
|
377
|
-
}
|
|
378
|
-
async deferDelivery(input, control) {
|
|
379
|
-
checkControl(control);
|
|
380
|
-
const match = validateMetadataMatch(input?.match), duration = validateDeferralDuration(input?.durationMs);
|
|
381
|
-
const square = this.owner, seat = this.seat;
|
|
382
|
-
return (await square.artifact.transactAs(participantAuthority(seat), facts => {
|
|
383
|
-
const now = square.clock(), expiresAt = now + duration, id = crypto.randomUUID();
|
|
384
|
-
const result = { id, expiresAt: new Date(expiresAt).toISOString() };
|
|
385
|
-
facts.putDeliveryDeferral(asSeatRef(seat), id, match, expiresAt, now);
|
|
386
|
-
return result;
|
|
387
|
-
}, control?.signal)).value;
|
|
388
|
-
}
|
|
389
|
-
async renewDeliveryDeferral(id, durationMs, control) {
|
|
390
|
-
checkControl(control);
|
|
391
|
-
validateDeferralId(id);
|
|
392
|
-
const duration = validateDeferralDuration(durationMs), square = this.owner;
|
|
393
|
-
const seat = this.seat;
|
|
394
|
-
return (await square.artifact.transactAs(participantAuthority(seat), facts => {
|
|
395
|
-
const now = square.clock(), expiresAt = now + duration;
|
|
396
|
-
const result = { id, expiresAt: new Date(expiresAt).toISOString() };
|
|
397
|
-
return facts.renewDeliveryDeferral(asSeatRef(seat), id, expiresAt, now) ? result : null;
|
|
398
|
-
}, control?.signal)).value;
|
|
399
|
-
}
|
|
400
|
-
async releaseDeliveryDeferral(id, control) {
|
|
401
|
-
checkControl(control);
|
|
402
|
-
validateDeferralId(id);
|
|
403
|
-
const square = this.owner, seat = this.seat;
|
|
404
|
-
return (await square.artifact.transactAs(participantAuthority(seat), facts => facts.releaseDeliveryDeferral(asSeatRef(seat), id, square.clock()), control?.signal)).value;
|
|
456
|
+
}, control?.signal).then(result => result.value);
|
|
405
457
|
}
|
|
406
458
|
async done(body, options = {}, control) {
|
|
407
459
|
validateDoneBody(body);
|
|
460
|
+
checkControl(control);
|
|
408
461
|
const square = this.owner;
|
|
462
|
+
await square.reconcile();
|
|
409
463
|
const name = (await square.artifact.read(facts => resolveKnownName(facts, this.name), control?.signal)).value;
|
|
410
464
|
const seat = this.seat, now = square.clock();
|
|
411
|
-
|
|
465
|
+
const result = (await square.artifact.transactAs(participantAuthority(seat), facts => {
|
|
412
466
|
const stored = facts.land(coreDone(facts, name, body ?? '', now));
|
|
413
467
|
return { activity: toPublicActivity(stored), ...(!options.observe ? {} : { observation: square.observation(inSquareCount(facts), name, now),
|
|
414
468
|
}) };
|
|
415
469
|
}, control?.signal)).value;
|
|
470
|
+
await square.flush();
|
|
471
|
+
return result;
|
|
416
472
|
}
|
|
417
473
|
};
|
|
418
|
-
/** Direct messages use explicit recipients, not participant expression admission or body attention. */
|
|
419
474
|
async send(input, control) {
|
|
420
475
|
checkControl(control);
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
const targets = typeof to === 'string' ? [to] : to;
|
|
426
|
-
if (!Array.isArray(targets) || targets.length < 1 || targets.length > MAX_IDENTITY_SET_SIZE)
|
|
427
|
-
throw new SquareError('invalid_args', `Channel needs between 1 and ${MAX_IDENTITY_SET_SIZE} recipients.`);
|
|
428
|
-
const unique = [];
|
|
429
|
-
for (const target of targets) {
|
|
430
|
-
if (typeof target !== 'string')
|
|
431
|
-
throw new SquareError('invalid_args', 'Channel recipients must be names.');
|
|
432
|
-
validateName(target);
|
|
433
|
-
if (!unique.some(name => sameName(name, target)))
|
|
434
|
-
unique.push(target);
|
|
435
|
-
}
|
|
436
|
-
if (typeof body !== 'string' || body.trim() === '')
|
|
437
|
-
throw new SquareError('invalid_args', 'Channel body must be non-empty.');
|
|
438
|
-
if (truncate !== false && (!Number.isSafeInteger(truncate) || truncate < 1))
|
|
439
|
-
throw new SquareError('invalid_args', 'Channel truncate must be false or a positive integer.');
|
|
440
|
-
const fullBody = body.replace(/\r\n/g, '\n');
|
|
441
|
-
const preview = truncate !== false && fullBody.length > truncate
|
|
442
|
-
? `${fullBody.slice(0, truncate).trimEnd()}… (truncated ${fullBody.length - truncate} chars)`
|
|
443
|
-
: undefined;
|
|
444
|
-
const delivery = preview === undefined ? { mode: 'direct' } : { mode: 'direct', preview };
|
|
445
|
-
const metadata = validateActivityMetadata(input.metadata);
|
|
446
|
-
const { stored, resolvedTo } = (await this.artifact.transactAs({ kind: 'send', actor: from }, facts => {
|
|
447
|
-
const standing = facts.roster().filter(p => p.joined), resolvedTo = [], missing = [];
|
|
448
|
-
for (const target of unique) {
|
|
449
|
-
const recipient = standing.find(p => sameName(p.name, target));
|
|
450
|
-
if (recipient === undefined)
|
|
451
|
-
missing.push(target);
|
|
452
|
-
else
|
|
453
|
-
resolvedTo.push(recipient.name);
|
|
454
|
-
}
|
|
455
|
-
if (missing.length > 0)
|
|
456
|
-
throw new SquareError('invalid_args', `Channel recipients are not standing: ${missing.join(', ')}`, {
|
|
457
|
-
reason: 'not_standing', targets: missing, activityUnsent: true,
|
|
458
|
-
});
|
|
459
|
-
return { stored: facts.land({ kind: 'say', actor: from, at: this.clock(), body: fullBody,
|
|
460
|
-
...(activityMetadataHasContent(metadata) ? { metadata } : {}), delivery, mentions: resolvedTo }), resolvedTo };
|
|
461
|
-
}, control?.signal)).value;
|
|
462
|
-
if (this.wakeTransport !== undefined)
|
|
463
|
-
await deliverPending({ artifact: this.artifact, transport: this.wakeTransport, location: this.deliveryLocation,
|
|
464
|
-
activity: stored.index, now: stored.at }).catch(() => undefined);
|
|
465
|
-
return { id: formatActivityId(stored.index), at: stored.at, from: stored.actor, to: resolvedTo, body: preview ?? fullBody,
|
|
466
|
-
...(stored.kind !== 'say' || stored.metadata === undefined ? {} : { metadata: stored.metadata }) };
|
|
476
|
+
await this.reconcile();
|
|
477
|
+
const result = (await this.artifact.transactAs({ kind: 'send', actor: input.from }, facts => facts.insertNotice(input, this.clock()), control?.signal)).value;
|
|
478
|
+
await this.flush();
|
|
479
|
+
return result;
|
|
467
480
|
}
|
|
468
|
-
async participant(name,
|
|
469
|
-
|
|
470
|
-
requireOwner(owner);
|
|
471
|
-
return (await this.artifact.read(facts => {
|
|
472
|
-
const participant = facts.participant(name), seat = facts.seat(name);
|
|
473
|
-
if (!participant?.joined || seat === undefined || seat.owner !== owner || seat.attachment !== 'attached') {
|
|
474
|
-
const reason = participant === undefined ? 'has never stepped into this square'
|
|
475
|
-
: !participant.joined ? 'needs a current attached seat; join first'
|
|
476
|
-
: seat !== undefined && seat.owner !== owner ? 'is not held by this owner; join before writing'
|
|
477
|
-
: seat?.attachment === 'detached' ? 'is away — join again before writing'
|
|
478
|
-
: 'needs a current attached seat; join first';
|
|
479
|
-
throw new SquareError('already_joined', `${participantIdentity(participant?.name ?? name)} ${reason}`, {
|
|
480
|
-
reason: 'needs_seat', join: joinRecoveryCommand(this.location, participant?.name ?? name),
|
|
481
|
-
});
|
|
482
|
-
}
|
|
483
|
-
return this.handle(seat);
|
|
484
|
-
})).value;
|
|
481
|
+
async participant(name, options) {
|
|
482
|
+
return this.handle(await this.requiredSeat(name, options));
|
|
485
483
|
}
|
|
486
484
|
async join(requested, options, control) {
|
|
487
485
|
let name = requested;
|
|
488
486
|
try {
|
|
489
487
|
checkControl(control);
|
|
490
488
|
name = requireJoinName(requested);
|
|
491
|
-
const last = options
|
|
489
|
+
const last = options.last ?? HISTORY_DEFAULT_LIMIT;
|
|
492
490
|
if (!Number.isSafeInteger(last) || last < 1 || last > HISTORY_MAX_LIMIT)
|
|
493
491
|
throw new SquareError('invalid_args', 'Join history limit must be between 1 and 100');
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
if (initial !== undefined) {
|
|
503
|
-
if (!current?.joined)
|
|
504
|
-
throw new SquareError('already_done', `${participantIdentity(participant)} is no longer standing here`);
|
|
505
|
-
facts.land(coreDone(facts, participant, '', now));
|
|
506
|
-
kind = 'taken-over';
|
|
507
|
-
}
|
|
508
|
-
else if (current?.joined) {
|
|
509
|
-
if (currentSeat === undefined || currentSeat.owner !== owner)
|
|
510
|
-
throw new SquareError('already_joined', `${participantIdentity(participant)} is held by another owner; take it over explicitly`);
|
|
511
|
-
if (currentSeat.attachment === 'detached')
|
|
512
|
-
facts.reattachSeat(asSeatRef(currentSeat), wakeAddress);
|
|
513
|
-
else if (wakeAddress !== undefined && canRefreshWakeAddress(currentSeat.wakeAddress, wakeAddress))
|
|
514
|
-
facts.updateWakeAddress(asSeatRef(currentSeat), wakeAddress);
|
|
515
|
-
kind = 'reconnected';
|
|
516
|
-
}
|
|
517
|
-
if (kind !== 'reconnected') {
|
|
518
|
-
const joinAct = decideJoin(facts, participant, now);
|
|
519
|
-
if (joinAct === undefined)
|
|
520
|
-
throw new SquareError('already_joined', `${participantIdentity(participant)} could not be joined`);
|
|
521
|
-
activity = toPublicActivity(facts.land(joinAct));
|
|
522
|
-
}
|
|
523
|
-
const seat = facts.seat(participant);
|
|
524
|
-
if (seat === undefined)
|
|
525
|
-
throw new SquareError('invalid_args', 'The join activity did not establish its artifact seat.');
|
|
526
|
-
return { participant: this.handle(seat), activity, kind,
|
|
527
|
-
...(!options.observe ? {} : { observation: { ...this.observation(inSquareCount(facts), seat.participant, now),
|
|
528
|
-
deliveryAvailable: seat.wakeAddress !== undefined } }) };
|
|
529
|
-
}, control?.signal)).value;
|
|
530
|
-
await this.publishSeat(owner);
|
|
492
|
+
if (!await ensureInitialDeliverySession(requireOwner(options.owner), this.env, options.wakeAddress))
|
|
493
|
+
throw new SquareError('already_joined', `${participantIdentity(name)} is away; resume the host session first`, { reason: 'needs_seat' });
|
|
494
|
+
await this.reconcile();
|
|
495
|
+
const committed = await this.joinSeat(name, options, control);
|
|
496
|
+
await this.flush();
|
|
497
|
+
const participant = this.handle(committed.seat);
|
|
498
|
+
const result = { participant, activity: committed.activity, kind: committed.kind,
|
|
499
|
+
...(committed.observation === undefined ? {} : { observation: committed.observation }) };
|
|
531
500
|
if (!options.observe)
|
|
532
|
-
return
|
|
533
|
-
let entry;
|
|
501
|
+
return result;
|
|
502
|
+
let entry, deliveryAvailable = false;
|
|
534
503
|
try {
|
|
535
|
-
|
|
504
|
+
const observed = await this.withHostPresence((facts, available) => ({
|
|
505
|
+
entry: entryPresentation(facts, participant.name, options.last ?? HISTORY_DEFAULT_LIMIT),
|
|
506
|
+
deliveryAvailable: available(participant.name),
|
|
507
|
+
}), control);
|
|
508
|
+
entry = observed.entry;
|
|
509
|
+
deliveryAvailable = observed.deliveryAvailable;
|
|
536
510
|
}
|
|
537
511
|
catch { /* Keep the committed join even when optional history is unavailable. */ }
|
|
538
|
-
return { ...
|
|
512
|
+
return { ...result, observation: { ...committed.observation, deliveryAvailable,
|
|
513
|
+
...(entry === undefined ? { enrichmentUnavailable: true } : { entry }) } };
|
|
539
514
|
}
|
|
540
515
|
catch (error) {
|
|
541
516
|
throw failure(error, { operation: 'join', subject: 'caller', participant: name });
|
|
542
517
|
}
|
|
543
518
|
}
|
|
544
|
-
/** Restore only a standing seat; a concurrent departure or takeover cannot be revived. */
|
|
545
|
-
async reattach(name, { owner, wakeAddress }, control) {
|
|
546
|
-
checkControl(control);
|
|
547
|
-
validateName(name);
|
|
548
|
-
requireOwner(owner);
|
|
549
|
-
const captured = (await this.artifact.read(facts => facts.seat(name), control?.signal)).value;
|
|
550
|
-
if (captured === undefined || captured.owner !== owner)
|
|
551
|
-
return null;
|
|
552
|
-
const seat = (await this.artifact.transactAs({ kind: 'join', participant: name, owner, wakeAddress }, facts => {
|
|
553
|
-
const current = facts.seat(name);
|
|
554
|
-
if (!facts.participant(name)?.joined || current === undefined || current.generation !== captured.generation || current.owner !== owner)
|
|
555
|
-
return null;
|
|
556
|
-
if (current.attachment === 'detached')
|
|
557
|
-
facts.reattachSeat(asSeatRef(current), wakeAddress);
|
|
558
|
-
else if (wakeAddress !== undefined && canRefreshWakeAddress(current.wakeAddress, wakeAddress))
|
|
559
|
-
facts.updateWakeAddress(asSeatRef(current), wakeAddress);
|
|
560
|
-
return facts.seat(name);
|
|
561
|
-
}, control?.signal)).value;
|
|
562
|
-
if (seat !== null)
|
|
563
|
-
await this.publishSeat(owner);
|
|
564
|
-
return seat === null ? null : this.handle(seat);
|
|
565
|
-
}
|
|
566
519
|
async history(query = {}, control) {
|
|
567
520
|
if (query.limit !== undefined && query.limit !== null && (!Number.isSafeInteger(query.limit) || query.limit < 1 || query.limit > HISTORY_MAX_LIMIT))
|
|
568
521
|
throw new SquareError('invalid_args', `History limit must be a positive integer no greater than ${HISTORY_MAX_LIMIT}`);
|
|
@@ -575,7 +528,7 @@ export class SqliteSquare {
|
|
|
575
528
|
throw new SquareError('invalid_args', 'History context must be a non-negative safe integer');
|
|
576
529
|
const limit = query.limit === undefined ? (query.at?.length ? null : HISTORY_DEFAULT_LIMIT) : query.limit;
|
|
577
530
|
const contextual = !!query.at?.length || query.contextBefore !== undefined || query.contextAfter !== undefined;
|
|
578
|
-
const kinds = contextual || query.since !== undefined ?
|
|
531
|
+
const kinds = contextual || query.since !== undefined ? [...NON_READ, 'read'] : NON_READ;
|
|
579
532
|
return (await this.artifact.read(facts => {
|
|
580
533
|
const selected = selectHistory(facts, query, limit, kinds, query.observe === true);
|
|
581
534
|
if (contextual && limit === null && selected.totalMatches > HISTORY_MAX_LIMIT)
|
|
@@ -594,12 +547,22 @@ export class SqliteSquare {
|
|
|
594
547
|
}, control?.signal)).value;
|
|
595
548
|
}
|
|
596
549
|
async status(options = {}, control) {
|
|
597
|
-
return
|
|
550
|
+
return this.withHostPresence((facts, available) => {
|
|
551
|
+
const result = queryStatus(facts, { location: this.location, now: this.clock() }, options.viewer, options.observe === true);
|
|
552
|
+
return { ...result, participants: result.participants.map(person => ({ ...person, deliveryAvailable: available(person.name) })) };
|
|
553
|
+
}, control);
|
|
598
554
|
}
|
|
555
|
+
/** Local mode reads only World-known facts; it does not verify host attachment. */
|
|
599
556
|
async participants(options = {}, control) {
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
557
|
+
if (options.local)
|
|
558
|
+
return (await this.artifact.read(facts => {
|
|
559
|
+
const result = queryParticipants(facts, { location: this.location, now: this.clock() }, options.observe === true);
|
|
560
|
+
return { ...result, participants: result.participants.map(person => ({ ...person, deliveryAvailable: false })) };
|
|
561
|
+
}, control?.signal)).value;
|
|
562
|
+
return this.withHostPresence((facts, available) => {
|
|
563
|
+
const result = queryParticipants(facts, { location: this.location, now: this.clock() }, options.observe === true);
|
|
564
|
+
return { ...result, participants: result.participants.map(person => ({ ...person, deliveryAvailable: available(person.name) })) };
|
|
565
|
+
}, control);
|
|
603
566
|
}
|
|
604
567
|
async recovery(requested, control) {
|
|
605
568
|
const facts = requested === undefined ? undefined : (await this.artifact.read(facts => recoveryObservation(facts, requested, this.clock()), control?.signal).catch(() => undefined))?.value;
|
|
@@ -612,39 +575,13 @@ export class SqliteSquare {
|
|
|
612
575
|
}
|
|
613
576
|
async seatsOf(owner) {
|
|
614
577
|
requireOwner(owner);
|
|
615
|
-
return (await this.artifact.read(facts => facts.seatsForOwner(owner)
|
|
578
|
+
return (await this.artifact.read(facts => facts.seatsForOwner(owner)
|
|
579
|
+
.filter(seat => facts.participant(seat.participant)?.joined && seat.attachment === 'attached')
|
|
580
|
+
.map(seat => ({ name: seat.participant, generation: seat.generation })))).value;
|
|
616
581
|
}
|
|
617
582
|
close() { return this.closesArtifact ? this.artifact.close() : Promise.resolve(); }
|
|
618
|
-
async heldSeat(name, { owner, generation }) {
|
|
619
|
-
requireOwner(owner);
|
|
620
|
-
const seat = (await this.artifact.read(facts => facts.seat(name))).value;
|
|
621
|
-
return seat?.generation === generation && seat.owner === owner ? seat : undefined;
|
|
622
|
-
}
|
|
623
|
-
async cancelDeliveries(name, identity) {
|
|
624
|
-
const seat = await this.heldSeat(name, identity);
|
|
625
|
-
return seat === undefined ? false : this.artifact.cancelDeliveries(asSeatRef(seat));
|
|
626
|
-
}
|
|
627
|
-
async detach(name, identity) {
|
|
628
|
-
const captured = await this.heldSeat(name, identity);
|
|
629
|
-
if (captured === undefined)
|
|
630
|
-
return null;
|
|
631
|
-
const seat = asSeatRef(captured);
|
|
632
|
-
return (await this.artifact.transactAs({ kind: 'host-cleanup', seat }, facts => facts.detachSeat(seat) ? true : null)).value;
|
|
633
|
-
}
|
|
634
|
-
async endHeld(name, identity) {
|
|
635
|
-
const captured = await this.heldSeat(name, identity);
|
|
636
|
-
if (captured === undefined)
|
|
637
|
-
return null;
|
|
638
|
-
return (await this.artifact.transactAs({ kind: 'host-cleanup', seat: asSeatRef(captured) }, facts => {
|
|
639
|
-
const current = facts.seat(name);
|
|
640
|
-
if (current === undefined || current.generation !== captured.generation || current.owner !== captured.owner)
|
|
641
|
-
return null;
|
|
642
|
-
return { activity: toPublicActivity(facts.land(coreDone(facts, current.participant, '', this.clock()))) };
|
|
643
|
-
})).value;
|
|
644
|
-
}
|
|
645
583
|
}
|
|
646
584
|
export function diagnoseSquare(input, control) { checkControl(control); return diagnoseSquareFile(squareLocation(input)); }
|
|
647
|
-
export function migrateSquare(input, control) { checkControl(control); const env = input.env ?? process.env; return migrateSquareFile(squareLocation(input), control?.signal, env); }
|
|
648
585
|
export function discover(input, options = {}, control) {
|
|
649
586
|
if (input.path !== undefined || (input.env ?? process.env).SQUARE_LOCATION)
|
|
650
587
|
squareLocation(input);
|
|
@@ -654,10 +591,10 @@ export function discover(input, options = {}, control) {
|
|
|
654
591
|
export async function inbox(input, owner, options = {}, control) {
|
|
655
592
|
checkControl(control);
|
|
656
593
|
requireOwner(owner);
|
|
657
|
-
const
|
|
594
|
+
const limit = options.limit ?? 20;
|
|
658
595
|
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100)
|
|
659
596
|
throw new SquareError('invalid_args', 'Inbox limit must be between 1 and 100');
|
|
660
|
-
const rows = await sessionInboxCounts({ owner,
|
|
597
|
+
const rows = await sessionInboxCounts({ owner, cwd: input.cwd }, control?.signal);
|
|
661
598
|
checkControl(control);
|
|
662
599
|
const compare = (left, right) => left < right ? -1 : left > right ? 1 : 0;
|
|
663
600
|
rows.sort((a, b) => compare(a.squarePath, b.squarePath) || compare(nameKey(a.name), nameKey(b.name)) || compare(a.name, b.name));
|