From 2e975c48dc0026bf7a86e740d9b305bf372fc32f Mon Sep 17 00:00:00 2001 From: iamnbutler Date: Fri, 23 Oct 2026 09:57:25 -0400 Subject: [PATCH 1/3] fix(host): name a refused ask's owner by GitHub login, the Mac serving the directory, and a rejoin in the command line --- apps/host/src/cli.ts | 33 ++++++++++++++++++++++++--------- apps/host/src/directory.ts | 4 +++- apps/host/src/protocol.ts | 2 ++ 3 files changed, 29 insertions(+), 10 deletions(-) diff --git a/apps/host/src/cli.ts b/apps/host/src/cli.ts index ba4c7ee5..5a70404d 100755 --- a/apps/host/src/cli.ts +++ b/apps/host/src/cli.ts @@ -263,6 +263,11 @@ async function isHostRunning(): Promise { return (await health(Number(flags.port || config.port)))?.home === config.home; } +/** The running host knows each owner's GitHub login, as the app shows it. */ +async function roster(): Promise { + return await isHostRunning() ? await local({ op: "team-roster" }) : []; +} + /** What a teammate can do in a channel from the app; everything else acts on this host's own. */ const SWITCH = new Set(["on", "off"]); @@ -547,6 +552,7 @@ async function main() { } case "directory": { const url = ref === "off" ? null : ref; + const rejoined = !!url && directory.url() === directory.canonical(url); const running = await isHostRunning(); if (ref && running) await local({ op: "directory-set", url }); else if (ref) await directory.configure(url || undefined); @@ -558,11 +564,14 @@ async function main() { } const { directory: status, tailscale } = await local({ op: "diagnostics" }); if (!status.url) return console.log("No directory"); - console.log(url ? `Joined ${status.url}` : status.url); + console.log( + rejoined ? `Already joined ${status.url}` : url ? `Joined ${status.url}` : status.url, + ); if (tailscale.loopback) { return console.log(dim("This host runs with --loopback, so it doesn't sync it.")); } if (status.serving) console.log("This Mac serves it."); + else if (status.server) console.log(`${status.server} serves it.`); if (status.error) { return console.log(`Last sync failed ${ago(status.failed!)}: ${status.error}`); } @@ -635,16 +644,13 @@ async function main() { case "info": { // docs/terms.md names it agent access, after `ace agents`; `shared` is the wire name. const { shared, owner, ...info } = await session.request({ op: "info" }); - // The running host knows each owner's GitHub login, as the app shows it. - const roster = await isHostRunning() - ? await local({ op: "team-roster" }) - : []; - const self = roster.find((member) => member.self); + const members = await roster(); + const self = members.find((member) => member.self); return console.log(JSON.stringify( { ...info, host: host || self?.host, - owner: roster.find((member) => member.login === owner)?.github || owner, + owner: members.find((member) => member.login === owner)?.github || owner, agents: shared, }, null, @@ -664,14 +670,23 @@ async function main() { text, model: flags.model ? model(flags.model) : undefined, } as const; + // The channel refuses by the owner's Tailscale login; name them as the app does. + const refused = async (error: Error): Promise => { + const { shared, owner } = await session.request({ op: "info" }); + if (shared || owner === catalog.user) throw error; + const github = (await roster()).find((member) => member.login === owner)?.github; + throw github ? new Error(error.message.replace(owner, github)) : error; + }; if (flags.detach) { - await session.request(body); + await session.request(body).catch(refused); return console.log( `Asked ${name}'s agent; the run continues. Follow it with: ace watch ${name}`, ); } await watch(session, false); - const { submission } = await session.request<{ submission: number }>(body); + const { submission } = await session.request<{ submission: number }>(body).catch( + refused, + ); process.prependListener("SIGINT", () => { console.log(dim(`\nDetached; the run continues. Stop it with: ace stop ${name}`)); process.exit(130); diff --git a/apps/host/src/directory.ts b/apps/host/src/directory.ts index 5daa18cb..cd498c61 100644 --- a/apps/host/src/directory.ts +++ b/apps/host/src/directory.ts @@ -168,8 +168,10 @@ export async function adopt(value: string): Promise { /** Lifecycle events applied while a read is in flight; that read may predate them. */ let reading: Lifecycle[] | undefined; +/** `server` is the teammate's Mac serving a tailnet directory, by its tailnet name. */ export function status() { - return { url: url(), synced, error, failed }; + const base = url(); + return { url: base, server: base && server(base)?.name, synced, error, failed }; } /** Sync now, or join the sync under way; nothing without a running host. */ diff --git a/apps/host/src/protocol.ts b/apps/host/src/protocol.ts index 5410cf93..7b297842 100644 --- a/apps/host/src/protocol.ts +++ b/apps/host/src/protocol.ts @@ -58,6 +58,8 @@ export type Diagnostics = { */ directory: { url?: string; + /** The Mac serving a tailnet directory, by its tailnet name; unset for a deployed one. */ + server?: string; synced?: number; error?: string; failed?: number; From 0b4f6c68b7eb75f7cdf5172e9247cf155d69b57a Mon Sep 17 00:00:00 2001 From: iamnbutler Date: Fri, 23 Oct 2026 09:57:33 -0400 Subject: [PATCH 2/3] lane: sync fix-cli-names memory --- ...YY5SXJFQK8VXT78X1E0KAE-every-command-but-serve-must.md | 6 +++--- ...J0BZWP8SCBK522YT2HMZDZ-ace-info-prints-agent-access.md | 6 +++--- ...MGTVCTXQJ8KR8JCAKZ7QEV-ace-ask-rewrites-the-channel.md | 8 ++++++++ 3 files changed, 14 insertions(+), 6 deletions(-) create mode 100644 .lane/memory/apps/host/src/cli.ts/01M5MGTVCTXQJ8KR8JCAKZ7QEV-ace-ask-rewrites-the-channel.md diff --git a/.lane/memory/apps/host/src/cli.ts/01M4YY5SXJFQK8VXT78X1E0KAE-every-command-but-serve-must.md b/.lane/memory/apps/host/src/cli.ts/01M4YY5SXJFQK8VXT78X1E0KAE-every-command-but-serve-must.md index e67edf80..46254e93 100644 --- a/.lane/memory/apps/host/src/cli.ts/01M4YY5SXJFQK8VXT78X1E0KAE-every-command-but-serve-must.md +++ b/.lane/memory/apps/host/src/cli.ts/01M4YY5SXJFQK8VXT78X1E0KAE-every-command-but-serve-must.md @@ -4,9 +4,9 @@ anchor: function main created: 2026-10-15T04:47:23Z norm: '1' sig: 7d5d3f63a3bddde4 -body_hash: 5b0f98c8033bcdda -raw_hash: ec46efe56f9ba131 -vouched: 2026-10-23T12:46:13Z +body_hash: e00a53c3d7fb5b0a +raw_hash: 3411b5935d9686e3 +vouched: 2026-10-23T13:57:30Z lines: 253-482 --- diff --git a/.lane/memory/apps/host/src/cli.ts/01M5J0BZWP8SCBK522YT2HMZDZ-ace-info-prints-agent-access.md b/.lane/memory/apps/host/src/cli.ts/01M5J0BZWP8SCBK522YT2HMZDZ-ace-info-prints-agent-access.md index a701b737..30836b81 100644 --- a/.lane/memory/apps/host/src/cli.ts/01M5J0BZWP8SCBK522YT2HMZDZ-ace-info-prints-agent-access.md +++ b/.lane/memory/apps/host/src/cli.ts/01M5J0BZWP8SCBK522YT2HMZDZ-ace-info-prints-agent-access.md @@ -4,9 +4,9 @@ anchor: function main created: 2026-10-22T14:31:16Z norm: '1' sig: 7d5d3f63a3bddde4 -body_hash: 5b0f98c8033bcdda -raw_hash: ec46efe56f9ba131 -vouched: 2026-10-23T12:46:13Z +body_hash: e00a53c3d7fb5b0a +raw_hash: 3411b5935d9686e3 +vouched: 2026-10-23T13:57:30Z lines: 332-614 --- diff --git a/.lane/memory/apps/host/src/cli.ts/01M5MGTVCTXQJ8KR8JCAKZ7QEV-ace-ask-rewrites-the-channel.md b/.lane/memory/apps/host/src/cli.ts/01M5MGTVCTXQJ8KR8JCAKZ7QEV-ace-ask-rewrites-the-channel.md new file mode 100644 index 00000000..40385525 --- /dev/null +++ b/.lane/memory/apps/host/src/cli.ts/01M5MGTVCTXQJ8KR8JCAKZ7QEV-ace-ask-rewrites-the-channel.md @@ -0,0 +1,8 @@ +--- +id: 01M5MGTVCTXQJ8KR8JCAKZ7QEV +anchor: function main +created: 2026-10-23T13:57:30Z +norm: '1' +--- + +ace ask rewrites the channel's refusal to name the owner by GitHub login from the running host's team-roster; the channel itself only knows the Tailscale login From cd230d1040135324608b5d8dcc40088578728288 Mon Sep 17 00:00:00 2001 From: iamnbutler Date: Fri, 23 Oct 2026 09:57:34 -0400 Subject: [PATCH 3/3] lane: sync fix-cli-names memory --- ...01M5MGTVCTXQJ8KR8JCAKZ7QEV-ace-ask-rewrites-the-channel.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.lane/memory/apps/host/src/cli.ts/01M5MGTVCTXQJ8KR8JCAKZ7QEV-ace-ask-rewrites-the-channel.md b/.lane/memory/apps/host/src/cli.ts/01M5MGTVCTXQJ8KR8JCAKZ7QEV-ace-ask-rewrites-the-channel.md index 40385525..ff80ef4b 100644 --- a/.lane/memory/apps/host/src/cli.ts/01M5MGTVCTXQJ8KR8JCAKZ7QEV-ace-ask-rewrites-the-channel.md +++ b/.lane/memory/apps/host/src/cli.ts/01M5MGTVCTXQJ8KR8JCAKZ7QEV-ace-ask-rewrites-the-channel.md @@ -3,6 +3,10 @@ id: 01M5MGTVCTXQJ8KR8JCAKZ7QEV anchor: function main created: 2026-10-23T13:57:30Z norm: '1' +sig: 7d5d3f63a3bddde4 +body_hash: e00a53c3d7fb5b0a +raw_hash: 3411b5935d9686e3 +lines: 415-830 --- ace ask rewrites the channel's refusal to name the owner by GitHub login from the running host's team-roster; the channel itself only knows the Tailscale login