Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ anchor: function handle
created: 2026-10-15T04:08:49Z
norm: '1'
sig: e597b5fbc8e664ce
body_hash: e0d9256e67814623
raw_hash: a6afeafd1ea5bd24
vouched: 2026-10-22T22:21:15Z
body_hash: 177d0cc7c38c0a16
raw_hash: 587e4316030f6250
vouched: 2026-10-23T12:27:32Z
lines: 266-519
---

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ anchor: function handle
created: 2026-10-15T06:00:20Z
norm: '1'
sig: e597b5fbc8e664ce
body_hash: e0d9256e67814623
raw_hash: a6afeafd1ea5bd24
vouched: 2026-10-22T22:21:16Z
body_hash: 177d0cc7c38c0a16
raw_hash: 587e4316030f6250
vouched: 2026-10-23T12:27:32Z
lines: 281-561
---

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ anchor: function handle
created: 2026-10-19T21:09:57Z
norm: '1'
sig: e597b5fbc8e664ce
body_hash: e0d9256e67814623
raw_hash: a6afeafd1ea5bd24
vouched: 2026-10-22T22:21:16Z
body_hash: 177d0cc7c38c0a16
raw_hash: 587e4316030f6250
vouched: 2026-10-23T12:27:33Z
lines: 384-700
supersedes: 01M5APK9YCQH467YNMKAC8BT5B
---
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ anchor: '@file'
created: 2026-10-20T17:04:56Z
norm: '1'
sig: 1acdc0f6a0895ac5
body_hash: 07a0161028eb0d88
raw_hash: 893db37db5fb95f0
vouched: 2026-10-23T12:24:46Z
body_hash: bda882f4e56d7c30
raw_hash: 291f5c1b799528ff
vouched: 2026-10-23T12:27:33Z
lines: 1-1153
---

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ anchor: '@file'
created: 2026-10-22T14:00:47Z
norm: '1'
sig: 1acdc0f6a0895ac5
body_hash: 07a0161028eb0d88
raw_hash: 893db37db5fb95f0
vouched: 2026-10-23T12:24:47Z
body_hash: bda882f4e56d7c30
raw_hash: 291f5c1b799528ff
vouched: 2026-10-23T12:27:33Z
lines: 1-1309
---

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
id: 01M5MBP1ATN8456SR3SX4RX6GV
anchor: function listed
created: 2026-10-23T12:27:29Z
norm: '1'
sig: ae9e4719dcd64268
body_hash: 151a865bdfdc6d8d
raw_hash: 89759af61ffab432
lines: 170-185
---

a directory or remembered channel whose host is a reachable peer is left out, not listed offline: the peer's own listing decides what it shares, and Offline must mean the same in People and the sidebar
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ anchor: function unreachable
created: 2026-10-15T02:50:26Z
norm: '1'
sig: dafb01d962243d11
body_hash: b1750761c8fca9d0
raw_hash: 4da6cfbe96c64428
body_hash: 81d4e7aafce6dcdc
raw_hash: b01930e371251a86
vouched: 2026-10-23T12:27:33Z
lines: 93-97
---

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ anchor: function discover
created: 2026-10-22T17:44:31Z
norm: '1'
sig: ab69f97386d0db50
body_hash: 9b0a75f289475140
raw_hash: 4713ceb74c23d1f3
vouched: 2026-10-22T21:25:00Z
body_hash: 2507fdcc2496fd80
raw_hash: 595cfbfed333995e
vouched: 2026-10-23T12:27:33Z
lines: 37-74
supersedes: 01M4YX4FKJR9W30M9Y5HVTKBW4
---
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ anchor: function discover
created: 2026-10-22T21:24:56Z
norm: '1'
sig: ab69f97386d0db50
body_hash: 9b0a75f289475140
raw_hash: 4713ceb74c23d1f3
body_hash: 2507fdcc2496fd80
raw_hash: 595cfbfed333995e
vouched: 2026-10-23T12:27:33Z
lines: 37-75
---

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
id: 01M5MBP1CFQYYTY8BQ9SAYWFMB
anchor: function members
created: 2026-10-23T12:27:29Z
norm: '1'
sig: 9304eaec2689fb32
body_hash: 2021266b63565369
raw_hash: 22504780ae4cf5bb
lines: 184-186
---

People lists every peer that answered since this host started, online only while its listing connection is open, matching which channels unreachable() lists offline
8 changes: 6 additions & 2 deletions apps/host/src/gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,11 +163,15 @@ async function local(): Promise<Listing[]> {
});
}

/** Channels only the directory or a peer's last answer knows: their host is unreachable, unless they are hosted. */
/**
* Channels only the directory or a peer's last answer knows: their host is unreachable, unless they
* are hosted. A reachable host's own listing leaves out what it no longer shares, which is not offline.
*/
function listed(seen: Set<string>): Listing[] {
return [...directory.read().channels, ...peers.unreachable()]
.filter((value) => {
if (seen.has(value.id) || value.host === name) return false;
if (!value.hosted && peers.isReachable(value.host)) return false;
seen.add(value.id);
return true;
})
Expand Down Expand Up @@ -726,7 +730,7 @@ async function handle(
case "team-roster": {
const roster: Member[] = [
{ host: name, login: catalog.user, online: true, self: true },
...peers.online(),
...peers.members(),
];
const seen = new Set(roster.map((member) => member.host));
for (const { name: host, login } of directory.read().hosts) {
Expand Down
26 changes: 17 additions & 9 deletions apps/host/src/peers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ const stalled = new Map<string, number>();
const named: People = {};
/** Peers whose last hello carried no GitHub login, asked again until one does. */
const unnamed = new Set<string>();
/** Owners' Tailscale logins of peers that answered, so one that stops answering stays in People as offline. */
const met = new Map<string, string>();

function emit() {
for (const listener of listeners) listener();
Expand Down Expand Up @@ -61,7 +63,10 @@ async function discover(signal: AbortSignal) {
});
status = client.status;
}
if (client.status === "open") remembered.set(name, client.channels);
if (client.status === "open") {
remembered.set(name, client.channels);
met.set(name, machine.login);
}
emit();
});
hosts.set(name, host);
Expand Down Expand Up @@ -136,9 +141,7 @@ export function listings(): Listing[] {

/** Channels of peers that answered before but are unreachable now. */
export function unreachable(): Listing[] {
return [...remembered].flatMap(([name, channels]) =>
hosts.get(name)?.client.status === "open" ? [] : channels
);
return [...remembered].flatMap(([name, channels]) => isReachable(name) ? [] : channels);
}

/** Peers' owners' GitHub logins as they last answered, reachable or not. */
Expand Down Expand Up @@ -174,11 +177,16 @@ export async function teams(): Promise<TeamPeer[]> {
return found.filter((peer) => !!peer);
}

/** Hosts running Ace on the tailnet now; the gateway names their owners from every source. */
export function online(): Member[] {
return [...hosts.values()].flatMap(({ machine, client }) =>
client.status === "open" ? [{ host: machine.name, login: machine.login, online: true }] : []
);
/**
* Peers that answered since this host started, online while they answer, like their channels in
* `listings` and `unreachable`; the gateway names their owners from every source.
*/
export function members(): Member[] {
return [...met].map(([host, login]) => ({ host, login, online: isReachable(host) }));
}

export function isReachable(name: string): boolean {
return hosts.get(name)?.client.status === "open";
}

/** Waits for the peer's owner to approve; see asks.ts. */
Expand Down