@agentdeploymentco/bishop 0.2.0 → 0.3.0

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.
Files changed (44) hide show
  1. package/README.md +115 -8
  2. package/dist/cli.js +17 -14
  3. package/dist/config.js +4 -4
  4. package/dist/env.js +4 -4
  5. package/dist/gmail/api.js +1 -1
  6. package/dist/gmail/etiquette.js +1 -0
  7. package/dist/gmail/fake.js +4 -4
  8. package/dist/gmail/history.js +1 -1
  9. package/dist/gmail/identity.js +1 -0
  10. package/dist/gmail/interface.js +2 -2
  11. package/dist/gmail/labels.js +1 -1
  12. package/dist/gmail/message.js +7 -10
  13. package/dist/gmail/presence.js +3 -3
  14. package/dist/gmail/route.js +1 -1
  15. package/dist/harness/principal.js +9 -8
  16. package/dist/interface/format.js +4 -0
  17. package/dist/{slack → interface}/phrases.js +1 -1
  18. package/dist/interface/verbosity.js +1 -0
  19. package/dist/log.js +1 -1
  20. package/dist/slack/format.js +1 -4
  21. package/dist/slack/interface.js +2 -2
  22. package/dist/slack/presence.js +2 -2
  23. package/dist/slack/tasks.js +1 -1
  24. package/dist/slack/verbosity.js +1 -1
  25. package/dist/teams/api.js +1 -0
  26. package/dist/teams/authorize.js +1 -0
  27. package/dist/teams/create.js +1 -0
  28. package/dist/teams/entra.js +3 -0
  29. package/dist/teams/fake.js +1 -0
  30. package/dist/teams/files.js +1 -0
  31. package/dist/teams/graph.js +1 -0
  32. package/dist/teams/interface.js +2 -0
  33. package/dist/teams/logger.js +1 -0
  34. package/dist/teams/presence.js +7 -0
  35. package/dist/teams/progress.js +1 -0
  36. package/dist/teams/provision.js +10 -0
  37. package/dist/teams/route.js +1 -0
  38. package/dist/teams/server.js +1 -0
  39. package/dist/teams/setup.js +190 -0
  40. package/dist/teams/thread.js +1 -0
  41. package/dist/teams/verify.js +2 -0
  42. package/package.json +3 -1
  43. /package/dist/{slack → interface}/etiquette.js +0 -0
  44. /package/dist/{slack → interface}/status.js +0 -0
@@ -0,0 +1,190 @@
1
+ import{loadConfig as y,writeConfig as F}from"../config.js";import{log as I}from"../log.js";import{ensureGitignore as he,resolveAppDetails as j,SetupError as m,withPrompt as G,writeEnv as de}from"../setup.js";import{AZ_LOGIN_COMMAND as z,azAccount as ce,FILES_READ_ALL as x,FILES_READ_ALL_ROLE as le,GRAPH_APP_ID as me,grantGraphFiles as ue,INSTALL_AZ_CLI as we}from"./entra.js";import{addRscPermission as fe,createTeamsApp as ge,describeTeamsApp as Te,getRscPermissions as Ie,getTeamsApp as Ae,INSTALL_TEAMS_CLI as U,INSTALL_TEAMS_CLI_COMMAND as _e,LOGIN_DID_NOT_PERSIST as Ee,RSC_PERMISSIONS as C,setTeamsEndpoint as Se,setTeamsFiles as N,teamsCliVersion as W,teamsLogin as be,teamsLoginWouldUseDeviceCode as $,teamsStatus as v}from"./provision.js";import{readGraphRoles as L,verifyTeamsCredentials as ye}from"./verify.js";const Y=["The Teams CLI cannot log in from this machine. With no local browser it falls","back to the device code flow, which Microsoft blocks by default in every","tenant."].join(`
2
+ `),K=[Y,"","So an app cannot be created over SSH. Two ways on from here:",""," Run `bishop teams setup` on a workstation and copy the three BISHOP_TEAMS_*"," lines it writes to .env over to this host.",""," Have somebody else create the app, then run `bishop teams setup --manual`"," here with what they send back. `bishop teams instructions` prints what to"," ask them for."].join(`
3
+ `),V=[Y,"","So the app has to be re-pointed from a machine that can log in:",""," bishop teams endpoint <url> --app-id <the app id>","","or by editing the bot endpoint in the Teams Developer Portal."].join(`
4
+ `);function Z(e){return new m($()?`${U}
5
+
6
+ ${e}`:U)}function R(e){return{...e.BISHOP_TEAMS_CLIENT_ID?.trim()?{clientId:e.BISHOP_TEAMS_CLIENT_ID.trim()}:{},...e.BISHOP_TEAMS_CLIENT_SECRET?.trim()?{clientSecret:e.BISHOP_TEAMS_CLIENT_SECRET.trim()}:{},...e.BISHOP_TEAMS_TENANT_ID?.trim()?{tenantId:e.BISHOP_TEAMS_TENANT_ID.trim()}:{}}}function O(e){const{clientId:n,clientSecret:t,tenantId:a}=R(e);if(n&&t&&a)return{clientId:n,clientSecret:t,tenantId:a}}function q(e,n){const t=e.teams?.app;if(t&&O(n))return{app:t}}async function J(e,n,t,a){let i;try{i=await de(e,{BISHOP_TEAMS_CLIENT_ID:a.clientId,BISHOP_TEAMS_CLIENT_SECRET:a.clientSecret,BISHOP_TEAMS_TENANT_ID:a.tenantId})}catch(r){throw new m(`The credentials could not be written to .env: ${r instanceof Error?r.message:String(r)}
7
+
8
+ The app was created, so save these somewhere before this scrolls away:
9
+
10
+ BISHOP_TEAMS_CLIENT_ID=${a.clientId}
11
+ BISHOP_TEAMS_CLIENT_SECRET=${a.clientSecret}
12
+ BISHOP_TEAMS_TENANT_ID=${a.tenantId}
13
+
14
+ Then run \`bishop teams setup --manual\` once .env is writable.`)}await F({...n,teams:{...n.teams,app:t}},e);const s=await he(e,[".env","bishop.db*"]);return` .bishop/config.json app configuration
15
+ ${i} credentials
16
+ `+(s.length?` .gitignore added ${s.join(", ")}
17
+ `:"")}async function k(e){if(e)try{return await Ae(e)}catch(n){I.debug({err:n,teamsAppId:e},"could not read the Teams app back");return}}function Q(e,n){return n.map(t=>` teams app rsc add ${e} ${t} --type Application
18
+ `).join("")}const S=`Without these the agent only hears a message that @mentions it, so a reply to
19
+ its own answer never reaches it.
20
+ `;async function Ce(e){const n=[],t=[];for(const a of C){const i=await fe(e,a);i.applied||(n.push(a),t.push(` ${a}: ${i.reason}
21
+ `))}return n.length===0?"":`
22
+ These permissions could not be added to the app:
23
+ `+t.join("")+`
24
+ ${S}Add them with:
25
+
26
+ `+Q(e,n)}async function X(e){if(e)try{const n=new Set(await Ie(e));return C.filter(t=>!n.has(t))}catch(n){I.debug({err:n,teamsAppId:e},"could not read the app\u2019s RSC permissions");return}}const Ne=`Then remove the app from every team and chat it is in, and add it again.
27
+ Teams asks for these permissions only when an app is installed. Updating an
28
+ app that is already installed does not ask, and it will keep hearing nothing
29
+ but mentions until it has been removed and installed again.
30
+ `,M=`Without it Teams shows no attach button in a chat with the agent, so nobody
31
+ can send it a file. An image pasted into a message arrives either way, and a
32
+ file attached in a channel never does.
33
+ `;function b(e){return` teams app manifest update ${e} --set-json 'bots[0].supportsFiles=true' --yes
34
+ `}function $e(e,n){return`
35
+ File support could not be turned on for this app: ${n??"the update failed"}
36
+
37
+ ${M}Turn it on with:
38
+
39
+ `+b(e)}function ve(e){return`
40
+ Bishop could not check whether this app has file support: the Teams CLI is
41
+ not installed here, or is not logged in. Run this again where it is, or turn
42
+ it on with:
43
+
44
+ `+b(e)}const Le=`This moved the app\u2019s manifest version, so update the app where it is already
45
+ installed. Until that reaches a chat, it will go on showing no attach button.
46
+ `;function ee(e){return`
47
+ This app does not have file support.
48
+
49
+ ${M}Turn it on with:
50
+
51
+ `+b(e)+`
52
+ ${Le}`}function P(e,n=" "){const t=e??"<client id>",a=`${n} `;return`${n}az ad app permission add --id ${t} \\
53
+ ${a}--api ${me} \\
54
+ ${a}--api-permissions ${le}=Role
55
+ ${n}az ad app permission admin-consent --id ${t}
56
+ `}const Re=`Without it the agent is still told what was attached and says it could not
57
+ read it. A file in a direct chat needs none of this.
58
+ `;function A(e,n){return`
59
+ ${n}
60
+
61
+ ${P(e)}
62
+ ${Re}`}const B=`A file attached in a channel is read through Microsoft Graph, which needs one
63
+ grant in Entra.`;function Oe(e,n=" "){return`${n}az ad app permission admin-consent --id ${e}
64
+ `}const ke=`
65
+ The Azure CLI accepted the grant for Files.Read.All, and the app cannot
66
+ read a file attached in a channel yet: its own token does not carry the
67
+ permission. Entra can take minutes to apply a consent on a registration
68
+ made seconds earlier. Re-run \`bishop teams setup\` to see whether it took,
69
+ and if it still reports the grant missing, run the consent again:
70
+
71
+ `,Me=`
72
+ The Azure CLI accepted the grant for Files.Read.All, and Bishop could not
73
+ reach Entra to check that it took. Re-run \`bishop teams setup\` to find out.
74
+ `,ne=`
75
+ The app can read a file attached in a channel: Files.Read.All is granted.
76
+ `,Pe=`Consenting an application permission takes an administrator, which is the
77
+ usual reason. An app registration made moments ago can also be too new for
78
+ Entra to have it everywhere yet, so this is worth running once by hand
79
+ before asking anybody. Re-run \`bishop teams setup\` to see whether it took.`;async function Be(e,n=L){const t=await ce();if(!t.installed)return A(e.clientId,`${B} Bishop makes it with the Azure CLI,
80
+ which is not installed here. ${we}
81
+ Then run:`);if(!t.loggedIn)return A(e.clientId,`${B} Bishop makes it with the Azure CLI,
82
+ which is not logged in here. Run \`${z}\`, which is
83
+ what a tenant with no Azure subscription needs, and then:`);if(t.tenantId&&t.tenantId.toLowerCase()!==e.tenantId.toLowerCase())return A(e.clientId,`${B} The Azure CLI is signed in to tenant
84
+ ${t.tenantId}, and this app is in ${e.tenantId}, so
85
+ Bishop left it alone. Sign in to the app\u2019s tenant and run:`);const a=await ue(e.clientId);if(!a.applied)return A(e.clientId,`Bishop could not grant Files.Read.All: ${a.reason}
86
+
87
+ ${Pe}
88
+ An administrator makes the grant with:`);const i=await n(e);return i?.includes(x)?ne:i?ke+Oe(e.clientId):Me}function te(e,n){return n?n.includes(x)?ne:A(e,`The app cannot read a file attached in a channel: Files.Read.All is not
89
+ granted on its app registration. An administrator grants it with:`):`
90
+ Bishop could not check whether the app can read a file attached in a
91
+ channel. That needs Files.Read.All on the app registration, which an
92
+ administrator grants with:
93
+
94
+ `+P(e)}function ae(e,n){return`
95
+ The app is missing ${n.length===1?"a permission":"permissions"}:
96
+ `+n.map(t=>` ${t}
97
+ `).join("")+`
98
+ ${S}Add them with:
99
+
100
+ `+Q(e,n)+`
101
+ ${Ne}`}const De=`
102
+ ${S}Bishop could not check whether this app has the permissions for that: the
103
+ Teams CLI is not installed here, or is not logged in. Run this again where it
104
+ is, or see \`bishop teams instructions\` for what they are.
105
+ `;function He(e,n){return n?n.length>0?ae(e,n):"":`
106
+ ${S}Bishop cannot check this app, so confirm with whoever created it that it has
107
+ the permissions \`bishop teams instructions\` asks for.
108
+ `}function Fe(e,n){return!e||!n?.applied?`
109
+ ${M}Bishop cannot check this app, so confirm with whoever created it that file
110
+ support is on.
111
+ `:n.changed?ee(e):""}async function ie(e,n,t){const a=await k(e.app.id),i=a?.endpoint,s=e.app.id?a?i??"not set":"unknown, the Teams CLI could not read the app":"unknown, no Teams app ID is recorded here";if(n(`This agent is already set up in Teams as "${e.app.displayName??e.app.name}".
112
+ `+(e.app.id?` App ID ${e.app.id}
113
+ `:"")+` Endpoint ${s}
114
+ Its credentials are in .env and its configuration in .bishop/config.json.
115
+ `),a&&!i&&n(`
116
+ Teams has nowhere to deliver to until the app is pointed at Bishop.
117
+ Run \`bishop teams endpoint <url>\` with the URL that reaches this host.
118
+ `),e.app.id){const p=await X(e.app.id);p?p.length>0&&n(ae(e.app.id,p)):n(De)}if(e.app.id){const p=await N(e.app.id,{check:!0});p.applied?p.changed&&n(ee(e.app.id)):n(ve(e.app.id))}const r=O(process.env);n(te(r?.clientId,r&&await t(r))),n(`
119
+ To change where Teams delivers, run \`bishop teams endpoint <url>\`.
120
+ To set this up again, remove teams.app from .bishop/config.json and the
121
+ BISHOP_TEAMS_* lines from .env first.
122
+ `)}function je(e,n){if(!e)return;const{tenant:t,user:a}=e;t!==!1&&a!==!1||n(`
123
+ Note: custom apps cannot be installed in Teams from here.
124
+ `+(t===!1?` The tenant-wide setting is off. An administrator turns it on in the Teams
125
+ Admin Center, under Org-wide app settings, Custom apps.
126
+ `:"")+(a===!1?` Your own app setup policy blocks it. An administrator changes it in the
127
+ Teams Admin Center, under Users, Policies, App setup policy.
128
+ `:"")+`Setup will still create the app, but somebody will have to install it.
129
+ `)}function Ge(e,n){const t=e.teams?.app,a=R(n),i=Object.keys(a).length>0;if(t&&!O(n)){const{clientId:s,clientSecret:r,tenantId:p}=a,h=[s?void 0:"BISHOP_TEAMS_CLIENT_ID",r?void 0:"BISHOP_TEAMS_CLIENT_SECRET",p?void 0:"BISHOP_TEAMS_TENANT_ID"].filter(Boolean);throw new m(`An app is already recorded here, but its credentials are not.
130
+
131
+ App ${t.displayName??t.name}${t.id?` (${t.id})`:""}
132
+ Missing ${h.join(`
133
+ `)}
134
+
135
+ Creating a new app would leave that one orphaned in your tenant. Either:
136
+ finish this one with \`bishop teams setup --manual\`, giving it a client
137
+ secret from \`teams app auth secret create ${t.id??"<app id>"}\`, or
138
+ remove teams.app from .bishop/config.json to start over.`)}if(i&&!t)throw new m(`Teams credentials are already in the environment, but no app is recorded
139
+ here: ${Object.keys(a).length} of 3 are set.
140
+
141
+ Creating a new app would overwrite them, and a client secret cannot be
142
+ read back once it is lost.
143
+
144
+ Run \`bishop teams setup --manual\` to record the app those credentials
145
+ belong to, or remove the BISHOP_TEAMS_* lines from .env to start over.`)}async function Ve(e,n=L){const t=e.cwd??process.cwd(),a=e.prompt&&process.stdin.isTTY===!0,i=o=>process.stdout.write(o),s=await y(t),r=q(s,process.env);if(r){await ie(r,i,n);return}Ge(s,process.env);const p=await W();if(!p)throw Z(K);I.debug({teamsCli:p},"found the teams cli");let h=await v();if(!h.loggedIn){if($())throw new m(K);if(!a)throw new m("The Teams CLI is not logged in to Microsoft 365.\nRun `teams login`, then run this again.");i("The Teams CLI is not logged in to Microsoft 365. Starting `teams login`.\n\n");const o=await be();if(h=o?await v():h,!h.loggedIn)throw new m(`The Teams CLI is still not logged in, so the app cannot be created.
146
+ `+(o?`
147
+ \`teams login\` reported success, but the login did not survive it.
148
+ ${Ee}`:"Run `teams login` on its own to see what went wrong."));i(`
149
+ Logged in.
150
+ `)}je(h.sideloading,i),await G(a,async o=>{const d=await j(e,t,o),u=e.endpoint?.trim()||await o("Messaging endpoint URL (leave blank to set later)","");i(`
151
+ Creating the Teams app for "${d.displayName}"${h.username?` in ${h.username}'s tenant`:""}...
152
+ `);const c=await ge({name:d.displayName,...u?{endpoint:u}:{}}),f=d.description?await Te(c.teamsAppId,d.description):{applied:!1,reason:void 0};d.description&&!f.applied&&i(`
153
+ The description was not applied: ${f.reason}
154
+ `);const w=await Ce(c.teamsAppId);w&&i(w);const l=await N(c.teamsAppId);l.applied||i($e(c.teamsAppId,l.reason));const g={id:c.teamsAppId,name:d.name,displayName:d.displayName,...f.applied&&d.description?{description:d.description}:{}},T=await J(t,s,g,c),_=await Be(c,n);I.info({teamsAppId:c.teamsAppId,endpoint:u||void 0},"teams app created"),i(`
155
+ Done.
156
+ ${T}`+_+(c.installLink?`
157
+ Install it in Teams:
158
+ ${c.installLink}
159
+ `:"")+(u?"":`
160
+ The app has no messaging endpoint yet, so Teams has nowhere to deliver to.
161
+ Run \`bishop teams endpoint <url>\` with the URL that reaches this host.
162
+ `)+"\nRun `bishop` to start the agent.\n")})}async function Ze(e,n=ye,t=L){const a=e.cwd??process.cwd(),i=e.prompt&&process.stdin.isTTY===!0,s=o=>process.stdout.write(o),r=await y(a),p=r.teams?.app,h=q(r,process.env);if(h){const o=e.appId?.trim();if(o&&!h.app.id){await F({...r,teams:{...r.teams,app:{...h.app,id:o}}},a),s(`Recorded Teams app ID ${o} for "${h.app.displayName??h.app.name}".
163
+ `);return}await ie(h,s,t);return}await G(i,async o=>{const d=R(process.env),u=d.clientId||await o("Client ID",""),c=d.clientSecret||await o("Client secret",""),f=d.tenantId||await o("Tenant ID","");if(!u||!c||!f)throw new m(`All three credentials are needed: a client ID, a client secret, and a
164
+ tenant ID. Set BISHOP_TEAMS_CLIENT_ID, BISHOP_TEAMS_CLIENT_SECRET and
165
+ BISHOP_TEAMS_TENANT_ID, or run this in a terminal so they can be typed in.
166
+
167
+ Run \`bishop teams instructions\` for what to send whoever creates the app.`);const w={clientId:u,clientSecret:c,tenantId:f},l=e.appId?.trim()||p?.id||await o("Teams app ID (optional)","");s(`
168
+ Checking the credentials...
169
+ `);const g=await n(w);if(!g.ok)throw new m(`Microsoft rejected these credentials.
170
+ ${g.detail}
171
+
172
+ This is the same request Bishop makes on every turn, so the agent would
173
+ not have been able to answer anything.`);g.detail?s(`${g.detail} Saving them anyway.
174
+ `):s(`Accepted.
175
+ `);const T=await k(l||void 0),_=e.name??p?.name??T?.name,D=e.displayName??p?.displayName??T?.name,H=e.description??p?.description,E=await j({...e,..._?{name:_}:{},...D?{displayName:D}:{},...H?{description:H}:{}},a,o),se={...l?{id:l}:{},name:E.name,displayName:E.displayName,...E.description?{description:E.description}:{}},oe=await J(a,r,se,w),re=await X(l||void 0),pe=l?await N(l,{check:!0}):void 0;I.info({teamsAppId:l||void 0},"teams app adopted"),s(`
176
+ Done.
177
+ ${oe}`+(T?.endpoint?`
178
+ The app is pointed at ${T.endpoint}
179
+ `:`
180
+ Bishop cannot tell where this app is pointed, so confirm with whoever
181
+ created it that its messaging endpoint reaches this host. Bishop serves it
182
+ at /api/messages.
183
+ `)+He(l,re)+Fe(l,pe)+te(w.clientId,await t(w))+"\nRun `bishop` to start the agent.\n")})}async function qe(e,n={}){const t=n.cwd??process.cwd(),a=o=>process.stdout.write(o),i=await y(t),s=n.appId?.trim()||i.teams?.app?.id;if(!s)throw new m("No Teams app is recorded here, so there is nothing to point.\nRun `bishop teams setup` first, or pass --app-id.");if(!await W())throw Z(V);if($()&&!(await v()).loggedIn)throw new m(V);const p=(await k(s))?.endpoint;if(p===e){a(`App ${s} already points at ${e}. Nothing to do.
184
+ `);return}const{needsReinstall:h}=await Se(s,e);I.info({teamsAppId:s,before:p,endpoint:e,needsReinstall:h},"teams messaging endpoint updated"),a(`App ${s} now points at ${e}
185
+ `),h&&a(`
186
+ The app's manifest changed, which usually means its valid domains did.
187
+ Reinstall it in Teams before expecting it to receive anything.
188
+ `)}function Je(e){const n=e?.trim()||"https://<the URL that reaches this host>/api/messages";return["Bishop needs a Microsoft Teams app. To create one for it:","","1. Create a Teams app with a bot. A Teams-managed bot is enough and needs no"," Azure subscription:","",` ${_e}`," teams login"," teams app create --teams-managed --name <app name> --endpoint <url>",""," The install flags are not optional: without them the CLI cannot store a"," login and every command starts logged out.","","2. Point the bot at this messaging endpoint:","",` ${n}`,""," Teams POSTs every activity here, so it has to be reachable over HTTPS.","","3. Give the app these resource-specific consent permissions:","",...C.map(t=>` teams app rsc add <app id> ${t} --type Application`),""," Without them Teams only delivers a message that @mentions the agent, so a"," reply to its own answer never reaches it. Whoever installs the app in a team"," or a chat is who consents, and Teams only asks at install time. An app that"," is already installed has to be removed from every team and chat it is in and"," added again: updating it in place never asks, so it goes on hearing nothing"," but mentions.","","4. Turn on file support, so the agent can be sent a file at all:","",` ${b("<app id>").trim()}`,""," Without it Teams shows no attach button in a chat with the agent, so there"," is nothing for anyone to send. An image pasted into a message arrives either"," way. `--yes` is not optional: the CLI refuses the write without it unless"," somebody is there to answer a prompt.","","5. Let the agent read a file attached in a channel. Those are on SharePoint"," rather than on the bot channel, so Bishop reads them through Microsoft"," Graph, which needs one application permission on the app registration:","",` ${z}`,...P("<client id>"," ").trimEnd().split(`
189
+ `),""," That is Files.Read.All, and it is tenant-wide, which is why it takes an"," administrator. `--allow-no-subscriptions` is what a tenant with no Azure"," subscription needs, which is the ordinary case for a Teams-managed bot."," Skipping this costs only channel files: the agent is still told one was"," attached, and says it could not read it.","","6. Allow the app to be installed. Custom app installation has to be on for the"," tenant (Teams Admin Center, Org-wide app settings, Custom apps) and for"," whoever installs it (Users, Policies, App setup policy).","","7. Send back three values, which are credentials and should travel like any"," other secret:",""," the app registration\u2019s client ID"," a client secret for it"," the tenant ID","","Then run `bishop teams setup --manual` here and give it those three.",""].join(`
190
+ `)}export{Je as administratorInstructions,Be as applyChannelFileGrant,q as existingTeamsSetup,qe as repointTeamsEndpoint,Ve as setupTeams,Ze as setupTeamsManually};
@@ -0,0 +1 @@
1
+ const i=new Set(["personal","groupChat"]),c=/^[1-9]\d*$/;function a(n){const t=n.conversationId;return i.has(n.conversationType)||t.includes(";messageid=")?t:c.test(n.id)?`${t};messageid=${n.id}`:t}function f(n){return`teams:${a(n)}`}function s(n){return n.split(";")[0]??n}function h(n,t){return o(n)===o(t)}function o(n){const[t="",...e]=n.split(";"),r=e.find(d=>d.startsWith("messageid="));return r?`${t};${r}`:t}function l(n){if(n.conversationType==="personal")return;if(i.has(n.conversationType))return{kind:"chat",chatId:s(n.conversationId),messageId:n.id};if(!n.channel)return;const t=u(n);return{kind:"channel",teamId:n.channel.teamId,channelId:n.channel.channelId,messageId:n.id,...t?{replyToId:t}:{}}}function u(n){const t=n.conversationId.split(";").find(e=>e.startsWith("messageid="))?.slice(10);return t&&t!==n.id?t:void 0}export{s as baseConversation,a as conversationOf,l as graphAddressOf,h as sameConversation,f as threadKeyOf};
@@ -0,0 +1,2 @@
1
+ import{log as a}from"../log.js";const d="https://login.microsoftonline.com",u="https://api.botframework.com/.default",l="https://graph.microsoft.com/.default",f=1e4;async function y(n,r=fetch){const t=new URLSearchParams({grant_type:"client_credentials",client_id:n.clientId,client_secret:n.clientSecret,scope:u});let e;try{e=await r(`${d}/${encodeURIComponent(n.tenantId)}/oauth2/v2.0/token`,{method:"POST",headers:{"content-type":"application/x-www-form-urlencoded"},body:t})}catch(s){return a.warn({err:s},"could not reach Entra to check the Teams credentials"),{ok:!0,detail:"Could not reach Microsoft to check the credentials."}}if(e.ok)return{ok:!0};if(e.status>=500||e.status===429)return a.warn({status:e.status},"Entra could not answer the Teams credential check"),{ok:!0,detail:`Microsoft could not answer the check (HTTP ${e.status}).`};const o=await p(e);return o?{ok:!1,detail:o}:(a.warn({status:e.status},"a Teams credential check was answered by something else"),{ok:!0,detail:`Something other than Microsoft answered the check (HTTP ${e.status}).`})}async function p(n){let r;try{const t=await n.json();typeof t.error_description=="string"?r=t.error_description:typeof t.error=="string"&&(r=t.error)}catch{}return r?.split(`
2
+ `)[0]?.trim()||void 0}async function w(n,r=fetch){const t=new URLSearchParams({grant_type:"client_credentials",client_id:n.clientId,client_secret:n.clientSecret,scope:l});let e;try{e=await r(`${d}/${encodeURIComponent(n.tenantId)}/oauth2/v2.0/token`,{method:"POST",headers:{"content-type":"application/x-www-form-urlencoded"},body:t,signal:AbortSignal.timeout(f)})}catch(i){a.debug({err:i},"could not reach Entra to read the app\u2019s Graph permissions");return}if(!e.ok){a.debug({status:e.status},"Entra would not issue a Graph token for the app");return}let o;try{o=(await e.json()).access_token}catch{return}if(typeof o!="string")return;const s=h(o);if(!s)return;const c=s.roles;if(c===void 0)return[];if(Array.isArray(c))return c.filter(i=>typeof i=="string")}function h(n){const r=n.split(".")[1];if(r)try{const t=JSON.parse(Buffer.from(r,"base64url").toString("utf8"));return typeof t=="object"&&t!==null?t:void 0}catch{return}}export{w as readGraphRoles,y as verifyTeamsCredentials};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentdeploymentco/bishop",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "An agent gateway. Connects an agent to Slack and email.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -25,12 +25,14 @@
25
25
  "typecheck": "tsc --noEmit",
26
26
  "test": "vitest run",
27
27
  "test:watch": "vitest",
28
+ "eval:etiquette": "tsx test/etiquette-eval.ts",
28
29
  "lint": "biome check src test",
29
30
  "format": "biome check --write src test",
30
31
  "check": "npm run lint && npm run typecheck && npm test"
31
32
  },
32
33
  "dependencies": {
33
34
  "@anthropic-ai/claude-agent-sdk": "^0.3",
35
+ "@microsoft/teams.apps": "^2.0.16",
34
36
  "@modelcontextprotocol/sdk": "^1.30.0",
35
37
  "@openai/codex-sdk": "^0.149.1",
36
38
  "@slack/bolt": "^5.0.0",
File without changes
File without changes