From d8d61d3695196a53969eaaf47a2f6829bc4ed1c3 Mon Sep 17 00:00:00 2001 From: gavrielc Date: Tue, 21 Apr 2026 10:16:13 +0300 Subject: [PATCH] fix: Teams user-id prefix + defer cli:local owner grant MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit parseUserId now falls back to user.kind when the id prefix isn't a registered adapter — Teams uses `29:` rather than `teams:`, so the literal prefix wouldn't resolve the channel adapter for cold DMs. init-cli-agent no longer claims the first-owner slot on `cli:local`. The CLI identity is scratch; owner promotion belongs to init-first-agent once the real channel user is wired. --- scripts/init-cli-agent.ts | 15 +++------------ src/modules/permissions/user-dm.ts | 15 +++++++++------ 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/scripts/init-cli-agent.ts b/scripts/init-cli-agent.ts index ccd9387a9..4a56827bc 100644 --- a/scripts/init-cli-agent.ts +++ b/scripts/init-cli-agent.ts @@ -30,7 +30,6 @@ import { } from '../src/db/messaging-groups.js'; import { runMigrations } from '../src/db/migrations/index.js'; import { normalizeName } from '../src/modules/agent-to-agent/db/agent-destinations.js'; -import { grantRole, hasAnyOwner } from '../src/modules/permissions/db/user-roles.js'; import { upsertUser } from '../src/modules/permissions/db/users.js'; import { initGroupFilesystem } from '../src/group-init.js'; import type { AgentGroup, MessagingGroup } from '../src/types.js'; @@ -91,17 +90,9 @@ async function main(): Promise { created_at: now, }); - let promotedToOwner = false; - if (!hasAnyOwner()) { - grantRole({ - user_id: CLI_SYNTHETIC_USER_ID, - role: 'owner', - agent_group_id: null, - granted_by: null, - granted_at: now, - }); - promotedToOwner = true; - } + // Owner grant deferred to init-first-agent when the real channel user is + // wired — cli:local is a scratch identity, not the operator. + const promotedToOwner = false; // 2. Agent group + filesystem. const folder = `cli-with-${normalizeName(args.displayName)}`; diff --git a/src/modules/permissions/user-dm.ts b/src/modules/permissions/user-dm.ts index ef9566ad8..a5274d19f 100644 --- a/src/modules/permissions/user-dm.ts +++ b/src/modules/permissions/user-dm.ts @@ -136,11 +136,14 @@ async function resolveDmPlatformId(channelType: string, handle: string): Promise function parseUserId(user: User): { channelType: string; handle: string } | { channelType: null; handle: null } { const idx = user.id.indexOf(':'); if (idx < 0) return { channelType: null, handle: null }; - const channelType = user.id.slice(0, idx); + const prefix = user.id.slice(0, idx); const handle = user.id.slice(idx + 1); - if (!channelType || !handle) return { channelType: null, handle: null }; - // The `kind` on users mirrors the channel_type prefix in our current - // scheme. Pull it from `user.kind` if we ever decouple them later, but - // today the id prefix is authoritative. - return { channelType, handle }; + if (!prefix || !handle) return { channelType: null, handle: null }; + // Teams user IDs use a `29:` prefix, not `teams:`. When the id prefix + // isn't a registered adapter, fall back to user.kind and treat the full + // id as the handle. + if (!getChannelAdapter(prefix) && user.kind && getChannelAdapter(user.kind)) { + return { channelType: user.kind, handle: user.id }; + } + return { channelType: prefix, handle }; }