The 1:1 is created with exactly one member (the owner), so first-non-bot is
correct by construction; .aadObjectId is not reliably present in the members
response and its GUID casing varies. Also default the display name.
Live-run evidence (VM run #7): token + create-conversation with the 28: bot
id and an AAD member id both work; only the members select failed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Same move as Slack/Discord: after the app install, open the bot<->owner 1:1
proactively with the bot credentials. The owner is the Teams-CLI account
that created the bot (teams status --json -> username + AAD object id); the
conversation-members fetch converts that to the 29: id inbound senders carry
and doubles as an identity cross-check; the platform id is composed exactly
as the adapter encodes thread ids. An operator note names who gets wired.
runChannelSkill gains wireIfResolved: a fresh create wires + welcomes like
any channel, a drop-through re-run resolves nothing and keeps the deferred
ending. Logout is now the operator's choice (yes/no prompt) instead of
automatic, and runs after the identity resolve.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The 4-line operator block duplicated the wizard's ending note and was the
'too much for the average joe' feedback. Setup now says it once, at the very
end; the SKILL.md keeps a one-line prose version for agents applying the
skill outside the wizard. The note gains 'with your coding agent'.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The libsecret warning fires even with libsecret installed (headless = no
keyring daemon) and the plaintext fallback persists fine — with the logout
step, nothing lingers either way. The warning is now a one-line 'safe to
ignore'; the AUTH_REQUIRED troubleshooting keeps only the real cause (pnpm
workspace install). The login step's sideloading probe flapped between runs
on the same account — documented as advisory; the install link is the
authoritative test.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The M365 session is only needed to create the bot — the adapter runs on the
.env app credentials. On a headless box the session is a plaintext token
file, so setup no longer leaves it on disk. teams logout is idempotent;
later CLI maintenance just needs a fresh teams login (device code).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The CLI-first flow creates a Teams-managed bot so az is never needed; the
line only matters for --azure bots and the manual portal path.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Teams ended with two conflicting banners: 'What's left: DM your bot' followed
by 'Check your Teams — your assistant is saying hi' (no welcome DM exists on
a deferred wire). The pending flag now gates the ending: What's left moves to
the final slot in the bright banner style, Go say hi only shows for channels
that actually sent a welcome.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The tenant fork cost every user a question that is almost always 'single'.
Create is unconditional --sign-in-audience myOrg again; the prereqs infobox
notes the default and points multi-tenant users at Alternatives, which
regains a manual multi-tenant section (create command + MultiTenant env
pairing without tenant ID).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Teams can't wire during setup (platform id only exists after the first
inbound), so a fresh teams-only install always ended 'A few things still
need your attention' + a debug offer. Zero groups now passes verify when
every configured channel is defer-wire and service+credentials are healthy;
the block gains WIRING=pending_first_dm and auto.ts prints a one-line
finish-wiring reminder instead. A wire-during-setup channel with zero
groups still fails.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The create step hardcoded --name NanoClaw and single-tenant; both are the
human's choice. Two new nc:prompt fences (app_name, tenant) feed a tenant-
branched create + env pairing — when:tenant=multi omits TEAMS_APP_TENANT_ID,
encoding the 401 pairing rule in the branch itself. Client-secret name and a
separate Azure bot name are NOT knobs on the CLI path (secret is auto-named
'default', 2y; --name covers registration+bot+Teams app) — documented, and
the manual portal path now names all four asks explicitly.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Run #3 on the VM: the global install fully succeeded (bin linked, keytar
binary built — this npm's allowScripts gate is advisory-only), but the box
exposes node via hand-picked symlinks in ~/.local/bin and npm's prefix bin
dir (~/node/bin) is not on PATH, so every 'teams …' step died with command
not found. Classic custom-prefix gotcha, so fix it universally: the login
and create fences call "$(npm prefix -g)/bin/teams"; prose points manual
runs at the same path; new Troubleshooting entry for 'teams: command not
found'. Test matchers updated for the absolute-path command shape.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Proven on the VM: as a workspace dep, keytar (msal-node-extensions' native
credential store, required at module load) never gets its binary because
pnpm's supply-chain policy skips dependency install scripts — the whole
store fails to import and teams.cli silently falls back to an in-memory
token cache, so every teams command after login starts logged out
(AUTH_REQUIRED ~1s into create). libsecret alone does not fix it; the
import dies before any keyring is touched.
npm install -g (pinned 3.0.2) runs keytar's install script in the global
env, keeps the workspace's onlyBuiltDependencies policy untouched, and
matches Microsoft's own install instruction. All commands drop the
pnpm exec prefix; REMOVE.md uninstalls globally after teams logout; the
AUTH_REQUIRED troubleshooting entry now names both causes (workspace
install, missing libsecret).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Found on a headless Ubuntu box: teams login succeeded but the session died
with the process — libsecret is absent, so msal-node-extensions silently
falls back to an in-memory cache (debug-level log), and teams app create (a
fresh process) fails in ~1s with AUTH_REQUIRED, two steps away from the
cause.
- The login step now re-reads the session from a fresh process
(teams status --json | grep loggedIn) before declaring success, so a
non-persisting cache fails AT the sign-in step with prose naming the fix
(apt install libsecret-1-0).
- Troubleshooting: new AUTH_REQUIRED-after-login entry; sideloading entry
now distinguishes tenant-level from per-user App setup policy (the login
output prints which one is blocking).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
One sign-in and one create command replace the ~7-step Azure portal walk:
- have_creds probe runs FIRST (either credential key in .env skips every
creation step — prompts included — so re-runs drop straight to Restart;
either-key semantics prevent a partial .env from pairing a new app with
stale keys, the guaranteed-401 mismatch).
- teams login runs as nc:run effect:step (browser locally, device code over
SSH; body emits the terminal status block the streaming exec requires).
- teams app create --json is one effect:external with a five-var JSON
capture (CLIENT_ID/CLIENT_SECRET/TENANT_ID + teamsAppId + installLink),
validate:^.+$ so a shape drift bounces instead of writing empty creds.
Single-tenant (--sign-in-audience myOrg) is the default pairing; the CLI
registers a Teams-managed bot — no Azure subscription, no manifest zip,
no manual sideload.
- Webhook endpoint corrected everywhere: /webhook/teams (was
/api/webhooks/teams, which silently receives nothing).
- Portal walk + multi-tenant variant moved to prose Alternatives; RSC via
teams app rsc add (CLI path) or manifest --rsc re-sideload (manual path).
- REMOVE.md: teams logout before CLI uninstall (MSAL cache outlives the
package), Entra deletion required on both paths (it revokes the secret),
drop the retired data/env mirror step.
- Parity tests rewritten: drop-through fixture (creds present → no create,
no prompts, fail() spy), runSkill-level fresh-create fixture (injected
execStream; proves capture→env-set chain + substituted install-link URL
offer), probe semantics executed from the shipped document, policy gate
inventory updated (3 operators, 1 barrier).
- claude-assist: point teams handoff file lists at files that exist on
this branch.
Known deferrals (engine-owned, flagged for the SSF branch owner):
reuseFromEnv offers capture-var env keys on re-runs (needs a one-line
promptShape guard); effect:external is not run-health gated; captures
cannot be marked secret. docs/skill-engine-seam.md line anchors into the
old teams SKILL.md are stale (pre-existing anchoring style).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Write the app-package zip in-process (stored entries, byte-deterministic,
fixed DOS timestamp) instead of shelling out to an external `zip` binary.
- Add the rsc option: authorization.permissions.resourceSpecific +
webApplicationInfo (RSC consent binds to webApplicationInfo.id, not
bots[].botId) and a manifest version bump so a re-upload supersedes the
original package. Plumbed through teams-manifest-build.ts as --rsc.
- New test suite parses the zip with an independent minimal reader
(EOCD → central directory → local headers) so a structure Teams would
reject goes red; builds run in beforeAll with tmpdir cleanup.
Ported from the parked main-based commit (tag backup/teams-cli-main-cf56c10).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Upstream added Socket Mode to the guided setup (cf8478ff) in the bespoke
setup/channels/slack.ts this branch deleted. The port expresses it the way
the engine was built for: a `connection` prompt (socket|webhook) with
when:-guarded branches —
- socket: app-level token walkthrough (connections:write scope, Socket Mode
toggle), `nc:prompt app_token` (xapp-, reuse:SLACK_APP_TOKEN),
SLACK_APP_TOKEN env-set. The adapter enables Socket Mode purely from the
token's presence, so no public URL and no signing secret.
- webhook: the existing signing-secret + Event Subscriptions path, now
guard-scoped to its branch.
Gate-policy parity holds: all three operator blocks remain prompt-barriered
(no confirms), matching the §5.1 rules; the placeholder-URL negative fixture
moves to operator index 2. Structure-tracking tests updated (directive
order, operator/prompt/env-set inventories, Option-A inputs gain
connection:webhook).
Suite 827 passed | 1 skipped; add-slack lint exit 0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Upstream removed every setup-side writer of data/env/env (c82f062d) because
nothing has read it since the container mount was dropped — before this
branch even forked. Our engine codified the dead pattern as a directive:
every apply copied the full .env (live tokens included) into a file nothing
consumes.
- engine: env-sync handler removed from selfStatus + applyOne
- grammar: dropped from KNOWN; a new RETIRED table gives a targeted lint
error ("delete the fence, the adapter reads .env directly") instead of a
generic unknown-directive message
- skills: fence stripped from the 14 converted skills; orphaned "sync to the
container" prose cleaned (incl. add-deltachat/add-wechat old-format prose
upstream's sweep missed); teams troubleshooting entry rewritten
- policy/spec doc lists updated
Suite 827 passed | 1 skipped; all nc: skills lint clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Conflict resolutions:
- 9 SKILL.mds we rewrote in the nc: format: kept ours, then bumped every
@chat-adapter pin 4.26.0 -> 4.29.0 to match the merged chat core (the
lockstep lint enforces equality; pin-test expectation updated).
- 14 setup/add-*.sh, install-*.sh + setup/channels/{slack,signal,whatsapp}.ts:
stay deleted — replaced by the directive engine + runChannelSkill. Upstream's
Slack Socket Mode (cf8478ff, setup-side) will be ported into add-slack's
SKILL.md as a when:-guarded mode in a follow-up commit.
- src/cli/resources/groups.ts: fused create — upstream's --template branch
first, else our idempotent-on-folder create with ensureContainerConfig.
- src/cli/registry.ts + dispatch.test.ts: both sides' additions kept
(hostOnly field/doc + upstream's resource doc and approval-context fixture).
Known follow-ups (next commits): remove the now-dead nc:env-sync directive
(upstream removed the data/env/env mirror writers; the reader died pre-fork),
port Slack Socket Mode into the SKILL.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>