@agentdeploymentco/bishop 0.3.0 → 0.3.2
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.
- package/README.md +15 -3
- package/dist/cli.js +12 -12
- package/dist/gmail/fake.js +6 -4
- package/dist/gmail/interface.js +2 -2
- package/dist/gmail/message.js +10 -9
- package/dist/gmail/presence.js +3 -3
- package/dist/harness/claude.js +5 -5
- package/dist/harness/codex.js +4 -4
- package/dist/interface/phrases.js +1 -1
- package/dist/outgoing.js +2 -0
- package/dist/schedule/presence.js +3 -3
- package/dist/schedule/runner.js +3 -3
- package/dist/slack/commands/run.js +3 -1
- package/dist/slack/destination.js +1 -1
- package/dist/slack/interface.js +2 -2
- package/dist/slack/manifest.js +1 -1
- package/dist/slack/presence.js +3 -3
- package/dist/slack/upload.js +1 -0
- package/dist/teams/create.js +2 -1
- package/dist/teams/entra.js +2 -2
- package/dist/teams/fake.js +1 -1
- package/dist/teams/graph.js +1 -1
- package/dist/teams/interface.js +4 -2
- package/dist/teams/presence.js +4 -4
- package/dist/teams/setup.js +67 -64
- package/dist/teams/thread.js +1 -1
- package/dist/thread-model.js +1 -0
- package/dist/tools/files.js +2 -2
- package/dist/tools/server.js +1 -1
- package/dist/turn.js +1 -1
- package/package.json +8 -3
package/dist/teams/setup.js
CHANGED
|
@@ -1,29 +1,29 @@
|
|
|
1
|
-
import{loadConfig as y,writeConfig as F}from"../config.js";import{log as I}from"../log.js";import{ensureGitignore as he,resolveAppDetails as
|
|
1
|
+
import{loadConfig as y,writeConfig as F}from"../config.js";import{log as I}from"../log.js";import{ensureGitignore as he,resolveAppDetails as W,SetupError as m,withPrompt as G,writeEnv as de}from"../setup.js";import{AZ_LOGIN_COMMAND as j,azAccount as ce,FILES_READ_ALL as le,FILES_READ_WRITE_ALL as z,FILES_READ_WRITE_ALL_ROLE as me,GRAPH_APP_ID as ue,grantGraphFiles as fe,INSTALL_AZ_CLI as we}from"./entra.js";import{addRscPermission as ge,createTeamsApp as Te,describeTeamsApp as Ie,getRscPermissions as Ae,getTeamsApp as _e,INSTALL_TEAMS_CLI as x,INSTALL_TEAMS_CLI_COMMAND as Ee,LOGIN_DID_NOT_PERSIST as Se,RSC_PERMISSIONS as C,setTeamsEndpoint as be,setTeamsFiles as N,teamsCliVersion as U,teamsLogin as ye,teamsLoginWouldUseDeviceCode as $,teamsStatus as v}from"./provision.js";import{readGraphRoles as R,verifyTeamsCredentials as Ce}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
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
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($()?`${
|
|
4
|
+
`);function Z(e){return new m($()?`${x}
|
|
5
5
|
|
|
6
|
-
${e}`:
|
|
6
|
+
${e}`:x)}function L(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:i}=L(e);if(n&&t&&i)return{clientId:n,clientSecret:t,tenantId:i}}function q(e,n){const t=e.teams?.app;if(t&&O(n))return{app:t}}async function J(e,n,t,i){let a;try{a=await de(e,{BISHOP_TEAMS_CLIENT_ID:i.clientId,BISHOP_TEAMS_CLIENT_SECRET:i.clientSecret,BISHOP_TEAMS_TENANT_ID:i.tenantId})}catch(r){throw new m(`The credentials could not be written to .env: ${r instanceof Error?r.message:String(r)}
|
|
7
7
|
|
|
8
8
|
The app was created, so save these somewhere before this scrolls away:
|
|
9
9
|
|
|
10
|
-
BISHOP_TEAMS_CLIENT_ID=${
|
|
11
|
-
BISHOP_TEAMS_CLIENT_SECRET=${
|
|
12
|
-
BISHOP_TEAMS_TENANT_ID=${
|
|
10
|
+
BISHOP_TEAMS_CLIENT_ID=${i.clientId}
|
|
11
|
+
BISHOP_TEAMS_CLIENT_SECRET=${i.clientSecret}
|
|
12
|
+
BISHOP_TEAMS_TENANT_ID=${i.tenantId}
|
|
13
13
|
|
|
14
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
|
-
${
|
|
15
|
+
${a} credentials
|
|
16
16
|
`+(s.length?` .gitignore added ${s.join(", ")}
|
|
17
|
-
`:"")}async function k(e){if(e)try{return await
|
|
17
|
+
`:"")}async function k(e){if(e)try{return await _e(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
18
|
`).join("")}const S=`Without these the agent only hears a message that @mentions it, so a reply to
|
|
19
19
|
its own answer never reaches it.
|
|
20
|
-
`;async function
|
|
20
|
+
`;async function Ne(e){const n=[],t=[];for(const i of C){const a=await ge(e,i);a.applied||(n.push(i),t.push(` ${i}: ${a.reason}
|
|
21
21
|
`))}return n.length===0?"":`
|
|
22
22
|
These permissions could not be added to the app:
|
|
23
23
|
`+t.join("")+`
|
|
24
24
|
${S}Add them with:
|
|
25
25
|
|
|
26
|
-
`+Q(e,n)}async function X(e){if(e)try{const n=new Set(await
|
|
26
|
+
`+Q(e,n)}async function X(e){if(e)try{const n=new Set(await Ae(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 $e=`Then remove the app from every team and chat it is in, and add it again.
|
|
27
27
|
Teams asks for these permissions only when an app is installed. Updating an
|
|
28
28
|
app that is already installed does not ask, and it will keep hearing nothing
|
|
29
29
|
but mentions until it has been removed and installed again.
|
|
@@ -31,12 +31,12 @@ but mentions until it has been removed and installed again.
|
|
|
31
31
|
can send it a file. An image pasted into a message arrives either way, and a
|
|
32
32
|
file attached in a channel never does.
|
|
33
33
|
`;function b(e){return` teams app manifest update ${e} --set-json 'bots[0].supportsFiles=true' --yes
|
|
34
|
-
`}function
|
|
34
|
+
`}function ve(e,n){return`
|
|
35
35
|
File support could not be turned on for this app: ${n??"the update failed"}
|
|
36
36
|
|
|
37
37
|
${M}Turn it on with:
|
|
38
38
|
|
|
39
|
-
`+b(e)}function
|
|
39
|
+
`+b(e)}function Re(e){return`
|
|
40
40
|
Bishop could not check whether this app has file support: the Teams CLI is
|
|
41
41
|
not installed here, or is not logged in. Run this again where it is, or turn
|
|
42
42
|
it on with:
|
|
@@ -49,84 +49,87 @@ This app does not have file support.
|
|
|
49
49
|
${M}Turn it on with:
|
|
50
50
|
|
|
51
51
|
`+b(e)+`
|
|
52
|
-
${Le}`}function
|
|
53
|
-
${
|
|
54
|
-
${
|
|
52
|
+
${Le}`}function D(e,n=" "){const t=e??"<client id>",i=`${n} `;return`${n}az ad app permission add --id ${t} \\
|
|
53
|
+
${i}--api ${ue} \\
|
|
54
|
+
${i}--api-permissions ${me}=Role
|
|
55
55
|
${n}az ad app permission admin-consent --id ${t}
|
|
56
|
-
`}const
|
|
57
|
-
read
|
|
56
|
+
`}const Oe=`Without it the agent cannot send a file to a channel. Files.Read.All still
|
|
57
|
+
lets it read files people attach there. A file in a direct chat needs none
|
|
58
|
+
of this.
|
|
58
59
|
`;function A(e,n){return`
|
|
59
60
|
${n}
|
|
60
61
|
|
|
61
|
-
${
|
|
62
|
-
${
|
|
63
|
-
grant in Entra.`;function
|
|
64
|
-
`}const
|
|
65
|
-
The Azure CLI accepted the grant for Files.
|
|
66
|
-
|
|
62
|
+
${D(e)}
|
|
63
|
+
${Oe}`}const P=`A file attached in a channel is read through Microsoft Graph, which needs one
|
|
64
|
+
grant in Entra.`;function ke(e,n=" "){return`${n}az ad app permission admin-consent --id ${e}
|
|
65
|
+
`}const Me=`
|
|
66
|
+
The Azure CLI accepted the grant for Files.ReadWrite.All, and the app cannot
|
|
67
|
+
send a file to a channel yet: its own token does not carry the
|
|
67
68
|
permission. Entra can take minutes to apply a consent on a registration
|
|
68
69
|
made seconds earlier. Re-run \`bishop teams setup\` to see whether it took,
|
|
69
70
|
and if it still reports the grant missing, run the consent again:
|
|
70
71
|
|
|
71
|
-
`,
|
|
72
|
-
The Azure CLI accepted the grant for Files.
|
|
72
|
+
`,De=`
|
|
73
|
+
The Azure CLI accepted the grant for Files.ReadWrite.All, and Bishop could not
|
|
73
74
|
reach Entra to check that it took. Re-run \`bishop teams setup\` to find out.
|
|
74
75
|
`,ne=`
|
|
75
|
-
The app can read
|
|
76
|
-
`,Pe=`
|
|
76
|
+
The app can read and send files in a channel: Files.ReadWrite.All is granted.
|
|
77
|
+
`,Pe=`
|
|
78
|
+
The app can read files attached in a channel, but cannot send them: Files.ReadWrite.All is not granted.
|
|
79
|
+
`,Be=`Consenting an application permission takes an administrator, which is the
|
|
77
80
|
usual reason. An app registration made moments ago can also be too new for
|
|
78
81
|
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
|
|
82
|
+
before asking anybody. Re-run \`bishop teams setup\` to see whether it took.`;async function He(e,n=R){const t=await ce();if(!t.installed)return A(e.clientId,`${P} Bishop makes it with the Azure CLI,
|
|
80
83
|
which is not installed here. ${we}
|
|
81
|
-
Then run:`);if(!t.loggedIn)return A(e.clientId,`${
|
|
82
|
-
which is not logged in here. Run \`${
|
|
83
|
-
what a tenant with no Azure subscription needs, and then:`);if(t.tenantId&&t.tenantId.toLowerCase()!==e.tenantId.toLowerCase())return A(e.clientId,`${
|
|
84
|
+
Then run:`);if(!t.loggedIn)return A(e.clientId,`${P} Bishop makes it with the Azure CLI,
|
|
85
|
+
which is not logged in here. Run \`${j}\`, which is
|
|
86
|
+
what a tenant with no Azure subscription needs, and then:`);if(t.tenantId&&t.tenantId.toLowerCase()!==e.tenantId.toLowerCase())return A(e.clientId,`${P} The Azure CLI is signed in to tenant
|
|
84
87
|
${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
|
|
88
|
+
Bishop left it alone. Sign in to the app\u2019s tenant and run:`);const i=await fe(e.clientId);if(!i.applied)return A(e.clientId,`Bishop could not grant Files.ReadWrite.All: ${i.reason}
|
|
86
89
|
|
|
87
|
-
${
|
|
88
|
-
An administrator makes the grant with:`);const
|
|
90
|
+
${Be}
|
|
91
|
+
An administrator makes the grant with:`);const a=await n(e);return a?.includes(z)?ne:a?Me+ke(e.clientId):De}function te(e,n){return n?n.includes(z)?ne:n.includes(le)?Pe:A(e,`The app cannot send a file to a channel: Files.ReadWrite.All is not
|
|
89
92
|
granted on its app registration. An administrator grants it with:`):`
|
|
90
93
|
Bishop could not check whether the app can read a file attached in a
|
|
91
|
-
channel. That needs Files.
|
|
94
|
+
channel. That needs Files.ReadWrite.All on the app registration, which an
|
|
92
95
|
administrator grants with:
|
|
93
96
|
|
|
94
|
-
`+
|
|
97
|
+
`+D(e)}function ie(e,n){return`
|
|
95
98
|
The app is missing ${n.length===1?"a permission":"permissions"}:
|
|
96
99
|
`+n.map(t=>` ${t}
|
|
97
100
|
`).join("")+`
|
|
98
101
|
${S}Add them with:
|
|
99
102
|
|
|
100
103
|
`+Q(e,n)+`
|
|
101
|
-
${
|
|
104
|
+
${$e}`}const Fe=`
|
|
102
105
|
${S}Bishop could not check whether this app has the permissions for that: the
|
|
103
106
|
Teams CLI is not installed here, or is not logged in. Run this again where it
|
|
104
107
|
is, or see \`bishop teams instructions\` for what they are.
|
|
105
|
-
`;function
|
|
108
|
+
`;function We(e,n){return n?n.length>0?ie(e,n):"":`
|
|
106
109
|
${S}Bishop cannot check this app, so confirm with whoever created it that it has
|
|
107
110
|
the permissions \`bishop teams instructions\` asks for.
|
|
108
|
-
`}function
|
|
111
|
+
`}function Ge(e,n){return!e||!n?.applied?`
|
|
109
112
|
${M}Bishop cannot check this app, so confirm with whoever created it that file
|
|
110
113
|
support is on.
|
|
111
|
-
`:n.changed?ee(e):""}async function
|
|
114
|
+
`:n.changed?ee(e):""}async function ae(e,n,t){const i=await k(e.app.id),a=i?.endpoint,s=e.app.id?i?a??"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
115
|
`+(e.app.id?` App ID ${e.app.id}
|
|
113
116
|
`:"")+` Endpoint ${s}
|
|
114
117
|
Its credentials are in .env and its configuration in .bishop/config.json.
|
|
115
|
-
`),a
|
|
118
|
+
`),i&&!a&&n(`
|
|
116
119
|
Teams has nowhere to deliver to until the app is pointed at Bishop.
|
|
117
120
|
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(
|
|
121
|
+
`),e.app.id){const p=await X(e.app.id);p?p.length>0&&n(ie(e.app.id,p)):n(Fe)}if(e.app.id){const p=await N(e.app.id,{check:!0});p.applied?p.changed&&n(ee(e.app.id)):n(Re(e.app.id))}const r=O(process.env);n(te(r?.clientId,r&&await t(r))),n(`
|
|
119
122
|
To change where Teams delivers, run \`bishop teams endpoint <url>\`.
|
|
120
123
|
To set this up again, remove teams.app from .bishop/config.json and the
|
|
121
124
|
BISHOP_TEAMS_* lines from .env first.
|
|
122
|
-
`)}function je(e,n){if(!e)return;const{tenant:t,user:
|
|
125
|
+
`)}function je(e,n){if(!e)return;const{tenant:t,user:i}=e;t!==!1&&i!==!1||n(`
|
|
123
126
|
Note: custom apps cannot be installed in Teams from here.
|
|
124
127
|
`+(t===!1?` The tenant-wide setting is off. An administrator turns it on in the Teams
|
|
125
128
|
Admin Center, under Org-wide app settings, Custom apps.
|
|
126
|
-
`:"")+(
|
|
129
|
+
`:"")+(i===!1?` Your own app setup policy blocks it. An administrator changes it in the
|
|
127
130
|
Teams Admin Center, under Users, Policies, App setup policy.
|
|
128
131
|
`:"")+`Setup will still create the app, but somebody will have to install it.
|
|
129
|
-
`)}function
|
|
132
|
+
`)}function ze(e,n){const t=e.teams?.app,i=L(n),a=Object.keys(i).length>0;if(t&&!O(n)){const{clientId:s,clientSecret:r,tenantId:p}=i,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
133
|
|
|
131
134
|
App ${t.displayName??t.name}${t.id?` (${t.id})`:""}
|
|
132
135
|
Missing ${h.join(`
|
|
@@ -135,23 +138,23 @@ Note: custom apps cannot be installed in Teams from here.
|
|
|
135
138
|
Creating a new app would leave that one orphaned in your tenant. Either:
|
|
136
139
|
finish this one with \`bishop teams setup --manual\`, giving it a client
|
|
137
140
|
secret from \`teams app auth secret create ${t.id??"<app id>"}\`, or
|
|
138
|
-
remove teams.app from .bishop/config.json to start over.`)}if(
|
|
139
|
-
here: ${Object.keys(
|
|
141
|
+
remove teams.app from .bishop/config.json to start over.`)}if(a&&!t)throw new m(`Teams credentials are already in the environment, but no app is recorded
|
|
142
|
+
here: ${Object.keys(i).length} of 3 are set.
|
|
140
143
|
|
|
141
144
|
Creating a new app would overwrite them, and a client secret cannot be
|
|
142
145
|
read back once it is lost.
|
|
143
146
|
|
|
144
147
|
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
|
|
148
|
+
belong to, or remove the BISHOP_TEAMS_* lines from .env to start over.`)}async function qe(e,n=R){const t=e.cwd??process.cwd(),i=e.prompt&&process.stdin.isTTY===!0,a=o=>process.stdout.write(o),s=await y(t),r=q(s,process.env);if(r){await ae(r,a,n);return}ze(s,process.env);const p=await U();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(!i)throw new m("The Teams CLI is not logged in to Microsoft 365.\nRun `teams login`, then run this again.");a("The Teams CLI is not logged in to Microsoft 365. Starting `teams login`.\n\n");const o=await ye();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
149
|
`+(o?`
|
|
147
150
|
\`teams login\` reported success, but the login did not survive it.
|
|
148
|
-
${
|
|
151
|
+
${Se}`:"Run `teams login` on its own to see what went wrong."));a(`
|
|
149
152
|
Logged in.
|
|
150
|
-
`)}je(h.sideloading,
|
|
153
|
+
`)}je(h.sideloading,a),await G(i,async o=>{const d=await W(e,t,o),u=e.endpoint?.trim()||await o("Messaging endpoint URL (leave blank to set later)","");a(`
|
|
151
154
|
Creating the Teams app for "${d.displayName}"${h.username?` in ${h.username}'s tenant`:""}...
|
|
152
|
-
`);const c=await
|
|
153
|
-
The description was not applied: ${
|
|
154
|
-
`);const
|
|
155
|
+
`);const c=await Te({name:d.displayName,...u?{endpoint:u}:{}}),w=d.description?await Ie(c.teamsAppId,d.description):{applied:!1,reason:void 0};d.description&&!w.applied&&a(`
|
|
156
|
+
The description was not applied: ${w.reason}
|
|
157
|
+
`);const f=await Ne(c.teamsAppId);f&&a(f);const l=await N(c.teamsAppId);l.applied||a(ve(c.teamsAppId,l.reason));const g={id:c.teamsAppId,name:d.name,displayName:d.displayName,...w.applied&&d.description?{description:d.description}:{}},T=await J(t,s,g,c),_=await He(c,n);I.info({teamsAppId:c.teamsAppId,endpoint:u||void 0},"teams app created"),a(`
|
|
155
158
|
Done.
|
|
156
159
|
${T}`+_+(c.installLink?`
|
|
157
160
|
Install it in Teams:
|
|
@@ -159,20 +162,20 @@ Install it in Teams:
|
|
|
159
162
|
`:"")+(u?"":`
|
|
160
163
|
The app has no messaging endpoint yet, so Teams has nowhere to deliver to.
|
|
161
164
|
Run \`bishop teams endpoint <url>\` with the URL that reaches this host.
|
|
162
|
-
`)+"\nRun `bishop` to start the agent.\n")})}async function
|
|
163
|
-
`);return}await
|
|
165
|
+
`)+"\nRun `bishop` to start the agent.\n")})}async function Je(e,n=Ce,t=R){const i=e.cwd??process.cwd(),a=e.prompt&&process.stdin.isTTY===!0,s=o=>process.stdout.write(o),r=await y(i),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}}},i),s(`Recorded Teams app ID ${o} for "${h.app.displayName??h.app.name}".
|
|
166
|
+
`);return}await ae(h,s,t);return}await G(a,async o=>{const d=L(process.env),u=d.clientId||await o("Client ID",""),c=d.clientSecret||await o("Client secret",""),w=d.tenantId||await o("Tenant ID","");if(!u||!c||!w)throw new m(`All three credentials are needed: a client ID, a client secret, and a
|
|
164
167
|
tenant ID. Set BISHOP_TEAMS_CLIENT_ID, BISHOP_TEAMS_CLIENT_SECRET and
|
|
165
168
|
BISHOP_TEAMS_TENANT_ID, or run this in a terminal so they can be typed in.
|
|
166
169
|
|
|
167
|
-
Run \`bishop teams instructions\` for what to send whoever creates the app.`);const
|
|
170
|
+
Run \`bishop teams instructions\` for what to send whoever creates the app.`);const f={clientId:u,clientSecret:c,tenantId:w},l=e.appId?.trim()||p?.id||await o("Teams app ID (optional)","");s(`
|
|
168
171
|
Checking the credentials...
|
|
169
|
-
`);const g=await n(
|
|
172
|
+
`);const g=await n(f);if(!g.ok)throw new m(`Microsoft rejected these credentials.
|
|
170
173
|
${g.detail}
|
|
171
174
|
|
|
172
175
|
This is the same request Bishop makes on every turn, so the agent would
|
|
173
176
|
not have been able to answer anything.`);g.detail?s(`${g.detail} Saving them anyway.
|
|
174
177
|
`):s(`Accepted.
|
|
175
|
-
`);const T=await k(l||void 0),_=e.name??p?.name??T?.name,
|
|
178
|
+
`);const T=await k(l||void 0),_=e.name??p?.name??T?.name,B=e.displayName??p?.displayName??T?.name,H=e.description??p?.description,E=await W({...e,..._?{name:_}:{},...B?{displayName:B}:{},...H?{description:H}:{}},i,o),se={...l?{id:l}:{},name:E.name,displayName:E.displayName,...E.description?{description:E.description}:{}},oe=await J(i,r,se,f),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
179
|
Done.
|
|
177
180
|
${oe}`+(T?.endpoint?`
|
|
178
181
|
The app is pointed at ${T.endpoint}
|
|
@@ -180,11 +183,11 @@ The app is pointed at ${T.endpoint}
|
|
|
180
183
|
Bishop cannot tell where this app is pointed, so confirm with whoever
|
|
181
184
|
created it that its messaging endpoint reaches this host. Bishop serves it
|
|
182
185
|
at /api/messages.
|
|
183
|
-
`)+
|
|
184
|
-
`);return}const{needsReinstall:h}=await
|
|
185
|
-
`),h&&
|
|
186
|
+
`)+We(l,re)+Ge(l,pe)+te(f.clientId,await t(f))+"\nRun `bishop` to start the agent.\n")})}async function Qe(e,n={}){const t=n.cwd??process.cwd(),i=o=>process.stdout.write(o),a=await y(t),s=n.appId?.trim()||a.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 U())throw Z(V);if($()&&!(await v()).loggedIn)throw new m(V);const p=(await k(s))?.endpoint;if(p===e){i(`App ${s} already points at ${e}. Nothing to do.
|
|
187
|
+
`);return}const{needsReinstall:h}=await be(s,e);I.info({teamsAppId:s,before:p,endpoint:e,needsReinstall:h},"teams messaging endpoint updated"),i(`App ${s} now points at ${e}
|
|
188
|
+
`),h&&i(`
|
|
186
189
|
The app's manifest changed, which usually means its valid domains did.
|
|
187
190
|
Reinstall it in Teams before expecting it to receive anything.
|
|
188
|
-
`)}function
|
|
189
|
-
`),""," That is Files.
|
|
190
|
-
`)}export{
|
|
191
|
+
`)}function Xe(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:","",` ${Ee}`," 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 and send files 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:","",` ${j}`,...D("<client id>"," ").trimEnd().split(`
|
|
192
|
+
`),""," That is Files.ReadWrite.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 keeps channel file sending unavailable. Files.Read.All still"," permits the agent to read files people attach there.","","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(`
|
|
193
|
+
`)}export{Xe as administratorInstructions,He as applyChannelFileGrant,q as existingTeamsSetup,Qe as repointTeamsEndpoint,qe as setupTeams,Je as setupTeamsManually};
|
package/dist/teams/thread.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const
|
|
1
|
+
const s=new Set(["personal","groupChat"]),p=/^[1-9]\d*$/;function u(n){const e=n.conversationId;return s.has(n.conversationType)||e.includes(";messageid=")?e:p.test(n.id)?`${e};messageid=${n.id}`:e}function f(n){return n.conversationType==="personal"?"personal":n.conversationType==="groupChat"?"groupChat":"channel"}function $(n,e){return n==="personal"?"consent":n==="channel"&&e?"upload":"none"}function v(n){const e=u(n),t=f(n);return t!=="channel"?`${e};scope=${t}`:n.channel?`${e};scope=channel;teamid=${encodeURIComponent(n.channel.teamId)};channelid=${encodeURIComponent(n.channel.channelId)}`:`${e};scope=channel`}function C(n){const[e="",...t]=n.split(";"),o=new Map(t.map(i=>{const a=i.indexOf("=");return a<0?[i,""]:[i.slice(0,a),i.slice(a+1)]})),c=o.get("messageid"),d=c?`${e};messageid=${c}`:e,r=o.get("scope");if(r==="personal")return{conversation:d,scope:r};if(r==="groupChat")return{conversation:d,scope:r};if(r==="channel"){const i=o.get("teamid"),a=o.get("channelid");return!i||!a?{conversation:d,scope:r}:{conversation:d,scope:r,channel:{teamId:decodeURIComponent(i),channelId:decodeURIComponent(a),...c?{threadId:c}:{}}}}return{conversation:d,scope:"unknown"}}function x(n){return`teams:${u(n)}`}function h(n){return n.split(";")[0]??n}function O(n,e){return l(n)===l(e)}function l(n){const[e="",...t]=n.split(";"),o=t.find(c=>c.startsWith("messageid="));return o?`${e};${o}`:e}function m(n){if(n.conversationType==="personal")return;if(s.has(n.conversationType))return{kind:"chat",chatId:h(n.conversationId),messageId:n.id};if(!n.channel)return;const e=I(n);return{kind:"channel",teamId:n.channel.teamId,channelId:n.channel.channelId,messageId:n.id,...e?{replyToId:e}:{}}}function I(n){const e=n.conversationId.split(";").find(t=>t.startsWith("messageid="))?.slice(10);return e&&e!==n.id?e:void 0}export{h as baseConversation,u as conversationOf,v as destinationHandleOf,C as destinationOfHandle,$ as fileRouteOf,m as graphAddressOf,O as sameConversation,f as scopeOf,x as threadKeyOf};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const f={model:"agent.model",effort:"agent.effort"},c=/^[\w./:@-]+$/;function d(o){return c.test(o)}const s=["model","effort"];function i(o){return{of(t){const e={};for(const n of s){const r=o.get(t,f[n]);r&&(e[n]=r)}return e},set(t,e){for(const n of s){const r=e[n];r!==void 0&&o.set(t,f[n],r)}},clear(t,e){o.clear(t,f[e])}}}export{s as MODEL_SETTINGS,d as isHarnessName,i as threadModel};
|
package/dist/tools/files.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{z as
|
|
2
|
-
`),input:{ref:
|
|
1
|
+
import{z as i}from"zod";import{FETCH_FILE_TOOL as s,fileTag as f}from"../harness/principal.js";import{SHARE_FILE_DESCRIPTION as l,SHARE_FILE_TOOL as c,ShareRefused as m}from"../outgoing.js";import{defineTool as a,ToolRefusal as r}from"./types.js";function y(t){return[d(t),w()]}function d(t){return a({name:s,description:["Downloads a file somebody shared in this conversation and gives you the path to it.","","Takes the ref from an <attachment> tag. Ask for a file when you need to open it, rather","than for everything in the conversation. Asking for one already downloaded hands back","the same path without fetching it again, so ask rather than reusing a path from earlier","in the conversation. It fails, with the reason, when there is no file to be had."].join(`
|
|
2
|
+
`),input:{ref:i.string().trim().min(1).describe("The ref attribute of the file tag naming the file you want")},async run(n,o){const e=await t.fetch(o.thread,n.ref);if(!e)throw new r(`No file with the ref ${n.ref} was shared in this conversation. Refs come from the file tags Bishop writes, and one only works in the conversation it was written in.`);if(!e.path){const h=e.unavailable??"the download produced no file";throw new r(`Bishop could not fetch the file with ref ${n.ref}: ${h}.`+(e.url?` The original is at ${e.url}`:""))}return f({...e,ref:n.ref})}})}function w(){return a({name:c,description:l,input:{path:i.string().trim().min(1).describe("Absolute path to the file to send"),name:i.string().trim().min(1).optional().describe("What the recipient sees it called. Defaults to the filename on disk.")},async run(t,n){const o=n.share;if(!o)throw new r("There is nowhere to put a file in this conversation, so nothing was sent.");try{const e=await o({path:t.path,...t.name?{name:t.name}:{}});return`Registered ${e.name}. Write <attachment ref="${e.ref}" /> in your reply where the file should appear.`}catch(e){throw e instanceof m?new r(e.message):e}}})}export{y as fileTools,w as shareTool};
|
package/dist/tools/server.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{randomBytes as
|
|
1
|
+
import{randomBytes as T,randomUUID as b}from"node:crypto";import{createServer as k}from"node:http";import{McpServer as B}from"@modelcontextprotocol/sdk/server/mcp.js";import{StreamableHTTPServerTransport as O}from"@modelcontextprotocol/sdk/server/streamableHttp.js";import{isInitializeRequest as _}from"@modelcontextprotocol/sdk/types.js";import{log as h}from"../log.js";import{ToolRefusal as y}from"./types.js";const x="bishop",v="/mcp",R="BISHOP_TOOL_TOKEN",A=1048576;function N(i){const r=i.headers.authorization;return r&&/^Bearer\s+(.+)$/i.exec(r.trim())?.[1]?.trim()||void 0}function p(i,r,a){i.writeHead(r,{"content-type":"application/json"}),i.end(JSON.stringify({jsonrpc:"2.0",id:null,error:{code:-32001,message:a}}))}async function I(i){const r=[];let a=0;for await(const n of i){if(a+=n.length,a>A)throw new Error("request body is too large");r.push(n)}if(r.length!==0)return JSON.parse(Buffer.concat(r).toString("utf8"))}const P="Bishop's tools answer to a person, and this message came from another bot. Ask someone in the conversation to do it.";function z(i,r){const a=new B({name:x,version:"1"});for(const n of i)a.registerTool(n.name,{description:n.description,inputSchema:n.input},async w=>{try{if(r.principal.isBot)throw new y(P);return{content:[{type:"text",text:await n.run(w,r)}]}}catch(c){const f=c instanceof Error?c.message:String(c);return c instanceof y?h.info({tool:n.name,thread:r.thread.threadKey,reason:f},"a tool refused"):h.error({tool:n.name,thread:r.thread.threadKey,err:c},"a tool failed"),{content:[{type:"text",text:f}],isError:!0}}});return a}async function D(i){const r=i.host??"127.0.0.1",a=new Map,n=new Map;async function w(e,t){const o=new O({sessionIdGenerator:()=>b(),onsessioninitialized:s=>{if(a.get(e)!==t){o.close().catch(()=>{});return}n.set(s,{token:e,transport:o}),t.sessions.add(s)}});return o.onclose=()=>{const s=o.sessionId;s&&(n.delete(s),t.sessions.delete(s))},await z(t.tools,t.context).connect(o),o}const c=k((e,t)=>{(async()=>{try{if(!e.url?.startsWith(v))return p(t,404,"no such endpoint");const o=N(e),s=o?a.get(o):void 0;if(!s||!o)return p(t,401,"this call carries no valid turn credential");const l=e.method==="POST"?await I(e):void 0,d=e.headers["mcp-session-id"],m=typeof d=="string"?n.get(d):void 0;if(m){if(m.token!==o)return p(t,404,"no such session");await m.transport.handleRequest(e,t,l);return}if(typeof d=="string")return p(t,404,"no such session");if(!_(l))return p(t,400,"the first call on a connection must be initialize");await(await w(o,s)).handleRequest(e,t,l)}catch(o){h.error({err:o},"a tool call could not be handled"),t.headersSent?t.end():p(t,400,"that request could not be handled")}})()});await new Promise((e,t)=>{c.once("error",t),c.listen(0,r,()=>{c.removeListener("error",t),e()})});const f=c.address();if(!f||typeof f=="string")throw new Error("the tool endpoint started without a port");const g=`http://${r}:${f.port}${v}`;return h.info({url:g,tools:i.tools.map(e=>e.name)},"serving the agent its own tools"),{url:g,toolNames:i.tools.map(e=>e.name),grant(e,t){const o=T(32).toString("base64url"),s=t?.only,l=s?i.tools.filter(u=>s.includes(u.name)):i.tools;s&&l.length!==s.length&&h.error({asked:[...s],serving:l.map(u=>u.name)},"a turn asked for tools this endpoint does not serve");const d={context:e,tools:l,sessions:new Set};return a.set(o,d),{access:{url:g,tokenEnvVar:R,token:o},revoke:()=>{if(a.delete(o)){for(const u of d.sessions){const S=n.get(u);n.delete(u),S?.transport.close().catch(E=>{h.debug({err:E},"a tool connection did not close cleanly")})}d.sessions.clear()}}}},async stop(){for(const{transport:e}of n.values())await e.close().catch(()=>{});n.clear(),a.clear(),c.closeAllConnections(),await new Promise(e=>c.close(()=>e()))}}}export{v as PATH,x as SERVER_NAME,R as TOKEN_ENV_VAR,D as startToolServer};
|
package/dist/turn.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{log as
|
|
1
|
+
import{log as d}from"./log.js";import{worktreeName as u}from"./worktrees.js";function A(n){const h=new Map,y=new Set;async function w(e){const a=n.sharedDirNotice;if(!(!a||y.has(e.thread.threadKey))){y.add(e.thread.threadKey);try{await e.notify(a)}catch(o){d.debug({thread:e.thread.threadKey,err:o},"could not deliver the shared-directory notice")}}}function K(e,a,o){if(!n.tools||e.tools===!1)return;const i=Array.isArray(e.tools)?e.tools:void 0;if(i&&i.length===0)return;const r=o.share?.bind(o);return n.tools.grant({thread:e.thread,principal:a,origin:e.origin,destination:()=>e.destination(),...r?{share:r}:{}},...i?[{only:i}]:[])}async function p(e){await w(e);const a=new AbortController,o=e.presence();let i;try{h.set(e.thread.threadKey,a);const r=e.freshSession?void 0:await n.sessions.get(e.thread.id),c=await n.cwd(e.thread),[b,g]=await Promise.all([e.messages(),r?void 0:e.history?.().catch(t=>{d.warn({thread:e.thread.threadKey,err:t},"could not read the history")})]),[f,...m]=b;if(!f)throw new Error("a turn with no messages");const k=m.at(-1)?.principal??f.principal;if(a.signal.aborted){d.info({thread:e.thread.threadKey},"turn stopped before the agent started");return}i=K(e,k,o);const l=n.model?.of(e.thread.id)??{};let s;for await(const t of n.harness.runTurn({messages:[f,...m],cwd:c,threadKey:e.thread.threadKey,worktree:u(e.thread.dirKey),...r?{sessionId:r}:{},...l.model?{model:l.model}:{},...l.effort?{effort:l.effort}:{},...e.note?{note:e.note}:{},...g?.length?{history:g}:{},origin:e.origin,...i?{tools:i.access}:{},signal:a.signal}))t.type==="session"&&(t.model&&(s=t.model),t.sessionId!==r&&await n.sessions.set(e.thread.id,t.sessionId)),t.type==="text"&&d.debug({thread:e.thread.threadKey,chars:t.text.length},"agent said something"),t.type==="tool"&&d.debug({thread:e.thread.threadKey,tool:t.name,detail:t.summary??t.detail},"tool activity"),t.type==="done"&&d.info({thread:e.thread.threadKey,...s?{model:s}:{},...t.usage},"turn complete"),t.type==="error"&&d.error({thread:e.thread.threadKey,...s?{model:s}:{},kind:t.kind,err:t.message},"turn errored"),await o.handle(t)}catch(r){const c=r instanceof Error?r.message:String(r);throw d.error({thread:e.thread.threadKey,err:r},"turn failed before the agent answered"),await o.handle({type:"error",kind:"turn_failed",message:c}),r}finally{i?.revoke(),h.get(e.thread.threadKey)===a&&h.delete(e.thread.threadKey),await o.finish()}}return{run:p,stop(e){const a=h.get(e);return a?(a.abort(),!0):!1}}}export{A as makeTurnRunner};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentdeploymentco/bishop",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "An agent gateway. Connects an agent to Slack and email.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|
|
@@ -22,6 +22,11 @@
|
|
|
22
22
|
"build:release": "rm -rf dist && tsc -p tsconfig.release.json && node scripts/minify.mjs && chmod +x dist/cli.js",
|
|
23
23
|
"prepublishOnly": "npm run build:release && node scripts/check-package.mjs",
|
|
24
24
|
"dev": "tsx src/cli.ts",
|
|
25
|
+
"dev:slack": "./scripts/dev.sh start",
|
|
26
|
+
"dev:slack:stop": "./scripts/dev.sh stop",
|
|
27
|
+
"dev:slack:restart": "./scripts/dev.sh restart",
|
|
28
|
+
"dev:slack:status": "./scripts/dev.sh status",
|
|
29
|
+
"dev:slack:logs": "./scripts/dev.sh logs",
|
|
25
30
|
"typecheck": "tsc --noEmit",
|
|
26
31
|
"test": "vitest run",
|
|
27
32
|
"test:watch": "vitest",
|
|
@@ -31,10 +36,10 @@
|
|
|
31
36
|
"check": "npm run lint && npm run typecheck && npm test"
|
|
32
37
|
},
|
|
33
38
|
"dependencies": {
|
|
34
|
-
"@anthropic-ai/claude-agent-sdk": "
|
|
39
|
+
"@anthropic-ai/claude-agent-sdk": "0.3.280",
|
|
35
40
|
"@microsoft/teams.apps": "^2.0.16",
|
|
36
41
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
37
|
-
"@openai/codex-sdk": "
|
|
42
|
+
"@openai/codex-sdk": "0.155.1",
|
|
38
43
|
"@slack/bolt": "^5.0.0",
|
|
39
44
|
"@slack/web-api": "^8",
|
|
40
45
|
"commander": "^13",
|