@ccmsg/cli 0.2.4 → 0.2.6
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/package.json +1 -1
- package/src/mesh/relay.ts +21 -5
- package/src/sessions/last-live.ts +17 -3
- package/src/sessions/registry.ts +1 -1
package/package.json
CHANGED
package/src/mesh/relay.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
|
+
type AgentInfo,
|
|
2
3
|
type InstanceId,
|
|
3
4
|
LAST_LIVE_RETENTION_MS,
|
|
5
|
+
type LastLiveSession,
|
|
4
6
|
type PeerInfo,
|
|
5
7
|
PLAIN_TOPICS,
|
|
6
8
|
type Sid,
|
|
@@ -116,12 +118,16 @@ export class Relay {
|
|
|
116
118
|
return values;
|
|
117
119
|
}
|
|
118
120
|
|
|
119
|
-
/** Which instance a session belongs to, read from the
|
|
120
|
-
*
|
|
121
|
+
/** Which instance a session belongs to, read from the cluster values the
|
|
122
|
+
* peers stated (§7.3).
|
|
121
123
|
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
124
|
+
* `peers` names every session an instance currently holds — connected or in
|
|
125
|
+
* `last_live` — and is checked first. A session hello has not reached yet
|
|
126
|
+
* has no row there but the harness may already know of it, so `agents` is
|
|
127
|
+
* checked next; `last_live` is the last resort for one whose instance has
|
|
128
|
+
* not stated `agents` at all. Every row names its own instance rather than
|
|
129
|
+
* the one that relayed it, so a value that travelled through a third
|
|
130
|
+
* instance still points at the session's own. */
|
|
125
131
|
owner(sid: Sid): InstanceId | undefined {
|
|
126
132
|
this.#sweep();
|
|
127
133
|
for (const held of this.#held.values()) {
|
|
@@ -129,6 +135,16 @@ export class Relay {
|
|
|
129
135
|
const row = value?.peers?.find((peer) => peer.sid === sid);
|
|
130
136
|
if (row !== undefined) return row.instance;
|
|
131
137
|
}
|
|
138
|
+
for (const held of this.#held.values()) {
|
|
139
|
+
const value = held.get("agents") as { agents?: AgentInfo[] } | undefined;
|
|
140
|
+
const row = value?.agents?.find((agent) => agent.sid === sid);
|
|
141
|
+
if (row !== undefined) return row.instance;
|
|
142
|
+
}
|
|
143
|
+
for (const held of this.#held.values()) {
|
|
144
|
+
const value = held.get("peers") as { last_live?: LastLiveSession[] } | undefined;
|
|
145
|
+
const row = value?.last_live?.find((session) => session.sid === sid);
|
|
146
|
+
if (row !== undefined) return row.instance;
|
|
147
|
+
}
|
|
132
148
|
return undefined;
|
|
133
149
|
}
|
|
134
150
|
|
|
@@ -2,6 +2,7 @@ import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
import {
|
|
4
4
|
LAST_LIVE_RETENTION_MS,
|
|
5
|
+
type InstanceId,
|
|
5
6
|
type LastLiveSession,
|
|
6
7
|
type Sid,
|
|
7
8
|
type Timestamp,
|
|
@@ -35,7 +36,14 @@ const VERSION = 1;
|
|
|
35
36
|
export class LastLiveStore {
|
|
36
37
|
#entries = new Map<Sid, StoredEntry>();
|
|
37
38
|
|
|
38
|
-
|
|
39
|
+
/** `id` is this instance's own: every entry this store holds is by
|
|
40
|
+
* definition an observation *this* instance made, so `instance` is forced
|
|
41
|
+
* to it on both ends (load and record) rather than trusted from whatever
|
|
42
|
+
* the field on disk happens to say. */
|
|
43
|
+
constructor(
|
|
44
|
+
private readonly file: string,
|
|
45
|
+
private readonly id: InstanceId,
|
|
46
|
+
) {}
|
|
39
47
|
|
|
40
48
|
/** Read at startup (§8.3 step 4), before anything can ask for the list. A
|
|
41
49
|
* file that is missing or unreadable starts an empty list: the daemon has no
|
|
@@ -50,7 +58,9 @@ export class LastLiveStore {
|
|
|
50
58
|
const sessions = (document as Document | null)?.sessions;
|
|
51
59
|
if (!Array.isArray(sessions)) return;
|
|
52
60
|
for (const entry of sessions as StoredEntry[]) {
|
|
53
|
-
if (typeof entry?.sid === "string")
|
|
61
|
+
if (typeof entry?.sid === "string") {
|
|
62
|
+
this.#entries.set(entry.sid, { ...entry, instance: this.id });
|
|
63
|
+
}
|
|
54
64
|
}
|
|
55
65
|
this.#prune(now);
|
|
56
66
|
}
|
|
@@ -71,7 +81,11 @@ export class LastLiveStore {
|
|
|
71
81
|
* Paused rather than Disappeared (§5.2). */
|
|
72
82
|
record(entry: StoredEntry): void {
|
|
73
83
|
const stopped = this.#entries.get(entry.sid)?.stopped_at ?? entry.stopped_at;
|
|
74
|
-
this.#entries.set(entry.sid,
|
|
84
|
+
this.#entries.set(entry.sid, {
|
|
85
|
+
...entry,
|
|
86
|
+
instance: this.id,
|
|
87
|
+
...(stopped === undefined ? {} : { stopped_at: stopped }),
|
|
88
|
+
});
|
|
75
89
|
this.#save();
|
|
76
90
|
}
|
|
77
91
|
|
package/src/sessions/registry.ts
CHANGED
|
@@ -190,7 +190,7 @@ export class Sessions implements UpstreamResource {
|
|
|
190
190
|
() => this.changed(),
|
|
191
191
|
deps.pollMs,
|
|
192
192
|
);
|
|
193
|
-
this.#lastLive = new LastLiveStore(join(deps.stateDir, "last-live.json"));
|
|
193
|
+
this.#lastLive = new LastLiveStore(join(deps.stateDir, "last-live.json"), deps.self);
|
|
194
194
|
this.#lastLive.load();
|
|
195
195
|
this.#terminals =
|
|
196
196
|
deps.terminals === undefined
|