@astrosheep/square 0.3.62 → 0.3.64
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/claude-plugin/.claude-plugin/plugin.json +3 -2
- package/claude-plugin/hooks/hooks.json +1 -22
- package/claude-plugin/hooks/register.js +119 -0
- package/claude-plugin/types.d.ts +7 -0
- package/codex-plugin/.codex-plugin/plugin.json +1 -1
- package/dist/activity-feed.d.ts +1 -10
- package/dist/activity-feed.js +1 -27
- package/dist/activity.js +57 -25
- package/dist/artifact.js +12 -24
- package/dist/attention-presentation.js +1 -0
- package/dist/automatic-session.d.ts +3 -1
- package/dist/automatic-session.js +21 -34
- package/dist/boundary-presentation.js +4 -13
- package/dist/canonical-path.d.ts +11 -0
- package/dist/canonical-path.js +47 -0
- package/dist/catch-decisions.js +14 -10
- package/dist/catch-selection.d.ts +6 -0
- package/dist/catch-selection.js +14 -0
- package/dist/claude-delivery.d.ts +11 -0
- package/dist/claude-delivery.js +145 -0
- package/dist/claude-mod.d.ts +8 -0
- package/dist/claude-mod.js +164 -0
- package/dist/cli/context.d.ts +0 -1
- package/dist/cli/context.js +9 -21
- package/dist/cli/harness-command.d.ts +0 -1
- package/dist/cli/harness-command.js +0 -6
- package/dist/cli/observation-commands.d.ts +1 -1
- package/dist/cli/observation-commands.js +31 -58
- package/dist/cli/program.js +20 -18
- package/dist/cli/registry.js +2 -2
- package/dist/cli/square-commands.js +10 -6
- package/dist/codex-hook.js +9 -12
- package/dist/codex-queue.d.ts +2 -2
- package/dist/codex-queue.js +7 -5
- package/dist/decisions.d.ts +2 -6
- package/dist/decisions.js +16 -17
- package/dist/delivery-health.d.ts +1 -2
- package/dist/delivery-health.js +4 -5
- package/dist/delivery-operations.d.ts +2 -5
- package/dist/delivery-operations.js +77 -214
- package/dist/delivery.d.ts +25 -47
- package/dist/delivery.js +14 -46
- package/dist/diagnostic-redaction.js +7 -9
- package/dist/file-changes.d.ts +0 -1
- package/dist/file-changes.js +2 -19
- package/dist/harness-claude.js +8 -6
- package/dist/harness-codex.d.ts +0 -2
- package/dist/harness-codex.js +0 -2
- package/dist/harness-links.js +13 -17
- package/dist/harness.js +3 -7
- package/dist/help.js +4 -3
- package/dist/host-ledger-file-adapter.d.ts +17 -8
- package/dist/host-ledger-file-adapter.js +164 -80
- package/dist/host-ledger.d.ts +55 -50
- package/dist/inbox.d.ts +0 -3
- package/dist/inbox.js +13 -30
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/list.js +1 -4
- package/dist/mcp.js +18 -40
- package/dist/mention-parse.js +3 -9
- package/dist/model.d.ts +11 -2
- package/dist/model.js +1 -1
- package/dist/notifications.d.ts +3 -16
- package/dist/notifications.js +41 -122
- package/dist/opencode-delivery.d.ts +14 -0
- package/dist/opencode-delivery.js +171 -0
- package/dist/opencode.d.ts +39 -18
- package/dist/opencode.js +145 -40
- package/dist/packages/agent-delivery/src/claude-auth.d.ts +13 -0
- package/dist/packages/agent-delivery/src/claude-auth.js +70 -0
- package/dist/packages/agent-delivery/src/claude-native.d.ts +28 -0
- package/dist/packages/agent-delivery/src/claude-native.js +65 -0
- package/dist/packages/agent-delivery/src/claude.d.ts +14 -0
- package/dist/packages/agent-delivery/src/claude.js +66 -0
- package/dist/packages/agent-delivery/src/deadline.d.ts +11 -0
- package/dist/packages/agent-delivery/src/deadline.js +41 -0
- package/dist/packages/agent-delivery/src/errors.d.ts +16 -0
- package/dist/packages/agent-delivery/src/errors.js +41 -0
- package/dist/packages/agent-delivery/src/guards.d.ts +6 -0
- package/dist/packages/agent-delivery/src/guards.js +6 -0
- package/dist/packages/agent-delivery/src/index.d.ts +83 -0
- package/dist/packages/agent-delivery/src/index.js +110 -0
- package/dist/packages/agent-delivery/src/local-endpoint.d.ts +26 -0
- package/dist/packages/agent-delivery/src/local-endpoint.js +114 -0
- package/dist/packages/agent-delivery/src/opencode-native.d.ts +33 -0
- package/dist/packages/agent-delivery/src/opencode-native.js +75 -0
- package/dist/packages/agent-delivery/src/opencode.d.ts +14 -0
- package/dist/packages/agent-delivery/src/opencode.js +127 -0
- package/dist/packages/agent-delivery/src/paseo-native.d.ts +58 -0
- package/dist/packages/agent-delivery/src/paseo-native.js +122 -0
- package/dist/packages/agent-delivery/src/paseo.d.ts +19 -0
- package/dist/packages/agent-delivery/src/paseo.js +106 -0
- package/dist/packages/agent-delivery/src/pi-client.d.ts +10 -0
- package/dist/packages/agent-delivery/src/pi-client.js +106 -0
- package/dist/packages/agent-delivery/src/pi-protocol.d.ts +15 -0
- package/dist/packages/agent-delivery/src/pi-protocol.js +50 -0
- package/dist/packages/agent-delivery/src/pi-send.d.ts +23 -0
- package/dist/packages/agent-delivery/src/pi-send.js +4 -0
- package/dist/packages/agent-delivery/src/pi.d.ts +25 -0
- package/dist/packages/agent-delivery/src/pi.js +209 -0
- package/dist/packages/agent-delivery/src/types.d.ts +120 -0
- package/dist/participant-host.js +12 -13
- package/dist/participant-identity.d.ts +21 -0
- package/dist/participant-identity.js +32 -11
- package/dist/paseo-delivery.d.ts +2 -2
- package/dist/paseo-delivery.js +35 -27
- package/dist/paseo-timeline.js +25 -21
- package/dist/paseo.d.ts +2 -2
- package/dist/paseo.js +1 -2
- package/dist/ports.d.ts +7 -20
- package/dist/presentation-operations.js +16 -4
- package/dist/presentation.d.ts +19 -10
- package/dist/presentation.js +99 -177
- package/dist/registry.d.ts +15 -66
- package/dist/registry.js +45 -123
- package/dist/routes.d.ts +1 -28
- package/dist/routes.js +22 -104
- package/dist/runtime.d.ts +3 -9
- package/dist/runtime.js +14 -67
- package/dist/search.d.ts +0 -2
- package/dist/search.js +3 -10
- package/dist/square-actions.js +48 -72
- package/dist/square-application.js +25 -23
- package/dist/square-core.d.ts +3 -0
- package/dist/square-core.js +22 -5
- package/dist/square-facade.d.ts +2 -0
- package/dist/square-file-adapter.js +4 -7
- package/dist/square-projections.d.ts +40 -45
- package/dist/square-projections.js +43 -73
- package/dist/square-state.js +1 -4
- package/dist/square-storage.d.ts +5 -8
- package/dist/square-storage.js +13 -36
- package/dist/square-wiring.d.ts +0 -5
- package/dist/square-wiring.js +3 -21
- package/dist/stream.js +4 -6
- package/dist/time.js +2 -7
- package/dist/views.d.ts +4 -16
- package/dist/views.js +12 -19
- package/dist/wake-attempts.d.ts +2 -56
- package/dist/wake-attempts.js +15 -110
- package/dist/wake-eligibility.d.ts +43 -0
- package/dist/wake-eligibility.js +106 -0
- package/dist/wake-evidence.d.ts +32 -9
- package/dist/wake-evidence.js +29 -24
- package/dist/wake-sink.d.ts +13 -9
- package/dist/wake-sink.js +35 -54
- package/dist/watch.js +13 -21
- package/extensions/square-pi.js +10 -5
- package/guides/architect.md +77 -77
- package/guides/brainstorm.md +197 -197
- package/guides/claude-native.md +101 -0
- package/guides/host-ledger.md +3 -1
- package/guides/participant.md +14 -7
- package/guides/pi-native-delivery.md +43 -0
- package/package.json +4 -3
- package/dist/claude-hook.d.ts +0 -10
- package/dist/claude-hook.js +0 -60
- package/dist/paseo-connection.d.ts +0 -15
- package/dist/paseo-connection.js +0 -141
- package/dist/presence.d.ts +0 -2
- package/dist/presence.js +0 -24
- package/dist/presented.d.ts +0 -2
- package/dist/presented.js +0 -10
- package/dist/state-cell.d.ts +0 -14
- package/dist/wake-port.d.ts +0 -23
- package/dist/wake-port.js +0 -27
- /package/dist/{state-cell.js → packages/agent-delivery/src/types.js} +0 -0
package/dist/runtime.js
CHANGED
|
@@ -1,43 +1,19 @@
|
|
|
1
1
|
import { audienceIncludes, audienceOf, fold, formatActivityId, replayLandedAudiences } from './square-core.js';
|
|
2
2
|
import { SquareError, findParticipantName, nameKey, sameName, } from './model.js';
|
|
3
|
-
function parseIntegerEnvValue(name, raw, fallback) {
|
|
4
|
-
if (raw === undefined)
|
|
5
|
-
return fallback;
|
|
6
|
-
const value = parseInt(raw, 10);
|
|
7
|
-
if (!Number.isFinite(value) || value <= 0) {
|
|
8
|
-
throw new SquareError('invalid_args', `Invalid ${name}: expected a positive integer.`);
|
|
9
|
-
}
|
|
10
|
-
return value;
|
|
11
|
-
}
|
|
12
3
|
function parseIntegerEnv(name, fallback) {
|
|
13
|
-
return parseIntegerEnvValue(name, process.env[name], fallback);
|
|
14
|
-
}
|
|
15
|
-
function parseIntegerEnvAlias(primary, aliases, fallback) {
|
|
16
|
-
const candidates = [primary, ...aliases];
|
|
17
|
-
for (const name of candidates) {
|
|
18
|
-
if (process.env[name] !== undefined)
|
|
19
|
-
return parseIntegerEnvValue(name, process.env[name], fallback);
|
|
20
|
-
}
|
|
21
|
-
return fallback;
|
|
22
|
-
}
|
|
23
|
-
function parseScaleEnv(name, fallback) {
|
|
24
4
|
const raw = process.env[name];
|
|
25
5
|
if (raw === undefined)
|
|
26
6
|
return fallback;
|
|
27
|
-
const value =
|
|
7
|
+
const value = parseInt(raw, 10);
|
|
28
8
|
if (!Number.isFinite(value) || value <= 0) {
|
|
29
|
-
throw new SquareError('invalid_args', `Invalid ${name}: expected a positive
|
|
9
|
+
throw new SquareError('invalid_args', `Invalid ${name}: expected a positive integer.`);
|
|
30
10
|
}
|
|
31
11
|
return value;
|
|
32
12
|
}
|
|
33
|
-
function scaledMs(baseMs, scale) {
|
|
34
|
-
return Math.max(1, Math.round(baseMs * scale));
|
|
35
|
-
}
|
|
36
13
|
export const WATCH_HEARTBEAT_MS = parseIntegerEnv('SQUARE_WATCH_HEARTBEAT_MS', 60000);
|
|
37
|
-
export const SLEEP_MS =
|
|
38
|
-
export const STALE_MS =
|
|
39
|
-
export const WATCH_STALE_MS = parseIntegerEnv('SQUARE_WATCH_STALE_MS',
|
|
40
|
-
export const LOCK_RETRY_MS = parseIntegerEnv('SQUARE_LOCK_RETRY_MS', 25);
|
|
14
|
+
export const SLEEP_MS = parseIntegerEnv('SQUARE_WATCH_POLL_MS', 10000);
|
|
15
|
+
export const STALE_MS = parseIntegerEnv('SQUARE_WATCH_QUIET_MS', 300000);
|
|
16
|
+
export const WATCH_STALE_MS = parseIntegerEnv('SQUARE_WATCH_STALE_MS', 180000);
|
|
41
17
|
export const THROTTLE_WINDOW_MS = parseIntegerEnv('SQUARE_THROTTLE_WINDOW_MS', 60000);
|
|
42
18
|
export const UNREAD_BLOCK_GRACE_MS = parseIntegerEnv('SQUARE_UNREAD_BLOCK_GRACE_MS', 90000);
|
|
43
19
|
/** A held express waits for the hand to lower, but not forever: past this budget it gives up exactly like --no-wait (draft saved, retry printed). 0 disables waiting. */
|
|
@@ -71,10 +47,6 @@ export function inSquareCount(squareState) {
|
|
|
71
47
|
export function resolveRosterName(squareState, name) {
|
|
72
48
|
return findParticipantName(rosterNames(squareState), name);
|
|
73
49
|
}
|
|
74
|
-
export function hasQuorum(squareState, name, outs) {
|
|
75
|
-
const peers = rosterNames(squareState).filter((participant) => !sameName(participant, name));
|
|
76
|
-
return peers.length > 0 && peers.every((peer) => outs.has(nameKey(peer)));
|
|
77
|
-
}
|
|
78
50
|
export function countSays(acts, name) {
|
|
79
51
|
return acts.filter((act) => act.kind === 'say' && sameName(act.actor, name)).length;
|
|
80
52
|
}
|
|
@@ -96,12 +68,10 @@ export function sayNumberFor(acts, target) {
|
|
|
96
68
|
export function watchTerminalStatus(squareState, name) {
|
|
97
69
|
if (squareState.hardCap !== null && countSays(squareState.acts, name) >= squareState.hardCap)
|
|
98
70
|
return 'capped';
|
|
99
|
-
const done =
|
|
71
|
+
const done = new Set(fold(squareState.acts).done.map((participant) => nameKey(participant)));
|
|
100
72
|
done.delete(nameKey(name));
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
export function doneNames(acts) {
|
|
104
|
-
return new Set(fold(acts).done.map((participant) => nameKey(participant)));
|
|
73
|
+
const peers = rosterNames(squareState).filter((participant) => !sameName(participant, name));
|
|
74
|
+
return peers.length > 0 && peers.every((peer) => done.has(nameKey(peer))) ? 'quorum' : undefined;
|
|
105
75
|
}
|
|
106
76
|
export function joinedNames(acts) {
|
|
107
77
|
return new Set(acts.filter((act) => act.kind === 'join').map((act) => nameKey(act.actor)));
|
|
@@ -127,13 +97,9 @@ export function actId(actOrIndex) {
|
|
|
127
97
|
const index = typeof actOrIndex === 'number' ? actOrIndex : actStableIndex(actOrIndex);
|
|
128
98
|
return formatActivityId(index);
|
|
129
99
|
}
|
|
130
|
-
function
|
|
100
|
+
function canonicalRuntimeName(squareState, name) {
|
|
131
101
|
return resolveRosterName(squareState, name) ?? name;
|
|
132
102
|
}
|
|
133
|
-
function observationMap(squareState, name) {
|
|
134
|
-
const key = observationRecipient(squareState, name);
|
|
135
|
-
return squareState.runtime.observations?.[key] ?? {};
|
|
136
|
-
}
|
|
137
103
|
export function currentHold(acts) {
|
|
138
104
|
const hold = fold(acts).hold;
|
|
139
105
|
return hold.active ? { active: true, at: hold.at, reason: hold.reason } : { active: false };
|
|
@@ -141,16 +107,10 @@ export function currentHold(acts) {
|
|
|
141
107
|
export function publicActs(acts) {
|
|
142
108
|
return acts.filter((act) => act.kind === 'say' || act.kind === 'done');
|
|
143
109
|
}
|
|
144
|
-
function canonicalRuntimeName(squareState, name) {
|
|
145
|
-
return resolveRosterName(squareState, name) ?? name;
|
|
146
|
-
}
|
|
147
|
-
export function observationFor(squareState, name, index) {
|
|
148
|
-
return observationMap(squareState, name)[formatActivityId(index)];
|
|
149
|
-
}
|
|
150
110
|
export function recordObservation(squareState, name, index, state, at = Date.now()) {
|
|
151
111
|
if (!Number.isSafeInteger(index) || index < 0 || !Number.isFinite(at))
|
|
152
112
|
return false;
|
|
153
|
-
const key =
|
|
113
|
+
const key = canonicalRuntimeName(squareState, name);
|
|
154
114
|
const id = formatActivityId(index);
|
|
155
115
|
const current = squareState.runtime.observations[key]?.[id];
|
|
156
116
|
if (current?.state === 'seen' || (current?.state === state && current.at >= at))
|
|
@@ -183,15 +143,14 @@ export function readCursor(squareState, name, landed = replayLandedAudiences(squ
|
|
|
183
143
|
}
|
|
184
144
|
return cursor;
|
|
185
145
|
}
|
|
186
|
-
export function latestActIndex(acts) {
|
|
187
|
-
return acts.reduce((max, act) => Math.max(max, act.index), -1);
|
|
188
|
-
}
|
|
189
146
|
/**
|
|
147
|
+
* The last public act the participant was verifiably present for, or -1 when there is no evidence.
|
|
148
|
+
*
|
|
190
149
|
* "Was here" is a claim of presence, so it rides on evidence: the participant's own acts
|
|
191
150
|
* and seen receipts after their latest join. Anything else — in particular acts merely
|
|
192
151
|
* not addressed to them — says nothing about where they actually were.
|
|
193
152
|
*/
|
|
194
|
-
export function
|
|
153
|
+
export function presenceAnchor(squareState, name, landed = replayLandedAudiences(squareState.acts)) {
|
|
195
154
|
const recipient = landed.resolveParticipant(name) ?? name;
|
|
196
155
|
const boundary = landed.lastJoinIndex(recipient) ?? -1;
|
|
197
156
|
if (boundary < 0)
|
|
@@ -205,15 +164,6 @@ export function presenceEvidenceCursor(squareState, name, landed = replayLandedA
|
|
|
205
164
|
evidence = act.index;
|
|
206
165
|
}
|
|
207
166
|
}
|
|
208
|
-
return evidence;
|
|
209
|
-
}
|
|
210
|
-
/** The last public act the participant was verifiably present for, or -1 when there is no evidence. */
|
|
211
|
-
export function presenceAnchor(squareState, name, landed = replayLandedAudiences(squareState.acts)) {
|
|
212
|
-
const recipient = landed.resolveParticipant(name) ?? name;
|
|
213
|
-
const boundary = landed.lastJoinIndex(recipient) ?? -1;
|
|
214
|
-
if (boundary < 0)
|
|
215
|
-
return -1;
|
|
216
|
-
const evidence = presenceEvidenceCursor(squareState, recipient, landed);
|
|
217
167
|
for (let i = squareState.acts.length - 1; i >= 0; i--) {
|
|
218
168
|
const event = squareState.acts[i];
|
|
219
169
|
if (event.index <= boundary || event.index > evidence)
|
|
@@ -225,14 +175,11 @@ export function presenceAnchor(squareState, name, landed = replayLandedAudiences
|
|
|
225
175
|
}
|
|
226
176
|
export function freshWatchLease(squareState, name, at = Date.now()) {
|
|
227
177
|
const key = canonicalRuntimeName(squareState, name);
|
|
228
|
-
const lease =
|
|
178
|
+
const lease = squareState.runtime.leases[key];
|
|
229
179
|
if (lease === undefined || lease.expiresAt <= at || at - lease.heartbeatAt > WATCH_STALE_MS)
|
|
230
180
|
return undefined;
|
|
231
181
|
return lease;
|
|
232
182
|
}
|
|
233
|
-
export function watchLease(squareState, name) {
|
|
234
|
-
return squareState.runtime.leases[canonicalRuntimeName(squareState, name)];
|
|
235
|
-
}
|
|
236
183
|
export function writeWatchLease(squareState, name, lease) {
|
|
237
184
|
squareState.runtime.leases[canonicalRuntimeName(squareState, name)] = lease;
|
|
238
185
|
}
|
package/dist/search.d.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
export declare function compileGrepPattern(pattern: string): RegExp;
|
|
2
|
-
export declare function compileFixedPattern(pattern: string): RegExp;
|
|
3
1
|
export declare function compileSearchPattern(pattern: string, fixed: boolean): RegExp;
|
|
4
2
|
export interface GrepSnippet {
|
|
5
3
|
before: string;
|
package/dist/search.js
CHANGED
|
@@ -3,10 +3,9 @@
|
|
|
3
3
|
// the local CLI user and are not treated as a hostile multi-tenant input. Use
|
|
4
4
|
// --fixed for literal text; do not claim regex mode has ReDoS protection.
|
|
5
5
|
import { SquareError } from './model.js';
|
|
6
|
-
function
|
|
7
|
-
|
|
8
|
-
}
|
|
9
|
-
export function compileGrepPattern(pattern) {
|
|
6
|
+
export function compileSearchPattern(pattern, fixed) {
|
|
7
|
+
if (fixed)
|
|
8
|
+
return new RegExp(pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i');
|
|
10
9
|
try {
|
|
11
10
|
return new RegExp(pattern, 'i');
|
|
12
11
|
}
|
|
@@ -15,12 +14,6 @@ export function compileGrepPattern(pattern) {
|
|
|
15
14
|
throw new SquareError('invalid_args', `Invalid --grep regex: ${detail}`);
|
|
16
15
|
}
|
|
17
16
|
}
|
|
18
|
-
export function compileFixedPattern(pattern) {
|
|
19
|
-
return new RegExp(escapeRegex(pattern), 'i');
|
|
20
|
-
}
|
|
21
|
-
export function compileSearchPattern(pattern, fixed) {
|
|
22
|
-
return fixed ? compileFixedPattern(pattern) : compileGrepPattern(pattern);
|
|
23
|
-
}
|
|
24
17
|
function compactWhitespace(text) {
|
|
25
18
|
return text.replace(/\s+/g, ' ');
|
|
26
19
|
}
|
package/dist/square-actions.js
CHANGED
|
@@ -1,25 +1,16 @@
|
|
|
1
|
-
import { formatActivityId, parseActivityId } from './square-core.js';
|
|
2
1
|
import { coreDone, coreHold, coreIgnore, coreListen, coreListening, coreResume, decideAct, decideImplicitJoin, decideJoin, resolveKnownName, validateDoneBody } from './decisions.js';
|
|
3
2
|
import { isSquareError, nameKey, SquareError, validateName } from './model.js';
|
|
4
3
|
import { participantIdentity } from './participant-identity.js';
|
|
5
4
|
import { deliverPending } from './delivery-operations.js';
|
|
6
5
|
import { decideCatch } from './catch-decisions.js';
|
|
7
|
-
import { claimSessionParticipant, claimSessionTakeover, releaseSessionParticipantClaim, readParticipantOwner
|
|
6
|
+
import { claimSessionParticipant, claimSessionTakeover, presenceEpoch, releaseSessionParticipantClaim, readParticipantOwner } from './registry.js';
|
|
8
7
|
import { assertLiveOwner, ensureLocalPresence, identityRouteDraft, processIdentity, publishIdentityRoute, retireIdentityRoute } from './participant-host.js';
|
|
9
8
|
import { applyWakeRouteToState, dropEndedSessionWakeRoutesFromState, dropParticipantWakeRoutesFromState, dropSessionWakeRoutesFromState, sessionCanEndParticipant } from './routes.js';
|
|
9
|
+
import { parseRequiredActivityId, toPublicActivity } from './views.js';
|
|
10
10
|
function throwIfAborted(control) { if (control?.signal?.aborted)
|
|
11
11
|
throw control.signal.reason ?? new Error('Operation aborted'); }
|
|
12
12
|
function exposeCaught(activity, perception) {
|
|
13
|
-
|
|
14
|
-
throw new Error(`Cannot expose stored activity ${formatActivityId(activity.index)}`);
|
|
15
|
-
const result = {
|
|
16
|
-
id: formatActivityId(activity.index), at: activity.at, kind: activity.kind, actor: activity.actor,
|
|
17
|
-
mentions: activity.kind === 'say' ? activity.mentions ?? [] : [],
|
|
18
|
-
...(activity.kind === 'say' && activity.reach !== undefined ? { reach: activity.reach } : {}),
|
|
19
|
-
...('body' in activity && activity.body !== undefined ? { body: activity.body } : {}),
|
|
20
|
-
...('target' in activity ? { target: activity.target } : {}),
|
|
21
|
-
...(activity.kind === 'say' && activity.reply !== undefined ? { reply: formatActivityId(activity.reply) } : {}),
|
|
22
|
-
};
|
|
13
|
+
const result = toPublicActivity(activity);
|
|
23
14
|
if (perception === 'full' || !('body' in result))
|
|
24
15
|
return { ...result, perception };
|
|
25
16
|
const { body: _body, ...withoutBody } = result;
|
|
@@ -82,42 +73,25 @@ function committedActivity(stored, verb) {
|
|
|
82
73
|
throw new Error(`${verb} activity did not commit`);
|
|
83
74
|
return activity;
|
|
84
75
|
}
|
|
85
|
-
function exposeActivity(stored) {
|
|
86
|
-
if (stored.kind === 'read' || stored.actor === undefined)
|
|
87
|
-
throw new Error(`Cannot expose stored activity ${formatActivityId(stored.index)}`);
|
|
88
|
-
return {
|
|
89
|
-
id: formatActivityId(stored.index), at: stored.at, kind: stored.kind, actor: stored.actor,
|
|
90
|
-
...('body' in stored && stored.body !== undefined ? { body: stored.body } : {}),
|
|
91
|
-
mentions: stored.kind === 'say' ? stored.mentions ?? [] : [],
|
|
92
|
-
...(stored.kind === 'say' && stored.reach !== undefined ? { reach: stored.reach } : {}),
|
|
93
|
-
...('target' in stored ? { target: stored.target } : {}),
|
|
94
|
-
...(stored.kind === 'say' && stored.reply !== undefined ? { reply: formatActivityId(stored.reply) } : {}),
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
function parseRequiredActivityId(id) {
|
|
98
|
-
const index = parseActivityId(id);
|
|
99
|
-
if (index === undefined)
|
|
100
|
-
throw new SquareError('invalid_args', `Invalid activity id: ${id}`);
|
|
101
|
-
return index;
|
|
102
|
-
}
|
|
103
76
|
export async function join(square, name, control) {
|
|
104
77
|
// Rejected validation must not perform an ownership claim.
|
|
105
78
|
throwIfAborted(control);
|
|
106
79
|
validateName(name);
|
|
80
|
+
const hostLedger = square.hostLedger;
|
|
107
81
|
const preview = await square.artifact.read(control?.signal);
|
|
108
82
|
const previewDecision = decideJoin(preview.state, name, square.clock());
|
|
109
83
|
if (previewDecision.joinAct === undefined) {
|
|
110
84
|
// An active artifact participant is idempotent only for its current session.
|
|
111
85
|
// A different owner must still pass through the registry CAS so it is refused
|
|
112
86
|
// instead of silently reconnecting as a foreign session.
|
|
113
|
-
if (
|
|
87
|
+
if (hostLedger !== undefined) {
|
|
114
88
|
const identity = processIdentity(square.env ?? process.env);
|
|
115
|
-
const owner = await readParticipantOwner(square.location, previewDecision.joinedName,
|
|
89
|
+
const owner = await readParticipantOwner(square.location, previewDecision.joinedName, hostLedger);
|
|
116
90
|
if (owner === undefined) {
|
|
117
91
|
// Keep the claim path for an active artifact whose ledger owner is not
|
|
118
92
|
// visible yet; concurrent callers must still serialize through CAS.
|
|
119
93
|
}
|
|
120
|
-
else if (owner.
|
|
94
|
+
else if (owner.session !== identity.session) {
|
|
121
95
|
// Continue into the ownership claim below; it will produce the stable
|
|
122
96
|
// already_joined error without mutating the artifact.
|
|
123
97
|
}
|
|
@@ -131,8 +105,8 @@ export async function join(square, name, control) {
|
|
|
131
105
|
}
|
|
132
106
|
let epoch;
|
|
133
107
|
let ownershipClaim;
|
|
134
|
-
if (
|
|
135
|
-
const claim = await claimSessionParticipant(square.location, name, square.env ?? process.env, control?.signal);
|
|
108
|
+
if (hostLedger !== undefined) {
|
|
109
|
+
const claim = await claimSessionParticipant(square.location, name, hostLedger, square.env ?? process.env, control?.signal);
|
|
136
110
|
ownershipClaim = claim;
|
|
137
111
|
epoch = claim?.epoch;
|
|
138
112
|
}
|
|
@@ -154,18 +128,20 @@ export async function join(square, name, control) {
|
|
|
154
128
|
}, control?.signal);
|
|
155
129
|
}
|
|
156
130
|
catch (error) {
|
|
157
|
-
|
|
131
|
+
if (hostLedger !== undefined)
|
|
132
|
+
await releaseSessionParticipantClaim(hostLedger, ownershipClaim).catch(() => undefined);
|
|
158
133
|
throw error;
|
|
159
134
|
}
|
|
160
135
|
await ensureLocalPresence(square, committed.name, epoch);
|
|
161
136
|
await publishIdentityRoute(square, committed.name, epoch);
|
|
162
|
-
return { name: committed.name, activity: committed.stored === null ? null :
|
|
137
|
+
return { name: committed.name, activity: committed.stored === null ? null : toPublicActivity(committed.stored) };
|
|
163
138
|
}
|
|
164
139
|
/** End the standing participant and immediately let the caller reclaim the name. */
|
|
165
140
|
export async function takeover(square, name, _oldSessionIds = [], control) {
|
|
166
141
|
// Rejected validation must not perform an ownership claim.
|
|
167
142
|
throwIfAborted(control);
|
|
168
143
|
validateName(name);
|
|
144
|
+
const hostLedger = square.hostLedger;
|
|
169
145
|
const commitLifecycle = async () => {
|
|
170
146
|
throwIfAborted(control);
|
|
171
147
|
const now = square.clock();
|
|
@@ -182,7 +158,7 @@ export async function takeover(square, name, _oldSessionIds = [], control) {
|
|
|
182
158
|
}, control?.signal);
|
|
183
159
|
return committed;
|
|
184
160
|
};
|
|
185
|
-
if (
|
|
161
|
+
if (hostLedger !== undefined) {
|
|
186
162
|
const env = square.env ?? process.env;
|
|
187
163
|
// Gate the ownership claim on the artifact's authoritative state: a takeover that cannot
|
|
188
164
|
// commit its lifecycle (a name that never joined, or a standing participant that is no
|
|
@@ -190,13 +166,13 @@ export async function takeover(square, name, _oldSessionIds = [], control) {
|
|
|
190
166
|
const { state } = await square.artifact.read(control?.signal);
|
|
191
167
|
const joinedName = resolveKnownName(state, name); // invalid_args when the name never joined
|
|
192
168
|
coreDone(state, joinedName, '', square.clock()); // already_done/not_joined when not standing
|
|
193
|
-
const owner = await readParticipantOwner(square.location, name,
|
|
169
|
+
const owner = await readParticipantOwner(square.location, name, hostLedger);
|
|
194
170
|
throwIfAborted(control);
|
|
195
171
|
// The claim and the artifact lifecycle are one fenced critical section: a losing or refused
|
|
196
172
|
// takeover never mutates the winner, and no second takeover can interleave mid-commit.
|
|
197
|
-
const outcome = await claimSessionTakeover(square.location, name, env, {
|
|
198
|
-
expectedEpoch: owner
|
|
199
|
-
expectedSession: owner?.
|
|
173
|
+
const outcome = await claimSessionTakeover(square.location, name, hostLedger, env, {
|
|
174
|
+
expectedEpoch: presenceEpoch(owner),
|
|
175
|
+
expectedSession: owner?.session ?? '',
|
|
200
176
|
}, async (claim) => {
|
|
201
177
|
throwIfAborted(control);
|
|
202
178
|
const committed = await commitLifecycle();
|
|
@@ -206,12 +182,12 @@ export async function takeover(square, name, _oldSessionIds = [], control) {
|
|
|
206
182
|
}, control?.signal);
|
|
207
183
|
if (outcome.status === 'busy')
|
|
208
184
|
throw new SquareError('already_joined', `✕ ${participantIdentity(name)} already stands here — another session holds the name`);
|
|
209
|
-
return { name: outcome.result.name, activities: outcome.result.stored.map(
|
|
185
|
+
return { name: outcome.result.name, activities: outcome.result.stored.map(toPublicActivity), epoch: outcome.epoch };
|
|
210
186
|
}
|
|
211
187
|
const committed = await commitLifecycle();
|
|
212
188
|
await ensureLocalPresence(square, committed.name);
|
|
213
189
|
await publishIdentityRoute(square, committed.name);
|
|
214
|
-
return { name: committed.name, activities: committed.stored.map(
|
|
190
|
+
return { name: committed.name, activities: committed.stored.map(toPublicActivity) };
|
|
215
191
|
}
|
|
216
192
|
export async function implicitJoin(square, name, control) {
|
|
217
193
|
throwIfAborted(control);
|
|
@@ -238,7 +214,7 @@ export async function implicitJoin(square, name, control) {
|
|
|
238
214
|
await retireIdentityRoute(square, committed.name);
|
|
239
215
|
else if (committed.stored !== null)
|
|
240
216
|
await publishIdentityRoute(square, committed.name);
|
|
241
|
-
return { name: committed.name, state: committed.state, activity: committed.stored === null ? null :
|
|
217
|
+
return { name: committed.name, state: committed.state, activity: committed.stored === null ? null : toPublicActivity(committed.stored) };
|
|
242
218
|
}
|
|
243
219
|
/** Only admission failures can advertise a safe retry; storage and post-commit
|
|
244
220
|
* failures must not be mistaken for a rejected send merely by their error code. */
|
|
@@ -265,7 +241,7 @@ export async function express(square, name, body, options = {}, control) {
|
|
|
265
241
|
const decision = rejectUnsentActivity(() => {
|
|
266
242
|
const decision = decideAct(state, { name, body, force: options.force ?? false, now, mentions: options.mentions, ...(options.reach === undefined ? {} : { reach: options.reach }), ...(options.noMention === undefined ? {} : { noMention: options.noMention }), ...(reply === undefined ? {} : { reply }) });
|
|
267
243
|
if (decision.type === 'blocked') {
|
|
268
|
-
const pending = decision.activitySummaries.reduce((count, summary) => count + summary.count, 0)
|
|
244
|
+
const pending = decision.activitySummaries.reduce((count, summary) => count + summary.count, 0);
|
|
269
245
|
throw new SquareError('behind', `${participantIdentity(name)} has pending activity`, { pending });
|
|
270
246
|
}
|
|
271
247
|
if (decision.type === 'held') {
|
|
@@ -284,7 +260,7 @@ export async function express(square, name, body, options = {}, control) {
|
|
|
284
260
|
return decision;
|
|
285
261
|
});
|
|
286
262
|
const stored = committedActivity(storeActs(state, [decision.act]), 'express');
|
|
287
|
-
return { state, result: { stored } };
|
|
263
|
+
return { state, result: { stored, ...(decision.replyAuthorNotHere === undefined ? {} : { replyAuthorNotHere: decision.replyAuthorNotHere }) } };
|
|
288
264
|
}, control?.signal);
|
|
289
265
|
await ensureLocalPresence(square, name);
|
|
290
266
|
await publishIdentityRoute(square, name);
|
|
@@ -295,7 +271,7 @@ export async function express(square, name, body, options = {}, control) {
|
|
|
295
271
|
else {
|
|
296
272
|
delivery = { attempted: 0, accepted: 0, failed: 0, unknown: 0, notCapable: 1 };
|
|
297
273
|
}
|
|
298
|
-
return { activity:
|
|
274
|
+
return { activity: toPublicActivity(committed.stored), delivery, ...(committed.replyAuthorNotHere === undefined ? {} : { replyAuthorNotHere: committed.replyAuthorNotHere }) };
|
|
299
275
|
}
|
|
300
276
|
async function landListenerChange(square, verb, actor, target, control) {
|
|
301
277
|
throwIfAborted(control);
|
|
@@ -306,7 +282,7 @@ async function landListenerChange(square, verb, actor, target, control) {
|
|
|
306
282
|
return { result: null };
|
|
307
283
|
return { state, result: committedActivity(storeActs(state, [act]), verb) };
|
|
308
284
|
}, control?.signal);
|
|
309
|
-
return { activity: stored === null ? null :
|
|
285
|
+
return { activity: stored === null ? null : toPublicActivity(stored) };
|
|
310
286
|
}
|
|
311
287
|
export function listen(square, actor, target, control) { return landListenerChange(square, 'listen', actor, target, control); }
|
|
312
288
|
export function ignore(square, actor, target, control) { return landListenerChange(square, 'ignore', actor, target, control); }
|
|
@@ -318,7 +294,8 @@ async function landCore(square, verb, actor, body = '', fence = {}, control) {
|
|
|
318
294
|
// session's route retirement, and presence cleanup share one ownership critical section.
|
|
319
295
|
// A takeover finalizing between validation and commit can never be completed by a stale
|
|
320
296
|
// done, and no post-lock cleanup can remove a replacement owner's fresh rows.
|
|
321
|
-
const
|
|
297
|
+
const hostLedger = verb === 'done' ? square.hostLedger : undefined;
|
|
298
|
+
const fenced = hostLedger !== undefined;
|
|
322
299
|
const session = fenced ? processIdentity(square.env ?? process.env).session : undefined;
|
|
323
300
|
const commitAndCleanup = async () => {
|
|
324
301
|
if (verb === 'done' && !await assertLiveOwner(square, actor, fence.expectedEpoch)) {
|
|
@@ -326,7 +303,7 @@ async function landCore(square, verb, actor, body = '', fence = {}, control) {
|
|
|
326
303
|
}
|
|
327
304
|
let ownerRecords = [];
|
|
328
305
|
if (fenced && session !== undefined) {
|
|
329
|
-
ownerRecords = (await
|
|
306
|
+
ownerRecords = (await hostLedger.listPresence({ location: square.location, participant: actor }))
|
|
330
307
|
.filter((row) => row.session === session);
|
|
331
308
|
}
|
|
332
309
|
const now = square.clock();
|
|
@@ -339,16 +316,16 @@ async function landCore(square, verb, actor, body = '', fence = {}, control) {
|
|
|
339
316
|
return { state, result };
|
|
340
317
|
}, control?.signal);
|
|
341
318
|
for (const ownerRecord of ownerRecords) {
|
|
342
|
-
await
|
|
319
|
+
await hostLedger.removePresenceIfUnchanged(ownerRecord).catch((error) => {
|
|
343
320
|
process.stderr.write(`! host presence cleanup degraded: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
344
321
|
});
|
|
345
322
|
}
|
|
346
323
|
return stored;
|
|
347
324
|
};
|
|
348
325
|
const stored = fenced
|
|
349
|
-
? await
|
|
326
|
+
? await hostLedger.withClaimLock(commitAndCleanup, control?.signal)
|
|
350
327
|
: await commitAndCleanup();
|
|
351
|
-
return { activity:
|
|
328
|
+
return { activity: toPublicActivity(stored) };
|
|
352
329
|
}
|
|
353
330
|
export function done(square, name, body = '', fence = {}, control) { return landCore(square, 'done', name, body, fence, control); }
|
|
354
331
|
function liveClaimedParticipants(rows, sessionId) {
|
|
@@ -365,11 +342,9 @@ function liveClaimedParticipants(rows, sessionId) {
|
|
|
365
342
|
claimed.delete(key);
|
|
366
343
|
return claimed;
|
|
367
344
|
}
|
|
368
|
-
async function readLiveClaimedParticipants(
|
|
369
|
-
if (square.hostLedger === undefined)
|
|
370
|
-
return new Set();
|
|
345
|
+
async function readLiveClaimedParticipants(hostLedger, location, sessionId, now) {
|
|
371
346
|
try {
|
|
372
|
-
return liveClaimedParticipants(await
|
|
347
|
+
return liveClaimedParticipants(await hostLedger.listPresence({ location, now }), sessionId);
|
|
373
348
|
}
|
|
374
349
|
catch {
|
|
375
350
|
return undefined;
|
|
@@ -385,23 +360,24 @@ async function readLiveClaimedParticipants(square, location, sessionId) {
|
|
|
385
360
|
*/
|
|
386
361
|
export async function endOwnedSession(square, name, sessionId, expectedEpoch) {
|
|
387
362
|
const location = square.location;
|
|
363
|
+
const hostLedger = square.hostLedger;
|
|
388
364
|
const run = async () => {
|
|
389
365
|
const now = square.clock();
|
|
390
366
|
let currentSessionId;
|
|
391
367
|
let ownerMatches = expectedEpoch === undefined;
|
|
392
368
|
let ownerRecords = [];
|
|
393
369
|
let liveParticipants = new Set();
|
|
394
|
-
if (location !== undefined && location !== 'memory' &&
|
|
370
|
+
if (location !== undefined && location !== 'memory' && hostLedger !== undefined) {
|
|
395
371
|
try {
|
|
396
|
-
const rows = await
|
|
372
|
+
const rows = await hostLedger.listPresence({ location, now });
|
|
397
373
|
const bindings = rows.filter((row) => nameKey(row.participant) === nameKey(name));
|
|
398
374
|
currentSessionId = bindings.find((binding) => binding.session !== sessionId)?.session
|
|
399
375
|
?? bindings.toSorted((left, right) => (right.updatedAt ?? 0) - (left.updatedAt ?? 0))[0]?.session;
|
|
400
|
-
ownerRecords = rows.filter((row) => row.session === sessionId);
|
|
376
|
+
ownerRecords = rows.filter((row) => row.session === sessionId && nameKey(row.participant) === nameKey(name));
|
|
401
377
|
liveParticipants = liveClaimedParticipants(rows, sessionId);
|
|
402
378
|
if (expectedEpoch !== undefined) {
|
|
403
|
-
const owner = await readParticipantOwner(location, name,
|
|
404
|
-
ownerMatches = owner
|
|
379
|
+
const owner = await readParticipantOwner(location, name, hostLedger);
|
|
380
|
+
ownerMatches = owner !== undefined && owner.session === sessionId && presenceEpoch(owner) === expectedEpoch;
|
|
405
381
|
}
|
|
406
382
|
}
|
|
407
383
|
catch {
|
|
@@ -420,24 +396,23 @@ export async function endOwnedSession(square, name, sessionId, expectedEpoch) {
|
|
|
420
396
|
}
|
|
421
397
|
}
|
|
422
398
|
if (location !== undefined && location !== 'memory') {
|
|
423
|
-
dropEndedSessionWakeRoutesFromState(state, location, sessionId, { expectedEpoch, liveParticipants, force: committed !== null });
|
|
399
|
+
dropEndedSessionWakeRoutesFromState(state, location, sessionId, { expectedEpoch, liveParticipants, force: committed !== null, participant: name });
|
|
424
400
|
}
|
|
425
401
|
return { state, result: committed };
|
|
426
402
|
});
|
|
427
403
|
if (stored !== null) {
|
|
428
404
|
for (const ownerRecord of ownerRecords) {
|
|
429
|
-
await
|
|
405
|
+
await hostLedger.removePresenceIfUnchanged(ownerRecord).catch((error) => {
|
|
430
406
|
process.stderr.write(`! host presence cleanup degraded: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
431
407
|
});
|
|
432
408
|
}
|
|
433
409
|
}
|
|
434
410
|
return stored;
|
|
435
411
|
};
|
|
436
|
-
const
|
|
437
|
-
|
|
438
|
-
? await withOwnershipClaimLock(square.env ?? process.env, run)
|
|
412
|
+
const stored = hostLedger !== undefined
|
|
413
|
+
? await hostLedger.withClaimLock(run)
|
|
439
414
|
: await run();
|
|
440
|
-
return stored === null ? null : { activity:
|
|
415
|
+
return stored === null ? null : { activity: toPublicActivity(stored) };
|
|
441
416
|
}
|
|
442
417
|
/**
|
|
443
418
|
* Retire an ended session's orphan routes when no participant name is known. The
|
|
@@ -446,10 +421,11 @@ export async function endOwnedSession(square, name, sessionId, expectedEpoch) {
|
|
|
446
421
|
*/
|
|
447
422
|
export async function retireEndedSessionRoutes(square, sessionId) {
|
|
448
423
|
const location = square.location;
|
|
449
|
-
|
|
424
|
+
const hostLedger = square.hostLedger;
|
|
425
|
+
if (location === undefined || hostLedger === undefined)
|
|
450
426
|
return;
|
|
451
|
-
await
|
|
452
|
-
const liveParticipants = await readLiveClaimedParticipants(
|
|
427
|
+
await hostLedger.withClaimLock(async () => {
|
|
428
|
+
const liveParticipants = await readLiveClaimedParticipants(hostLedger, location, sessionId, square.clock());
|
|
453
429
|
await square.artifact.transact((state) => {
|
|
454
430
|
dropEndedSessionWakeRoutesFromState(state, location, sessionId, { liveParticipants, force: false });
|
|
455
431
|
return { state, result: undefined };
|
|
@@ -3,22 +3,23 @@ import { setTimeout as sleep } from 'node:timers/promises';
|
|
|
3
3
|
import { SquareError } from './model.js';
|
|
4
4
|
import { validateDoneBody } from './decisions.js';
|
|
5
5
|
import { currentHold, HELD_WAIT_BUDGET_MS } from './runtime.js';
|
|
6
|
-
import { Square
|
|
6
|
+
import { Square } from './square-wiring.js';
|
|
7
7
|
import { openSquare } from './square-file-adapter.js';
|
|
8
8
|
import { closeOpenSquare } from './open-square.js';
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
9
|
+
import { catchUp, express, ignore, listen, listening } from './square-actions.js';
|
|
10
|
+
import { participantHistory, resolveParticipant, statusPresentation, participantsPresentation } from './views.js';
|
|
11
|
+
import { hostLedgerForEnv, localParticipantName, localSessionIdentities, presenceEpoch, readParticipantOwner } from './registry.js';
|
|
11
12
|
function requireLocation(context) {
|
|
12
13
|
const selected = context.squarePath ?? context.env.SQUARE_LOCATION;
|
|
13
14
|
if (selected === undefined || selected.trim() === '')
|
|
14
15
|
throw new SquareError('invalid_args', 'Square application context needs a square location');
|
|
15
16
|
return path.resolve(context.cwd, selected);
|
|
16
17
|
}
|
|
17
|
-
async function requireParticipant(context, squarePath, env = context.env) {
|
|
18
|
+
async function requireParticipant(context, squarePath, hostLedger, env = context.env) {
|
|
18
19
|
const selected = context.participant ?? env.SQUARE_PARTICIPANT_NAME;
|
|
19
20
|
if (selected?.trim())
|
|
20
21
|
return selected;
|
|
21
|
-
const discovered = await localParticipantName(squarePath, env).catch(() => undefined);
|
|
22
|
+
const discovered = await localParticipantName(squarePath, hostLedger, env).catch(() => undefined);
|
|
22
23
|
if (discovered !== undefined)
|
|
23
24
|
return discovered;
|
|
24
25
|
throw new SquareError('invalid_args', 'Square application context needs one unambiguous participant name');
|
|
@@ -35,21 +36,22 @@ export function createSquareApplication(context) {
|
|
|
35
36
|
async function existing(operation, control) {
|
|
36
37
|
checkControl(control);
|
|
37
38
|
const squarePath = location();
|
|
38
|
-
const participantName = await requireParticipant(context, squarePath, env);
|
|
39
|
-
const
|
|
39
|
+
const participantName = await requireParticipant(context, squarePath, hostLedger, env);
|
|
40
|
+
const square = await openSquare(squarePath, { clock: context.clock, env, hostLedger, wakeTransport: context.wakeTransport });
|
|
40
41
|
try {
|
|
42
|
+
const { name } = await resolveParticipant(square, participantName);
|
|
41
43
|
checkControl(control);
|
|
42
|
-
return await operation(
|
|
44
|
+
return await operation(square, name);
|
|
43
45
|
}
|
|
44
46
|
finally {
|
|
45
|
-
await
|
|
47
|
+
await closeOpenSquare(square);
|
|
46
48
|
}
|
|
47
49
|
}
|
|
48
50
|
async function joined(operation, control) {
|
|
49
51
|
checkControl(control);
|
|
50
52
|
const square = await open();
|
|
51
53
|
try {
|
|
52
|
-
return await operation(await square.join(await requireParticipant(context, square.location, env), control));
|
|
54
|
+
return await operation(await square.join(await requireParticipant(context, square.location, hostLedger, env), control));
|
|
53
55
|
}
|
|
54
56
|
finally {
|
|
55
57
|
await square.close();
|
|
@@ -61,12 +63,12 @@ export function createSquareApplication(context) {
|
|
|
61
63
|
const squarePath = location();
|
|
62
64
|
const square = await open();
|
|
63
65
|
try {
|
|
64
|
-
const participantName = await requireParticipant(context, squarePath, env);
|
|
66
|
+
const participantName = await requireParticipant(context, squarePath, hostLedger, env);
|
|
65
67
|
const before = await square.snapshot();
|
|
66
68
|
const standing = before.participants.some((item) => item.name.toLocaleLowerCase() === participantName.toLocaleLowerCase() && item.state === 'joined');
|
|
67
69
|
const identity = localSessionIdentities(env)[0];
|
|
68
|
-
const owner = standing ? await readParticipantOwner(squarePath, participantName,
|
|
69
|
-
const reconnect = standing && identity !== undefined && owner?.
|
|
70
|
+
const owner = standing ? await readParticipantOwner(squarePath, participantName, hostLedger).catch(() => undefined) : undefined;
|
|
71
|
+
const reconnect = standing && identity !== undefined && owner?.session === identity.sessionId;
|
|
70
72
|
if (standing && owner !== undefined && identity !== undefined && !options.takeover && !reconnect) {
|
|
71
73
|
throw new SquareError('already_joined', `✕ ${participantName} already stands here — another session holds the name`, { pending: before.participants.length });
|
|
72
74
|
}
|
|
@@ -88,7 +90,7 @@ export function createSquareApplication(context) {
|
|
|
88
90
|
while (true) {
|
|
89
91
|
checkControl(control);
|
|
90
92
|
try {
|
|
91
|
-
const result = await existing((
|
|
93
|
+
const result = await existing((square, name) => express(square, name, body, options, control), control);
|
|
92
94
|
return { ...result, waited };
|
|
93
95
|
}
|
|
94
96
|
catch (error) {
|
|
@@ -125,11 +127,11 @@ export function createSquareApplication(context) {
|
|
|
125
127
|
}
|
|
126
128
|
}
|
|
127
129
|
},
|
|
128
|
-
catch(options, control) { return existing((
|
|
130
|
+
catch(options, control) { return existing((square, name) => catchUp(square, name, options, undefined, control), control); },
|
|
129
131
|
async history(query, control) {
|
|
130
132
|
checkControl(control);
|
|
131
133
|
if (context.participant !== undefined || env.SQUARE_PARTICIPANT_NAME !== undefined)
|
|
132
|
-
return existing((
|
|
134
|
+
return existing((square, name) => participantHistory(square, name, query, control), control);
|
|
133
135
|
const square = await open();
|
|
134
136
|
try {
|
|
135
137
|
checkControl(control);
|
|
@@ -139,21 +141,21 @@ export function createSquareApplication(context) {
|
|
|
139
141
|
await square.close();
|
|
140
142
|
}
|
|
141
143
|
},
|
|
142
|
-
listen(target, control) { return existing((
|
|
143
|
-
ignore(target, control) { return existing((
|
|
144
|
-
listening(control) { return existing((
|
|
144
|
+
listen(target, control) { return existing((square, name) => listen(square, name, target, control), control); },
|
|
145
|
+
ignore(target, control) { return existing((square, name) => ignore(square, name, target, control), control); },
|
|
146
|
+
listening(control) { return existing((square, name) => listening(square, name, control), control); },
|
|
145
147
|
hold(reason, control) { return joined((participant) => participant.hold(reason, control), control); },
|
|
146
148
|
resume(control) { return joined((participant) => participant.resume(control), control); },
|
|
147
149
|
async done(body, control) {
|
|
148
150
|
validateDoneBody(body);
|
|
149
151
|
const squarePath = location();
|
|
150
|
-
const participantName = await requireParticipant(context, squarePath, env);
|
|
152
|
+
const participantName = await requireParticipant(context, squarePath, hostLedger, env);
|
|
151
153
|
const identity = localSessionIdentities(env)[0];
|
|
152
|
-
const owner = identity === undefined ? undefined : await readParticipantOwner(squarePath, participantName,
|
|
153
|
-
if (identity !== undefined && owner?.
|
|
154
|
+
const owner = identity === undefined ? undefined : await readParticipantOwner(squarePath, participantName, hostLedger).catch(() => undefined);
|
|
155
|
+
if (identity !== undefined && owner?.session === identity.sessionId && presenceEpoch(owner) > 0) {
|
|
154
156
|
const square = await open();
|
|
155
157
|
try {
|
|
156
|
-
return await square.doneOwnedSession(participantName, body ?? '', owner
|
|
158
|
+
return await square.doneOwnedSession(participantName, body ?? '', presenceEpoch(owner), control);
|
|
157
159
|
}
|
|
158
160
|
finally {
|
|
159
161
|
await square.close();
|