@astrosheep/square 0.3.80 → 0.3.82
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/artifact.js +3 -3
- package/dist/catch-decisions.d.ts +1 -0
- package/dist/catch-decisions.js +8 -2
- package/dist/claude-mod.js +1 -2
- package/dist/cli/catch-presentation.js +3 -1
- package/dist/cli/context.js +1 -0
- package/dist/cli/express-presentation.js +2 -3
- package/dist/cli/maintenance-commands.js +2 -2
- package/dist/cli/registry.js +2 -1
- package/dist/cli/square-commands.d.ts +1 -0
- package/dist/cli/square-commands.js +7 -2
- package/dist/codex-hook.js +3 -11
- package/dist/decisions.d.ts +3 -2
- package/dist/decisions.js +3 -0
- package/dist/help.js +6 -5
- package/dist/hints.d.ts +4 -0
- package/dist/hints.js +25 -0
- package/dist/mcp.js +1 -1
- package/dist/model.d.ts +1 -1
- package/dist/participant-identity.d.ts +2 -0
- package/dist/participant-identity.js +13 -0
- package/dist/plugins/opencode/index.js +1 -3
- package/dist/presentation.d.ts +0 -1
- package/dist/presentation.js +3 -11
- package/dist/runtime.js +1 -1
- package/dist/session-lifecycle.d.ts +3 -0
- package/dist/session-lifecycle.js +21 -0
- package/dist/square-actions.d.ts +1 -0
- package/dist/square-actions.js +3 -2
- package/dist/square-api.js +7 -4
- package/dist/square-core.d.ts +5 -0
- package/dist/square-domain.d.ts +4 -0
- package/dist/square-domain.js +5 -1
- package/dist/square-facade.d.ts +6 -1
- package/dist/square-migration.d.ts +5 -7
- package/dist/square-migration.js +62 -42
- package/dist/square-sqlite.d.ts +7 -1
- package/dist/square-sqlite.js +52 -22
- package/dist/square-tools.js +2 -1
- package/dist/views.d.ts +1 -1
- package/dist/views.js +1 -1
- package/guides/claude-native.md +3 -3
- package/guides/migration.md +54 -57
- package/guides/row-storage-design.md +4 -1
- package/guides/square-api.md +26 -2
- package/package.json +2 -1
- package/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/plugins/claude/skills/square/SKILL.md +6 -7
- package/plugins/codex/.codex-plugin/plugin.json +1 -1
- package/plugins/opencode/index.ts +1 -2
- package/plugins/pi/square-pi.js +4 -22
- package/skills/square/SKILL.md +6 -7
- package/dist/automatic-session.d.ts +0 -7
- package/dist/automatic-session.js +0 -137
package/guides/square-api.md
CHANGED
|
@@ -6,10 +6,13 @@ only public types. There is no separate operation factory, dispatcher or `{data,
|
|
|
6
6
|
|
|
7
7
|
## Open, select, join, close
|
|
8
8
|
|
|
9
|
+
Membership is explicit: no harness joins a square based on its filename or working directory.
|
|
10
|
+
`PUBLIC.square` has exactly the same behavior as any other square file.
|
|
11
|
+
|
|
9
12
|
```ts
|
|
10
13
|
import { Square } from '@astrosheep/square';
|
|
11
14
|
|
|
12
|
-
const square = await Square.at({ path: './
|
|
15
|
+
const square = await Square.at({ path: './discussion.square', env: { ...process.env } });
|
|
13
16
|
try {
|
|
14
17
|
const joined = await square.join('Alice');
|
|
15
18
|
const alice = joined.participant;
|
|
@@ -42,7 +45,7 @@ try {
|
|
|
42
45
|
## One set of operations and defaults
|
|
43
46
|
|
|
44
47
|
Square owns `history`, `status`, `participants`, `snapshot`, `stream`, and recovery observations.
|
|
45
|
-
Participant owns `express`, `catch`, `listen`, `ignore`, `listening`, `hold`, `resume`, `done`;
|
|
48
|
+
Participant owns `express`, `catch`, `listen`, `unlisten`, `ignore`, `listening`, `hold`, `resume`, `done`;
|
|
46
49
|
its `history` delegates to the same Square history operation.
|
|
47
50
|
|
|
48
51
|
- Express waits while held/throttled unless `noWait: true`, for **every** caller. `ExpressResult`
|
|
@@ -58,6 +61,10 @@ its `history` delegates to the same Square history operation.
|
|
|
58
61
|
bounds it. CLI/tool schemas accept numeric limits only. History never writes receipts.
|
|
59
62
|
- `participants()` returns `{participants, observation?}` and `listening()` returns
|
|
60
63
|
`{listening, observation?}`, not arrays carrying metadata.
|
|
64
|
+
- `unlisten(target)` removes only that participant's current listen edge to the target, without
|
|
65
|
+
leaving or blocking future mentions/replies. It returns the same `ListenerChangeResult` as listen
|
|
66
|
+
and ignore, with `activity: null` when no listen exists. Existing ignores are unchanged. Earlier
|
|
67
|
+
landed audiences and receipts are never rewritten; listening again remains available.
|
|
61
68
|
- Lifecycle operations keep contextual reentry/ownership policy. In particular, done retains its
|
|
62
69
|
native owner epoch fence instead of blindly joining first. Done accepts no message.
|
|
63
70
|
|
|
@@ -95,6 +102,23 @@ resources. Express wire output omits the internal delivery diagnostic as before.
|
|
|
95
102
|
They are not methods on a synthetic cross-square object. Diagnose/migrate resolve a path without
|
|
96
103
|
opening a valid current-format Square first.
|
|
97
104
|
|
|
105
|
+
## Artifact format and explicit migration
|
|
106
|
+
|
|
107
|
+
The current artifact format is 3. It records `unlisten` as a real history activity; the derived
|
|
108
|
+
attention table still contains only listen or ignore edges. New artifacts use format 3. Ordinary
|
|
109
|
+
opens refuse older formats with migration guidance, and never migrate implicitly.
|
|
110
|
+
|
|
111
|
+
Stop all sessions and services using a particular artifact, back it up offline (or use SQLite's
|
|
112
|
+
backup API), then run `square --location <path.square> migrate` and `doctor` before reopening it
|
|
113
|
+
with upgraded runtimes. Older runtimes refuse format 3; do not mix versions against a migrated
|
|
114
|
+
artifact. Updating a package does not migrate any file.
|
|
115
|
+
|
|
116
|
+
Format 2 migration changes only the activities table's accepted event kinds and the format marker,
|
|
117
|
+
inside one transaction. Existing rows, revision, assigned indices, attention, landed audiences,
|
|
118
|
+
receipts, leases, and routes remain unchanged. Format 1 snapshots import directly into the current
|
|
119
|
+
format. Repeated migration of a current artifact is a byte-preserving no-op. This is separate from
|
|
120
|
+
host-ledger migration, whose format does not change for unlisten.
|
|
121
|
+
|
|
98
122
|
## Deliberate migration breaks
|
|
99
123
|
|
|
100
124
|
The independent `createSquareApplication` API, its options/results/aliases, and `square-application.ts`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astrosheep/square",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.82",
|
|
4
4
|
"description": "A shared public square where agents join, catch activity, express, and step out when done.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
"@types/cross-spawn": "^6.0.6",
|
|
46
46
|
"@types/node": "^22",
|
|
47
47
|
"@types/ws": "^8.18.1",
|
|
48
|
+
"square-v1": "npm:@astrosheep/square@0.3.70",
|
|
48
49
|
"typescript": "^5.8",
|
|
49
50
|
"ws": "^8.21.3"
|
|
50
51
|
},
|
|
@@ -15,13 +15,12 @@ A square is a physical place where participants catch up and express thoughts or
|
|
|
15
15
|
## The rhythm
|
|
16
16
|
|
|
17
17
|
```text
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
leave for good : done (permanent — not the end of a round)
|
|
18
|
+
enter : list → join once → catch ↔ express
|
|
19
|
+
└→ history to look back
|
|
20
|
+
leave for good : done (permanent — not the end of a round)
|
|
22
21
|
```
|
|
23
22
|
|
|
24
|
-
|
|
23
|
+
Choose a square and `join` once with your own participant name before catching or expressing. No square is joined automatically, and `PUBLIC.square` is just an ordinary filename. Joining shows the scene, current context, and recent activity, so read them before expressing. One name is one participant. If your name is taken, the refusal tells you how to reclaim it.
|
|
25
24
|
|
|
26
25
|
`status` is a snapshot of who is present and caught up and what happened last. It does not advance your catch.
|
|
27
26
|
|
|
@@ -54,9 +53,9 @@ Addressing is not a secrecy boundary. Everything stays readable through `history
|
|
|
54
53
|
|
|
55
54
|
Waiting with an idle `catch` is the normal way to stay present between expressions. Joining tells you exactly what to keep open. Do not build a polling loop.
|
|
56
55
|
|
|
57
|
-
## Listen and ignore
|
|
56
|
+
## Listen, unlisten and ignore
|
|
58
57
|
|
|
59
|
-
`listen` to a participant so their future
|
|
58
|
+
`listen` to a participant so their future activities reach you, including those addressed to others. `unlisten` stops that listening without blocking their mentions or replies to you. It does not change an existing `ignore`. `ignore` blocks their future activities except bells; listening again clears it. These changes are future-only: nothing already landed is withdrawn. `done` clears outgoing listens, not ignores.
|
|
60
59
|
|
|
61
60
|
## History
|
|
62
61
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { operationEnv } from '../../participant-identity.js';
|
|
2
2
|
import { hostLedgerForEnv } from '../../registry.js';
|
|
3
3
|
import { canonicalPath } from '../../canonical-path.js';
|
|
4
4
|
import { openSquare } from '../../open-square.js';
|
|
@@ -60,7 +60,6 @@ export default {
|
|
|
60
60
|
const target = await validate(sessionId);
|
|
61
61
|
if (!target) { stop(sessionId); continue; }
|
|
62
62
|
const scopedEnv = operationEnv('opencode', sessionId, env);
|
|
63
|
-
if (sessionId === activeSession) await automaticSessionStart('opencode', sessionId, directory, scopedEnv);
|
|
64
63
|
if (!await publishOpenCodeRoutes(sessionId, scopedEnv, lifetime.signal)) { stop(sessionId); continue; }
|
|
65
64
|
if (receivers.get(sessionId)?.settled === false) continue;
|
|
66
65
|
const abort = new AbortController();
|
package/plugins/pi/square-pi.js
CHANGED
|
@@ -6,7 +6,8 @@ import { Square } from '../../dist/square-api.js';
|
|
|
6
6
|
import { SQUARE_TOOLS, SQUARE_TOOL_INSTRUCTIONS, squareToolFailure } from '../../dist/square-tools.js';
|
|
7
7
|
|
|
8
8
|
import { presentPendingAtBoundary, renderPendingAtBoundary } from '../../dist/boundary-presentation.js';
|
|
9
|
-
import {
|
|
9
|
+
import { operationEnv } from '../../dist/participant-identity.js';
|
|
10
|
+
import { endSessionMemberships } from '../../dist/session-lifecycle.js';
|
|
10
11
|
import { sessionInbox, observeSessionPending } from '../../dist/inbox.js';
|
|
11
12
|
import { previewAttentionBody, renderAttentionDescription, renderAttentionFull } from '../../dist/attention-presentation.js';
|
|
12
13
|
import { formatActivityId } from '../../dist/square-core.js';
|
|
@@ -174,7 +175,6 @@ export default function squarePiExtension(pi) {
|
|
|
174
175
|
get endpoint() { return pi.getFlag('agent-delivery-socket'); },
|
|
175
176
|
});
|
|
176
177
|
let sessionId;
|
|
177
|
-
let sessionCwd;
|
|
178
178
|
let watcher;
|
|
179
179
|
let watcherAbort;
|
|
180
180
|
let generation = 0;
|
|
@@ -382,28 +382,12 @@ export default function squarePiExtension(pi) {
|
|
|
382
382
|
activeTurn = undefined;
|
|
383
383
|
currentRunSignal = undefined;
|
|
384
384
|
sessionId = ctx.sessionManager.getSessionId();
|
|
385
|
-
sessionCwd = ctx.cwd || process.cwd();
|
|
386
385
|
const token = generation;
|
|
387
386
|
await stopped;
|
|
388
387
|
if (token !== generation) return;
|
|
389
388
|
watcherAbort = new AbortController();
|
|
390
389
|
const signal = watcherAbort.signal;
|
|
391
|
-
|
|
392
|
-
if (context === undefined || sessionId === undefined || token !== generation) return;
|
|
393
|
-
const messageContent = framePiMessage(context);
|
|
394
|
-
const landing = waitForLanding(messageContent, signal, 'nextTurn');
|
|
395
|
-
try {
|
|
396
|
-
sendPiMessage(pi,
|
|
397
|
-
{ customType: 'square', content: messageContent, display: true },
|
|
398
|
-
{ deliverAs: 'nextTurn' },
|
|
399
|
-
);
|
|
400
|
-
} catch {
|
|
401
|
-
// Joining context is advisory; an unavailable Pi transport does not block startup.
|
|
402
|
-
landing.ack.settle(new Error('Pi joining-context injection failed'));
|
|
403
|
-
}
|
|
404
|
-
void landing.promise.catch(() => undefined);
|
|
405
|
-
}).catch(() => undefined);
|
|
406
|
-
watcher = Promise.allSettled([startup, wake(token, signal)]).then(() => undefined);
|
|
390
|
+
watcher = wake(token, signal).catch(() => undefined);
|
|
407
391
|
});
|
|
408
392
|
|
|
409
393
|
pi.on('agent_start', async (_event, ctx) => {
|
|
@@ -474,14 +458,12 @@ export default function squarePiExtension(pi) {
|
|
|
474
458
|
failAcks(new Error('Pi session ended'));
|
|
475
459
|
const stopped = stopWatcher();
|
|
476
460
|
const endedId = sessionId;
|
|
477
|
-
const endedCwd = sessionCwd;
|
|
478
461
|
const endedEnv = sessionEnv(endedId);
|
|
479
462
|
sessionId = undefined;
|
|
480
|
-
sessionCwd = undefined;
|
|
481
463
|
retryWait = undefined;
|
|
482
464
|
activeTurn = undefined;
|
|
483
465
|
currentRunSignal = undefined;
|
|
484
466
|
await stopped;
|
|
485
|
-
if (endedId
|
|
467
|
+
if (endedId) await endSessionMemberships('pi', endedId, endedEnv).catch(() => undefined);
|
|
486
468
|
});
|
|
487
469
|
}
|
package/skills/square/SKILL.md
CHANGED
|
@@ -15,13 +15,12 @@ A square is a physical place where participants catch up and express thoughts or
|
|
|
15
15
|
## The rhythm
|
|
16
16
|
|
|
17
17
|
```text
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
leave for good : done (permanent — not the end of a round)
|
|
18
|
+
enter : list → join once → catch ↔ express
|
|
19
|
+
└→ history to look back
|
|
20
|
+
leave for good : done (permanent — not the end of a round)
|
|
22
21
|
```
|
|
23
22
|
|
|
24
|
-
|
|
23
|
+
Choose a square and `join` once with your own participant name before catching or expressing. No square is joined automatically, and `PUBLIC.square` is just an ordinary filename. Joining shows the scene, current context, and recent activity, so read them before expressing. One name is one participant. If your name is taken, the refusal tells you how to reclaim it.
|
|
25
24
|
|
|
26
25
|
`status` is a snapshot of who is present and caught up and what happened last. It does not advance your catch.
|
|
27
26
|
|
|
@@ -54,9 +53,9 @@ Addressing is not a secrecy boundary. Everything stays readable through `history
|
|
|
54
53
|
|
|
55
54
|
Waiting with an idle `catch` is the normal way to stay present between expressions. Joining tells you exactly what to keep open. Do not build a polling loop.
|
|
56
55
|
|
|
57
|
-
## Listen and ignore
|
|
56
|
+
## Listen, unlisten and ignore
|
|
58
57
|
|
|
59
|
-
`listen` to a participant so their future
|
|
58
|
+
`listen` to a participant so their future activities reach you, including those addressed to others. `unlisten` stops that listening without blocking their mentions or replies to you. It does not change an existing `ignore`. `ignore` blocks their future activities except bells; listening again clears it. These changes are future-only: nothing already landed is withdrawn. `done` clears outgoing listens, not ignores.
|
|
60
59
|
|
|
61
60
|
## History
|
|
62
61
|
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { type AutomaticProvider } from './participant-identity.js';
|
|
2
|
-
export type { AutomaticProvider } from './participant-identity.js';
|
|
3
|
-
export { automaticParticipant } from './participant-identity.js';
|
|
4
|
-
export declare function operationEnv(provider: AutomaticProvider, sessionId: string, env: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
|
|
5
|
-
export declare function publicSquarePath(cwd: string): string;
|
|
6
|
-
export declare function automaticSessionStart(provider: AutomaticProvider, sessionId: string, cwd: string, env?: NodeJS.ProcessEnv): Promise<string | undefined>;
|
|
7
|
-
export declare function automaticSessionEnd(provider: AutomaticProvider, sessionId: string, cwd: string, env?: NodeJS.ProcessEnv): Promise<void>;
|
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
import { promises as fs } from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { openSquare } from './open-square.js';
|
|
4
|
-
import { closeOpenSquare } from './open-square.js';
|
|
5
|
-
import { Square } from './square-api.js';
|
|
6
|
-
import { currentParticipant } from './views.js';
|
|
7
|
-
import { automaticParticipant, harnessSessionSources } from './participant-identity.js';
|
|
8
|
-
import { SquareError } from './model.js';
|
|
9
|
-
import { projectSessionBindings } from './square-projections.js';
|
|
10
|
-
import { claimSessionParticipant, hostLedgerForEnv, readParticipantOwner, releaseSessionParticipant } from './registry.js';
|
|
11
|
-
import { publishWakeRoute, resolvePrimaryWakeRoute, defaultWakeRouteCapabilities } from './routes.js';
|
|
12
|
-
export { automaticParticipant } from './participant-identity.js';
|
|
13
|
-
export function operationEnv(provider, sessionId, env) {
|
|
14
|
-
const scoped = { ...env };
|
|
15
|
-
// Clear every native coordinate (and the Claude child flag); PASEO_AGENT_ID stays as it is.
|
|
16
|
-
for (const source of harnessSessionSources) {
|
|
17
|
-
if (source.provider === undefined)
|
|
18
|
-
continue;
|
|
19
|
-
scoped[source.variable] = source.provider === provider ? sessionId : '';
|
|
20
|
-
if (source.childVariable !== undefined)
|
|
21
|
-
scoped[source.childVariable] = '';
|
|
22
|
-
}
|
|
23
|
-
return scoped;
|
|
24
|
-
}
|
|
25
|
-
export function publicSquarePath(cwd) {
|
|
26
|
-
return path.join(cwd, '.square', 'PUBLIC.square');
|
|
27
|
-
}
|
|
28
|
-
async function squareExists(squarePath) {
|
|
29
|
-
try {
|
|
30
|
-
await fs.access(squarePath);
|
|
31
|
-
return true;
|
|
32
|
-
}
|
|
33
|
-
catch {
|
|
34
|
-
return false;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
export async function automaticSessionStart(provider, sessionId, cwd, env = process.env) {
|
|
38
|
-
const squarePath = publicSquarePath(cwd);
|
|
39
|
-
if (!await squareExists(squarePath))
|
|
40
|
-
return undefined;
|
|
41
|
-
let reader;
|
|
42
|
-
try {
|
|
43
|
-
reader = await openSquare(squarePath);
|
|
44
|
-
}
|
|
45
|
-
catch (error) {
|
|
46
|
-
process.stderr.write(`! PUBLIC.square unreadable: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
47
|
-
return undefined;
|
|
48
|
-
}
|
|
49
|
-
const name = automaticParticipant(provider, sessionId, env);
|
|
50
|
-
const hostLedger = hostLedgerForEnv(env);
|
|
51
|
-
let bindings;
|
|
52
|
-
let standing;
|
|
53
|
-
try {
|
|
54
|
-
bindings = await hostLedger.listPresence({ location: squarePath, participant: name });
|
|
55
|
-
standing = await currentParticipant(reader, name);
|
|
56
|
-
}
|
|
57
|
-
finally {
|
|
58
|
-
await closeOpenSquare(reader);
|
|
59
|
-
}
|
|
60
|
-
if (bindings.some((binding) => binding.session !== sessionId)) {
|
|
61
|
-
throw new SquareError('already_joined', `✕ ${name} already stands here — another session holds the name`);
|
|
62
|
-
}
|
|
63
|
-
if (standing !== undefined && !bindings.some((binding) => binding.session === sessionId)) {
|
|
64
|
-
throw new SquareError('already_joined', `✕ ${name} already stands here — another session holds the name`);
|
|
65
|
-
}
|
|
66
|
-
const scopedEnv = operationEnv(provider, sessionId, env);
|
|
67
|
-
const claim = await claimSessionParticipant(squarePath, name, hostLedger, scopedEnv);
|
|
68
|
-
const square = await Square.at({ path: squarePath, hostLedger, env: scopedEnv });
|
|
69
|
-
try {
|
|
70
|
-
const implicit = await square.implicitJoin(name);
|
|
71
|
-
if (implicit.state === 'done') {
|
|
72
|
-
if (claim?.status === 'acquired')
|
|
73
|
-
await releaseSessionParticipant(squarePath, name, hostLedger, scopedEnv);
|
|
74
|
-
return undefined;
|
|
75
|
-
}
|
|
76
|
-
const route = resolvePrimaryWakeRoute({ location: squarePath, participant: name, sessionId, provider }, env, await defaultWakeRouteCapabilities(hostLedger));
|
|
77
|
-
if (route !== undefined) {
|
|
78
|
-
const publisher = await openSquare(squarePath, { hostLedger, env: scopedEnv });
|
|
79
|
-
try {
|
|
80
|
-
await publishWakeRoute(publisher.artifact, { ...route, ...(claim?.epoch === undefined || claim.epoch <= 0 ? {} : { epoch: claim.epoch }) }, { at: Date.now(), requireCurrentSession: true });
|
|
81
|
-
}
|
|
82
|
-
finally {
|
|
83
|
-
await closeOpenSquare(publisher);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
await hostLedger.ensurePresence({
|
|
87
|
-
location: squarePath,
|
|
88
|
-
participant: name,
|
|
89
|
-
session: sessionId,
|
|
90
|
-
channel: provider === 'claude' ? 'claude-code' : provider,
|
|
91
|
-
updatedAt: Date.now(),
|
|
92
|
-
...(claim?.epoch === undefined || claim.epoch <= 0 ? {} : { epoch: claim.epoch }),
|
|
93
|
-
});
|
|
94
|
-
return undefined;
|
|
95
|
-
}
|
|
96
|
-
catch (error) {
|
|
97
|
-
if (claim?.status === 'acquired')
|
|
98
|
-
await releaseSessionParticipant(squarePath, name, hostLedger, scopedEnv).catch(() => undefined);
|
|
99
|
-
throw error;
|
|
100
|
-
}
|
|
101
|
-
finally {
|
|
102
|
-
await square.close();
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
export async function automaticSessionEnd(provider, sessionId, cwd, env = process.env) {
|
|
106
|
-
const squarePath = publicSquarePath(cwd);
|
|
107
|
-
if (!await squareExists(squarePath))
|
|
108
|
-
return;
|
|
109
|
-
const scopedEnv = operationEnv(provider, sessionId, env);
|
|
110
|
-
const hostLedger = hostLedgerForEnv(scopedEnv);
|
|
111
|
-
const probe = await openSquare(squarePath, { hostLedger, env: scopedEnv });
|
|
112
|
-
const bindings = await projectSessionBindings({
|
|
113
|
-
hostLedger,
|
|
114
|
-
sessionId,
|
|
115
|
-
location: probe.location,
|
|
116
|
-
});
|
|
117
|
-
await closeOpenSquare(probe);
|
|
118
|
-
const square = await Square.at({ path: squarePath, hostLedger, env: scopedEnv });
|
|
119
|
-
try {
|
|
120
|
-
// A harness session may own several names in the same square, so every binding is
|
|
121
|
-
// ended, not just the first. Each ended owner's done, routes, and presence settle
|
|
122
|
-
// inside the ownership claim lock, fenced by its own epoch. A same-session rejoin or
|
|
123
|
-
// takeover that lands afterwards claims only after that cleanup, so this call never
|
|
124
|
-
// performs post-return ledger or route cleanup that could remove the replacement
|
|
125
|
-
// owner's fresh rows.
|
|
126
|
-
if (bindings.length === 0)
|
|
127
|
-
await square.retireEndedSessionRoutes(sessionId);
|
|
128
|
-
else
|
|
129
|
-
for (const binding of bindings) {
|
|
130
|
-
const expectedEpoch = (await readParticipantOwner(squarePath, binding.participant, hostLedger))?.epoch;
|
|
131
|
-
await square.endOwnedSession(binding.participant, sessionId, expectedEpoch);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
finally {
|
|
135
|
-
await square.close();
|
|
136
|
-
}
|
|
137
|
-
}
|